diff --git a/.codeclimate.yml b/.codeclimate.yml index 520557c190..906c909c2a 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,7 +1,13 @@ +version: "2" # required to adjust maintainability checks languages: JavaScript: true +checks: + file-lines: + enabled: false + + exclude_paths: - - "tests/acceptance/*" - - "tests/fixtures/*" - - "tests/unit/*" + - "**/tests" + - "**/tmp" + - "**/node_modules/" diff --git a/.eslintrc.js b/.eslintrc.js index 733b1b0671..e6872be6eb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,7 +6,7 @@ module.exports = { ecmaVersion: 2018, }, plugins: ['node', 'prettier'], - extends: ['eslint:recommended', 'plugin:node/recommended', 'prettier'], + extends: ['eslint:recommended', 'plugin:node/recommended', 'plugin:prettier/recommended'], env: { browser: false, node: true, @@ -16,57 +16,55 @@ module.exports = { rules: { /*** Possible Errors ***/ - 'no-console': 0, - 'no-template-curly-in-string': 2, - 'no-unsafe-negation': 2, + 'no-console': 'off', + 'no-template-curly-in-string': 'error', + 'no-unsafe-negation': 'error', /*** Best Practices ***/ - curly: 2, - eqeqeq: 2, - 'guard-for-in': 0, - 'no-caller': 2, - 'no-eq-null': 2, - 'no-eval': 2, - 'no-new': 0, + curly: 'error', + eqeqeq: 'error', + 'guard-for-in': 'off', + 'no-caller': 'error', + 'no-eq-null': 'error', + 'no-eval': 'error', + 'no-new': 'off', 'no-unused-expressions': [ - 2, + 'error', { allowShortCircuit: true, allowTernary: true, }, ], - 'wrap-iife': 0, - yoda: 2, + 'wrap-iife': 'off', + yoda: 'error', /*** Strict Mode ***/ - strict: [2, 'global'], + strict: ['error', 'global'], /*** Variables ***/ - 'no-undef': 2, - 'no-unused-vars': 2, - 'no-use-before-define': [2, 'nofunc'], + 'no-undef': 'error', + 'no-unused-vars': 'error', + 'no-use-before-define': ['error', 'nofunc'], /*** Stylistic Issues ***/ - camelcase: 2, - 'new-cap': [2, { properties: false }], - 'no-array-constructor': 2, - 'no-bitwise': 2, - 'no-lonely-if': 2, - 'no-plusplus': 0, - 'no-unneeded-ternary': 2, + camelcase: 'error', + 'new-cap': ['error', { properties: false }], + 'no-array-constructor': 'error', + 'no-bitwise': 'error', + 'no-lonely-if': 'error', + 'no-plusplus': 'off', + 'no-unneeded-ternary': 'error', /*** ECMAScript 6 ***/ - 'no-useless-computed-key': 2, - 'no-var': 2, - 'object-shorthand': 2, - 'prefer-template': 2, - 'symbol-description': 2, - - 'prettier/prettier': 'error', + 'no-useless-computed-key': 'error', + 'no-var': 'error', + 'object-shorthand': 'error', + 'prefer-template': 'error', + 'symbol-description': 'error', }, }; diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..4df07ddd67 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: +- package-ecosystem: npm + directory: "/" + schedule: + interval: monthly + time: "03:00" + open-pull-requests-limit: 10 + versioning-strategy: increase +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: monthly + time: "03:00" + open-pull-requests-limit: 10 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..e6ee336d03 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,93 @@ +name: CI + +on: + push: + branches: + - master + - beta + - release + - next + - 'v*' + - 'release-*' + - 'lts-*' + pull_request: + schedule: + - cron: '0 3 * * *' # daily, at 3am + +jobs: + linting: + name: Linting + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2.1.5 + with: + node-version: 12.x + + - run: yarn install --frozen-lockfile --non-interactive + - run: yarn lint + + basic-tests: + name: "Basic Tests - ${{ matrix.os }}" + runs-on: "${{ matrix.os }}-latest" + + strategy: + matrix: + os: [ubuntu, macOS, windows] + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2.1.5 + with: + node-version: 12.x + + - run: yarn install --frozen-lockfile --non-interactive + - run: yarn test + + tests: + name: "Node ${{ matrix.node-version }} - ${{ matrix.os }} " + runs-on: "${{ matrix.os }}-latest" + + needs: [linting, basic-tests] + + strategy: + fail-fast: false + matrix: + node-version: [12.x, 14.x, 16.x] + os: [ubuntu, windows] + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2.1.5 + with: + node-version: ${{ matrix.node-version }} + + - run: yarn install --frozen-lockfile --non-interactive + - run: yarn test:all + + feature-flags: + name: "Feature: ${{ matrix.feature-flag }}" + runs-on: ubuntu-latest + + needs: [linting, basic-tests] + + strategy: + fail-fast: false + matrix: + feature-flag: + - ENABLE_ALL_EXPERIMENTS + - PACKAGER + - EMBROIDER + - CLASSIC + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2.1.5 + with: + node-version: 12.x + + - run: yarn install --frozen-lockfile --non-interactive + - run: yarn test:all + env: + "EMBER_CLI_${{ matrix.feature-flag }}": true diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000000..dabece3db2 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,31 @@ +name: Code Coverage + +on: + push: + branches: + - master + +jobs: + coverage: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v2.1.5 + with: + node-version: 12.x + + - run: yarn install --frozen-lockfile --non-interactive + + - name: Test && Report to Code Climate + uses: paambaati/codeclimate-action@v2.7.5 + env: + CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} + with: + coverageCommand: yarn test:cover + coverageLocations: "coverage/lcov.info:lcov" + + - name: Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.npmignore b/.npmignore index 95112164c5..339697d4bb 100644 --- a/.npmignore +++ b/.npmignore @@ -7,5 +7,12 @@ /tmp/ /.* *.log -!/.travis.yml /yarn.lock +!docs/build/data.json + +# the GitHub Action CI configuration is used by +# lib/utilities/platform-checker.js to determine what versions of Node are +# under test by the current version of ember-cli being used, this means we +# **must** publish this file (but it would be ignored by the `/.*` line just +# above) +!/.github/workflows/ci.yml diff --git a/.nycrc.json b/.nycrc.json new file mode 100644 index 0000000000..7cef8daac3 --- /dev/null +++ b/.nycrc.json @@ -0,0 +1,11 @@ +{ + "include": [ + "bin/**/*.js", + "lib/**/*.js", + "blueprints/**/*.js" + ], + + "exclude": [ + "blueprints/*/files/**" + ] +} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index dd36e4a243..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,77 +0,0 @@ -language: node_js -node_js: - - "12" - -addons: - chrome: stable - -branches: - only: - - master - - beta - - release - - next - # release branches - - /^(?:release|lts)-\d+-\d+$/ - # npm version tags - - /^v\d+\.\d+\.\d+/ - -cache: - yarn: true - directories: - - $HOME/.npm - - $HOME/.cache # includes bower's cache - -stages: - - basic test - - additional tests - - name: deploy - if: type IN (push) AND branch = release - -jobs: - include: - - stage: basic test - name: Basic Tests - script: - - yarn lint - - yarn test - - - stage: additional tests - name: Node.js 8 - node_js: 8 - - - name: Node.js 10 - node_js: 10 - - - name: Node.js 12 - node_js: 12 - script: - - yarn test:cover - after_success: - - .travis/codecoverage.sh - - - env: EMBER_CLI_ENABLE_ALL_EXPERIMENTS=true - - env: EMBER_CLI_PACKAGER=true - - env: EMBER_CLI_MODULE_UNIFICATION=true - - env: EMBER_CLI_DELAYED_TRANSPILATION=true - - env: EMBER_CLI_SYSTEM_TEMP=false - - env: EMBER_CLI_BROCCOLI_WATCHER=true - - - stage: deploy - name: Publish API Docs - script: skip - deploy: - provider: script - script: ./.travis/deploy.sh - -before_install: - # prevent the npm loading indicator - - npm config --global set spin false - - # travis currently includes yarn v0.17.8 (20170705) - # this isn't new enough for our use of --non-interactive - - curl -o- -L https://yarnpkg.com/install.sh | bash - - export PATH=$HOME/.yarn/bin:$PATH - -script: - - yarn test:all diff --git a/.travis/codecoverage.sh b/.travis/codecoverage.sh deleted file mode 100755 index f865d64f5e..0000000000 --- a/.travis/codecoverage.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -# fail script immediately on any errors in external commands -set -e - -# Use `yarn` to install globals -yarn global add coveralls codeclimate-test-reporter - -./node_modules/.bin/nyc report --reporter text-lcov | coveralls -./node_modules/.bin/nyc report --reporter text-lcov | codeclimate-test-reporter diff --git a/.travis/deploy.sh b/.travis/deploy.sh deleted file mode 100755 index a175b121ce..0000000000 --- a/.travis/deploy.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash - -set -e - -eval "$(ssh-agent -s)" - -openssl aes-256-cbc -K $encrypted_c177ff031535_key -iv $encrypted_c177ff031535_iv -in .travis/deploy_key.pem.enc -out .travis/deploy_key.pem -d -chmod 600 .travis/deploy_key.pem -ssh-add .travis/deploy_key.pem - -rm -rf docs/build -npm run docs -cd docs/build - -git init -git config user.name "Travis" -git config user.email "noreply@travis-ci.org" -git add . -git commit -m "Deploy to GitHub Pages" -git push --force --quiet git@github.com:ember-cli/api.git master:gh-pages > /dev/null 2>&1 diff --git a/.travis/deploy_key.pem.enc b/.travis/deploy_key.pem.enc deleted file mode 100644 index 87f11ee51d..0000000000 Binary files a/.travis/deploy_key.pem.enc and /dev/null differ diff --git a/.travis/deploy_key.pem.pub b/.travis/deploy_key.pem.pub deleted file mode 100644 index 3e1c704b82..0000000000 --- a/.travis/deploy_key.pem.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCstb7To736Qn6i5iXrpeVeTz+1mldSq0r25KrNMY1PYJRmKEgyh8qrQnUz4qV89vblGU9HDfdiLAXepRk115Ma3B3bIUa6lLqyLDYkyKdpnwOh2X6HqxSkSkS+0wamPkwM9KDDRs9l+lYKWeYggkvkmw8JffKevbPAEyp6k0IOkDbdlwxq6MJnkqcXSm962Gpp/5nrRlxbMeTn0KnHjKASZEwaEafy3W9IAkoT254qWX4oc9k+Uj7IY91ASBkOz1TaR7N1ZMrbDUSjS3lIpenSEIuXkvkob8YVNOu9KqJ9eNofud5vwmoKe6g1+CAQiq2P21/cyGeetgruOVVL+1HV diff --git a/CHANGELOG.md b/CHANGELOG.md index 552c617455..bd96a49d98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,607 @@ # ember-cli Changelog +## v3.28.6 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.28.5...v3.28.6) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.28.5...v3.28.6) + +#### Changelog + +- [#10013](https://github.com/ember-cli/ember-cli/pull/10013) [BUGFIX] Handle rebuild failures without exiting [@bendemboski](https://github.com/bendemboski) + +Thank you to all who took the time to contribute! + +## v3.28.5 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.28.4...v3.28.5) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.28.4...v3.28.5) + +#### Changelog + +- [#9730](https://github.com/ember-cli/ember-cli/pull/9730) Add Ember v3.28 LTS to `addon` blueprint (drop support for Ember v3.20 LTS) [@kellyselden](https://github.com/kellyselden) +- [#9740](https://github.com/ember-cli/ember-cli/pull/9740) update embroider deps in LTS [@kellyselden](https://github.com/kellyselden) + +Thank you to all who took the time to contribute! + +## v3.28.4 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.28.3...v3.28.4) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.28.3...v3.28.4) + +#### Changelog + +- [#9694](https://github.com/ember-cli/ember-cli/pull/9694) test in node 16 LTS [@kellyselden](https://github.com/kellyselden) + +Thank you to all who took the time to contribute! + +## v3.28.3 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.28.2...v3.28.3) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.28.2...v3.28.3) + +#### Changelog + +- [#9670](https://github.com/ember-cli/ember-cli/pull/9670) Don't emit an error when the `lint:fix` script fails post blueprint generation [@bertdeblock](https://github.com/bertdeblock) + +Thank you to all who took the time to contribute! + +## v3.28.2 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.28.1...v3.28.2) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.28.1...v3.28.2) + +#### Changelog + +- [#9659](https://github.com/ember-cli/ember-cli/pull/9659) Ensure `ember-classic` ember-try scenario uses Ember 3.x [@rwjblue](https://github.com/rwjblue) + +Thank you to all who took the time to contribute! + +## v3.28.1 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.28.0...v3.28.1) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.28.0...v3.28.1) + +#### Changelog + +- [#9618](https://github.com/ember-cli/ember-cli/pull/9618) Ensure discovered addons are refreshed after `ember install` (fix usage of default blueprints) [@brendenpalmer](https://github.com/brendenpalmer) + +## v3.28.0 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.27.0...v3.28.0) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.27.0...v3.28.0) + +#### Changelog + +- [#9505](https://github.com/ember-cli/ember-cli/pull/9505) Pass `realPath` as `root` rather than the dirname for `addonMainPath` [@brendenpalmer](https://github.com/brendenpalmer) +- [#9507](https://github.com/ember-cli/ember-cli/pull/9507) Add a new config, `ember-addon.projectRoot`, to specify the location of the project [@brendenpalmer](https://github.com/brendenpalmer) +- [#9530](https://github.com/ember-cli/ember-cli/pull/9530) Drop Node 10 support [@rwjblue](https://github.com/rwjblue) +- [#9487](https://github.com/ember-cli/ember-cli/pull/9487) Add support for creating a single addon instance per bundle root (which enables dramatically reducing the total number of addon instances) [@davecombs](https://github.com/davecombs) +- [#9524](https://github.com/ember-cli/ember-cli/pull/9524) Update CONTRIBUTING.md to reference cli.emberjs.com [@loganrosen](https://github.com/loganrosen) +- [#9533](https://github.com/ember-cli/ember-cli/pull/9533) Ensure package-info objects are stable when they represent the same addon [@brendenpalmer](https://github.com/brendenpalmer) +- [#9538](https://github.com/ember-cli/ember-cli/pull/9538) ensure backwards compatibility is maintained with `packageRoot` and `root` [@brendenpalmer](https://github.com/brendenpalmer) +- [#9539](https://github.com/ember-cli/ember-cli/pull/9539) avoid setting `root` as `realPath` from the package-info object [@brendenpalmer](https://github.com/brendenpalmer) +- [#9537](https://github.com/ember-cli/ember-cli/pull/9537) Implement LCA host/host addons logic in `ember-cli` [@brendenpalmer](https://github.com/brendenpalmer) +- [#9540](https://github.com/ember-cli/ember-cli/pull/9540) Use relative override paths in blueprint ESLint config [@loganrosen](https://github.com/loganrosen) +- [#9542](https://github.com/ember-cli/ember-cli/pull/9542) Add validation checks for addon instance bundle caching [@brendenpalmer](https://github.com/brendenpalmer) +- [#9543](https://github.com/ember-cli/ember-cli/pull/9543) Add ability to specify a custom `ember-addon.perBundleAddonCacheUtil` utility [@brendenpalmer](https://github.com/brendenpalmer) +- [#9562](https://github.com/ember-cli/ember-cli/pull/9562) Update `addon-proxy` to support Embroider [@brendenpalmer](https://github.com/brendenpalmer) +- [#9565](https://github.com/ember-cli/ember-cli/pull/9565) Drop Node 10 support in blueprint engine spec [@elwayman02](https://github.com/elwayman02) +- [#9568](https://github.com/ember-cli/ember-cli/pull/9568) [BUGFIX release] Skip babel for qunit with embroider [@ctjhoa](https://github.com/ctjhoa) + +Thank you to all who took the time to contribute! + + +## v3.27.0 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.26.0...v3.27.0) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.26.0-beta.2...v3.27.0) + +#### Changelog + +- [#9504](https://github.com/ember-cli/ember-cli/pull/9504) Update minimum version of broccoli-concat to address a major issue with cache invalidation [@brendenpalmer](https://github.com/brendenpalmer) +- [#9535](https://github.com/ember-cli/ember-cli/pull/9535) Disable Embroider by default. [@rwjblue](https://github.com/rwjblue) +- [#9557](https://github.com/ember-cli/ember-cli/pull/9557) Update app and addon blueprint dependencies to latest. [@rwjblue](https://github.com/rwjblue) +- [#9558](https://github.com/ember-cli/ember-cli/pull/9558) Switch from `octane` template lint config to `recommended` [@bmish](https://github.com/bmish) +- [#9453](https://github.com/ember-cli/ember-cli/pull/9453) Prevent "yarn-error.log" files being published for addons [@bertdeblock](https://github.com/bertdeblock) +- [#9392](https://github.com/ember-cli/ember-cli/pull/9392) / [#9484](https://github.com/ember-cli/ember-cli/pull/9484) Add eslint-plugin-qunit to blueprint [@bmish](https://github.com/bmish) +- [#9454](https://github.com/ember-cli/ember-cli/pull/9454) / [#9492](https://github.com/ember-cli/ember-cli/pull/9492) Add --embroider as an option for new and init [@thoov](https://github.com/thoov) +- [#9456](https://github.com/ember-cli/ember-cli/pull/9456) Add `.*/` to eslint ignore [@chancancode](https://github.com/chancancode) +- [#9469](https://github.com/ember-cli/ember-cli/pull/9469) Run `lint:fix` script automatically after blueprint generation [@rpemberton](https://github.com/rpemberton) +- [#9480](https://github.com/ember-cli/ember-cli/pull/9480) Refactor getPort to only check required port [@Cartmanishere](https://github.com/Cartmanishere) +- [#9485](https://github.com/ember-cli/ember-cli/pull/9485) Add Ember 3.24 LTS to ember-try configuration [@bertdeblock](https://github.com/bertdeblock) +- [#9488](https://github.com/ember-cli/ember-cli/pull/9488) Update supported Ember version in addon blueprint [@bertdeblock](https://github.com/bertdeblock) +- [#9490](https://github.com/ember-cli/ember-cli/pull/9490) Prevent window.Ember deprecation on Ember 3.27+. [@rwjblue](https://github.com/rwjblue) +- [#9491](https://github.com/ember-cli/ember-cli/pull/9491) Update supported Ember CLI version in addon blueprint [@bertdeblock](https://github.com/bertdeblock) +- [#9495](https://github.com/ember-cli/ember-cli/pull/9495) Enable Embroider by default for new projects [@thoov](https://github.com/thoov) +- [#9500](https://github.com/ember-cli/ember-cli/pull/9500) Fix `lint:fix` script for Windows users [@lupestro](https://github.com/lupestro) + +Thank you to all who took the time to contribute! + + +## v3.26.1 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.26.0...v3.26.1) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.26.0...v3.26.1) + +#### Changelog + +- [#9504](https://github.com/ember-cli/ember-cli/pull/9504) Update `broccoli-concat` to avoid a cache invalidation problem in files larger than 10000 characters. [@brendenpalmer](https://github.com/brendenpalmer) + +Thank you to all who took the time to contribute! + +## v3.26.0 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.25.3...v3.26.0) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.25.3...v3.26.0) + +#### Changelog + +- [#9473](https://github.com/ember-cli/ember-cli/pull/9473) Issue a better error message for add-on's missing an entry point (e.g. invalid `ember-addon.main` path) [@ef4](https://github.com/ef4) +- [#9437](https://github.com/ember-cli/ember-cli/pull/9437) Add Prettier files to ".npmignore" file in addon blueprint [@bertdeblock](https://github.com/bertdeblock) +- [#9436](https://github.com/ember-cli/ember-cli/pull/9436) Enable Embroider test scenario for addons [@thoov](https://github.com/thoov) +- [#9435](https://github.com/ember-cli/ember-cli/pull/9435) Use "lint:fix" script in app and addon README files [@bertdeblock](https://github.com/bertdeblock) +- [#9451](https://github.com/ember-cli/ember-cli/pull/9451) update blueprint deps [@kellyselden](https://github.com/kellyselden) + +Thank you to all who took the time to contribute! + + +## v3.25.3 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.25.2...v3.25.3) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.25.2...v3.25.3) + +#### Changelog + +- [#9490](https://github.com/ember-cli/ember-cli/pull/9490) Prevent `window.Ember` deprecation when testing (for Ember 3.27+) [@rwjblue](https://github.com/rwjblue) + +Thank you to all who took the time to contribute! + +## v3.25.2 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.25.1...v3.25.2) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.25.1...v3.25.2) + +#### Changelog + +- [#9473](https://github.com/ember-cli/ember-cli/pull/9473) Issue a better error message for add-on's missing an entry point (e.g. invalid `ember-addon.main` path) [@ef4](https://github.com/ef4) + +Thank you to all who took the time to contribute! + + +## v3.25.1 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.25.0...v3.25.1) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.25.0...v3.25.1) + +#### Changelog + +- [#9467](https://github.com/ember-cli/ember-cli/pull/9467) Defer `The tests file was not loaded.` warning until after `DOMContentLoaded` [@ef4](https://github.com/ef4) + + +Thank you to all who took the time to contribute! + +## v3.25.0 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.24.0...v3.25.0) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.24.0...v3.25.0) + +#### Changelog + +- [#9450](https://github.com/ember-cli/ember-cli/pull/9450) update blueprint deps [@kellyselden](https://github.com/kellyselden) +- Update `ember-data` and `ember-source` to 3.25.0-beta [@kellyselden](https://github.com/kellyselden) / [@rwjblue](https://github.com/rwjblue) + +Thank you to all who took the time to contribute! + +## v3.24.0 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.23.0...v3.24.0) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.23.0...v3.24.0) + +#### Changelog + +- [#9410](https://github.com/ember-cli/ember-cli/pull/9410) Add `.eslintcache` to `.gitignore` for applications and addons [@simonihmig](https://github.com/simonihmig) +- [#9425](https://github.com/ember-cli/ember-cli/pull/9425) Update blueprint dependecies to latest. [@rwjblue](https://github.com/rwjblue) +- [#9372](https://github.com/ember-cli/ember-cli/pull/9372) / [#9382](https://github.com/ember-cli/ember-cli/pull/9382) Add `ember-page-title` to app blueprint [@raido](https://github.com/raido) +- [#9391](https://github.com/ember-cli/ember-cli/pull/9391) / [#9407](https://github.com/ember-cli/ember-cli/pull/9407) Add `prettier` to blueprint [@bmish](https://github.com/bmish) +- [#9402](https://github.com/ember-cli/ember-cli/pull/9402) Prevent build cycles when app is within a watched dir [@ef4](https://github.com/ef4) +- [#9403](https://github.com/ember-cli/ember-cli/pull/9403) Update blueprint to eslint-plugin-ember v10 [@bmish](https://github.com/bmish) +- [#9340](https://github.com/ember-cli/ember-cli/pull/9340) / [#9371](https://github.com/ember-cli/ember-cli/pull/9371) Update blueprints with new testing configuration [@scalvert](https://github.com/scalvert) + + +Thank you to all who took the time to contribute! + +## v3.23.0 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.22.0...v3.23.0) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.22.0...v3.23.0) + +#### Changelog + +- [#9369](https://github.com/ember-cli/ember-cli/pull/9369) / [#9406](https://github.com/ember-cli/ember-cli/pull/9406) Update blueprint dependencies to latest. [@rwjblue](https://github.com/rwjblue) +- [#9361](https://github.com/ember-cli/ember-cli/pull/9361) / [#9364](https://github.com/ember-cli/ember-cli/pull/9364) / [#9365](https://github.com/ember-cli/ember-cli/pull/9365) / [#9368](https://github.com/ember-cli/ember-cli/pull/9368) Update dependencies to latest. [@rwjblue](https://github.com/rwjblue) + +Thank you to all who took the time to contribute! + +## v3.22.0 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.21.0...v3.22.0) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.21.0...v3.22.0) + +#### Changelog + +- [#9325](https://github.com/ember-cli/ember-cli/pull/9325) Update dependencies for 3.22 beta series. [@rwjblue](https://github.com/rwjblue) +- [#9325](https://github.com/ember-cli/ember-cli/pull/9325) Update to `eslint-plugin-ember@9.0.0`. [@rwjblue](https://github.com/rwjblue) +- [#9336](https://github.com/ember-cli/ember-cli/pull/9336) Fixup internal test harness fixturify-project helper. [@rwjblue](https://github.com/rwjblue) +- [#9338](https://github.com/ember-cli/ember-cli/pull/9338) Remove requirement to have `loader.js`. [@rwjblue](https://github.com/rwjblue) +- [#9343](https://github.com/ember-cli/ember-cli/pull/9343) Fix yuidoc for private APIs [@jenweber](https://github.com/jenweber) +- [#9359](https://github.com/ember-cli/ember-cli/pull/9359) Upgrade to tiny-lr v2.0.0 [@elwayman02](https://github.com/elwayman02) +- [#9360](https://github.com/ember-cli/ember-cli/pull/9360) Update blueprint dependencies to latest version. [@rwjblue](https://github.com/rwjblue) + +Thank you to all who took the time to contribute! + +## v3.21.2 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.21.1...v3.21.2) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.21.1...v3.21.2) + +#### Changelog + +- [#9327](https://github.com/ember-cli/ember-cli/pull/9327) Update addon `README.md` to indicate Ember 3.16 minimum. [@kellyselden](https://github.com/kellyselden) + +Thank you to all who took the time to contribute! + +## v3.21.1 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.21.0...v3.21.1) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.21.0...v3.21.1) + +#### Changelog + +- [#9321](https://github.com/ember-cli/ember-cli/pull/9321) Add missing `ember-lts-3.20` matrix build to CI configuration. [@kellyselden](https://github.com/kellyselden) +- [#9323](https://github.com/ember-cli/ember-cli/pull/9323) Remove errant `ember-lts-3.12` matrix build from CI configuration. [@rwjblue](https://github.com/rwjblue) +- [#9324](https://github.com/ember-cli/ember-cli/pull/9324) Fix transpilation issues with modern browsers by migrating from `ember-cli-uglify` to `ember-cli-terser` [@rwjblue](https://github.com/rwjblue) + +Thank you to all who took the time to contribute! + +## v3.21.0 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.20.1...v3.21.0) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.20.1...v3.21.0) + +#### Changelog + +- [#9305](https://github.com/ember-cli/ember-cli/pull/9305) Update blueprint dependencies to latest versions. [@rwjblue](https://github.com/rwjblue) +- [#9306](https://github.com/ember-cli/ember-cli/pull/9306) Ensure that `outputReady` receives the final output directory. [@rwjblue](https://github.com/rwjblue) +- [#9308](https://github.com/ember-cli/ember-cli/pull/9308) Add Ember 3.20 LTS to ember-try configuration. [@rwjblue](https://github.com/rwjblue) +- [#9309](https://github.com/ember-cli/ember-cli/pull/9309) Update blueprint dependencies to latest [@rwjblue](https://github.com/rwjblue) +- [#9310](https://github.com/ember-cli/ember-cli/pull/9310) Drop Ember 3.12 from default addon testing matrix. [@rwjblue](https://github.com/rwjblue) +- [#9259](https://github.com/ember-cli/ember-cli/pull/9259) Implement [emberjs/rfcs#635](https://github.com/emberjs/rfcs/blob/master/text/0635-ember-new-lang.md): `ember new --lang` [@josephdsumner](https://github.com/josephdsumner) +- [#9299](https://github.com/ember-cli/ember-cli/pull/9299) Remove explicit `yarn install` in blueprint generated `.travis.yml` (use the Travis CI default of `yarn install --frozen-lockfile`) [@kellyselden](https://github.com/kellyselden) +- [#9289](https://github.com/ember-cli/ember-cli/pull/9289) Update blueprint dependencies / devDependencies [@rwjblue](https://github.com/rwjblue) + +## v3.20.2 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.20.1...v3.20.2) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.20.1...v3.20.2) + +#### Changelog + +- [#9321](https://github.com/ember-cli/ember-cli/pull/9321) Add missing `ember-lts-3.20` invocation to CI [@kellyselden](https://github.com/kellyselden) + +Thank you to all who took the time to contribute! + +## v3.20.1 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.20.0...v3.20.1) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.20.0...v3.20.1) + +#### Changelog + +- [#9308](https://github.com/ember-cli/ember-cli/pull/9308) Add Ember 3.20 LTS to ember-try configuration. [@rwjblue](https://github.com/rwjblue) + +Thank you to all who took the time to contribute! + +## v3.20.0 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.19.0...v3.20.0) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.19.0...v3.20.0) + +#### Changelog + +- [#9211](https://github.com/ember-cli/ember-cli/pull/9211) bugfix: processAppMiddlewares - check server middleware files [@lifeart](https://github.com/lifeart) +- [#9215](https://github.com/ember-cli/ember-cli/pull/9215) Handle unexpected errors in development mode proxy [@houfeng0923](https://github.com/houfeng0923) +- [#9238](https://github.com/ember-cli/ember-cli/pull/9238) Add config/ember-cli-update.json to app and addon blueprints. [@rwjblue](https://github.com/rwjblue) +- [#9262](https://github.com/ember-cli/ember-cli/pull/9262) Refactor release process. [@rwjblue](https://github.com/rwjblue) +- [#9264](https://github.com/ember-cli/ember-cli/pull/9264) refactor: use Boolean constructor to cast variable in config/targets.js blueprint [@bmish](https://github.com/bmish) +- [032e9a8851af869c7e0cf5ef8c3d930ade38b6c1](https://github.com/ember-cli/ember-cli/commit/032e9a8851af869c7e0cf5ef8c3d930ade38b6c1) Merge branch 'master' into default-blueprint-absolute-imports [@dfreeman](https://github.com/dfreeman) +- [#9273](https://github.com/ember-cli/ember-cli/pull/9273) Avoid relative imports in the default blueprint [@dfreeman](https://github.com/dfreeman) +- [#9277](https://github.com/ember-cli/ember-cli/pull/9277) Allow `ember install` to work with Yarn v2 [@caassandra](https://github.com/caassandra) +- [#9280](https://github.com/ember-cli/ember-cli/pull/9280) Remove `ember-default` ember-try scenario [@mehulkar](https://github.com/mehulkar) +- [#9281](https://github.com/ember-cli/ember-cli/pull/9281) Update blueprint dependencies to latest versions. [@rwjblue](https://github.com/rwjblue) +- [#9282](https://github.com/ember-cli/ember-cli/pull/9282) Deprecate `PACKAGER` experiment. [@rwjblue](https://github.com/rwjblue) +- [#9283](https://github.com/ember-cli/ember-cli/pull/9283) Remove macOS from CI matrix for slow/acceptance tests. [@rwjblue](https://github.com/rwjblue) +- [#9284](https://github.com/ember-cli/ember-cli/pull/9284) Drop support for Node 13. [@rwjblue](https://github.com/rwjblue) +- [56461f26a9b81833f424bf1a23c7ce502d35a43b](https://github.com/ember-cli/ember-cli/commit/56461f26a9b81833f424bf1a23c7ce502d35a43b) Merge branch 'master' into master [@caassandra](https://github.com/caassandra) +- [#9286](https://github.com/ember-cli/ember-cli/pull/9286) Remove unused `lib/utilities/symbol.js` [@IzzatN](https://github.com/IzzatN) +- [#9287](https://github.com/ember-cli/ember-cli/pull/9287) Remove redundant guard in `Addon.prototype.moduleName` [@IzzatN](https://github.com/IzzatN) + +Thank you to all who took the time to contribute! + +## v3.19.0 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.18.0...v3.19.0) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.19.0...v3.19.0) + +#### Changelog + +- [#9239](https://github.com/ember-cli/ember-cli/pull/9239) Update app / addon dependencies to latest. [@rwjblue](https://github.com/rwjblue) +- [#9205](https://github.com/ember-cli/ember-cli/pull/9205) Pass options to middleware [@mrloop](https://github.com/mrloop) +- [#9209](https://github.com/ember-cli/ember-cli/pull/9209) Create small development script to update blueprint dependencies. [@rwjblue](https://github.com/rwjblue) +- [#9212](https://github.com/ember-cli/ember-cli/pull/9212) Ensure that the captured exit is released. [@rwjblue](https://github.com/rwjblue) +- [#9218](https://github.com/ember-cli/ember-cli/pull/9218) Update eslint to 7.0.0. [@rwjblue](https://github.com/rwjblue) +- [#9219](https://github.com/ember-cli/ember-cli/pull/9219) Add ability to pass `--filter` to `dev/update-blueprint-dependencies.js` [@rwjblue](https://github.com/rwjblue) +- [#9240](https://github.com/ember-cli/ember-cli/pull/9240) Ensure `ember serve` property waits for the serve task. [@rwjblue](https://github.com/rwjblue) +- [#9242](https://github.com/ember-cli/ember-cli/pull/9242) Move travis configuration from trusty to xenial [@Gaurav0](https://github.com/Gaurav0) +- [#7538](https://github.com/ember-cli/ember-cli/pull/7538) Fix `configPath` caching [@kanongil](https://github.com/kanongil) +- [#8258](https://github.com/ember-cli/ember-cli/pull/8258) Tweak `isDevelopingAddon` error message [@stefanpenner](https://github.com/stefanpenner) +- [#8813](https://github.com/ember-cli/ember-cli/pull/8813) Update NPM version check to avoid double `npm install` when using `npm@5.7.1` or higher. [@deepan83](https://github.com/deepan83) +- [#9126](https://github.com/ember-cli/ember-cli/pull/9126) chore: fix init help text with the right description [@rajasegar](https://github.com/rajasegar) +- [#9132](https://github.com/ember-cli/ember-cli/pull/9132) Convert commands to use async/await syntax [@locks](https://github.com/locks) +- [#9134](https://github.com/ember-cli/ember-cli/pull/9134) [DOC] Update locals hook example [@locks](https://github.com/locks) +- [#9146](https://github.com/ember-cli/ember-cli/pull/9146) Convert express-server task to async await [@locks](https://github.com/locks) +- [#9147](https://github.com/ember-cli/ember-cli/pull/9147) Convert serve task to async await [@locks](https://github.com/locks) +- [#9148](https://github.com/ember-cli/ember-cli/pull/9148) Convert npm-task task to async/await syntax [@locks](https://github.com/locks) +- [#9149](https://github.com/ember-cli/ember-cli/pull/9149) Update blueprint dependencies to latest [@bmish](https://github.com/bmish) +- [#9157](https://github.com/ember-cli/ember-cli/pull/9157) Convert insert-into-file to async/await syntax [@locks](https://github.com/locks) +- [#9158](https://github.com/ember-cli/ember-cli/pull/9158) Convert clean-remove to async/await syntax [@locks](https://github.com/locks) +- [#9163](https://github.com/ember-cli/ember-cli/pull/9163) Convert in-option-generate-test to async/await syntax [@locks](https://github.com/locks) +- [#9183](https://github.com/ember-cli/ember-cli/pull/9183) Ensure processed addon styles are not doubly-included in vendor.css [@bantic](https://github.com/bantic) + +## v3.18.0 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.17.0...v3.18.0) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.17.0...v3.18.0) + +#### Changelog + +- [#9063](https://github.com/ember-cli/ember-cli/pull/9063) Fix typo in `Blueprint` documentation. [@bartocc](https://github.com/bartocc) +- [#9068](https://github.com/ember-cli/ember-cli/pull/9068) Adds link to CLI commands doc from README [@entendu](https://github.com/entendu) +- [#9070](https://github.com/ember-cli/ember-cli/pull/9070) Fix a number of causes of unhandled rejections (and ensure tests fail when unhandled rejection occurs). [@stefanpenner](https://github.com/stefanpenner) +- [#9072](https://github.com/ember-cli/ember-cli/pull/9072) Ensure errors during build are properly reported to the console. [@stefanpenner](https://github.com/stefanpenner) +- [#9092](https://github.com/ember-cli/ember-cli/pull/9092) Update `ember-source` and `ember-data` to 3.18 betas. [@rwjblue](https://github.com/rwjblue) +- [#9097](https://github.com/ember-cli/ember-cli/pull/9097) Update production dependencies to latest. [@rwjblue](https://github.com/rwjblue) +- [#9108](https://github.com/ember-cli/ember-cli/pull/9108) Cleanup of async in `CLI` / `Builder` while digging into issues around progress clean up. [@rwjblue](https://github.com/rwjblue) +- [#9188](https://github.com/ember-cli/ember-cli/pull/9188) Add Node 14 to CI [@rwjblue](https://github.com/rwjblue) +- [#9208](https://github.com/ember-cli/ember-cli/pull/9208) Update blueprint dependencies to latest versions. [@rwjblue](https://github.com/rwjblue) +- [#9090](https://github.com/ember-cli/ember-cli/pull/9183) Ensure processed addon styles are not doubly-included in vendor.css [@bantic](https://github.com/bantic) + +Thank you to all who took the time to contribute! + +## v3.17.0 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.16.1...v3.17.0) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.16.1...v3.17.0) + +#### Changelog + +- [#9045](https://github.com/ember-cli/ember-cli/pull/9045) Add final newline in CONTRIBUTING.md [@kellyselden](https://github.com/kellyselden) +- [c30ed27181257ab4319b3a06134e13067ac1e76e](https://github.com/ember-cli/ember-cli/commit/c30ed27181257ab4319b3a06134e13067ac1e76e) Handle a number of unhandled rejections scenarios [@stefanpenner](https://github.com/stefanpenner) +- [c377300bb21485faf0137ce69b54a10b3a458828](https://github.com/ember-cli/ember-cli/commit/c377300bb21485faf0137ce69b54a10b3a458828) Publish yuidoc json as a part of npm package [@sivakumar-kailasam](https://github.com/sivakumar-kailasam) +- [0a8d7a18b5f27147f2cec5574625e53784841601](https://github.com/ember-cli/ember-cli/commit/0a8d7a18b5f27147f2cec5574625e53784841601) Consistently 'use strict'; for our node js files [@kellyselden](https://github.com/kellyselden) +- [64e635c48c76f177769ca73eb9a228149ffbd863](https://github.com/ember-cli/ember-cli/commit/64e635c48c76f177769ca73eb9a228149ffbd863) Ensure buildFailures are reported correctly [@stefanpenner](https://github.com/stefanpenner) +- [#9037](https://github.com/ember-cli/ember-cli/pull/9037) Update Ember and Ember Data to 3.17 betas. [@rwjblue](https://github.com/rwjblue) +- [#9039](https://github.com/ember-cli/ember-cli/pull/9039) Remove long enabled EMBER_CLI_SYSTEM_TEMP experiment. [@rwjblue](https://github.com/rwjblue) +- [#9038](https://github.com/ember-cli/ember-cli/pull/9038) Remove EMBER_CLI_DELAYED_TRANSPILATION experiment. [@rwjblue](https://github.com/rwjblue) +- [#9040](https://github.com/ember-cli/ember-cli/pull/9040) Remove MODULE_UNIFICATION experiment. [@rwjblue](https://github.com/rwjblue) +- [#9009](https://github.com/ember-cli/ember-cli/pull/9009) Use `eslint` and `ember-template-lint` directly (no longer lint during builds/rebuilds by default) [@dcyriller](https://github.com/dcyriller) +- [#9041](https://github.com/ember-cli/ember-cli/pull/9041) Remove usage of RSVP. [@rwjblue](https://github.com/rwjblue) +- [#9042](https://github.com/ember-cli/ember-cli/pull/9042) Include API documentation `yuidoc` JSON output when publishing [@sivakumar-kailasam](https://github.com/sivakumar-kailasam) +- [#9045](https://github.com/ember-cli/ember-cli/pull/9045) Add final newline in CONTRIBUTING.md [@kellyselden](https://github.com/kellyselden) + +## v3.16.2 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.16.1...v3.16.2) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.16.1...v3.16.2) + +#### Changelog + +- [#9090](https://github.com/ember-cli/ember-cli/pull/9183) Ensure processed addon styles are not doubly-included in vendor.css [@bantic](https://github.com/bantic) + +## v3.16.1 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.16.0...v3.16.1) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.16.0...v3.16.1) + +#### Changelog + +- [#9090](https://github.com/ember-cli/ember-cli/pull/9090) Backports of critical bugfixes to LTS (3.16) [@rwjblue](https://github.com/rwjblue) +- [#9045](https://github.com/ember-cli/ember-cli/pull/9045) Add final newline in CONTRIBUTING.md [@kellyselden](https://github.com/kellyselden) +- [c30ed27181257ab4319b3a06134e13067ac1e76e](https://github.com/ember-cli/ember-cli/commit/c30ed27181257ab4319b3a06134e13067ac1e76e) Handle a number of unhandled rejections scenarios [@stefanpenner](https://github.com/stefanpenner) +- [c377300bb21485faf0137ce69b54a10b3a458828](https://github.com/ember-cli/ember-cli/commit/c377300bb21485faf0137ce69b54a10b3a458828) Publish yuidoc json as a part of npm package [@sivakumar-kailasam](https://github.com/sivakumar-kailasam) +- [0a8d7a18b5f27147f2cec5574625e53784841601](https://github.com/ember-cli/ember-cli/commit/0a8d7a18b5f27147f2cec5574625e53784841601) Consistently 'use strict'; for our node js files [@kellyselden](https://github.com/kellyselden) +- [64e635c48c76f177769ca73eb9a228149ffbd863](https://github.com/ember-cli/ember-cli/commit/64e635c48c76f177769ca73eb9a228149ffbd863) Ensure buildFailures are reported correctly [@stefanpenner](https://github.com/stefanpenner) + +Thank you to all who took the time to contribute! + +## v3.16.0 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.15.2...v3.16.0) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.15.2...v3.16.0) + +#### Changelog + +- [#8905](https://github.com/ember-cli/ember-cli/pull/8905) Use production environment for `npm run build` / `yarn build` by default [@pichfl](https://github.com/pichfl) +- [#8930](https://github.com/ember-cli/ember-cli/pull/8930) / [#8929](https://github.com/ember-cli/ember-cli/pull/8929) Drop Node 11 support [@SergeAstapov](https://github.com/SergeAstapov) +- [#8932](https://github.com/ember-cli/ember-cli/pull/8932) Add Node.js 13 to test matrix [@SergeAstapov](https://github.com/SergeAstapov) +- [#8941](https://github.com/ember-cli/ember-cli/pull/8941) feat(blueprint): resolve remote blueprints via package manager [@buschtoens](https://github.com/buschtoens) +- [#8944](https://github.com/ember-cli/ember-cli/pull/8944) Travis.yml: Remove deprecated `sudo: false` option [@tniezurawski](https://github.com/tniezurawski) +- [#8943](https://github.com/ember-cli/ember-cli/pull/8943) Travis.yml: use fast_finish instead of undocumented fail_fast [@tniezurawski](https://github.com/tniezurawski) +- [#8962](https://github.com/ember-cli/ember-cli/pull/8962) Drop Ember 3.8 and add Ember 3.16 scenarios in default `config/ember-try.js`. [@kellyselden](https://github.com/kellyselden) +- [#8986](https://github.com/ember-cli/ember-cli/pull/8986) Increase testem browser timeout. [@rwjblue](https://github.com/rwjblue) +- [#9012](https://github.com/ember-cli/ember-cli/pull/9012) Drop support for Node v8 [@jrjohnson](https://github.com/jrjohnson) +- [#9013](https://github.com/ember-cli/ember-cli/pull/9013) Remove useless line break in `.editorconfig` file [@dcyriller](https://github.com/dcyriller) +- [#9023](https://github.com/ember-cli/ember-cli/pull/9023) Update to use Ember + Ember Data 3.16. [@rwjblue](https://github.com/rwjblue) +- [#9026](https://github.com/ember-cli/ember-cli/pull/9026) Add @glimmer/tracking to default blueprint. [@rwjblue](https://github.com/rwjblue) +- [#9028](https://github.com/ember-cli/ember-cli/pull/9028) Update minimum versions of app / addon blueprint dependencies. [@rwjblue](https://github.com/rwjblue) + +Thank you to all who took the time to contribute! + +## v3.15.2 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.15.1...v3.15.2) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.15.1...v3.15.2) + +#### Changelog + +- [#8924](https://github.com/ember-cli/ember-cli/pull/8924) Fix named UMD imports [@kellyselden](https://github.com/kellyselden) +- [#9015](https://github.com/ember-cli/ember-cli/pull/9015) Allow failure of Node 8 CI jobs. [@rwjblue](https://github.com/rwjblue) +- [#9014](https://github.com/ember-cli/ember-cli/pull/9014) Avoid errors when `ui` is not present and a warning will be emitted. [@tmquinn](https://github.com/tmquinn) + +Thank you to all who took the time to contribute! + +## v3.15.1 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.15.0...v3.15.1) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.15.0...v3.15.1) + +#### Changelog + +- [#8977](https://github.com/ember-cli/ember-cli/pull/8977) Fix invalid syntax with ember-classic ember-try scenario. [@rwjblue](https://github.com/rwjblue) + +Thank you to all who took the time to contribute! + +## v3.15.0 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.14.0...v3.15.0) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.14.0...v3.15.0) + +#### Changelog + +- [#8963](https://github.com/ember-cli/ember-cli/pull/8963) Remove `app/templates/components` [@chancancode](https://github.com/chancancode) +- [#8964](https://github.com/ember-cli/ember-cli/pull/8964) Add support for `ember new @scope-here/name-here`. [@rwjblue](https://github.com/rwjblue) +- [#8965](https://github.com/ember-cli/ember-cli/pull/8965) Update ember-resolver to v7.0.0. [@rwjblue](https://github.com/rwjblue) +- [#8971](https://github.com/ember-cli/ember-cli/pull/8971) Add an ember-try scenario for Ember "classic" (pre-octane). [@rwjblue](https://github.com/rwjblue) +- [#8972](https://github.com/ember-cli/ember-cli/pull/8972) Update ember-data to 3.15.0. [@rwjblue](https://github.com/rwjblue) +- [#8933](https://github.com/ember-cli/ember-cli/pull/8933) Remove `app/resolver.js` in favor of importing in `app/app.js` [@rwjblue](https://github.com/rwjblue) +- [#8945](https://github.com/ember-cli/ember-cli/pull/8945) Fix issue with addon `.travis.yml` configuration when using `npm` [@kellyselden](https://github.com/kellyselden) +- [#8946](https://github.com/ember-cli/ember-cli/pull/8946) Drop testing of ember-source@3.4 in the addon blueprints ember-try config [@kellyselden](https://github.com/kellyselden) +- [#8946](https://github.com/ember-cli/ember-cli/pull/8946) Add testing of ember-source@3.12 in the addon blueprints ember-try config [@kellyselden](https://github.com/kellyselden) +- [#8959](https://github.com/ember-cli/ember-cli/pull/8959) Fix issue with addon discovery when npm/yarn leave empty directories in resolvable locations [@stefanpenner](https://github.com/stefanpenner) +- [#8961](https://github.com/ember-cli/ember-cli/pull/8961) Prepare for Octane release in 3.15 [@rwjblue](https://github.com/rwjblue) + * Adds `ember` property to `package.json` to implement [emberjs/rfcs#558](https://github.com/emberjs/rfcs/pull/558) + * Adds `@glimmer/component@1.0.0` as a development dependency for both apps and addons + * Updates `ember-try` to at least 1.4.0 in order to support `config/ember-try.js` scenarios with `ember` `package.json` property (mentioned in emberjs/rfcs#558) + * Enables Octane related optional features + * Updates ember-template-lint configuration to use `octane` preset + * Update to ember-source@3.15 stable + * Updates all packages in the application blueprint to their latest version +- [#8827](https://github.com/ember-cli/ember-cli/pull/8827) Remove module-unification blueprints [@dcyriller](https://github.com/dcyriller) +- [#8878](https://github.com/ember-cli/ember-cli/pull/8878) Adds flag to throw an error for invalid addon locations [@tmquinn](https://github.com/tmquinn) +- [#8906](https://github.com/ember-cli/ember-cli/pull/8906) Enable broccoli memoization by default in Ember-CLI [@SparshithNR](https://github.com/SparshithNR) +- [#8917](https://github.com/ember-cli/ember-cli/pull/8917) Update CI configuration for applications using `npm` to run a "floating dependencies" test. [@kellyselden](https://github.com/kellyselden) +- [#8926](https://github.com/ember-cli/ember-cli/pull/8926) Add `application` to invalid names [@kennethlarsen](https://github.com/kennethlarsen) + +Thank you to all who took the time to contribute! + +## v3.14.0 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.13.2...v3.14.0) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.13.2...v3.14.0) + +#### Changelog + +- [#8875](https://github.com/ember-cli/ember-cli/pull/8875) Fix ember-cli-htmlbars-inline-precompile deprecation [@HeroicEric](https://github.com/HeroicEric) +- [#8882](https://github.com/ember-cli/ember-cli/pull/8882) Simplify "Get started" message for `ember new` [@dcyriller](https://github.com/dcyriller) +- [#8899](https://github.com/ember-cli/ember-cli/pull/8899) Don't reload the config for instrumentation [@pzuraq](https://github.com/pzuraq) +- [#8900](https://github.com/ember-cli/ember-cli/pull/8900) Include `legacyDecorators` eslint option by default [@pzuraq](https://github.com/pzuraq) +- [#8901](https://github.com/ember-cli/ember-cli/pull/8901) Merge `config/environment.js`'s `EmberENV` configuration with any pre-existing `EmberENV` [@chancancode](https://github.com/chancancode) +- [#8910](https://github.com/ember-cli/ember-cli/pull/8910) Update TravisCI config for `ember new` to restrict CI runs to `master` branch and pull requests [@kellyselden](https://github.com/kellyselden) +- [#8915](https://github.com/ember-cli/ember-cli/pull/8915) Revert changes made to enable "octane" as the default for `ember new` [@rwjblue](https://github.com/rwjblue) +- [#8916](https://github.com/ember-cli/ember-cli/pull/8916) Update ember-source and ember-data to 3.14.x [@rwjblue](https://github.com/rwjblue) +- [#8853](https://github.com/ember-cli/ember-cli/pull/8853) Update ember-resolver to 5.3.0. [@rwjblue](https://github.com/rwjblue) +- [#8812](https://github.com/ember-cli/ember-cli/pull/8812) Clarify installation error message [@jrjohnson](https://github.com/jrjohnson) +- [#8820](https://github.com/ember-cli/ember-cli/pull/8820) Issue deprecation when enabling MODULE_UNIFICATION flag. [@rwjblue](https://github.com/rwjblue) + +## v3.13.2 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.13.1...v3.13.2) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.13.1...v3.13.2) + +#### Changelog + +- [#8875](https://github.com/ember-cli/ember-cli/pull/8875) Fix ember-cli-htmlbars-inline-precompile deprecation [@HeroicEric](https://github.com/HeroicEric) +- [#8882](https://github.com/ember-cli/ember-cli/pull/8882) Simplify "Get started" message [@dcyriller](https://github.com/dcyriller) +- [#8901](https://github.com/ember-cli/ember-cli/pull/8901) Merge `config/environment.js`'s `EmberENV` configuration with any pre-existing `EmberENV` [@chancancode](https://github.com/chancancode) + +Thank you to all who took the time to contribute! ## v3.13.1 @@ -8,6 +610,7 @@ - [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.13.0...v3.13.1) - [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.13.0...v3.13.1) +#### Changelog - [#8857](https://github.com/ember-cli/ember-cli/pull/8857) Tweaks to release scripts. [@rwjblue](https://github.com/rwjblue) - [#8862](https://github.com/ember-cli/ember-cli/pull/8862) Adjust message for when a new app is created [@dcyriller](https://github.com/dcyriller) @@ -21,6 +624,7 @@ Thank you to all who took the time to contribute! - [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.12.0...v3.13.0) - [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.12.0...v3.13.0) +#### Changelog - [#8797](https://github.com/ember-cli/ember-cli/pull/8797) Update heimdalljs-fs-monitor to 0.2.3. [@rwjblue](https://github.com/rwjblue) - [#8798](https://github.com/ember-cli/ember-cli/pull/8798) Update blueprint reference for ember-source to 3.13.0-beta.2. [@rwjblue](https://github.com/rwjblue) @@ -46,6 +650,19 @@ Thank you to all who took the time to contribute! Thank you to all who took the time to contribute! +## v3.12.1 + +#### Blueprint Changes + +- [`ember new` diff](https://github.com/ember-cli/ember-new-output/compare/v3.12.0...v3.12.1) +- [`ember addon` diff](https://github.com/ember-cli/ember-addon-output/compare/v3.12.0...v3.12.1) + +#### Community Contributions + +- [#8797](https://github.com/ember-cli/ember-cli/pull/8797) Update heimdalljs-fs-monitor to 0.2.3. [@rwjblue](https://github.com/rwjblue) +- [#8959](https://github.com/ember-cli/ember-cli/pull/8959) Ensure `node_modules/*` directories without a `package.json` are not considered as part of the addon discovery process [@stefanpenner](https://github.com/stefanpenner) + +Thank you to all who took the time to contribute! ## v3.12.0 @@ -73,10 +690,10 @@ Thank you to all who took the time to contribute! - [#8662](https://github.com/ember-cli/ember-cli/pull/8662) use async/await in ember-try config [@kellyselden](https://github.com/kellyselden) - [#8664](https://github.com/ember-cli/ember-cli/pull/8664) fix README ember min version [@kellyselden](https://github.com/kellyselden) - [#8674](https://github.com/ember-cli/ember-cli/pull/8674) ensure we use Promise.prototype.finally polyfil for node 8 compat [@stefanpenner](https://github.com/stefanpenner) -- [#8675](https://github.com/stefanpenner/stefanpenner/pull/8675) [beta] Gather hardware information when creating instrumentation summaries. [@stefanpenner](https://github.com/stefanpenner) -- [#8679](https://github.com/stefanpenner/stefanpenner/pull/8679) Adding change logging for backwards compatibility [@thoov](https://github.com/thoov) -- [#8680](https://github.com/stefanpenner/stefanpenner/pull/8680) [Fixes #8677] ensure watcher parity [@stefanpenner](https://github.com/stefanpenner) -- [#8595](https://github.com/stefanpenner/stefanpenner/pull/8595) `Project#config` should use `EMBER_ENV` as default environment when none is passed in [@nlfurniss](https://github.com/nlfurniss) +- [#8675](https://github.com/ember-cli/ember-cli/pull/8675) [beta] Gather hardware information when creating instrumentation summaries. [@stefanpenner](https://github.com/stefanpenner) +- [#8679](https://github.com/ember-cli/ember-cli/pull/8679) Adding change logging for backwards compatibility [@thoov](https://github.com/thoov) +- [#8680](https://github.com/ember-cli/ember-cli/pull/8680) [Fixes #8677] ensure watcher parity [@stefanpenner](https://github.com/stefanpenner) +- [#8595](https://github.com/ember-cli/ember-cli/pull/8595) `Project#config` should use `EMBER_ENV` as default environment when none is passed in [@nlfurniss](https://github.com/nlfurniss) - [#8604](https://github.com/stefanpenner/ember-cli/pull/8604) CONTRIBUTING: Clarify the way to start working on the repo. [@MonsieurDart](https://github.com/MonsieurDart) - [#8621](https://github.com/ember-cli/ember-cli/pull/8621) project.findAddonByName was intended to be public [@stefanpenner](https://github.com/stefanpenner) - [#8697](https://github.com/ember-cli/ember-cli/pull/8697) Update to Broccoli 3.1. [@thoov](https://github.com/thoov) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4f028ea4fa..3b5e31947e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -210,8 +210,8 @@ updating or writing new documentation: #### Website -The codebase for the website [ember-cli.com](https://ember-cli.com) is located -at: https://github.com/ember-cli/ember-cli.github.io +The codebase for the website [cli.emberjs.com](https://cli.emberjs.com) is located +at: https://github.com/ember-learn/cli-guides #### Code Words diff --git a/README.md b/README.md index 17b4a30393..bfc0f563a0 100644 --- a/README.md +++ b/README.md @@ -3,22 +3,19 @@ ember-cli ============================================================================== [![Latest npm release][npm-badge]][npm-badge-url] -[![TravisCI Build Status][travis-badge]][travis-badge-url] -[![AppVeyor Build Status][appveyor-badge]][appveyor-badge-url] +[![GitHub Actions CI][github-actions-badge]][github-actions-ci-url] [![Test Coverage][coveralls-badge]][coveralls-badge-url] [![Code Climate][codeclimate-badge]][codeclimate-badge-url] [logo]: https://avatars0.githubusercontent.com/u/10262982?v=3&s=150 [npm-badge]: https://img.shields.io/npm/v/ember-cli.svg [npm-badge-url]: https://www.npmjs.com/package/ember-cli -[travis-badge]: https://img.shields.io/travis/ember-cli/ember-cli/master.svg?label=TravisCI -[travis-badge-url]: https://travis-ci.org/ember-cli/ember-cli -[appveyor-badge]: https://img.shields.io/appveyor/ci/embercli/ember-cli/master.svg?label=AppVeyor -[appveyor-badge-url]: https://ci.appveyor.com/project/embercli/ember-cli/branch/master [coveralls-badge]: https://img.shields.io/coveralls/ember-cli/ember-cli/master.svg [coveralls-badge-url]: https://coveralls.io/github/ember-cli/ember-cli [codeclimate-badge]: https://codeclimate.com/github/ember-cli/ember-cli/badges/gpa.svg [codeclimate-badge-url]: https://codeclimate.com/github/ember-cli/ember-cli +[github-actions-badge]: https://github.com/ember-cli/ember-cli/workflows/CI/badge.svg +[github-actions-ci-url]: https://github.com/ember-cli/ember-cli/actions?query=workflow%3ACI The Ember.js command line utility. @@ -49,8 +46,8 @@ Usage After installation the `ember` CLI tool will be available to you. It is the entrypoint for all the functionality mentioned above. -You can call `ember --help` to find out more about all of the -following commands or visit to read +You can call `ember --help` to find out more about [all of the +following commands](https://cli.emberjs.com/release/basic-use/cli-commands/) or visit to read the in-depth documentation. diff --git a/RELEASE.md b/RELEASE.md index d6d12b71bb..76b67cdbc3 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,262 +1,175 @@ # Release Process -Although not very tricky, it is quite easy to deploy something that doesn't -quite work as expected. The following steps help you navigate through some of -the release gotchas and will hopefully result in a successful release. +ember-cli follows the same channel based release process that Ember does: ---- +* `release` - This branch represents the `latest` dist-tag on NPM +* `beta` - This branch represents the `beta` dist-tag on NPM +* `master` - The branch is not released to the NPM registry, but (generally speaking) can be used directly -## Preparation +## Initial Stable Release -### Communication +* Update blueprint dependencies to latest -- Ensure that homu isn't presently processing any PRs. -- Post a note in [#dev-ember-cli](https://discordapp.com/channels/480462759797063690/480501885837770763) letting us know you're doing a release. - -> I'm starting an Ember CLI release. Please hold off merging PRs and pushing new code! - -### Environment - -Make sure that you're running the most recent stable `node` and bundled `npm`. - -```sh -node --version -npm --version +``` +node ./dev/update-blueprint-dependencies.js --ember-source=latest --ember-data=latest ``` -## Branching +* Commit +* Send pull request to `beta` branch +* Wait for PR (for updating blueprint dependencies) to be merged -If you're planning to release a stable/bugfix version _and_ a beta, make sure to release the beta _after_ the stable version. +* Checkout the `release` branch -```sh -# Fetch changes from GitHub +``` git fetch origin +git checkout -B release --track origin/release ``` -Once you're done following these instructions make sure that you push your `master`, `beta`, and `release` branches back to GitHub. - - -### Promoting beta to stable +* Merge `beta` branch into `release` -Follow these steps if you're releasing a new minor or major version (e.g. from `v2.5.0` to `v2.6.0`): +``` +git fetch origin +git merge origin/beta +git push origin release +``` -```sh -# Switch to "release" branch and reset it to "origin/beta" -git checkout -B release --track origin/beta +* Ensure you have the correct dependencies -# Merge any unmerged changes from "origin/release" back in -git merge origin/release +``` +git clean -fdx +yarn +``` -# ... do the stable release ... +* Update the CHANGELOG.md + * Run `node ./dev/changelog` + * Copy output into `CHANGELOG.md` + * Edit to make clearer for consumers (remove non-consumer facing entries, etc) + * Ensure blueprint change diff URLs are correct + * Merge any prior beta entries together + * Update changelog header for current release + * Commit +* Release: `npx release-it` +* Update GitHub Release with changelog contents +* Merge `release` into `beta` branch -# Switch to "beta" branch and reset it to "origin/beta" +``` git checkout -B beta --track origin/beta - -# Merge the new stable release into the "beta" branch -git merge vX.Y.0 +git merge origin/release +git push origin beta ``` -### Stable bugfix release +## Stable Patch Release -Follow these steps if you're releasing a bugfix for a stable version (e.g. from `v2.5.0` to `v2.5.1`) +* Checkout the `release` branch -```sh -# Switch to "release" branch and reset it to "origin/release" +``` +git fetch origin git checkout -B release --track origin/release - -# ... do the stable release ... - -# Switch to "beta" branch and reset it to "origin/beta" -git checkout -B beta --track origin/beta - -# Merge the new stable release into the "beta" branch -git merge vX.Y.Z ``` +* Ensure you have the correct dependencies -### Promoting canary to beta - -Follow these steps if you're releasing a beta version after a new minor/major release (e.g. `v2.7.0-beta.1`) - -```sh -# Switch to "beta" branch and reset it to "origin/master" -git checkout -B beta --track origin/master - -# Merge any unmerged changes from "origin/beta" back in -git merge origin/beta - -# ... do the beta release ... - -# Switch to "master" branch and reset it to "origin/master" -git checkout -B master --track origin/master - -# Merge the new beta release into the "master" branch -git merge vX.Y.0-beta.1 - -# Push back upstream. -git push origin +``` +git clean -fdx +yarn ``` +* Update the CHANGELOG.md + * Run `node ./dev/changelog` + * Copy output into `CHANGELOG.md` + * Edit to make clearer for consumers (remove non-consumer facing entries, etc) + * Ensure blueprint change diff URLs are correct + * Update changelog header for current release + * Commit +* Release: `npx release-it` +* Update GitHub Release with changelog contents +* Merge `release` into `beta` branch -### Incremental beta release - -Follow these steps if you're releasing a beta version following another beta (e.g. `v2.7.0-beta.N` with `N != 1`) - -```sh -# Switch to "beta" branch and reset it to "origin/beta" +``` git checkout -B beta --track origin/beta - -# ... do the beta release ... - -# Switch to "master" branch and reset it to "origin/master" -git checkout -B master --track origin/master - -# Merge the new beta release into the "master" branch -git merge vX.Y.0-beta.N +git merge origin/release +git push origin beta ``` -## Release +## Initial Beta Release -### Setup +* Update `ember-source` and `ember-data` to latest beta -* Update Ember and Ember Data versions. - * `blueprints/app/files/package.json` - * if you're releasing a new minor or major version: - * `tests/fixtures/addon/npm/package.json` - * `tests/fixtures/addon/yarn/package.json` - * `tests/fixtures/app/npm/package.json` - * `tests/fixtures/app/yarn/package.json` -* generate changelog - * if on master branch - * run `./dev/changelog` - * if this is a beta - * run `./dev/changelog beta` - * if this is a release - * run `./dev/changelog release` -* prepend changelog output to `CHANGELOG.md` -* edit changelog output to be as user-friendly as possible (drop [INTERNAL] changes, non-code changes, etc.) -* replace any "ember-cli" user references in the changelog to whomever made the change -* bump `package.json` version -* don't commit these changes until later -* run `./dev/prepare-release` -* the `du` command should give you ballpark 190K as of `3.8.0` - -### Test - -* `cd to/someplace/to/test/` -* ensure `ember version` is the newly packaged version - -```shell -# ensure new project generation works -ember new --skip-npm my-cool-test-project -cd my-cool-test-project - -# link your local ember-cli -npm link ember-cli - -# install other deps -npm install - -# test the server -ember serve +``` +node ./dev/update-blueprint-dependencies.js --ember-source=beta --ember-data=beta ``` -* test other things like generators and live-reload -* generate an http mock `ember g http-mock my-http-mock` -* test upgrades of other apps -* if releasing using Windows, check that it works on a Linux VM - * we are checking if any Windows line endings snuck in, because they won't work on Unix -* if releasing using Unix, you are set, Windows can read your line endings - -### Update Artifacts - -* run `node ./dev/update-output-repos.js` - -### Publish - -If everything went well, publish. Please note, we must have an extremely low -tolerance for quirks and failures. We do not want our users to endure any extra -pain. - -1. go back to ember-cli directory -* `git add` the modified `package.json` and `CHANGELOG.md` -* Commit the changes `git commit -m "Release vx.y.z"` and push `git push` -* `git tag "vx.y.z"` -* `git push origin ` -* publish to npm - * if normal release - * `npm publish ./ember-cli-.tgz` - * if beta release - * `npm publish ./ember-cli-.tgz --tag beta` - - -### Test Again - -Test published version - -1. `npm uninstall -g ember-cli` -* `npm cache clear` -* install - * if normal release - * `npm install -g ember-cli` - * if beta release - * `npm install -g ember-cli@beta` -* ensure version is as expected `ember version` -* ensure new project generates -* ensure old project upgrades nicely - - -## Promote - -Announce release! +* Commit +* Send pull request to `master` branch +* Wait for PR (for updating blueprint dependencies) to be merged +* Checkout the `beta` branch -### Create a new release on GitHub +``` +git fetch origin +git checkout -B beta --track origin/beta +``` -* [Draft a new release.](https://github.com/ember-cli/ember-cli/releases/new) - * enter the new version number as the tag prefixed with `v` e.g. (`v0.1.12`) - * Make sure to include the links for diffs between the versions. - * for release title choose a great name, no pressure - * in the description paste the following upgrade instructions +* Merge `master` branch into `beta` ``` -## Setup - -`npm install -g ember-cli@NEW_VERSION_NUMBER` -- Install new global ember-cli +git fetch origin +git merge origin/master +git push origin beta +``` -## Project Update +* Ensure you have the correct dependencies -1. `rm -rf node_modules dist tmp` -- Delete temporary development folders. -2. `npm install -g ember-cli-update` -- Install Ember CLI update tool globally. -3. Run `ember-cli-update --to NEW_VERSION_NUMBER` -- This will update your app or addon to the latest ember-cli release. You will probably encounter merge conflicts that you should resolve in your normal git workflow. -4. Run `ember-cli-update --run-codemods` -- This will let you pick codemods to run against your project, to ensure you are using the latest patterns and platform features. +``` +git clean -fdx +yarn +``` -## Changelog +* Update the CHANGELOG.md + * Run `node ./dev/changelog` + * Copy output into `CHANGELOG.md` + * Edit to make clearer for consumers (remove non-consumer facing entries, etc) + * Ensure blueprint change diff URLs are correct + * Update changelog header for current release + * Commit +* Release: `npx release-it` +* Update GitHub Release with changelog contents +* Merge `beta` into `master` branch -INSERT_CHANGELOG_HERE +``` +git checkout master +git merge origin/beta +git push origin master ``` - * Fill in the version number for `NEW_VERSION_NUMBER` - * Replace `INSERT_CHANGELOG_HERE` with the new content from `CHANGELOG.md` - * Attach the `ember-cli-.tgz` from above - * Check the "Pre-release" checkbox for beta releases. - * Publish the release. - -### Twitter +## Subsequent Beta Release -> Ember CLI vX.Y.Z "Release name goes here." released! -https://github.com/ember-cli/ember-cli/releases/tag/vX.Y.Z -\#emberjs +* Checkout the `beta` branch -### Discord +``` +git fetch origin +git checkout -B beta --track origin/beta +``` -Grab a link to your tweet and post in: -* [#news-and-announcements](https://discordapp.com/channels/480462759797063690/480499624663056390) -* [#dev-ember-cli](https://discordapp.com/channels/480462759797063690/480501885837770763) -* [#ember-cli](https://discordapp.com/channels/480462759797063690/486548111221719040) +* Ensure you have the correct dependencies +``` +git clean -fdx +yarn +``` -## Troubleshooting +* Update the CHANGELOG.md + * Run `node ./dev/changelog` + * Copy output into `CHANGELOG.md` + * Edit to make clearer for consumers (remove non-consumer facing entries, etc) + * Ensure blueprint change diff URLs are correct + * Update changelog header for current release + * Commit +* Release: `npx release-it` +* Update GitHub Release with changelog contents +* Merge `beta` into `master` branch -* if a few mins after release you notice an issue, you can unpublish - * `npm unpublish ember-cli@` (`npm unpublish` is write-only, that is you can unpublish but cannot push `ember-cli` with the same version, you have to bump `version` in `package.json`) -* if it is completely broken, feel free to unpublish a few hours later or the next morning, even if you don't have time to immediately rerelease +``` +git checkout master +git merge origin/beta +git push origin master +``` diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 3ce793d1b4..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,41 +0,0 @@ -# https://www.appveyor.com/docs/appveyor-yml/ - -image: Visual Studio 2017 - -# Test against these versions of Node.js. -environment: - MOCHA_REPORTER: "mocha-appveyor-reporter" - matrix: - - nodejs_version: "8" - - nodejs_version: "10" - - nodejs_version: "12" - -matrix: - fast_finish: true - -branches: - only: - - master - - beta - - release - -# Install scripts. (runs after repo cloning) -install: - - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) - - appveyor-retry npm i -g npm@^4 - - appveyor-retry yarn - - appveyor-retry yarn add mocha-appveyor-reporter # must be installed locally. - -cache: - - '%LOCALAPPDATA%\Yarn' - - '%APPDATA%\Roaming\bower' - -# Post-install test scripts. -test_script: - - cmd: yarn test:all - -# Don't actually build. -build: off - -# Set build version format here instead of in the admin panel. -version: "{build}" diff --git a/blueprints/addon/additional-dev-dependencies.json b/blueprints/addon/additional-dev-dependencies.json new file mode 100644 index 0000000000..5f279ee559 --- /dev/null +++ b/blueprints/addon/additional-dev-dependencies.json @@ -0,0 +1,8 @@ +{ + "devDependencies": { + "@embroider/test-setup": "^0.48.1", + "ember-disable-prototype-extensions": "^1.1.3", + "ember-try": "^1.4.0", + "ember-source-channel-url": "^3.0.0" + } +} diff --git a/blueprints/addon/files/.travis.yml b/blueprints/addon/files/.travis.yml index e4a1ab6062..aac2d419fe 100644 --- a/blueprints/addon/files/.travis.yml +++ b/blueprints/addon/files/.travis.yml @@ -3,10 +3,9 @@ language: node_js node_js: # we recommend testing addons with the same minimum supported node version as Ember CLI # so that your addon works for all apps - - "8" + - "12" -sudo: false -dist: trusty +dist: xenial addons: chrome: stable @@ -30,42 +29,41 @@ branches: - /^v\d+\.\d+\.\d+/ jobs: - fail_fast: true + fast_finish: true allow_failures: - env: EMBER_TRY_SCENARIO=ember-canary include: # runs linting and tests with current locked deps - - stage: "Tests" - name: "Tests"<% if (yarn) { %> - install: - - yarn install --non-interactive<% } %> + name: "Tests" script: - - <% if (yarn) { %>yarn lint:hbs<% } else { %>npm run lint:hbs<% } %> - - <% if (yarn) { %>yarn lint:js<% } else { %>npm run lint:js<% } %> - - <% if (yarn) { %>yarn test<% } else { %>npm test<% } %><% if (yarn) { %> + - <% if (yarn) { %>yarn<% } else { %>npm run<% } %> lint + - <% if (yarn) { %>yarn<% } else { %>npm run<% } %> test:ember - - name: "Floating Dependencies" + - stage: "Additional Tests" + name: "Floating Dependencies" + install:<% if (yarn) { %> + - yarn install --no-lockfile --non-interactive<% } else { %> + - npm install --no-package-lock<% } %> script: - - yarn test<% } %> + - <% if (yarn) { %>yarn<% } else { %>npm run<% } %> test:ember # we recommend new addons test the current and previous LTS # as well as latest stable release (bonus points to beta/canary) - - stage: "Additional Tests" - env: EMBER_TRY_SCENARIO=ember-lts-3.4 - - env: EMBER_TRY_SCENARIO=ember-lts-3.8 + - env: EMBER_TRY_SCENARIO=ember-lts-3.24 + - env: EMBER_TRY_SCENARIO=ember-lts-3.28 - env: EMBER_TRY_SCENARIO=ember-release - env: EMBER_TRY_SCENARIO=ember-beta - env: EMBER_TRY_SCENARIO=ember-canary - env: EMBER_TRY_SCENARIO=ember-default-with-jquery + - env: EMBER_TRY_SCENARIO=ember-classic + - env: EMBER_TRY_SCENARIO=embroider-safe + - env: EMBER_TRY_SCENARIO=embroider-optimized <% if (yarn) { %> before_install: - curl -o- -L https://yarnpkg.com/install.sh | bash - export PATH=$HOME/.yarn/bin:$PATH - -install: - - yarn install --no-lockfile --non-interactive <% } %> script: - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO diff --git a/blueprints/addon/files/CONTRIBUTING.md b/blueprints/addon/files/CONTRIBUTING.md index 7a36e49be8..e8a9a70644 100644 --- a/blueprints/addon/files/CONTRIBUTING.md +++ b/blueprints/addon/files/CONTRIBUTING.md @@ -8,9 +8,8 @@ ## Linting -* `<% if (yarn) { %>yarn lint:hbs<% } else { %>npm run lint:hbs<% } %>` -* `<% if (yarn) { %>yarn lint:js<% } else { %>npm run lint:js<% } %>` -* `<% if (yarn) { %>yarn lint:js --fix<% } else { %>npm run lint:js -- --fix<% } %>` +* `<% if (yarn) { %>yarn lint<% } else { %>npm run lint<% } %>` +* `<% if (yarn) { %>yarn lint:fix<% } else { %>npm run lint:fix<% } %>` ## Running tests @@ -23,4 +22,4 @@ * `ember serve` * Visit the dummy application at [http://localhost:4200](http://localhost:4200). -For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). \ No newline at end of file +For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). diff --git a/blueprints/addon/files/README.md b/blueprints/addon/files/README.md index 3c8113c5bb..8622b2f86b 100644 --- a/blueprints/addon/files/README.md +++ b/blueprints/addon/files/README.md @@ -7,9 +7,9 @@ Compatibility ------------------------------------------------------------------------------ -* Ember.js v3.4 or above -* Ember CLI v2.13 or above -* Node.js v8 or above +* Ember.js v3.24 or above +* Ember CLI v3.24 or above +* Node.js v12 or above Installation diff --git a/blueprints/addon/files/addon-config/ember-try.js b/blueprints/addon/files/addon-config/ember-try.js index 17e3904195..cc564ade16 100644 --- a/blueprints/addon/files/addon-config/ember-try.js +++ b/blueprints/addon/files/addon-config/ember-try.js @@ -1,74 +1,85 @@ 'use strict'; const getChannelURL = require('ember-source-channel-url'); +const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup'); -module.exports = async function() { +module.exports = async function () { return { <% if (yarn) { %>useYarn: true, <% } %>scenarios: [ { - name: 'ember-lts-3.4', + name: 'ember-lts-3.24', npm: { devDependencies: { - 'ember-source': '~3.4.0' - } - } + 'ember-source': '~3.24.3', + }, + }, }, { - name: 'ember-lts-3.8', + name: 'ember-lts-3.28', npm: { devDependencies: { - 'ember-source': '~3.8.0' - } - } + 'ember-source': '~3.28.0', + }, + }, }, { name: 'ember-release', npm: { devDependencies: { - 'ember-source': await getChannelURL('release') - } - } + 'ember-source': await getChannelURL('release'), + }, + }, }, { name: 'ember-beta', npm: { devDependencies: { - 'ember-source': await getChannelURL('beta') - } - } + 'ember-source': await getChannelURL('beta'), + }, + }, }, { name: 'ember-canary', npm: { devDependencies: { - 'ember-source': await getChannelURL('canary') - } - } + 'ember-source': await getChannelURL('canary'), + }, + }, }, - // The default `.travis.yml` runs this scenario via `<% if (yarn) { %>yarn<% } else { %>npm<% } %> test`, - // not via `ember try`. It's still included here so that running - // `ember try:each` manually or from a customized CI config will run it - // along with all the other scenarios. { - name: 'ember-default', + name: 'ember-default-with-jquery', + env: { + EMBER_OPTIONAL_FEATURES: JSON.stringify({ + 'jquery-integration': true, + }), + }, npm: { - devDependencies: {} - } + devDependencies: { + '@ember/jquery': '^1.1.0', + }, + }, }, { - name: 'ember-default-with-jquery', + name: 'ember-classic', env: { EMBER_OPTIONAL_FEATURES: JSON.stringify({ - 'jquery-integration': true - }) + 'application-template-wrapper': true, + 'default-async-observers': false, + 'template-only-glimmer-components': false, + }), }, npm: { devDependencies: { - '@ember/jquery': '^0.5.1' - } - } - } - ] + 'ember-source': '~3.28.0', + }, + ember: { + edition: 'classic', + }, + }, + }, + embroiderSafe(), + embroiderOptimized(), + ], }; }; diff --git a/blueprints/addon/files/addon-config/environment.js b/blueprints/addon/files/addon-config/environment.js index 0dfaed4728..331ab30dfe 100644 --- a/blueprints/addon/files/addon-config/environment.js +++ b/blueprints/addon/files/addon-config/environment.js @@ -1,5 +1,5 @@ 'use strict'; -module.exports = function(/* environment, appConfig */) { - return { }; +module.exports = function (/* environment, appConfig */) { + return {}; }; diff --git a/blueprints/addon/files/config/optional-features.json b/blueprints/addon/files/config/optional-features.json index b1902623ae..b26286e2ec 100644 --- a/blueprints/addon/files/config/optional-features.json +++ b/blueprints/addon/files/config/optional-features.json @@ -1,3 +1,6 @@ { - "jquery-integration": false + "application-template-wrapper": false, + "default-async-observers": true, + "jquery-integration": false, + "template-only-glimmer-components": true } diff --git a/blueprints/addon/files/ember-cli-build.js b/blueprints/addon/files/ember-cli-build.js index dc5a39e1b7..e211c63343 100644 --- a/blueprints/addon/files/ember-cli-build.js +++ b/blueprints/addon/files/ember-cli-build.js @@ -2,7 +2,7 @@ const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); -module.exports = function(defaults) { +module.exports = function (defaults) { let app = new EmberAddon(defaults, { // Add options here }); @@ -14,5 +14,12 @@ module.exports = function(defaults) { behave. You most likely want to be modifying `./index.js` or app's build file */ - return app.toTree(); + const { maybeEmbroider } = require('@embroider/test-setup'); + return maybeEmbroider(app, { + skipBabel: [ + { + package: 'qunit', + }, + ], + }); }; diff --git a/blueprints/addon/files/index.js b/blueprints/addon/files/index.js index 2e1d1d8d5f..0ca063d427 100644 --- a/blueprints/addon/files/index.js +++ b/blueprints/addon/files/index.js @@ -1,5 +1,5 @@ 'use strict'; module.exports = { - name: require('./package').name + name: require('./package').name, }; diff --git a/blueprints/addon/files/npmignore b/blueprints/addon/files/npmignore index bd09adff92..f30effe9bf 100644 --- a/blueprints/addon/files/npmignore +++ b/blueprints/addon/files/npmignore @@ -10,10 +10,13 @@ /.editorconfig /.ember-cli /.env* +/.eslintcache /.eslintignore /.eslintrc.js /.git/ /.gitignore +/.prettierignore +/.prettierrc.js /.template-lintrc.js /.travis.yml /.watchmanconfig @@ -23,6 +26,7 @@ /ember-cli-build.js /testem.js /tests/ +/yarn-error.log /yarn.lock .gitkeep diff --git a/blueprints/addon/index.js b/blueprints/addon/index.js index ba2796aa08..0e8cd16529 100644 --- a/blueprints/addon/index.js +++ b/blueprints/addon/index.js @@ -21,6 +21,8 @@ const replacers = { }, }; +const ADDITIONAL_DEV_DEPENDENCIES = require('./additional-dev-dependencies.json').devDependencies; + const description = 'The default blueprint for ember-cli addons.'; module.exports = { description, @@ -71,17 +73,10 @@ module.exports = { contents.keywords.push('ember-addon'); } - // add `ember-disable-prototype-extensions` to addons by default - contents.devDependencies['ember-disable-prototype-extensions'] = '^1.1.3'; - - // add ember-try - contents.devDependencies['ember-try'] = '^1.2.1'; - - // add ember-source-channel-url - contents.devDependencies['ember-source-channel-url'] = '^2.0.1'; + Object.assign(contents.devDependencies, ADDITIONAL_DEV_DEPENDENCIES); - // add `ember-try` as `test:all` script in addons - contents.scripts['test:all'] = 'ember try:each'; + // add `ember-compatibility` script in addons + contents.scripts['test:ember-compatibility'] = 'ember try:each'; contents['ember-addon'] = contents['ember-addon'] || {}; contents['ember-addon'].configPath = 'tests/dummy/config'; @@ -121,7 +116,7 @@ module.exports = { let packagePath = path.join(this.path, 'files', 'package.json'); let bowerPath = path.join(this.path, 'files', 'bower.json'); - [packagePath, bowerPath].forEach(filePath => { + [packagePath, bowerPath].forEach((filePath) => { fs.removeSync(filePath); }); }, @@ -137,6 +132,18 @@ module.exports = { let addonName = stringUtil.dasherize(addonRawName); let addonNamespace = stringUtil.classify(addonRawName); + let hasOptions = options.welcome || options.yarn; + let blueprintOptions = ''; + if (hasOptions) { + let indent = `\n `; + let outdent = `\n `; + + blueprintOptions = + indent + + [options.welcome && '"--welcome"', options.yarn && '"--yarn"'].filter(Boolean).join(',\n ') + + outdent; + } + return { name, modulePrefix: name, @@ -148,6 +155,9 @@ module.exports = { yarn: options.yarn, welcome: options.welcome, blueprint: 'addon', + blueprintOptions, + embroider: false, + lang: options.lang, }; }, diff --git a/blueprints/app/files/.editorconfig b/blueprints/app/files/.editorconfig index 219985c228..c35a002406 100644 --- a/blueprints/app/files/.editorconfig +++ b/blueprints/app/files/.editorconfig @@ -4,7 +4,6 @@ root = true - [*] end_of_line = lf charset = utf-8 diff --git a/blueprints/app/files/.eslintignore b/blueprints/app/files/.eslintignore index 72df373072..701947ed3a 100644 --- a/blueprints/app/files/.eslintignore +++ b/blueprints/app/files/.eslintignore @@ -13,6 +13,8 @@ # misc /coverage/ !.* +.*/ +.eslintcache # ember-try /.node_modules.ember-try/ diff --git a/blueprints/app/files/.eslintrc.js b/blueprints/app/files/.eslintrc.js index c5c5557f08..13f59d4928 100644 --- a/blueprints/app/files/.eslintrc.js +++ b/blueprints/app/files/.eslintrc.js @@ -1,59 +1,60 @@ +'use strict'; + module.exports = { root: true, parser: 'babel-eslint', parserOptions: { ecmaVersion: 2018, - sourceType: 'module' + sourceType: 'module', + ecmaFeatures: { + legacyDecorators: true, + }, }, - plugins: [ - 'ember' - ], + plugins: ['ember'], extends: [ 'eslint:recommended', - 'plugin:ember/recommended' + 'plugin:ember/recommended', + 'plugin:prettier/recommended', ], env: { - browser: true - }, - rules: { - 'ember/no-jquery': 'error' + browser: true, }, + rules: {}, overrides: [ // node files { files: [ - '.eslintrc.js', - '.template-lintrc.js', - 'ember-cli-build.js',<% if (blueprint !== 'app') { %> - 'index.js',<% } %> - 'testem.js', - 'blueprints/*/index.js', - 'config/**/*.js'<% if (blueprint === 'app') { %>, - 'lib/*/index.js', - 'server/**/*.js'<% } else { %>, - 'tests/dummy/config/**/*.js'<% } %> - ],<% if (blueprint !== 'app') { %> - excludedFiles: [ - 'addon/**', - 'addon-test-support/**', - 'app/**', - 'tests/dummy/app/**' - ],<% } %> + './.eslintrc.js', + './.prettierrc.js', + './.template-lintrc.js', + './ember-cli-build.js',<% if (blueprint !== 'app') { %> + './index.js',<% } %> + './testem.js', + './blueprints/*/index.js', + './config/**/*.js',<% if (blueprint === 'app') { %> + './lib/*/index.js', + './server/**/*.js',<% } else { %> + './tests/dummy/config/**/*.js',<% } %> + ], parserOptions: { - sourceType: 'script' + sourceType: 'script', }, env: { browser: false, - node: true + node: true, }, plugins: ['node'], - rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { - // add your custom rules and overrides for node files here<% if (blueprint === 'app') { %> - + extends: ['plugin:node/recommended'],<% if (blueprint === 'app') {%> + rules: { // this can be removed once the following is fixed // https://github.com/mysticatea/eslint-plugin-node/issues/77 - 'node/no-unpublished-require': 'off'<% } %> - }) - } - ] + 'node/no-unpublished-require': 'off', + },<% } %> + }, + { + // Test files: + files: ['tests/**/*-test.{js,ts}'], + extends: ['plugin:qunit/recommended'], + }, + ], }; diff --git a/blueprints/module-unification-app/files/.eslintignore b/blueprints/app/files/.prettierignore similarity index 94% rename from blueprints/module-unification-app/files/.eslintignore rename to blueprints/app/files/.prettierignore index 72df373072..9221655522 100644 --- a/blueprints/module-unification-app/files/.eslintignore +++ b/blueprints/app/files/.prettierignore @@ -13,6 +13,7 @@ # misc /coverage/ !.* +.eslintcache # ember-try /.node_modules.ember-try/ diff --git a/blueprints/module-unification-app/files/.template-lintrc.js b/blueprints/app/files/.prettierrc.js similarity index 59% rename from blueprints/module-unification-app/files/.template-lintrc.js rename to blueprints/app/files/.prettierrc.js index b45e96ffdd..534e6d35aa 100644 --- a/blueprints/module-unification-app/files/.template-lintrc.js +++ b/blueprints/app/files/.prettierrc.js @@ -1,5 +1,5 @@ 'use strict'; module.exports = { - extends: 'recommended' + singleQuote: true, }; diff --git a/blueprints/app/files/.template-lintrc.js b/blueprints/app/files/.template-lintrc.js index b45e96ffdd..f35f61c7b3 100644 --- a/blueprints/app/files/.template-lintrc.js +++ b/blueprints/app/files/.template-lintrc.js @@ -1,5 +1,5 @@ 'use strict'; module.exports = { - extends: 'recommended' + extends: 'recommended', }; diff --git a/blueprints/app/files/.travis.yml b/blueprints/app/files/.travis.yml index 25dc4c779b..bf9a217b21 100644 --- a/blueprints/app/files/.travis.yml +++ b/blueprints/app/files/.travis.yml @@ -1,10 +1,9 @@ --- language: node_js node_js: - - "8" + - "12" -sudo: false -dist: trusty +dist: xenial addons: chrome: stable @@ -20,15 +19,14 @@ env: global: # See https://git.io/vdao3 for details. - JOBS=1 + +branches: + only: + - master <% if (yarn) { %> before_install: - curl -o- -L https://yarnpkg.com/install.sh | bash - export PATH=$HOME/.yarn/bin:$PATH - -install: - - yarn install --non-interactive <% } %> script: - - <% if (yarn) { %>yarn<% } else { %>npm run<% } %> lint:hbs - - <% if (yarn) { %>yarn<% } else { %>npm run<% } %> lint:js - <% if (yarn) { %>yarn<% } else { %>npm<% } %> test diff --git a/blueprints/app/files/README.md b/blueprints/app/files/README.md index a970428ca9..469bce68e5 100644 --- a/blueprints/app/files/README.md +++ b/blueprints/app/files/README.md @@ -36,9 +36,8 @@ Make use of the many generators for code, try `ember help generate` for more det ### Linting -* `<% if (yarn) { %>yarn lint:hbs<% } else { %>npm run lint:hbs<% } %>` -* `<% if (yarn) { %>yarn lint:js<% } else { %>npm run lint:js<% } %>` -* `<% if (yarn) { %>yarn lint:js --fix<% } else { %>npm run lint:js -- --fix<% } %>` +* `<% if (yarn) { %>yarn lint<% } else { %>npm run lint<% } %>` +* `<% if (yarn) { %>yarn lint:fix<% } else { %>npm run lint:fix<% } %>` ### Building diff --git a/blueprints/app/files/app/app.js b/blueprints/app/files/app/app.js index b3b2bd677e..9efa116333 100644 --- a/blueprints/app/files/app/app.js +++ b/blueprints/app/files/app/app.js @@ -1,14 +1,12 @@ import Application from '@ember/application'; -import Resolver from './resolver'; +import Resolver from 'ember-resolver'; import loadInitializers from 'ember-load-initializers'; -import config from './config/environment'; +import config from '<%= modulePrefix %>/config/environment'; -const App = Application.extend({ - modulePrefix: config.modulePrefix, - podModulePrefix: config.podModulePrefix, - Resolver -}); +export default class App extends Application { + modulePrefix = config.modulePrefix; + podModulePrefix = config.podModulePrefix; + Resolver = Resolver; +} loadInitializers(App, config.modulePrefix); - -export default App; diff --git a/blueprints/app/files/app/index.html b/blueprints/app/files/app/index.html index 788cee4c8a..9de6f2eae3 100644 --- a/blueprints/app/files/app/index.html +++ b/blueprints/app/files/app/index.html @@ -1,5 +1,5 @@ - + lang="<%= lang %>"<% } %>> diff --git a/blueprints/app/files/app/resolver.js b/blueprints/app/files/app/resolver.js deleted file mode 100644 index 2fb563d6c0..0000000000 --- a/blueprints/app/files/app/resolver.js +++ /dev/null @@ -1,3 +0,0 @@ -import Resolver from 'ember-resolver'; - -export default Resolver; diff --git a/blueprints/app/files/app/router.js b/blueprints/app/files/app/router.js index d0bb00952f..9d2bbff522 100644 --- a/blueprints/app/files/app/router.js +++ b/blueprints/app/files/app/router.js @@ -1,12 +1,9 @@ import EmberRouter from '@ember/routing/router'; -import config from './config/environment'; +import config from '<%= modulePrefix %>/config/environment'; -const Router = EmberRouter.extend({ - location: config.locationType, - rootURL: config.rootURL -}); +export default class Router extends EmberRouter { + location = config.locationType; + rootURL = config.rootURL; +} -Router.map(function() { -}); - -export default Router; +Router.map(function () {}); diff --git a/blueprints/app/files/app/templates/application.hbs b/blueprints/app/files/app/templates/application.hbs index e7e67e966a..78970cd25d 100644 --- a/blueprints/app/files/app/templates/application.hbs +++ b/blueprints/app/files/app/templates/application.hbs @@ -1,3 +1,5 @@ +{{page-title "<%= namespace %>"}} + <% if (welcome) { %>{{!-- The following component displays Ember's default welcome message. --}} {{!-- Feel free to remove this! --}} diff --git a/blueprints/app/files/config/ember-cli-update.json b/blueprints/app/files/config/ember-cli-update.json new file mode 100644 index 0000000000..ff2630f575 --- /dev/null +++ b/blueprints/app/files/config/ember-cli-update.json @@ -0,0 +1,18 @@ +{ + "schemaVersion": "1.0.0", + "packages": [ + { + "name": "ember-cli", + "version": "<%= emberCLIVersion %>", + "blueprints": [ + { + "name": "<%= blueprint %>", + "outputRepo": "https://github.com/ember-cli/ember-<%= blueprint === 'app' ? 'new' : 'addon' %>-output", + "codemodsSource": "ember-<%= blueprint %>-codemods-manifest@1", + "isBaseBlueprint": true, + "options": [<%= blueprintOptions %>] + } + ] + } + ] +} diff --git a/blueprints/app/files/config/environment.js b/blueprints/app/files/config/environment.js index 19ed234454..25cbe0b0f0 100644 --- a/blueprints/app/files/config/environment.js +++ b/blueprints/app/files/config/environment.js @@ -1,6 +1,6 @@ 'use strict'; -module.exports = function(environment) { +module.exports = function (environment) { let ENV = { modulePrefix: '<%= modulePrefix %>', environment, @@ -13,14 +13,14 @@ module.exports = function(environment) { }, EXTEND_PROTOTYPES: { // Prevent Ember Data from overriding Date.parse. - Date: false - } + Date: false, + }, }, APP: { // Here you can pass flags/options to your application instance // when it is created - } + }, }; if (environment === 'development') { diff --git a/blueprints/app/files/config/optional-features.json b/blueprints/app/files/config/optional-features.json index b1902623ae..b26286e2ec 100644 --- a/blueprints/app/files/config/optional-features.json +++ b/blueprints/app/files/config/optional-features.json @@ -1,3 +1,6 @@ { - "jquery-integration": false + "application-template-wrapper": false, + "default-async-observers": true, + "jquery-integration": false, + "template-only-glimmer-components": true } diff --git a/blueprints/app/files/config/targets.js b/blueprints/app/files/config/targets.js index 8ffae36361..3cd797ab4f 100644 --- a/blueprints/app/files/config/targets.js +++ b/blueprints/app/files/config/targets.js @@ -3,16 +3,24 @@ const browsers = [ 'last 1 Chrome versions', 'last 1 Firefox versions', - 'last 1 Safari versions' + 'last 1 Safari versions', ]; -const isCI = !!process.env.CI; -const isProduction = process.env.EMBER_ENV === 'production'; - -if (isCI || isProduction) { - browsers.push('ie 11'); -} +// Ember's browser support policy is changing, and IE11 support will end in +// v4.0 onwards. +// +// See https://deprecations.emberjs.com/v3.x#toc_3-0-browser-support-policy +// +// If you need IE11 support on a version of Ember that still offers support +// for it, uncomment the code block below. +// +// const isCI = Boolean(process.env.CI); +// const isProduction = process.env.EMBER_ENV === 'production'; +// +// if (isCI || isProduction) { +// browsers.push('ie 11'); +// } module.exports = { - browsers + browsers, }; diff --git a/blueprints/app/files/ember-cli-build.js b/blueprints/app/files/ember-cli-build.js index d690a2531e..2e9b033636 100644 --- a/blueprints/app/files/ember-cli-build.js +++ b/blueprints/app/files/ember-cli-build.js @@ -2,7 +2,7 @@ const EmberApp = require('ember-cli/lib/broccoli/ember-app'); -module.exports = function(defaults) { +module.exports = function (defaults) { let app = new EmberApp(defaults, { // Add options here }); @@ -20,5 +20,12 @@ module.exports = function(defaults) { // please specify an object with the list of modules as keys // along with the exports of each module as its value. - return app.toTree(); + <% if (embroider) { %>const { Webpack } = require('@embroider/webpack'); + return require('@embroider/compat').compatBuild(app, Webpack, { + skipBabel: [ + { + package: 'qunit', + }, + ], + });<% } else { %>return app.toTree();<% } %> }; diff --git a/blueprints/app/files/gitignore b/blueprints/app/files/gitignore index c40a1b2aba..7e0f7ddce8 100644 --- a/blueprints/app/files/gitignore +++ b/blueprints/app/files/gitignore @@ -12,6 +12,7 @@ /.env* /.pnp* /.sass-cache +/.eslintcache /connect.lock /coverage/ /libpeerconnection.log diff --git a/blueprints/app/files/package.json b/blueprints/app/files/package.json index d2ec6c4d9c..ca5b590b2d 100644 --- a/blueprints/app/files/package.json +++ b/blueprints/app/files/package.json @@ -11,42 +11,64 @@ "test": "tests" }, "scripts": { - "build": "ember build", + "build": "ember build --environment=production", + "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", + "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", "lint:hbs": "ember-template-lint .", - "lint:js": "eslint .", + "lint:hbs:fix": "ember-template-lint . --fix", + "lint:js": "eslint . --cache", + "lint:js:fix": "eslint . --fix", "start": "ember serve", - "test": "ember test" + "test": "npm-run-all lint test:*", + "test:ember": "ember test" }, "devDependencies": { - "@ember/optional-features": "^1.0.0", - "babel-eslint": "^10.0.3", + "@ember/optional-features": "^2.0.0", + "@ember/test-helpers": "^2.6.0<% if (embroider) { %>", + "@embroider/compat": "^0.48.1", + "@embroider/core": "^0.48.1", + "@embroider/webpack": "^0.48.1<% } %>", + "@glimmer/component": "^1.0.4", + "@glimmer/tracking": "^1.0.4", + "babel-eslint": "^10.1.0", "broccoli-asset-rev": "^3.0.0", + "ember-auto-import": "^1.12.0", "ember-cli": "~<%= emberCLIVersion %>", - "ember-cli-app-version": "^3.2.0", - "ember-cli-babel": "^7.11.1", - "ember-cli-dependency-checker": "^3.1.0", - "ember-cli-eslint": "^5.1.0", - "ember-cli-htmlbars": "^4.0.0", - "ember-cli-htmlbars-inline-precompile": "^3.0.0", - "ember-cli-inject-live-reload": "^2.0.1", + "ember-cli-app-version": "^5.0.0", + "ember-cli-babel": "^7.26.10", + "ember-cli-dependency-checker": "^3.2.0", + "ember-cli-htmlbars": "^5.7.2", + "ember-cli-inject-live-reload": "^2.1.0", "ember-cli-sri": "^2.1.1", - "ember-cli-template-lint": "^1.0.0-beta.3", - "ember-cli-uglify": "^3.0.0", - "ember-data": "~3.13.0", - "ember-export-application-global": "^2.0.0", - "ember-fetch": "^6.7.0", - "ember-load-initializers": "^2.1.0", + "ember-cli-terser": "^4.0.2", + "ember-data": "~3.28.6", + "ember-export-application-global": "^2.0.1", + "ember-fetch": "^8.1.1", + "ember-load-initializers": "^2.1.2", "ember-maybe-import-regenerator": "^0.1.6", - "ember-qunit": "^4.5.1", - "ember-resolver": "^5.3.0", - "ember-source": "~3.13.0<% if (welcome) { %>", - "ember-welcome-page": "^4.0.0<% } %>", - "eslint-plugin-ember": "^7.1.0", - "eslint-plugin-node": "^10.0.0", + "ember-page-title": "^6.2.2", + "ember-qunit": "^5.1.5", + "ember-resolver": "^8.0.3", + "ember-source": "~3.28.8", + "ember-template-lint": "^3.15.0<% if (welcome) { %>", + "ember-welcome-page": "^4.1.0<% } %>", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-ember": "^10.5.8", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.4.1", + "eslint-plugin-qunit": "^6.2.0", "loader.js": "^4.7.0", - "qunit-dom": "^0.9.0" + "npm-run-all": "^4.1.5", + "prettier": "^2.5.1", + "qunit": "^2.17.2", + "qunit-dom": "^1.6.0<% if (embroider) { %>", + "webpack": "^5.65.0<% } %>" }, "engines": { - "node": "8.* || >= 10.*" + "node": "12.* || 14.* || >= 16" + }, + "ember": { + "edition": "octane" } } diff --git a/blueprints/app/files/testem.js b/blueprints/app/files/testem.js index 367f5659c1..ed2f37124a 100644 --- a/blueprints/app/files/testem.js +++ b/blueprints/app/files/testem.js @@ -1,12 +1,11 @@ +'use strict'; + module.exports = { test_page: 'tests/index.html?hidepassed', disable_watching: true, - launch_in_ci: [ - 'Chrome' - ], - launch_in_dev: [ - 'Chrome' - ], + launch_in_ci: ['Chrome'], + launch_in_dev: ['Chrome'], + browser_start_timeout: 120, browser_args: { Chrome: { ci: [ @@ -17,8 +16,8 @@ module.exports = { '--disable-software-rasterizer', '--mute-audio', '--remote-debugging-port=0', - '--window-size=1440,900' - ].filter(Boolean) - } - } + '--window-size=1440,900', + ].filter(Boolean), + }, + }, }; diff --git a/blueprints/app/files/tests/index.html b/blueprints/app/files/tests/index.html index 140695b91b..ae7ad9ff8d 100644 --- a/blueprints/app/files/tests/index.html +++ b/blueprints/app/files/tests/index.html @@ -21,7 +21,14 @@ {{content-for "body"}} {{content-for "test-body"}} - +
+
+
+
+
+
+ + diff --git a/blueprints/app/files/tests/test-helper.js b/blueprints/app/files/tests/test-helper.js index 0382a848dd..959791ce73 100644 --- a/blueprints/app/files/tests/test-helper.js +++ b/blueprints/app/files/tests/test-helper.js @@ -1,8 +1,12 @@ -import Application from '../app'; -import config from '../config/environment'; +import Application from '<%= modulePrefix %>/app'; +import config from '<%= modulePrefix %>/config/environment'; +import * as QUnit from 'qunit'; import { setApplication } from '@ember/test-helpers'; +import { setup } from 'qunit-dom'; import { start } from 'ember-qunit'; setApplication(Application.create(config.APP)); +setup(QUnit.assert); + start(); diff --git a/blueprints/app/index.js b/blueprints/app/index.js index d8aaf33166..73d3c8db7c 100644 --- a/blueprints/app/index.js +++ b/blueprints/app/index.js @@ -2,6 +2,7 @@ const stringUtil = require('ember-cli-string-utils'); const chalk = require('chalk'); +const { isExperimentEnabled } = require('../../lib/experiments'); module.exports = { description: 'The default blueprint for ember-cli projects.', @@ -20,6 +21,21 @@ module.exports = { let rawName = entity.name; let name = stringUtil.dasherize(rawName); let namespace = stringUtil.classify(rawName); + let embroider = isExperimentEnabled('EMBROIDER') || options.embroider; + + let hasOptions = !options.welcome || options.yarn || embroider; + let blueprintOptions = ''; + if (hasOptions) { + let indent = `\n `; + let outdent = `\n `; + + blueprintOptions = + indent + + [!options.welcome && '"--no-welcome"', options.yarn && '"--yarn"', embroider && '"--embroider"'] + .filter(Boolean) + .join(',\n ') + + outdent; + } return { name, @@ -29,6 +45,9 @@ module.exports = { yarn: options.yarn, welcome: options.welcome, blueprint: 'app', + blueprintOptions, + embroider, + lang: options.lang, }; }, diff --git a/blueprints/blueprint/index.js b/blueprints/blueprint/index.js index e0a37bef05..d21affb601 100644 --- a/blueprints/blueprint/index.js +++ b/blueprints/blueprint/index.js @@ -7,7 +7,7 @@ module.exports = { let files = this._super.files.apply(this, arguments); if (!this.hasJSHint()) { - files = files.filter(file => file !== 'blueprints/.jshintrc'); + files = files.filter((file) => file !== 'blueprints/.jshintrc'); } return files; diff --git a/blueprints/in-repo-addon/index.js b/blueprints/in-repo-addon/index.js index de389b9fdd..134dbf6497 100755 --- a/blueprints/in-repo-addon/index.js +++ b/blueprints/in-repo-addon/index.js @@ -15,8 +15,7 @@ module.exports = { }, _processTokens(name) { - let isModuleUnification = this.project.isModuleUnification && this.project.isModuleUnification(); - let root = isModuleUnification ? 'packages' : 'lib'; + let root = 'lib'; if (name.match(/[./]/)) { root = path.dirname(name); @@ -38,8 +37,8 @@ module.exports = { fileMapTokens() { return { - __root__: options => this._processTokens(options.dasherizedModuleName).root, - __name__: options => this._processTokens(options.dasherizedModuleName).name, + __root__: (options) => this._processTokens(options.dasherizedModuleName).root, + __name__: (options) => this._processTokens(options.dasherizedModuleName).name, }; }, diff --git a/blueprints/module-unification-addon/files/.editorconfig b/blueprints/module-unification-addon/files/.editorconfig deleted file mode 100644 index 219985c228..0000000000 --- a/blueprints/module-unification-addon/files/.editorconfig +++ /dev/null @@ -1,20 +0,0 @@ -# EditorConfig helps developers define and maintain consistent -# coding styles between different editors and IDEs -# editorconfig.org - -root = true - - -[*] -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true -indent_style = space -indent_size = 2 - -[*.hbs] -insert_final_newline = false - -[*.{diff,md}] -trim_trailing_whitespace = false diff --git a/blueprints/module-unification-addon/files/.ember-cli b/blueprints/module-unification-addon/files/.ember-cli deleted file mode 100644 index ee64cfed2a..0000000000 --- a/blueprints/module-unification-addon/files/.ember-cli +++ /dev/null @@ -1,9 +0,0 @@ -{ - /** - Ember CLI sends analytics information by default. The data is completely - anonymous, but there are times when you might want to disable this behavior. - - Setting `disableAnalytics` to true will prevent any data from being sent. - */ - "disableAnalytics": false -} diff --git a/blueprints/module-unification-addon/files/.eslintrc.js b/blueprints/module-unification-addon/files/.eslintrc.js deleted file mode 100644 index 657ee6fd51..0000000000 --- a/blueprints/module-unification-addon/files/.eslintrc.js +++ /dev/null @@ -1,54 +0,0 @@ -module.exports = { - root: true, - parser: 'babel-eslint', - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module' - }, - plugins: [ - 'ember' - ], - extends: [ - 'eslint:recommended', - 'plugin:ember/recommended' - ], - env: { - browser: true - }, - rules: { - }, - overrides: [ - // node files - { - files: [ - '.eslintrc.js', - '.template-lintrc.js', - 'ember-cli-build.js', - 'index.js', - 'testem.js', - 'blueprints/*/index.js', - 'config/**/*.js', - 'tests/dummy/config/**/*.js' - ], - excludedFiles: [ - 'src/**', - 'tests/dummy/app/**' - ], - parserOptions: { - sourceType: 'script' - }, - env: { - browser: false, - node: true - }, - plugins: ['node'], - rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { - // add your custom rules and overrides for node files here - - // this can be removed once the following is fixed - // https://github.com/mysticatea/eslint-plugin-node/issues/77 - 'node/no-unpublished-require': 'off' - }) - } - ] -}; diff --git a/blueprints/module-unification-addon/files/.travis.yml b/blueprints/module-unification-addon/files/.travis.yml deleted file mode 100644 index 1bad18f943..0000000000 --- a/blueprints/module-unification-addon/files/.travis.yml +++ /dev/null @@ -1,42 +0,0 @@ ---- -language: node_js -node_js: - # we recommend testing addons with the same minimum supported node version as Ember CLI - # so that your addon works for all apps - - "8" - -sudo: false -dist: trusty - -addons: - chrome: stable - -cache: - directories: - - $HOME/.npm - -env: - global: - # See https://git.io/vdao3 for details. - - JOBS=1 - matrix: - # we recommend new addons test the current and previous LTS - # as well as latest stable release (bonus points to beta/canary) - - EMBER_TRY_SCENARIO=ember-lts-3.4 - - EMBER_TRY_SCENARIO=ember-lts-3.8 - - EMBER_TRY_SCENARIO=ember-release - - EMBER_TRY_SCENARIO=ember-beta - - EMBER_TRY_SCENARIO=ember-canary - - EMBER_TRY_SCENARIO=ember-default - -matrix: - fast_finish: true - allow_failures: - - env: EMBER_TRY_SCENARIO=ember-canary - -script: - - npm run lint:hbs - - npm run lint:js - # Usually, it's ok to finish the test scenario without reverting - # to the addon's original dependency state, skipping "cleanup". - - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO --skip-cleanup diff --git a/blueprints/module-unification-addon/files/CONTRIBUTING.md b/blueprints/module-unification-addon/files/CONTRIBUTING.md deleted file mode 100644 index 7a36e49be8..0000000000 --- a/blueprints/module-unification-addon/files/CONTRIBUTING.md +++ /dev/null @@ -1,26 +0,0 @@ -# How To Contribute - -## Installation - -* `git clone ` -* `cd <%= addonName %>` -* `<% if (yarn) { %>yarn<% } else { %>npm<% } %> install` - -## Linting - -* `<% if (yarn) { %>yarn lint:hbs<% } else { %>npm run lint:hbs<% } %>` -* `<% if (yarn) { %>yarn lint:js<% } else { %>npm run lint:js<% } %>` -* `<% if (yarn) { %>yarn lint:js --fix<% } else { %>npm run lint:js -- --fix<% } %>` - -## Running tests - -* `ember test` – Runs the test suite on the current Ember version -* `ember test --server` – Runs the test suite in "watch mode" -* `ember try:each` – Runs the test suite against multiple Ember versions - -## Running the dummy application - -* `ember serve` -* Visit the dummy application at [http://localhost:4200](http://localhost:4200). - -For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). \ No newline at end of file diff --git a/blueprints/module-unification-addon/files/LICENSE.md b/blueprints/module-unification-addon/files/LICENSE.md deleted file mode 100644 index f5799851dc..0000000000 --- a/blueprints/module-unification-addon/files/LICENSE.md +++ /dev/null @@ -1,9 +0,0 @@ -The MIT License (MIT) - -Copyright (c) <%= year %> - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/blueprints/module-unification-addon/files/README.md b/blueprints/module-unification-addon/files/README.md deleted file mode 100644 index 3c8113c5bb..0000000000 --- a/blueprints/module-unification-addon/files/README.md +++ /dev/null @@ -1,38 +0,0 @@ -<%= addonName %> -============================================================================== - -[Short description of the addon.] - - -Compatibility ------------------------------------------------------------------------------- - -* Ember.js v3.4 or above -* Ember CLI v2.13 or above -* Node.js v8 or above - - -Installation ------------------------------------------------------------------------------- - -``` -ember install <%= addonName %> -``` - - -Usage ------------------------------------------------------------------------------- - -[Longer description of how to use the addon in apps.] - - -Contributing ------------------------------------------------------------------------------- - -See the [Contributing](CONTRIBUTING.md) guide for details. - - -License ------------------------------------------------------------------------------- - -This project is licensed under the [MIT License](LICENSE.md). diff --git a/blueprints/module-unification-addon/files/addon-config/ember-try.js b/blueprints/module-unification-addon/files/addon-config/ember-try.js deleted file mode 100644 index c98bf42a4d..0000000000 --- a/blueprints/module-unification-addon/files/addon-config/ember-try.js +++ /dev/null @@ -1,57 +0,0 @@ -'use strict'; - -const getChannelURL = require('ember-source-channel-url'); - -module.exports = async function() { - return { - <% if (yarn) { %>useYarn: true, - <% } %>scenarios: [ - { - name: 'ember-lts-3.4', - npm: { - devDependencies: { - 'ember-source': '~3.4.0' - } - } - }, - { - name: 'ember-lts-3.8', - npm: { - devDependencies: { - 'ember-source': '~3.8.0' - } - } - }, - { - name: 'ember-release', - npm: { - devDependencies: { - 'ember-source': await getChannelURL('release') - } - } - }, - { - name: 'ember-beta', - npm: { - devDependencies: { - 'ember-source': await getChannelURL('beta') - } - } - }, - { - name: 'ember-canary', - npm: { - devDependencies: { - 'ember-source': await getChannelURL('canary') - } - } - }, - { - name: 'ember-default', - npm: { - devDependencies: {} - } - } - ] - }; -}; diff --git a/blueprints/module-unification-addon/files/addon-config/environment.js b/blueprints/module-unification-addon/files/addon-config/environment.js deleted file mode 100644 index 0dfaed4728..0000000000 --- a/blueprints/module-unification-addon/files/addon-config/environment.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -module.exports = function(/* environment, appConfig */) { - return { }; -}; diff --git a/blueprints/module-unification-addon/files/addon-src/.gitkeep b/blueprints/module-unification-addon/files/addon-src/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/blueprints/module-unification-addon/files/ember-cli-build.js b/blueprints/module-unification-addon/files/ember-cli-build.js deleted file mode 100644 index ba0649dc93..0000000000 --- a/blueprints/module-unification-addon/files/ember-cli-build.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); - -module.exports = function (defaults) { - let app = new EmberAddon(defaults, { - // Add options here - }); - - /* - This build file specifies the options for the dummy test app of this - addon, located in `/tests/dummy` - This build file does *not* influence how the addon or the app using it - behave. You most likely want to be modifying `./index.js` or app's build file - */ - - return app.toTree(); -}; diff --git a/blueprints/module-unification-addon/files/index.js b/blueprints/module-unification-addon/files/index.js deleted file mode 100644 index 2e1d1d8d5f..0000000000 --- a/blueprints/module-unification-addon/files/index.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -module.exports = { - name: require('./package').name -}; diff --git a/blueprints/module-unification-addon/files/npmignore b/blueprints/module-unification-addon/files/npmignore deleted file mode 100644 index 0b609faf1a..0000000000 --- a/blueprints/module-unification-addon/files/npmignore +++ /dev/null @@ -1,30 +0,0 @@ -# compiled output -/dist/ -/tmp/ - -# dependencies -/bower_components/ - -# misc -/.bowerrc -/.editorconfig -/.ember-cli -/.env* -/.eslintignore -/.eslintrc.js -/.gitignore -/.watchmanconfig -/.travis.yml -/bower.json -/config/ember-try.js -/CONTRIBUTING.md -/ember-cli-build.js -/testem.js -/tests/ -/yarn.lock -.gitkeep - -# ember-try -/.node_modules.ember-try/ -/bower.json.ember-try -/package.json.ember-try diff --git a/blueprints/module-unification-addon/files/tests/dummy/.gitkeep b/blueprints/module-unification-addon/files/tests/dummy/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/blueprints/module-unification-addon/files/tests/index.html b/blueprints/module-unification-addon/files/tests/index.html deleted file mode 100644 index 5209b85232..0000000000 --- a/blueprints/module-unification-addon/files/tests/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - Dummy Tests - - - - {{content-for "head"}} - {{content-for "test-head"}} - - - - - - {{content-for "head-footer"}} - {{content-for "test-head-footer"}} - - - {{content-for "body"}} - {{content-for "test-body"}} - - - - - - - - {{content-for "body-footer"}} - {{content-for "test-body-footer"}} - - diff --git a/blueprints/module-unification-addon/files/tests/test-helper.js b/blueprints/module-unification-addon/files/tests/test-helper.js deleted file mode 100644 index 007bd6e506..0000000000 --- a/blueprints/module-unification-addon/files/tests/test-helper.js +++ /dev/null @@ -1,8 +0,0 @@ -import Application from '../src/main'; -import config from '../config/environment'; -import { setApplication } from '@ember/test-helpers'; -import { start } from 'ember-qunit'; - -setApplication(Application.create(config.APP)); - -start(); diff --git a/blueprints/module-unification-addon/files/vendor/.gitkeep b/blueprints/module-unification-addon/files/vendor/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/blueprints/module-unification-addon/index.js b/blueprints/module-unification-addon/index.js deleted file mode 100644 index 40c0855a29..0000000000 --- a/blueprints/module-unification-addon/index.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; -const addonBlueprint = require('../addon'); -const getURLFor = require('ember-source-channel-url'); - -module.exports = Object.assign({}, addonBlueprint, { - description: 'Generates an Ember addon with a module unification layout.', - appBlueprintName: 'module-unification-app', - - fileMap: Object.assign({}, addonBlueprint.fileMap, { - '^src.*': 'tests/dummy/:path', - '^addon-src/.gitkeep': 'src/.gitkeep', - }), - - locals(options) { - return getURLFor('canary').then(url => { - let result = addonBlueprint.locals(options); - result.emberCanaryVersion = url; - return result; - }); - }, -}); diff --git a/blueprints/module-unification-app/files/.editorconfig b/blueprints/module-unification-app/files/.editorconfig deleted file mode 100644 index 219985c228..0000000000 --- a/blueprints/module-unification-app/files/.editorconfig +++ /dev/null @@ -1,20 +0,0 @@ -# EditorConfig helps developers define and maintain consistent -# coding styles between different editors and IDEs -# editorconfig.org - -root = true - - -[*] -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true -indent_style = space -indent_size = 2 - -[*.hbs] -insert_final_newline = false - -[*.{diff,md}] -trim_trailing_whitespace = false diff --git a/blueprints/module-unification-app/files/.ember-cli b/blueprints/module-unification-app/files/.ember-cli deleted file mode 100644 index ee64cfed2a..0000000000 --- a/blueprints/module-unification-app/files/.ember-cli +++ /dev/null @@ -1,9 +0,0 @@ -{ - /** - Ember CLI sends analytics information by default. The data is completely - anonymous, but there are times when you might want to disable this behavior. - - Setting `disableAnalytics` to true will prevent any data from being sent. - */ - "disableAnalytics": false -} diff --git a/blueprints/module-unification-app/files/.eslintrc.js b/blueprints/module-unification-app/files/.eslintrc.js deleted file mode 100644 index bb2c27ecb0..0000000000 --- a/blueprints/module-unification-app/files/.eslintrc.js +++ /dev/null @@ -1,51 +0,0 @@ -module.exports = { - root: true, - parser: 'babel-eslint', - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module' - }, - plugins: [ - 'ember' - ], - extends: [ - 'eslint:recommended', - 'plugin:ember/recommended' - ], - env: { - browser: true - }, - rules: { - 'ember/no-jquery': 'error' - }, - overrides: [ - // node files - { - files: [ - '.eslintrc.js', - '.template-lintrc.js', - 'ember-cli-build.js', - 'index.js', - 'testem.js', - 'blueprints/*/index.js', - 'config/**/*.js', - 'tests/dummy/config/**/*.js' - ], - parserOptions: { - sourceType: 'script' - }, - env: { - browser: false, - node: true - }, - plugins: ['node'], - rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { - // add your custom rules and overrides for node files here - - // this can be removed once the following is fixed - // https://github.com/mysticatea/eslint-plugin-node/issues/77 - 'node/no-unpublished-require': 'off' - }) - } - ] -}; diff --git a/blueprints/module-unification-app/files/.travis.yml b/blueprints/module-unification-app/files/.travis.yml deleted file mode 100644 index 25dc4c779b..0000000000 --- a/blueprints/module-unification-app/files/.travis.yml +++ /dev/null @@ -1,34 +0,0 @@ ---- -language: node_js -node_js: - - "8" - -sudo: false -dist: trusty - -addons: - chrome: stable -<% if (yarn) { %> -cache: - yarn: true -<% } else { %> -cache: - directories: - - $HOME/.npm -<% } %> -env: - global: - # See https://git.io/vdao3 for details. - - JOBS=1 -<% if (yarn) { %> -before_install: - - curl -o- -L https://yarnpkg.com/install.sh | bash - - export PATH=$HOME/.yarn/bin:$PATH - -install: - - yarn install --non-interactive -<% } %> -script: - - <% if (yarn) { %>yarn<% } else { %>npm run<% } %> lint:hbs - - <% if (yarn) { %>yarn<% } else { %>npm run<% } %> lint:js - - <% if (yarn) { %>yarn<% } else { %>npm<% } %> test diff --git a/blueprints/module-unification-app/files/.watchmanconfig b/blueprints/module-unification-app/files/.watchmanconfig deleted file mode 100644 index e7834e3e4f..0000000000 --- a/blueprints/module-unification-app/files/.watchmanconfig +++ /dev/null @@ -1,3 +0,0 @@ -{ - "ignore_dirs": ["tmp", "dist"] -} diff --git a/blueprints/module-unification-app/files/README.md b/blueprints/module-unification-app/files/README.md deleted file mode 100644 index a970428ca9..0000000000 --- a/blueprints/module-unification-app/files/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# <%= name %> - -This README outlines the details of collaborating on this Ember application. -A short introduction of this app could easily go here. - -## Prerequisites - -You will need the following things properly installed on your computer. - -* [Git](https://git-scm.com/) -* [Node.js](https://nodejs.org/)<% if (yarn) { %> -* [Yarn](https://yarnpkg.com/)<% } else { %> (with npm)<% } %> -* [Ember CLI](https://ember-cli.com/) -* [Google Chrome](https://google.com/chrome/) - -## Installation - -* `git clone ` this repository -* `cd <%= name %>` -* `<% if (yarn) { %>yarn<% } else { %>npm<% } %> install` - -## Running / Development - -* `ember serve` -* Visit your app at [http://localhost:4200](http://localhost:4200). -* Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). - -### Code Generators - -Make use of the many generators for code, try `ember help generate` for more details - -### Running Tests - -* `ember test` -* `ember test --server` - -### Linting - -* `<% if (yarn) { %>yarn lint:hbs<% } else { %>npm run lint:hbs<% } %>` -* `<% if (yarn) { %>yarn lint:js<% } else { %>npm run lint:js<% } %>` -* `<% if (yarn) { %>yarn lint:js --fix<% } else { %>npm run lint:js -- --fix<% } %>` - -### Building - -* `ember build` (development) -* `ember build --environment production` (production) - -### Deploying - -Specify what it takes to deploy your app. - -## Further Reading / Useful Links - -* [ember.js](https://emberjs.com/) -* [ember-cli](https://ember-cli.com/) -* Development Browser Extensions - * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) - * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) diff --git a/blueprints/module-unification-app/files/config/environment.js b/blueprints/module-unification-app/files/config/environment.js deleted file mode 100644 index 6184d7d5da..0000000000 --- a/blueprints/module-unification-app/files/config/environment.js +++ /dev/null @@ -1,57 +0,0 @@ -'use strict'; - -module.exports = function(environment) { - let ENV = { - 'ember-resolver': { - features: { - EMBER_RESOLVER_MODULE_UNIFICATION: true - } - }, - modulePrefix: '<%= modulePrefix %>', - environment, - rootURL: '/', - locationType: 'auto', - EmberENV: { - FEATURES: { - // Here you can enable experimental features on an ember canary build - // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true - EMBER_MODULE_UNIFICATION: true - }, - EXTEND_PROTOTYPES: { - // Prevent Ember Data from overriding Date.parse. - Date: false - } - }, - - APP: { - // Here you can pass flags/options to your application instance - // when it is created - } - }; - - if (environment === 'development') { - // ENV.APP.LOG_RESOLVER = true; - // ENV.APP.LOG_ACTIVE_GENERATION = true; - // ENV.APP.LOG_TRANSITIONS = true; - // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; - // ENV.APP.LOG_VIEW_LOOKUPS = true; - } - - if (environment === 'test') { - // Testem prefers this... - ENV.locationType = 'none'; - - // keep test console output quieter - ENV.APP.LOG_ACTIVE_GENERATION = false; - ENV.APP.LOG_VIEW_LOOKUPS = false; - - ENV.APP.rootElement = '#ember-testing'; - ENV.APP.autoboot = false; - } - - if (environment === 'production') { - // here you can enable a production-specific feature - } - - return ENV; -}; diff --git a/blueprints/module-unification-app/files/config/optional-features.json b/blueprints/module-unification-app/files/config/optional-features.json deleted file mode 100644 index b1902623ae..0000000000 --- a/blueprints/module-unification-app/files/config/optional-features.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "jquery-integration": false -} diff --git a/blueprints/module-unification-app/files/config/targets.js b/blueprints/module-unification-app/files/config/targets.js deleted file mode 100644 index 1a7647d06b..0000000000 --- a/blueprints/module-unification-app/files/config/targets.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - browsers: [ - 'ie 11', - 'last 1 Chrome versions', - 'last 1 Firefox versions', - 'last 1 Safari versions' - ] -}; diff --git a/blueprints/module-unification-app/files/gitignore b/blueprints/module-unification-app/files/gitignore deleted file mode 100644 index c40a1b2aba..0000000000 --- a/blueprints/module-unification-app/files/gitignore +++ /dev/null @@ -1,25 +0,0 @@ -# See https://help.github.com/ignore-files/ for more about ignoring files. - -# compiled output -/dist/ -/tmp/ - -# dependencies -/bower_components/ -/node_modules/ - -# misc -/.env* -/.pnp* -/.sass-cache -/connect.lock -/coverage/ -/libpeerconnection.log -/npm-debug.log* -/testem.log -/yarn-error.log - -# ember-try -/.node_modules.ember-try/ -/bower.json.ember-try -/package.json.ember-try diff --git a/blueprints/module-unification-app/files/package.json b/blueprints/module-unification-app/files/package.json deleted file mode 100644 index a776590ccb..0000000000 --- a/blueprints/module-unification-app/files/package.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "<%= name %>", - "version": "0.0.0", - "private": true, - "description": "Small description for <%= name %> goes here", - "repository": "", - "license": "MIT", - "author": "", - "directories": { - "doc": "doc", - "test": "tests" - }, - "scripts": { - "build": "ember build", - "lint:hbs": "ember-template-lint .", - "lint:js": "eslint .", - "start": "ember serve", - "test": "ember test" - }, - "devDependencies": { - "@ember/optional-features": "^0.7.0", - "babel-eslint": "^10.0.3", - "broccoli-asset-rev": "^3.0.0", - "ember-cli": "github:ember-cli/ember-cli", - "ember-cli-app-version": "^3.2.0", - "ember-cli-babel": "^7.11.0", - "ember-cli-dependency-checker": "^3.1.0", - "ember-cli-eslint": "^5.1.0", - "ember-cli-htmlbars": "^3.1.0", - "ember-cli-htmlbars-inline-precompile": "^3.0.0", - "ember-cli-inject-live-reload": "^2.0.1", - "ember-cli-sri": "^2.1.1", - "ember-cli-template-lint": "^1.0.0-beta.3", - "ember-cli-uglify": "^3.0.0", - "ember-data": "~3.13.0-beta.0", - "ember-export-application-global": "^2.0.0", - "ember-fetch": "^6.7.0", - "ember-load-initializers": "^2.1.0", - "ember-maybe-import-regenerator": "^0.1.6", - "ember-qunit": "^4.5.1", - "ember-resolver": "^5.2.1", - "ember-source": "<%= emberCanaryVersion %><% if (welcome) { %>", - "ember-welcome-page": "^4.0.0<% } %>", - "eslint-plugin-ember": "^7.0.0", - "eslint-plugin-node": "^9.2.0", - "loader.js": "^4.7.0", - "qunit-dom": "^0.9.0" - }, - "engines": { - "node": "8.* || >= 10.*" - } -} diff --git a/blueprints/module-unification-app/files/public/robots.txt b/blueprints/module-unification-app/files/public/robots.txt deleted file mode 100644 index f5916452e5..0000000000 --- a/blueprints/module-unification-app/files/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# http://www.robotstxt.org -User-agent: * -Disallow: diff --git a/blueprints/module-unification-app/files/src/main.js b/blueprints/module-unification-app/files/src/main.js deleted file mode 100644 index 84b092ff19..0000000000 --- a/blueprints/module-unification-app/files/src/main.js +++ /dev/null @@ -1,19 +0,0 @@ -import Application from "@ember/application"; -import Resolver from "./resolver"; -import loadInitializers from "ember-load-initializers"; -import config from "../config/environment"; - -const App = Application.extend({ - modulePrefix: config.modulePrefix, - podModulePrefix: config.podModulePrefix, - Resolver -}); - -loadInitializers(App, config.modulePrefix + "/src/init"); - -/* - * This line is added to support initializers in the `app/` directory - */ -loadInitializers(App, config.modulePrefix); - -export default App; diff --git a/blueprints/module-unification-app/files/src/resolver.js b/blueprints/module-unification-app/files/src/resolver.js deleted file mode 100644 index f0b8a1ccea..0000000000 --- a/blueprints/module-unification-app/files/src/resolver.js +++ /dev/null @@ -1,13 +0,0 @@ -import Resolver from 'ember-resolver/resolvers/fallback'; -import buildResolverConfig from 'ember-resolver/ember-config'; -import config from '../config/environment'; - -let moduleConfig = buildResolverConfig(config.modulePrefix); -/* - * If your application has custom types and collections, modify moduleConfig here - * to add support for them. - */ - -export default Resolver.extend({ - config: moduleConfig -}); diff --git a/blueprints/module-unification-app/files/src/router.js b/blueprints/module-unification-app/files/src/router.js deleted file mode 100644 index 7f2298b0dc..0000000000 --- a/blueprints/module-unification-app/files/src/router.js +++ /dev/null @@ -1,11 +0,0 @@ -import EmberRouter from "@ember/routing/router"; -import config from "../config/environment"; - -const Router = EmberRouter.extend({ - location: config.locationType, - rootURL: config.rootURL -}); - -Router.map(function() {}); - -export default Router; diff --git a/blueprints/module-unification-app/files/src/ui/components/.gitkeep b/blueprints/module-unification-app/files/src/ui/components/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/blueprints/module-unification-app/files/src/ui/index.html b/blueprints/module-unification-app/files/src/ui/index.html deleted file mode 100644 index 2567509b55..0000000000 --- a/blueprints/module-unification-app/files/src/ui/index.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - <%= name %> - - - - {{content-for "head"}} - - - - - {{content-for "head-footer"}} - - - {{content-for "body"}} - - - - - {{content-for "body-footer"}} - - diff --git a/blueprints/module-unification-app/files/src/ui/routes/application/template.hbs b/blueprints/module-unification-app/files/src/ui/routes/application/template.hbs deleted file mode 100644 index e7e67e966a..0000000000 --- a/blueprints/module-unification-app/files/src/ui/routes/application/template.hbs +++ /dev/null @@ -1,6 +0,0 @@ -<% if (welcome) { %>{{!-- The following component displays Ember's default welcome message. --}} - -{{!-- Feel free to remove this! --}} -<% } else { %>

Welcome to Ember

-<% } %> -{{outlet}} \ No newline at end of file diff --git a/blueprints/module-unification-app/files/src/ui/styles/app.css b/blueprints/module-unification-app/files/src/ui/styles/app.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/blueprints/module-unification-app/files/testem.js b/blueprints/module-unification-app/files/testem.js deleted file mode 100644 index 96589a75d2..0000000000 --- a/blueprints/module-unification-app/files/testem.js +++ /dev/null @@ -1,23 +0,0 @@ -module.exports = { - test_page: 'tests/index.html?hidepassed', - disable_watching: true, - launch_in_ci: [ - 'Chrome' - ], - launch_in_dev: [ - 'Chrome' - ], - browser_args: { - Chrome: { - mode: 'ci', - args: [ - // --no-sandbox is needed when running Chrome inside a container - process.env.TRAVIS ? '--no-sandbox' : null, - - '--headless', - '--remote-debugging-port=0', - '--window-size=1440,900' - ].filter(Boolean) - } - } -}; diff --git a/blueprints/module-unification-app/files/tests/acceptance/.gitkeep b/blueprints/module-unification-app/files/tests/acceptance/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/blueprints/module-unification-app/files/tests/index.html b/blueprints/module-unification-app/files/tests/index.html deleted file mode 100644 index 140695b91b..0000000000 --- a/blueprints/module-unification-app/files/tests/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - <%= namespace %> Tests - - - - {{content-for "head"}} - {{content-for "test-head"}} - - - - - - {{content-for "head-footer"}} - {{content-for "test-head-footer"}} - - - {{content-for "body"}} - {{content-for "test-body"}} - - - - - - - - {{content-for "body-footer"}} - {{content-for "test-body-footer"}} - - diff --git a/blueprints/module-unification-app/files/tests/test-helper.js b/blueprints/module-unification-app/files/tests/test-helper.js deleted file mode 100644 index 007bd6e506..0000000000 --- a/blueprints/module-unification-app/files/tests/test-helper.js +++ /dev/null @@ -1,8 +0,0 @@ -import Application from '../src/main'; -import config from '../config/environment'; -import { setApplication } from '@ember/test-helpers'; -import { start } from 'ember-qunit'; - -setApplication(Application.create(config.APP)); - -start(); diff --git a/blueprints/module-unification-app/files/vendor/.gitkeep b/blueprints/module-unification-app/files/vendor/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/blueprints/module-unification-app/index.js b/blueprints/module-unification-app/index.js deleted file mode 100644 index 5148990a02..0000000000 --- a/blueprints/module-unification-app/index.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -const stringUtil = require('ember-cli-string-utils'); -const getURLFor = require('ember-source-channel-url'); - -module.exports = { - description: 'Generates an Ember application with a module unification layout.', - - filesToRemove: [], - - locals(options) { - let entity = options.entity; - let rawName = entity.name; - let name = stringUtil.dasherize(rawName); - let namespace = stringUtil.classify(rawName); - - return getURLFor('canary').then(url => ({ - name, - modulePrefix: name, - namespace, - emberCLIVersion: require('../../package').version, - emberCanaryVersion: url, - yarn: options.yarn, - welcome: options.welcome, - })); - }, - - fileMapTokens(options) { - return { - __component__() { - return options.locals.component; - }, - }; - }, -}; diff --git a/blueprints/packages/index.js b/blueprints/packages/index.js deleted file mode 100644 index 6b7e922df0..0000000000 --- a/blueprints/packages/index.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -const fs = require('fs-extra'); - -module.exports = { - description: 'Generates a packages directory for module unification in-repo addons.', - - normalizeEntityName(name) { - return name; - }, - - beforeInstall() { - // make sure to create `packages` directory - fs.mkdirsSync('packages'); - }, -}; diff --git a/dev/changelog b/dev/changelog index 6e25014af1..dbd14ee786 100755 --- a/dev/changelog +++ b/dev/changelog @@ -14,12 +14,12 @@ * dev/changelog */ -const { EOL } = require('os'); -const GitHubApi = require('@octokit/rest'); +const { Octokit } = require('@octokit/rest'); const semver = require('semver'); +const gitRepoInfo = require('git-repo-info'); let currentVersion = `v${require('../package').version}`; -let branch = process.argv[2] || 'master'; +let branch = process.argv[2] || gitRepoInfo().branch; let nextVersion = process.argv[3]; if (nextVersion === undefined) { if (branch === 'master') { @@ -30,6 +30,10 @@ if (nextVersion === undefined) { nextVersion = semver.inc(currentVersion, 'prerelease', 'beta'); } else if (branch === 'release') { nextVersion = semver.inc(currentVersion, 'patch'); + } else { + console.error('Cannot detect branch, please specify on the command line'); + process.exitCode = 1; + return; } } if (nextVersion[0] !== 'v') { @@ -37,21 +41,33 @@ if (nextVersion[0] !== 'v') { } function generateChangelog(contributions) { - let header = ` + return ` +## ${nextVersion} + #### Blueprint Changes - [\`ember new\` diff](https://github.com/ember-cli/ember-new-output/compare/${currentVersion}...${nextVersion}) - [\`ember addon\` diff](https://github.com/ember-cli/ember-addon-output/compare/${currentVersion}...${nextVersion}) -`; - let footer = 'Thank you to all who took the time to contribute!'; +#### Changelog + +${contributions} - return header + EOL + EOL + contributions + EOL + EOL + footer; +Thank you to all who took the time to contribute! +`; } // only filters when dependabot itself merges a PR function excludeDependabot(commitInfo) { - let author = commitInfo.author.login; + let author; + + if (commitInfo.author) { + author = commitInfo.author.login; + } else if (commitInfo.committer) { + author = commitInfo.committer.login; + } else { + return true; + } return author !== 'dependabot-preview[bot]' && author !== 'dependabot[bot]'; } @@ -113,7 +129,7 @@ async function processPages(res) { } async function main() { - let github = new GitHubApi({ version: '3.0.0' }); + let github = new Octokit({ version: '3.0.0', auth: process.env.GITHUB_AUTH }); github.repos .compareCommits({ diff --git a/dev/prepare-release b/dev/prepare-release deleted file mode 100755 index 9cc1b5a81e..0000000000 --- a/dev/prepare-release +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -git clean -fdx && \ -yarn install && \ -npm pack && \ -npm uninstall -g ember-cli && \ -npm i -g ./ember-cli-*.tgz && \ -du -sh ./ember-cli-*.tgz diff --git a/dev/update-blueprint-dependencies.js b/dev/update-blueprint-dependencies.js new file mode 100644 index 0000000000..eed2250d77 --- /dev/null +++ b/dev/update-blueprint-dependencies.js @@ -0,0 +1,160 @@ +'use strict'; + +function usage() { + console.log(`This script updates the dependencies / devDependencies in the main addon and app blueprints along with their corresponding test fixtures. + +Options: + + - '--ember-source' (required) - The dist-tag to use for ember-source + - '--ember-data' (required) - The dist-tag to use for ember-data + - '--filter' (optional) - A RegExp to filter the packages to update by + +Example: + +node dev/update-blueprint-dependencies.js --ember-source=beta --ember-data=beta + +node dev/update-blueprint-dependencies.js --filter /eslint/ + +node dev/update-blueprint-dependencies.js --filter some-package@beta +`); +} + +const fs = require('fs'); +const path = require('path'); +const util = require('util'); +const nopt = require('nopt'); +const npmPackageArg = require('npm-package-arg'); +const _latestVersion = require('latest-version'); + +nopt.typeDefs['regexp'] = { + type: RegExp, + validate(data, key, value) { + let regexp = new RegExp(value); + + data[key] = regexp; + }, +}; + +const OPTIONS = nopt({ + 'ember-source': String, + 'ember-data': String, + filter: String, +}); + +const PACKAGE_FILES = [ + '../blueprints/app/files/package.json', + '../blueprints/addon/additional-dev-dependencies.json', + '../tests/fixtures/app/defaults/package.json', + '../tests/fixtures/app/npm/package.json', + '../tests/fixtures/app/yarn/package.json', + '../tests/fixtures/app/embroider/package.json', + '../tests/fixtures/app/embroider-yarn/package.json', + '../tests/fixtures/app/embroider-no-welcome/package.json', + '../tests/fixtures/addon/defaults/package.json', + '../tests/fixtures/addon/yarn/package.json', +]; + +let filter = { + nameRegexp: null, + fetchSpec: null, +}; + +if (OPTIONS.filter) { + if (OPTIONS.filter.startsWith('/')) { + filter.nameRegexp = new RegExp(OPTIONS.filter.substring(1, OPTIONS.filter.length - 1)); + // can only use latest when using a regexp style + filter.fetchSpec = 'latest'; + } else { + let packageArgResult = npmPackageArg(OPTIONS.filter); + filter.nameRegexp = packageArgResult.name; + filter.fetchSpec = packageArgResult.fetchSpec; + } +} + +function shouldCheckDependency(dependency) { + if (filter.nameRegexp) { + return filter.nameRegexp.test(dependency); + } + + return true; +} + +const LATEST = new Map(); +async function latestVersion(packageName) { + let result = LATEST.get(packageName); + + if (result === undefined) { + let options = { + version: 'latest', + }; + + if (OPTIONS[packageName]) { + options.version = OPTIONS[packageName]; + } + + result = _latestVersion(packageName, options); + LATEST.set(packageName, result); + } + + return result; +} + +async function updateDependencies(dependencies) { + for (let dependency in dependencies) { + if (!shouldCheckDependency(dependency)) { + continue; + } + + let previousValue = dependencies[dependency]; + + // grab the first char (~ or ^) + let prefix = previousValue[0]; + let isValidPrefix = prefix === '~' || prefix === '^'; + + // handle things from blueprints/app/files/package.json like `^2.4.0<% if (welcome) { %>` + let templateSuffix = previousValue.includes('<') ? previousValue.slice(previousValue.indexOf('<')) : ''; + + // check if we are dealing with `~<%= emberCLIVersion %>` + let hasVersion = previousValue[1] !== '<'; + + if (hasVersion && isValidPrefix) { + dependencies[dependency] = `${prefix}${await latestVersion(dependency)}${templateSuffix}`; + } + } +} + +async function main() { + for (let packageFile of PACKAGE_FILES) { + let filePath = path.join(__dirname, packageFile); + let pkg = JSON.parse(fs.readFileSync(filePath, { encoding: 'utf8' })); + + await updateDependencies(pkg.dependencies); + await updateDependencies(pkg.devDependencies); + + let output = `${JSON.stringify(pkg, null, 2)}\n`; + + fs.writeFileSync(filePath, output, { encoding: 'utf8' }); + } +} + +if (module === require.main) { + // ensure promise rejection is a failure + process.on('unhandledRejection', (error) => { + if (!(error instanceof Error)) { + error = new Error(`Promise rejected with value: ${util.inspect(error)}`); + } + + console.error(error.stack); + + /* eslint-disable-next-line no-process-exit */ + process.exit(1); + }); + + if (OPTIONS.filter || (OPTIONS['ember-source'] && OPTIONS['ember-data'])) { + main(); + } else { + usage(); + process.exitCode = 1; + return; + } +} diff --git a/docs/brocfile-transition.md b/docs/brocfile-transition.md index dd4863acb2..c542f23c87 100644 --- a/docs/brocfile-transition.md +++ b/docs/brocfile-transition.md @@ -33,7 +33,7 @@ module.exports = app.toTree(); ``` var EmberApp = require('ember-cli/lib/broccoli/ember-app'); -module.exports = function(defaults) { +module.exports = function (defaults) { var app = new EmberApp(defaults, { // Any other options }); diff --git a/docs/build-concurrency.md b/docs/build-concurrency.md index 3f91c60eb2..2960f2cac9 100644 --- a/docs/build-concurrency.md +++ b/docs/build-concurrency.md @@ -9,7 +9,7 @@ The default value for `process.env.JOBS` is (max concurrency) - 1 (via `require('os').cpus().length - 1`), however there may be times when you need to customize this value to avoid issues. -The most common case for this is in CI systems like TravisCI and CircleCI where -the total number of CPU's available on the system is very large (> 32) but the -individual CI jobs are limited to only 1.5 or 2 concurrent processes. - +The most common case for this is in CI systems like GitHub Actions, TravisCI, +and CircleCI where the total number of CPU's available on the system is very +large (> 32) but the individual CI jobs are limited to only 1.5 or 2 concurrent +processes. diff --git a/docs/code-coverage.md b/docs/code-coverage.md index 05f59387d4..c2b9d391ff 100644 --- a/docs/code-coverage.md +++ b/docs/code-coverage.md @@ -7,7 +7,8 @@ Code coverage information is generated using [istanbuljs](https://github.com/ist and then later uploaded to both [Coveralls](https://coveralls.io/github/ember-cli/ember-cli) and [Code Climate](https://codeclimate.com/github/ember-cli/ember-cli) via -[`.travis/codecoverage.sh`](../.travis/codecoverage.sh) after each Pull Request. +[`.github/workflows/coverage.yml`](../.github/workflows/coverage.yml) after each Pull Request. -`CODECLIMATE_REPO_TOKEN`, `COVERALLS_REPO_TOKEN` and `COVERALLS_SERVICE_NAME` are set via Travis CI -Settings and not exposed to the public as they are private tokens. +`CC_TEST_REPORTER_ID` is set via [GitHub encrypted +secrets](https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets) +and not exposed to the public as they are private tokens. diff --git a/docs/experiments.md b/docs/experiments.md index 5a993195f7..4093584e62 100644 --- a/docs/experiments.md +++ b/docs/experiments.md @@ -17,7 +17,7 @@ to ensure all tests pass when the feature is enabled _or_ disabled: const { isExperimentEnabled } = require('../experiments'); // ...snip... -if (isExperimentEnabled('MODULE_UNIFICATION')) { +if (isExperimentEnabled('SOME_EXPERIMENT')) { ... } else { ... diff --git a/docs/node-support.md b/docs/node-support.md index a16ae28f2a..98216207b6 100644 --- a/docs/node-support.md +++ b/docs/node-support.md @@ -8,11 +8,15 @@ | 0.12.x | 0.0.0 - 2.11.x | | 4.x | 1.13.9 - 3.1.x | | 5.x | 1.13.9 - 2.6.3 | -| 6.x | 2.9.0 - Current | +| 6.x | 2.9.0 - 3.9.x | | 7.x | 2.10.0 - 2.16.x | -| 8.x | 2.13.3 - Current | -| 9.x | 2.16.2 - Current | -| 10.x | 3.1.3 - Current | +| 8.x | 2.13.3 - 3.16.x | +| 9.x | 2.16.2 - 3.2.x | +| 10.x | 3.1.3 - 3.28.0 | +| 11.x | 3.9.0 - 3.13.0 | +| 12.x | 3.10.0 - Current | +| 13.x | 3.15.0 - 3.20.0 | +| 14.x | 3.19.0 - Current | ## Design @@ -26,18 +30,14 @@ Node.js](https://github.com/nodejs/LTS#lts_schedule). ## Current support: -* v6: Released as stable version then converted to LTS. - * Supported by ember-cli/ember-cli#master until: 2019-04-30. -* v8: Released as stable version then converted to LTS. - * Supported by ember-cli/ember-cli#master until: 2019-12-31 -* v9: Released as stable (not an LTS) - * Supported by ember-cli/ember-cli#master until: 2018-07-01. -* v10: Released as stable version then converted to LTS. - * Supported by ember-cli/ember-cli#master until: 2021-04-30. +* v12: Released as stable version then converted to LTS. + * Supported by ember-cli/ember-cli#master until: 2022-04-30. +* v14: Released as stable version then converted to LTS. + * Supported by ember-cli/ember-cli#master until: 2023-04-30. ## Release Process and Support Policy Ember and Ember CLI have committed to supporting the [Node.js LTS schedule](https://github.com/nodejs/LTS#lts-schedule) -for the `HEAD` of our `master` branch(es). This means that we will will drop support +for the `HEAD` of our `master` branch(es). This means that we will drop support per the [Node.js Release Working Group](https://github.com/nodejs/Release)'s schedule without a major version bump/change of ember-cli itself. diff --git a/docs/project_version_preprocessor.js b/docs/project_version_preprocessor.js index 03d7b7aafb..2a50955da4 100644 --- a/docs/project_version_preprocessor.js +++ b/docs/project_version_preprocessor.js @@ -3,6 +3,6 @@ let versionUtils = require('../lib/utilities/version-utils'); let emberCLIVersion = versionUtils.emberCLIVersion; -module.exports = function(data, options) { +module.exports = function (data, options) { options.project.version = emberCLIVersion(); }; diff --git a/lib/broccoli/amd-shim.js b/lib/broccoli/amd-shim.js deleted file mode 100644 index 3fa3499e22..0000000000 --- a/lib/broccoli/amd-shim.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; -const stew = require('broccoli-stew'); - -module.exports = function shimAmd(tree, nameMapping) { - return stew.map(tree, (content, relativePath) => { - let name = nameMapping[relativePath]; - if (name) { - return [ - '(function(define){\n', - content, - '\n})((function(){ function newDefine(){ var args = Array.prototype.slice.call(arguments); args.unshift("', - name, - '"); return define.apply(null, args); }; newDefine.amd = true; return newDefine; })());', - ].join(''); - } else { - return content; - } - }); -}; diff --git a/lib/broccoli/default-packager.js b/lib/broccoli/default-packager.js index 2520adb0bf..3d38e26d52 100644 --- a/lib/broccoli/default-packager.js +++ b/lib/broccoli/default-packager.js @@ -110,7 +110,6 @@ module.exports = class DefaultPackager { this._cachedPublic = null; this._cachedConfig = null; this._cachedJavascript = null; - this._cachedProcessedSrc = null; this._cachedProcessedIndex = null; this._cachedTransformedTree = null; this._cachedProcessedStyles = null; @@ -140,7 +139,6 @@ module.exports = class DefaultPackager { this.additionalAssetPaths = this.options.additionalAssetPaths; this.vendorTestStaticStyles = this.options.vendorTestStaticStyles; this.legacyTestFilesToAppend = this.options.legacyTestFilesToAppend; - this.isModuleUnificationEnabled = this.options.isModuleUnificationEnabled; } /* @@ -181,24 +179,10 @@ module.exports = class DefaultPackager { annotation: 'Classic: index.html', }); - if (this.isModuleUnificationEnabled) { - let srcIndex = new Funnel(tree, { - files: ['src/ui/index.html'], - getDestinationPath: () => indexFilePath, - annotation: 'Module Unification `index.html`', - }); - - index = mergeTrees([index, srcIndex], { - overwrite: true, - annotation: 'Classic And Module Unification `index.html`', - }); - } - let patterns = configReplacePatterns({ addons: this.project.addons, autoRun: this.autoRun, storeConfigInMeta: this.storeConfigInMeta, - isModuleUnification: this.isModuleUnificationEnabled, }); this._cachedProcessedIndex = new ConfigReplace(index, this.packageConfig(), { @@ -242,7 +226,7 @@ module.exports = class DefaultPackager { 'app-and-deps:post-templates' ); - let trees = [allTrees, appContentsWithCompiledTemplates, this.processJavascriptSrc(allTrees)].filter(Boolean); + let trees = [allTrees, appContentsWithCompiledTemplates].filter(Boolean); let mergedTree = this._debugTree( mergeTrees(trees, { @@ -290,7 +274,7 @@ module.exports = class DefaultPackager { */ importAdditionalAssets(tree) { if (this._cachedProcessedAdditionalAssets === null) { - let otherAssetTrees = funnelReducer(this.additionalAssetPaths).map(options => { + let otherAssetTrees = funnelReducer(this.additionalAssetPaths).map((options) => { let files = options.include.join(','); options.annotation = `${options.srcDir}/{${files}} => ${options.destDir}/{${files}}`; @@ -387,7 +371,6 @@ module.exports = class DefaultPackager { addons: this.project.addons, autoRun: this.autoRun, storeConfigInMeta: this.storeConfigInMeta, - isModuleUnification: this.isModuleUnificationEnabled, }); let configTree = this.packageConfig(); @@ -449,20 +432,6 @@ module.exports = class DefaultPackager { let mergedTemplates = [appFiles]; - if (this.isModuleUnificationEnabled) { - let include = this.registry.extensionsForType('template').map(extension => `**/*/template.${extension}`); - - let mu = new Funnel(inputTree, { - srcDir: 'src', - destDir: `${this.name}/src`, - include, - exclude: ['templates/**/*'], - annotation: 'MU Templates', - }); - mu = this._debugTree(mu, 'mu-layout:tree:template'); - mergedTemplates.push(mu); - } - mergedTemplates = mergeTrees(mergedTemplates, { overwrite: true, annotation: 'Templates', @@ -474,6 +443,7 @@ module.exports = class DefaultPackager { 'template', preprocessTemplates(preprocessedTemplatesFromAddons, { registry: this.registry, + treeType: 'templates', }) ); } @@ -533,6 +503,7 @@ module.exports = class DefaultPackager { let preprocessedApp = preprocessJs(app, '/', this.name, { registry: this.registry, + treeType: 'app', }); this._cachedProcessedJavascript = callAddonsPostprocessTreeHook(this.project, 'js', preprocessedApp); @@ -581,24 +552,20 @@ module.exports = class DefaultPackager { outputPaths: this.distPaths.appCssFile, registry: this.registry, minifyCSS: this.minifyCSS.options, + treeType: 'styles', }; let stylesAndVendor = callAddonsPreprocessTreeHook(this.project, 'css', tree); stylesAndVendor = this._debugTree(stylesAndVendor, 'mu-layout:addonsPreprocessTree:css'); - let preprocessedStyles = preprocessCss( - stylesAndVendor, - this.isModuleUnificationEnabled ? 'src/ui/styles' : '/app/styles', - '/assets', - options - ); + let preprocessedStyles = preprocessCss(stylesAndVendor, '/app/styles', '/assets', options); preprocessedStyles = this._debugTree(preprocessedStyles, 'mu-layout:preprocess:css'); let vendorStyles = []; for (let outputFile in this.styleOutputFiles) { let isMainVendorFile = outputFile === this.distPaths.vendorCssFile; let headerFiles = this.styleOutputFiles[outputFile]; - let inputFiles = isMainVendorFile ? ['addon-tree-output/**/*.css'] : []; + let inputFiles = isMainVendorFile ? ['addon-tree-output/**/__COMPILED_STYLES__/**/*.css'] : []; vendorStyles.push( concat(stylesAndVendor, { @@ -634,63 +601,6 @@ module.exports = class DefaultPackager { return this._cachedProcessedStyles; } - /* - * Runs pre/post-processors hooks on the application javascript files - * and returns a single tree with the processed ones. - * - * Used only when `MODULE_UNIFICATION` experiment is enabled. - * - * Given a tree: - * - * ``` - * / - * └── src - * ├── main.js - * ├── resolver.js - * ├── router.js - * └── ui - * ├── components - * ├── index.html - * ├── routes - * │ └── application - * │ └── template.hbs - * └── styles - * └── app.css - * ``` - * - * Returns the tree with only the JS/TS files, but with processed files. - * - * @private - * @method processJavascriptSrc - * @param {BroccoliTree} tree - * @return {BroccoliTree} - */ - processJavascriptSrc(tree) { - if (this._cachedProcessedSrc === null && this.isModuleUnificationEnabled) { - let src = new Funnel(tree, { - srcDir: 'src', - destDir: 'src', - include: ['**/*.{js,ts}'], - exclude: ['**/*-test.{js,ts}'], - annotation: 'Module Unification Src', - }); - - let srcAfterPreprocessTreeHook = callAddonsPreprocessTreeHook(this.project, 'src', src); - srcAfterPreprocessTreeHook = this._debugTree(srcAfterPreprocessTreeHook, 'mu-layout:addonsPreprocessTree:js'); - - let srcAfterPostprocessTreeHook = callAddonsPostprocessTreeHook(this.project, 'src', srcAfterPreprocessTreeHook); - srcAfterPostprocessTreeHook = this._debugTree(srcAfterPostprocessTreeHook, 'mu-layout:addonsPostprocessTree:js'); - - this._cachedProcessedSrc = new Funnel(mergeTrees([srcAfterPostprocessTreeHook], { ovewrite: true }), { - srcDir: '/', - destDir: this.name, - annotation: 'Funnel: src', - }); - } - - return this._cachedProcessedSrc; - } - /* * Given an input tree, returns a properly assembled Broccoli tree with bower * components. @@ -825,38 +735,12 @@ module.exports = class DefaultPackager { annotation: 'Tests To Process', }); - if (this.isModuleUnificationEnabled) { - let destDir, testSrcTree, srcDir; - - // ember-addon - if (this.name === 'dummy') { - testSrcTree = 'src'; - destDir = `${this.project.name()}/src`; - } else { - testSrcTree = tree; - destDir = `${this.name}/src`; - srcDir = 'src'; - } - testSrcTree = new Funnel(testSrcTree, { - srcDir, - include: ['**/*-test.{js,ts}'], - annotation: 'Module Unification Tests', - }); - - testSrcTree = new Funnel(testSrcTree, { - destDir, - }); - - treeToCompile = mergeTrees([treeToCompile, testSrcTree], { - annotation: 'Merge MU Tests', - }); - } - treeToCompile = callAddonsPreprocessTreeHook(this.project, 'test', treeToCompile); - const inputPath = this.isModuleUnificationEnabled ? '/' : '/tests'; + const inputPath = '/tests'; let preprocessedTests = preprocessJs(treeToCompile, inputPath, this.name, { registry: this.registry, + treeType: 'test', }); let mergedTestTrees = mergeTrees([addonTestSupportTree, preprocessedTests], { @@ -957,7 +841,6 @@ module.exports = class DefaultPackager { addons: this.project.addons, autoRun: this.autoRun, storeConfigInMeta: this.storeConfigInMeta, - isModuleUnification: this.isModuleUnificationEnabled, }); let configPath = path.join(this.name, 'config', 'environments', 'test.json'); @@ -1004,7 +887,6 @@ module.exports = class DefaultPackager { addons: this.project.addons, autoRun: this.autoRun, storeConfigInMeta: this.storeConfigInMeta, - isModuleUnification: this.isModuleUnificationEnabled, }); let configPath = path.join(this.name, 'config', 'environments', `test.json`); @@ -1065,7 +947,7 @@ module.exports = class DefaultPackager { }); return concat(appTestTrees, { - inputFiles: ['**/{tests,src}/**/*.js'], + inputFiles: ['**/tests/**/*.js'], headerFiles: ['vendor/ember-cli/tests-prefix.js'], footerFiles: ['vendor/ember-cli/app-config.js', 'vendor/ember-cli/tests-suffix.js'], outputFile: this.distPaths.testJsFile, @@ -1425,7 +1307,7 @@ module.exports = class DefaultPackager { // iterate over the keys and concat files // to support scenarios like // app.import('vendor/foobar.js', { outputFile: 'assets/baz.js' }); - let vendorTrees = importPaths.map(importPath => { + let vendorTrees = importPaths.map((importPath) => { let files = this.scriptOutputFiles[importPath]; let isMainVendorFile = importPath === this.distPaths.vendorJsFile; diff --git a/lib/broccoli/ember-addon.js b/lib/broccoli/ember-addon.js index c89c32f901..55d0b27af6 100644 --- a/lib/broccoli/ember-addon.js +++ b/lib/broccoli/ember-addon.js @@ -35,7 +35,6 @@ class EmberAddon extends EmberApp { trees: { app: 'tests/dummy/app', public: 'tests/dummy/public', - src: null, styles: 'tests/dummy/app/styles', templates: 'tests/dummy/app/templates', tests: new Funnel('tests', { @@ -54,10 +53,6 @@ class EmberAddon extends EmberApp { overrides.trees.styles = null; overrides.trees.templates = null; } - if (fs.existsSync('tests/dummy/src')) { - overrides.trees.src = 'tests/dummy/src'; - overrides.trees.styles = 'tests/dummy/src/ui/styles'; - } if (fs.existsSync('tests/dummy/vendor')) { overrides.trees.vendor = 'tests/dummy/vendor'; diff --git a/lib/broccoli/ember-app.js b/lib/broccoli/ember-app.js index 826b096f27..0b52fe442a 100644 --- a/lib/broccoli/ember-app.js +++ b/lib/broccoli/ember-app.js @@ -1,4 +1,3 @@ -/* global require, module */ 'use strict'; /** @@ -11,7 +10,6 @@ const chalk = require('chalk'); const resolve = require('resolve'); const Project = require('../models/project'); -const SilentError = require('silent-error'); let preprocessJs = p.preprocessJs; let isType = p.isType; @@ -20,9 +18,7 @@ let preprocessTemplates = p.preprocessTemplates; const concat = require('broccoli-concat'); const BroccoliDebug = require('broccoli-debug'); -const ModuleNormalizer = require('broccoli-module-normalizer'); const AmdFunnel = require('broccoli-amd-funnel'); -const ConfigReplace = require('broccoli-config-replace'); const mergeTrees = require('./merge-trees'); const WatchedDir = require('broccoli-source').WatchedDir; const UnwatchedDir = require('broccoli-source').UnwatchedDir; @@ -34,7 +30,6 @@ const omitBy = require('ember-cli-lodash-subset').omitBy; const isNull = require('ember-cli-lodash-subset').isNull; const Funnel = require('broccoli-funnel'); const logger = require('heimdalljs-logger')('ember-cli:ember-app'); -const emberAppUtils = require('../utilities/ember-app-utils'); const addonProcessTree = require('../utilities/addon-process-tree'); const lintAddonsByType = require('../utilities/lint-addons-by-type'); const emberCLIBabelConfigKey = require('../utilities/ember-cli-babel-config-key'); @@ -42,8 +37,6 @@ const { isExperimentEnabled } = require('../experiments'); const semver = require('semver'); const DefaultPackager = require('./default-packager'); -const configReplacePatterns = emberAppUtils.configReplacePatterns; - let DEFAULT_CONFIG = { storeConfigInMeta: true, autoRun: true, @@ -138,15 +131,11 @@ class EmberApp { this.populateLegacyFiles(); this.initializeAddons(); - this.project.addons.forEach(addon => (addon.app = this)); + this.project.addons.forEach((addon) => (addon.app = this)); p.setupRegistry(this); this._importAddonTransforms(); this._notifyAddonIncluded(); - if (!this._addonInstalled('loader.js') && !this.options._ignoreMissingLoader) { - throw new SilentError('The loader.js addon is missing from your project, please add it to `package.json`.'); - } - this._debugTree = BroccoliDebug.buildDebugCallback('ember-app'); this._defaultPackager = new DefaultPackager({ @@ -165,7 +154,6 @@ class EmberApp { additionalAssetPaths: this.otherAssetPaths, vendorTestStaticStyles: this.vendorTestStaticStyles, legacyTestFilesToAppend: this.legacyTestFilesToAppend, - isModuleUnificationEnabled: isExperimentEnabled('MODULE_UNIFICATION') && !!this.trees.src, distPaths: { appJsFile: this.options.outputPaths.app.js, appCssFile: this.options.outputPaths.app.css, @@ -180,6 +168,30 @@ class EmberApp { this._isPackageHookSupplied = typeof this.options.package === 'function'; this._cachedAddonBundles = {}; + + if (this.project.perBundleAddonCache && this.project.perBundleAddonCache.numProxies > 0) { + if (this.options.addons.whitelist && this.options.addons.whitelist.length) { + throw new Error( + [ + '[ember-cli] addon bundle caching is disabled for apps that specify an addon `whitelist`', + '', + 'All addons using bundle caching:', + ...this.project.perBundleAddonCache.getPathsToAddonsOptedIn(), + ].join('\n') + ); + } + + if (this.options.addons.blacklist && this.options.addons.blacklist.length) { + throw new Error( + [ + '[ember-cli] addon bundle caching is disabled for apps that specify an addon `blacklist`', + '', + 'All addons using bundle caching:', + ...this.project.perBundleAddonCache.getPathsToAddonsOptedIn(), + ].join('\n') + ); + } + } } /** @@ -192,10 +204,10 @@ class EmberApp { @param {Object} options */ _initTestsAndHinting(options) { - let testsEnabledDefault = process.env.EMBER_CLI_TEST_COMMAND || !this.isProduction; + let testsEnabledDefault = process.env.EMBER_CLI_TEST_COMMAND === 'true' || !this.isProduction; - this.tests = options.hasOwnProperty('tests') ? options.tests : testsEnabledDefault; - this.hinting = options.hasOwnProperty('hinting') ? options.hinting : testsEnabledDefault; + this.tests = 'tests' in options ? options.tests : testsEnabledDefault; + this.hinting = 'hinting' in options ? options.hinting : testsEnabledDefault; } /** @@ -212,9 +224,10 @@ class EmberApp { this.project = options.project || Project.closestSync(process.cwd()); if (options.configPath) { - this.project.configPath = function() { + this.project.configPath = function () { return app._resolveLocal(options.configPath); }; + this.project.configCache.clear(); } } @@ -256,7 +269,6 @@ class EmberApp { }; let trees = (options && options.trees) || {}; - let srcTree = buildTreeFor('src', trees.src); let appTree = buildTreeFor('app', trees.app); let testsPath = typeof trees.tests === 'string' ? resolvePathFor('tests', trees.tests) : null; @@ -264,13 +276,7 @@ class EmberApp { // these are contained within app/ no need to watch again // (we should probably have the builder or the watcher dedup though) - - if (isExperimentEnabled('MODULE_UNIFICATION')) { - let srcStylesPath = `${resolvePathFor('src', trees.src)}/ui/styles`; - this._stylesPath = fs.existsSync(srcStylesPath) ? srcStylesPath : resolvePathFor('app/styles', trees.styles); - } else { - this._stylesPath = resolvePathFor('app/styles', trees.styles); - } + this._stylesPath = resolvePathFor('app/styles', trees.styles); let stylesTree = null; if (fs.existsSync(this._stylesPath)) { @@ -302,6 +308,7 @@ class EmberApp { enabled: this.isProduction, options: { processImport: false }, }, + // TODO: remove this with a deprecation (nothing in the default app/addon setup consumes it) minifyJS: { enabled: this.isProduction, options: { @@ -331,7 +338,6 @@ class EmberApp { extensions: ['js'], }, trees: { - src: srcTree, app: appTree, tests: testsTree, styles: stylesTree, @@ -580,7 +586,7 @@ class EmberApp { result = result || false; roots = roots || {}; - let babelInstance = addons.find(addon => addon.name === 'ember-cli-babel'); + let babelInstance = addons.find((addon) => addon.name === 'ember-cli-babel'); if (babelInstance) { let version = babelInstance.pkg.version; if (semver.lt(version, '6.6.0')) { @@ -589,14 +595,12 @@ class EmberApp { if (semver.lt(version, '6.0.0') && !roots[babelInstance.root]) { roots[babelInstance.root] = true; this.project.ui.writeDeprecateLine( - `ember-cli-babel 5.x has been deprecated. Please upgrade to at least ember-cli-babel 6.6. Version ${version} located: ${ - babelInstance.root - }` + `ember-cli-babel 5.x has been deprecated. Please upgrade to at least ember-cli-babel 6.6. Version ${version} located: ${babelInstance.root}` ); } } - return addons.some(addon => this._checkEmberCliBabel(addon.addons, result, roots)) || result; + return addons.some((addon) => this._checkEmberCliBabel(addon.addons, result, roots)) || result; } /** @@ -622,10 +626,10 @@ class EmberApp { @method _notifyAddonIncluded */ _notifyAddonIncluded() { - let addonNames = this.project.addons.map(addon => addon.name); + let addonNames = this.project.addons.map((addon) => addon.name); if (this.options.addons.blacklist) { - this.options.addons.blacklist.forEach(addonName => { + this.options.addons.blacklist.forEach((addonName) => { if (addonNames.indexOf(addonName) === -1) { throw new Error(`Addon "${addonName}" defined in blacklist is not found`); } @@ -633,7 +637,7 @@ class EmberApp { } if (this.options.addons.whitelist) { - this.options.addons.whitelist.forEach(addonName => { + this.options.addons.whitelist.forEach((addonName) => { if (addonNames.indexOf(addonName) === -1) { throw new Error(`Addon "${addonName}" defined in whitelist is not found`); } @@ -642,9 +646,9 @@ class EmberApp { // the addons must be filtered before the `included` hook is called // in case an addon caches the project.addons list - this.project.addons = this.project.addons.filter(addon => this.shouldIncludeAddon(addon)); + this.project.addons = this.project.addons.filter((addon) => this.shouldIncludeAddon(addon)); - this.project.addons.forEach(addon => { + this.project.addons.forEach((addon) => { if (addon.included) { addon.included(this); } @@ -658,7 +662,7 @@ class EmberApp { @method _importAddonTransforms */ _importAddonTransforms() { - this.project.addons.forEach(addon => { + this.project.addons.forEach((addon) => { if (this.shouldIncludeAddon(addon)) { if (addon.importTransforms) { let transforms = addon.importTransforms(); @@ -667,7 +671,7 @@ class EmberApp { throw new Error(`Addon "${addon.name}" did not return a transform map from importTransforms function`); } - Object.keys(transforms).forEach(transformName => { + Object.keys(transforms).forEach((transformName) => { let transformConfig = { files: [], options: {}, @@ -689,11 +693,7 @@ class EmberApp { if (this._customTransformsMap.has(transformName)) { // there is already a transform with a same name, therefore we warn the user this.project.ui.writeWarnLine( - `Addon "${ - addon.name - }" is defining a transform name: ${transformName} that is already being defined. Using transform from addon: "${ - addon.name - }".` + `Addon "${addon.name}" is defining a transform name: ${transformName} that is already being defined. Using transform from addon: "${addon.name}".` ); } @@ -740,17 +740,17 @@ class EmberApp { @return {Array} List of trees */ addonTreesFor(type) { - return this._addonTreesFor(type).map(addonBundle => addonBundle.tree); + return this._addonTreesFor(type).map((addonBundle) => addonBundle.tree); } _getDefaultPluginForType(type) { let plugins = this.registry.load(type); - let defaultsForType = plugins.filter(plugin => plugin.isDefaultForType); + let defaultsForType = plugins.filter((plugin) => plugin.isDefaultForType); if (defaultsForType.length > 1) { throw new Error( `There are multiple preprocessor plugins marked as default for '${type}': ${defaultsForType - .map(p => p.name) + .map((p) => p.name) .join(', ')}` ); } @@ -820,6 +820,7 @@ class EmberApp { tree = preprocessJs(tree, '/', '/', { annotation: `_precompileAppJsTree`, registry: this.registry, + treeType: 'app', }); // return the original params because there are multiple @@ -974,36 +975,6 @@ class EmberApp { return this._cachedTemplateTree; } - /** - Returns the tree for /tests/index.html - - @private - @method testIndex - @return {Tree} Tree for /tests/index.html - */ - testIndex() { - let index = new Funnel(this.trees.tests, { - srcDir: '/', - files: ['index.html'], - destDir: '/tests', - annotation: 'Funnel (test index)', - }); - - let patterns = configReplacePatterns({ - addons: this.project.addons, - autoRun: this.options.autoRun, - storeConfigInMeta: this.options.storeConfigInMeta, - isModuleUnification: isExperimentEnabled('MODULE_UNIFICATION') && !!this.trees.src, - }); - - return new ConfigReplace(index, this._defaultPackager.packageConfig(this.tests), { - configPath: path.join(this.name, 'config', 'environments', 'test.json'), - files: ['tests/index.html'], - env: 'test', - patterns, - }); - } - /* * Gather application and add-ons javascript files and return them in a single * tree. @@ -1058,26 +1029,6 @@ class EmberApp { return appTree; } - /* - * Returns a `src/` directory of the application or `null` if it is not - * present. - * - * @private - * @method getSrc - * @return {BroccoliTree} - */ - getSrc() { - let rawSrcTree = this.trees.src; - - if (!rawSrcTree) { - return null; - } - - return new Funnel(rawSrcTree, { - destDir: 'src', - }); - } - /* * Gather add-ons style (css/sass/less) files and return them in a single * tree. @@ -1111,24 +1062,7 @@ class EmberApp { overwrite: true, annotation: 'Styles', }); - if (isExperimentEnabled('MODULE_UNIFICATION') && this.trees.src) { - // move only app/styles to /src/ui/styles - let styles1 = new Funnel(styles, { - allowEmpty: true, - srcDir: '/app/styles', - destDir: '/src/ui/styles', - }); - let styles2 = new Funnel(styles, { - allowEmpty: true, - srcDir: '.', - exclude: ['app/styles/**'], - destDir: '.', - }); - styles = mergeTrees([styles1, styles2], { - annotation: 'Styles MU', - }); - styles = this._debugTree(styles, 'mu-layout:styles'); - } + return styles; } @@ -1175,14 +1109,14 @@ class EmberApp { @return {Array} An array of regular expressions. */ _podTemplatePatterns() { - return this.registry.extensionsForType('template').map(extension => `**/*/template.${extension}`); + return this.registry.extensionsForType('template').map((extension) => `**/*/template.${extension}`); } _nodeModuleTrees() { if (!this._cachedNodeModuleTrees) { this._cachedNodeModuleTrees = Array.from( this._nodeModules.values(), - module => + (module) => new Funnel(module.path, { srcDir: '/', destDir: `node_modules/${module.name}/`, @@ -1194,39 +1128,10 @@ class EmberApp { return this._cachedNodeModuleTrees; } - _addonBundles(type, _options) { + _addonBundles(type) { if (!this._cachedAddonBundles[type]) { - let options = Object.assign( - { - moduleNormalizerDisabled: this.options.moduleNormalizerDisabled, - }, - _options - ); - let addonBundles = this._addonTreesFor(type); - for (let addonBundle of addonBundles) { - let { name, root } = addonBundle; - - if (isExperimentEnabled('DELAYED_TRANSPILATION')) { - if (!options.moduleNormalizerDisabled) { - // move legacy /modules/addon to /addon - let hasAlreadyPrintedModuleDeprecation; - addonBundle.tree = new ModuleNormalizer(addonBundle.tree, { - callback: () => { - if (!hasAlreadyPrintedModuleDeprecation) { - this.project.ui.writeDeprecateLine( - `Addon "${name}" (found at "${root}") is manually placing files in the legacy "modules" folder. Support for this will be removed in a future version.` - ); - hasAlreadyPrintedModuleDeprecation = true; - } - }, - annotation: `ModuleNormalizer (${type} ${name})`, - }); - } - } - } - this._cachedAddonBundles[type] = addonBundles; } @@ -1240,10 +1145,13 @@ class EmberApp { createAddonTree(type, outputDir, options) { let addonBundles = this._addonBundles(type, options); - let tree = mergeTrees(addonBundles.map(({ tree }) => tree), { - overwrite: true, - annotation: `TreeMerger (${type})`, - }); + let tree = mergeTrees( + addonBundles.map(({ tree }) => tree), + { + overwrite: true, + annotation: `TreeMerger (${type})`, + } + ); return new Funnel(tree, { destDir: outputDir, @@ -1267,16 +1175,6 @@ class EmberApp { return this._cachedAddonTestSupportTree; } - addonSrcTree() { - if (!this._cachedAddonSrcTree) { - this._cachedAddonSrcTree = this.createAddonTree('src', 'addon-tree-output', { - moduleNormalizerDisabled: true, - }); - } - - return this._cachedAddonSrcTree; - } - /* * Gather all dependencies external to `ember-cli`, namely: * @@ -1312,9 +1210,8 @@ class EmberApp { ); let addons = this.addonTree(); - let addonsSrc = this.addonSrcTree(); - let trees = [vendor].concat(addons, addonsSrc); + let trees = [vendor].concat(addons); if (this._bowerEnabled) { let bower = this._defaultPackager.packageBower(this.trees.bower, this.bowerDirectory); @@ -1443,16 +1340,6 @@ class EmberApp { lintTrees.push(lintedApp); } - if (isExperimentEnabled('MODULE_UNIFICATION') && this.trees.src) { - let lintedSrc = this.addonLintTree('src', this.trees.src); - lintedSrc = new Funnel(lintedSrc, { - destDir: 'lint/src/', - annotation: 'Funnel (lint src)', - }); - - lintTrees.push(lintedSrc); - } - let lintedTests = this.addonLintTree('tests', this.trees.tests); let lintedTemplates = this.addonLintTree('templates', this._templatesTree()); @@ -1554,7 +1441,7 @@ class EmberApp { if (!Array.isArray(options.using)) { throw new Error('You must pass an array of transformations for `using` option'); } - options.using.forEach(entry => { + options.using.forEach((entry) => { if (!entry.transformation) { throw new Error( `while importing ${assetPath}: each entry in the \`using\` list must have a \`transformation\` name` @@ -1676,7 +1563,6 @@ class EmberApp { this.getTests(), this.getExternalTree(), this.getPublic(), - this.getSrc(), this.getAppJavascript(this._isPackageHookSupplied), ].filter(Boolean); } @@ -1693,7 +1579,7 @@ class EmberApp { let addonBundles = this._cachedAddonBundles[type]; - let addonTrees = addonBundles.map(addonBundle => { + let addonTrees = addonBundles.map((addonBundle) => { let { name, tree, root } = addonBundle; let precompiledSource = tree; @@ -1717,8 +1603,8 @@ class EmberApp { return [tree, precompiledSource]; }); - let precompiledSource = addonTrees.map(pair => pair[1]); - addonTrees = addonTrees.map(pair => pair[0]); + let precompiledSource = addonTrees.map((pair) => pair[1]); + addonTrees = addonTrees.map((pair) => pair[0]); precompiledSource = mergeTrees(precompiledSource, { overwrite: true, @@ -1749,27 +1635,6 @@ class EmberApp { } _legacyPackage(fullTree) { - if (isExperimentEnabled('DELAYED_TRANSPILATION')) { - fullTree = mergeTrees( - [ - fullTree, - this._legacyAddonCompile('addon', 'addon-tree-output'), - this._legacyAddonCompile('addon-test-support', 'tests/addon-test-support', { - skipTemplates: true, - }), - this._legacyAddonCompile('src', 'addon-tree-output', { - // moduleNormalizerDisabled: true, - amdFunnelDisabled: true, - }), - ], - { - overwrite: true, - } - ); - - fullTree = this._debugTree(fullTree, 'postcompiledAddonTrees'); - } - let javascriptTree = this._defaultPackager.packageJavascript(fullTree); let stylesTree = this._defaultPackager.packageStyles(fullTree); let appIndex = this._defaultPackager.processIndex(fullTree); diff --git a/lib/broccoli/merge-trees.js b/lib/broccoli/merge-trees.js index 301bb5cb28..3e5116a9aa 100644 --- a/lib/broccoli/merge-trees.js +++ b/lib/broccoli/merge-trees.js @@ -20,7 +20,7 @@ function getEmptyTree() { }); let originalCleanup = EMPTY_MERGE_TREE.cleanup; - EMPTY_MERGE_TREE.cleanup = function() { + EMPTY_MERGE_TREE.cleanup = function () { // this tree is being cleaned up, we must // ensure that our shared EMPTY_MERGE_TREE is // reset (otherwise it will not have a valid diff --git a/lib/broccoli/vendor-prefix.js b/lib/broccoli/vendor-prefix.js index ea8843249f..e5f80de603 100644 --- a/lib/broccoli/vendor-prefix.js +++ b/lib/broccoli/vendor-prefix.js @@ -1,4 +1,11 @@ -window.EmberENV = {{EMBER_ENV}}; +window.EmberENV = (function(EmberENV, extra) { + for (var key in extra) { + EmberENV[key] = extra[key]; + } + + return EmberENV; +})(window.EmberENV || {}, {{EMBER_ENV}}); + var runningTests = false; {{content-for 'vendor-prefix'}} diff --git a/lib/cli/cli.js b/lib/cli/cli.js index 7f605cbe34..bbd1dbf7d2 100644 --- a/lib/cli/cli.js +++ b/lib/cli/cli.js @@ -1,16 +1,14 @@ 'use strict'; -const RSVP = require('rsvp'); - const lookupCommand = require('./lookup-command'); const getOptionArgs = require('../utilities/get-option-args'); +const hash = require('promise.hash.helper'); const logger = require('heimdalljs-logger')('ember-cli:cli'); const loggerTesting = require('heimdalljs-logger')('ember-cli:testing'); const Instrumentation = require('../models/instrumentation'); const PackageInfoCache = require('../models/package-info-cache'); const heimdall = require('heimdalljs'); -const Promise = RSVP.Promise; const onProcessInterrupt = require('../utilities/will-interrupt-process'); class CLI { @@ -82,159 +80,183 @@ class CLI { logger.info('testing %o', !!this.testing); } + /** + * @private + * @method maybeMakeCommand + * @param commandName + * @param commandArgs + * @return {null|CurrentCommand} + */ + maybeMakeCommand(commandName, commandArgs) { + if (this._environment === undefined) { + throw new Error('Unable to make command without environment, you have to execute "run" method first.'); + } + let CurrentCommand = lookupCommand(this._environment.commands, commandName, commandArgs, { + project: this._environment.project, + ui: this.ui, + }); + + /* + * XXX Need to decide what to do here about showing errors. For + * a non-CLI project the cache is local and probably should. For + * a CLI project the cache is there, but not sure when we'll know + * about all the errors, because there may be multiple projects. + * if (this.packageInfoCache.hasErrors()) { + * this.packageInfoCache.showErrors(); + * } + */ + let command = new CurrentCommand({ + ui: this.ui, + analytics: this.analytics, + commands: this._environment.commands, + tasks: this._environment.tasks, + project: this._environment.project, + settings: this._environment.settings, + testing: this.testing, + cli: this, + }); + + return command; + } + /** * @private * @method run - * @param environment + * @param {Promise} environmentPromiseHash * @return {Promise} */ - run(environment) { + async run(environmentPromiseHash) { + if (environmentPromiseHash === undefined) { + return Promise.reject(new Error('Unable to execute "run" command without environment argument')); + } let shutdownOnExit = null; - return RSVP.hash(environment) - .then(environment => { - let args = environment.cliArgs.slice(); - let commandName = args.shift(); - let commandArgs = args; - let helpOptions; - - let commandLookupCreationToken = heimdall.start('lookup-command'); - - let CurrentCommand = lookupCommand(environment.commands, commandName, commandArgs, { - project: environment.project, - ui: this.ui, - }); - - /* - * XXX Need to decide what to do here about showing errors. For - * a non-CLI project the cache is local and probably should. For - * a CLI project the cache is there, but not sure when we'll know - * about all the errors, because there may be multiple projects. - * if (this.packageInfoCache.hasErrors()) { - * this.packageInfoCache.showErrors(); - * } - */ - let command = new CurrentCommand({ - ui: this.ui, - analytics: this.analytics, - commands: environment.commands, - tasks: environment.tasks, - project: environment.project, - settings: environment.settings, - testing: this.testing, - cli: this, - }); - - commandLookupCreationToken.stop(); - - getOptionArgs('--verbose', commandArgs).forEach(arg => { - process.env[`EMBER_VERBOSE_${arg.toUpperCase()}`] = 'true'; - }); - - let platformCheckerToken = heimdall.start('platform-checker'); - - const PlatformChecker = require('../utilities/platform-checker'); - let platform = new PlatformChecker(process.version); - let recommendation = - ' We recommend that you use the most-recent "Active LTS" version of Node.js.' + - ' See https://git.io/v7S5n for details.'; - - if (!this.testing) { - if (platform.isDeprecated) { - this.ui.writeDeprecateLine(`Node ${process.version} is no longer supported by Ember CLI.${recommendation}`); - } + let environment = (this._environment = await hash(environmentPromiseHash)); - if (!platform.isTested) { - this.ui.writeWarnLine( - `Node ${process.version} is not tested against Ember CLI on your platform.${recommendation}` - ); - } + try { + let args = environment.cliArgs.slice(); + let commandName = args.shift(); + let commandArgs = args; + let helpOptions; + + let commandLookupCreationToken = heimdall.start('lookup-command'); + + let command = this.maybeMakeCommand(commandName, commandArgs); + + commandLookupCreationToken.stop(); + + getOptionArgs('--verbose', commandArgs).forEach((arg) => { + process.env[`EMBER_VERBOSE_${arg.toUpperCase()}`] = 'true'; + }); + + let platformCheckerToken = heimdall.start('platform-checker'); + + const PlatformChecker = require('../utilities/platform-checker'); + let platform = new PlatformChecker(process.version); + let recommendation = + ' We recommend that you use the most-recent "Active LTS" version of Node.js.' + + ' See https://git.io/v7S5n for details.'; + + if (!this.testing) { + if (platform.isDeprecated) { + this.ui.writeDeprecateLine(`Node ${process.version} is no longer supported by Ember CLI.${recommendation}`); } - platformCheckerToken.stop(); + if (!platform.isTested) { + this.ui.writeWarnLine( + `Node ${process.version} is not tested against Ember CLI on your platform.${recommendation}` + ); + } + } - logger.info('command: %s', commandName); + platformCheckerToken.stop(); - if (!this.testing) { - process.chdir(environment.project.root); - let skipInstallationCheck = commandArgs.indexOf('--skip-installation-check') !== -1; - if (environment.project.isEmberCLIProject() && !skipInstallationCheck) { - const InstallationChecker = require('../models/installation-checker'); - new InstallationChecker({ project: environment.project }).checkInstallations(); - } + logger.info('command: %s', commandName); + + if (!this.testing) { + process.chdir(environment.project.root); + let skipInstallationCheck = commandArgs.indexOf('--skip-installation-check') !== -1; + if (environment.project.isEmberCLIProject() && !skipInstallationCheck) { + const InstallationChecker = require('../models/installation-checker'); + new InstallationChecker({ project: environment.project }).checkInstallations(); } + } + + let instrumentation = this.instrumentation; + let onCommandInterrupt; - let instrumentation = this.instrumentation; - let onCommandInterrupt; + let runPromise = Promise.resolve().then(async () => { + let resultOrExitCode; - let runPromise = Promise.resolve() - .then(() => { - instrumentation.stopAndReport('init'); + try { + instrumentation.stopAndReport('init'); + + try { instrumentation.start('command'); loggerTesting.info('cli: command.beforeRun'); onProcessInterrupt.addHandler(onCommandInterrupt); - return command.beforeRun(commandArgs); - }) - .then(() => { + await command.beforeRun(commandArgs); + loggerTesting.info('cli: command.validateAndRun'); - return command.validateAndRun(commandArgs); - }) - .then(result => { + resultOrExitCode = await command.validateAndRun(commandArgs); + } finally { instrumentation.stopAndReport('command', commandName, commandArgs); onProcessInterrupt.removeHandler(onCommandInterrupt); + } + } finally { + instrumentation.start('shutdown'); + shutdownOnExit = function () { + instrumentation.stopAndReport('shutdown'); + }; + } - return result; - }) - .finally(() => { - instrumentation.start('shutdown'); - shutdownOnExit = function() { - instrumentation.stopAndReport('shutdown'); - }; - }) - .then(result => { - // if the help option was passed, call the help command - if (result === 'callHelp') { - helpOptions = { - environment, - commandName, - commandArgs, - }; - - return this.callHelp(helpOptions); - } - - return result; - }) - .then(exitCode => { - loggerTesting.info(`cli: command run complete. exitCode: ${exitCode}`); - // TODO: fix this - // Possibly this issue: https://github.com/joyent/node/issues/8329 - // Wait to resolve promise when running on windows. - // This ensures that stdout is flushed so acceptance tests get full output - - return new Promise(resolve => { - if (process.platform === 'win32') { - setTimeout(resolve, 250, exitCode); - } else { - resolve(exitCode); - } - }); - }); + // if the help option was passed, call the help command + if (resultOrExitCode === 'callHelp') { + helpOptions = { + environment, + commandName, + commandArgs, + }; + + resultOrExitCode = await this.callHelp(helpOptions); + } + + let exitCode = resultOrExitCode; - onCommandInterrupt = () => Promise.resolve(command.onInterrupt()).then(() => runPromise); + loggerTesting.info(`cli: command run complete. exitCode: ${exitCode}`); + // TODO: fix this + // Possibly this issue: https://github.com/joyent/node/issues/8329 + // Wait to resolve promise when running on windows. + // This ensures that stdout is flushed so acceptance tests get full output - return runPromise; - }) - .finally(() => { - if (shutdownOnExit) { - shutdownOnExit(); + if (process.platform === 'win32') { + return new Promise((resolve) => { + setTimeout(resolve, 250, exitCode); + }); + } else { + return exitCode; } - }) - .catch(this.logError.bind(this)); + }); + + onCommandInterrupt = async () => { + await command.onInterrupt(); + + return await runPromise; + }; + + return await runPromise; + } catch (error) { + this.logError(error); + return 1; + } finally { + if (shutdownOnExit) { + shutdownOnExit(); + } + } } /** diff --git a/lib/cli/index.js b/lib/cli/index.js index a0c3030fef..b2ac760015 100644 --- a/lib/cli/index.js +++ b/lib/cli/index.js @@ -51,9 +51,7 @@ function clientId() { if (id) { return id; } else { - id = require('uuid') - .v4() - .toString(); + id = require('uuid').v4().toString(); configStore.set('client-id', id); return id; } @@ -68,80 +66,84 @@ function configureLogger(env) { } // Options: Array cliArgs, Stream inputStream, Stream outputStream, EventEmitter process -module.exports = function(options) { +module.exports = async function (options) { // `process` should be captured before we require any libraries which // may use `process.exit` work arounds for async cleanup. willInterruptProcess.capture(options.process || process); - let UI = options.UI || require('console-ui'); - const CLI = require('./cli'); - let Leek = options.Leek || require('leek'); - const Project = require('../models/project'); - let config = getConfig(options.Yam); - - configureLogger(process.env); - - // TODO: one UI (lib/models/project.js also has one for now...) - let ui = new UI({ - inputStream: options.inputStream, - outputStream: options.outputStream, - errorStream: options.errorStream || process.stderr, - errorLog: options.errorLog || [], - ci: ciInfo.isCI || /^(dumb|emacs)$/.test(process.env.TERM), - writeLevel: process.argv.indexOf('--silent') !== -1 ? 'ERROR' : undefined, - }); - - let leekOptions; - - let disableAnalytics = - (options.cliArgs && - (options.cliArgs.indexOf('--disable-analytics') > -1 || - options.cliArgs.indexOf('-v') > -1 || - options.cliArgs.indexOf('--version') > -1)) || - config.get('disableAnalytics'); - - let defaultLeekOptions = { - trackingCode, - globalName: name, - name: clientId(), - version, - silent: disableAnalytics, - }; - - let defaultUpdateCheckerOptions = { - checkForUpdates: false, - }; - - if (config.get('leekOptions')) { - leekOptions = merge(defaultLeekOptions, config.get('leekOptions')); - } else { - leekOptions = defaultLeekOptions; + try { + let UI = options.UI || require('console-ui'); + const CLI = require('./cli'); + let Leek = options.Leek || require('leek'); + const Project = require('../models/project'); + let config = getConfig(options.Yam); + + configureLogger(process.env); + + // TODO: one UI (lib/models/project.js also has one for now...) + let ui = new UI({ + inputStream: options.inputStream, + outputStream: options.outputStream, + errorStream: options.errorStream || process.stderr, + errorLog: options.errorLog || [], + ci: ciInfo.isCI || /^(dumb|emacs)$/.test(process.env.TERM), + writeLevel: process.argv.indexOf('--silent') !== -1 ? 'ERROR' : undefined, + }); + + let leekOptions; + + let disableAnalytics = + (options.cliArgs && + (options.cliArgs.indexOf('--disable-analytics') > -1 || + options.cliArgs.indexOf('-v') > -1 || + options.cliArgs.indexOf('--version') > -1)) || + config.get('disableAnalytics'); + + let defaultLeekOptions = { + trackingCode, + globalName: name, + name: clientId(), + version, + silent: disableAnalytics, + }; + + let defaultUpdateCheckerOptions = { + checkForUpdates: false, + }; + + if (config.get('leekOptions')) { + leekOptions = merge(defaultLeekOptions, config.get('leekOptions')); + } else { + leekOptions = defaultLeekOptions; + } + + logger.info('leek: %o', leekOptions); + + let leek = new Leek(leekOptions); + + let cli = new CLI({ + ui, + analytics: leek, + testing: options.testing, + name: options.cli ? options.cli.name : 'ember', + disableDependencyChecker: options.disableDependencyChecker, + root: options.cli ? options.cli.root : path.resolve(__dirname, '..', '..'), + npmPackage: options.cli ? options.cli.npmPackage : 'ember-cli', + initInstrumentation, + }); + + let project = Project.projectOrnullProject(ui, cli); + + let environment = { + tasks: loadTasks(), + cliArgs: options.cliArgs, + commands: loadCommands(), + project, + settings: merge(defaultUpdateCheckerOptions, config.getAll()), + }; + + return await cli.run(environment); + } finally { + willInterruptProcess.release(); } - - logger.info('leek: %o', leekOptions); - - let leek = new Leek(leekOptions); - - let cli = new CLI({ - ui, - analytics: leek, - testing: options.testing, - name: options.cli ? options.cli.name : 'ember', - disableDependencyChecker: options.disableDependencyChecker, - root: options.cli ? options.cli.root : path.resolve(__dirname, '..', '..'), - npmPackage: options.cli ? options.cli.npmPackage : 'ember-cli', - initInstrumentation, - }); - - let project = Project.projectOrnullProject(ui, cli); - - let environment = { - tasks: loadTasks(), - cliArgs: options.cliArgs, - commands: loadCommands(), - project, - settings: merge(defaultUpdateCheckerOptions, config.getAll()), - }; - - return cli.run(environment).finally(() => willInterruptProcess.release()); }; diff --git a/lib/cli/lookup-command.js b/lib/cli/lookup-command.js index 5e8f1d1915..1ddaae044a 100644 --- a/lib/cli/lookup-command.js +++ b/lib/cli/lookup-command.js @@ -2,7 +2,7 @@ const UnknownCommand = require('../commands/unknown'); -module.exports = function(commands, commandName, commandArgs, optionHash) { +module.exports = function (commands, commandName, commandArgs, optionHash) { let options = optionHash || {}; let project = options.project; let ui = options.ui; diff --git a/lib/commands/addon.js b/lib/commands/addon.js index b1321e619e..ad0d649451 100644 --- a/lib/commands/addon.js +++ b/lib/commands/addon.js @@ -15,6 +15,11 @@ module.exports = NewCommand.extend({ { name: 'skip-git', type: Boolean, default: false, aliases: ['sg'] }, { name: 'yarn', type: Boolean }, // no default means use yarn if the blueprint has a yarn.lock { name: 'directory', type: String, aliases: ['dir'] }, + { + name: 'lang', + type: String, + description: "Sets the base human language of the addon's own test application via index.html", + }, ], anonymousOptions: [''], diff --git a/lib/commands/build.js b/lib/commands/build.js index da3227044b..b4e2113e6b 100644 --- a/lib/commands/build.js +++ b/lib/commands/build.js @@ -22,19 +22,17 @@ module.exports = Command.extend({ { name: 'suppress-sizes', type: Boolean, default: false }, ], - run(commandOptions) { - return Win.checkIfSymlinksNeedToBeEnabled(this.ui) - .then(() => { - let buildTaskName = commandOptions.watch ? 'BuildWatch' : 'Build'; - return this.runTask(buildTaskName, commandOptions); - }) - .then(() => { - let isProduction = commandOptions.environment === 'production' || process.env.EMBER_ENV === 'production'; - if (!commandOptions.suppressSizes && isProduction) { - return this.runTask('ShowAssetSizes', { - outputPath: commandOptions.outputPath, - }); - } + async run(commandOptions) { + await Win.checkIfSymlinksNeedToBeEnabled(this.ui); + + let buildTaskName = commandOptions.watch ? 'BuildWatch' : 'Build'; + await this.runTask(buildTaskName, commandOptions); + + let isProduction = commandOptions.environment === 'production' || process.env.EMBER_ENV === 'production'; + if (!commandOptions.suppressSizes && isProduction) { + return this.runTask('ShowAssetSizes', { + outputPath: commandOptions.outputPath, }); + } }, }); diff --git a/lib/commands/destroy.js b/lib/commands/destroy.js index 50cdc61208..f6089c25fc 100644 --- a/lib/commands/destroy.js +++ b/lib/commands/destroy.js @@ -2,7 +2,6 @@ const path = require('path'); const Command = require('../models/command'); -const Promise = require('rsvp').Promise; const mergeBlueprintOptions = require('../utilities/merge-blueprint-options'); const merge = require('ember-cli-lodash-subset').merge; const SilentError = require('silent-error'); diff --git a/lib/commands/generate.js b/lib/commands/generate.js index 1e1730629b..92a7ffe165 100644 --- a/lib/commands/generate.js +++ b/lib/commands/generate.js @@ -3,7 +3,6 @@ const path = require('path'); const chalk = require('chalk'); const Command = require('../models/command'); -const Promise = require('rsvp').Promise; const Blueprint = require('../models/blueprint'); const mergeBlueprintOptions = require('../utilities/merge-blueprint-options'); const _ = require('ember-cli-lodash-subset'); @@ -23,6 +22,7 @@ module.exports = Command.extend({ { name: 'classic', type: Boolean, default: false, aliases: ['c'] }, { name: 'dummy', type: Boolean, default: false, aliases: ['dum', 'id'] }, { name: 'in-repo-addon', type: String, default: null, aliases: ['in-repo', 'ir'] }, + { name: 'lint-fix', type: Boolean, default: true }, { name: 'in', type: String, @@ -104,7 +104,7 @@ module.exports = Command.extend({ let collectionsJson = []; - blueprintList.forEach(function(collection) { + blueprintList.forEach(function (collection) { let result = this.getPackageBlueprints(collection, options, singleBlueprintName); if (options.json) { let collectionJson = {}; @@ -142,7 +142,7 @@ module.exports = Command.extend({ let blueprintsJson = []; - blueprints.forEach(function(blueprint) { + blueprints.forEach(function (blueprint) { let singleMatch = singleBlueprintName === blueprint.name; if (singleMatch) { verbose = true; diff --git a/lib/commands/help.js b/lib/commands/help.js index 03b4a321e7..e1fd4e1878 100644 --- a/lib/commands/help.js +++ b/lib/commands/help.js @@ -42,7 +42,7 @@ module.exports = Command.extend({ this.ui.writeLine('Available commands in ember-cli:'); this.ui.writeLine(''); - Object.keys(this.commands).forEach(function(commandName) { + Object.keys(this.commands).forEach(function (commandName) { this._printHelpForCommand(commandName, false, commandOptions); }, this); @@ -53,7 +53,7 @@ module.exports = Command.extend({ this.ui.writeLine(''); this.ui.writeLine(`Available commands from ${addonName}:`); - Object.keys(this.commands).forEach(function(commandName) { + Object.keys(this.commands).forEach(function (commandName) { this._printHelpForCommand(commandName, false, commandOptions); }, this); }); @@ -82,7 +82,7 @@ module.exports = Command.extend({ // Iterate through each arg beyond the initial 'help' command, // and try to display usage instructions. - rawArgs.forEach(function(commandName) { + rawArgs.forEach(function (commandName) { this._printHelpForCommand(commandName, true, commandOptions); }, this); } diff --git a/lib/commands/init.js b/lib/commands/init.js index 8e44e42b8f..a486a945f7 100644 --- a/lib/commands/init.js +++ b/lib/commands/init.js @@ -3,17 +3,17 @@ const clone = require('ember-cli-lodash-subset').clone; const merge = require('ember-cli-lodash-subset').merge; const Command = require('../models/command'); -const Promise = require('rsvp').Promise; const SilentError = require('silent-error'); const chalk = require('chalk'); -const validProjectName = require('../utilities/valid-project-name'); +const isValidProjectName = require('../utilities/valid-project-name'); const normalizeBlueprint = require('../utilities/normalize-blueprint-option'); const mergeBlueprintOptions = require('../utilities/merge-blueprint-options'); const isYarnProject = require('../utilities/is-yarn-project'); +const getLangArg = require('../../lib/utilities/get-lang-arg'); module.exports = Command.extend({ name: 'init', - description: 'Creates a new ember-cli project in the current folder.', + description: 'Reinitializes a new ember-cli project in the current folder.', works: 'everywhere', availableOptions: [ @@ -22,6 +22,7 @@ module.exports = Command.extend({ { name: 'blueprint', type: String, aliases: ['b'] }, { name: 'skip-npm', type: Boolean, default: false, aliases: ['sn'] }, { name: 'skip-bower', type: Boolean, default: false, aliases: ['sb'] }, + { name: 'lint-fix', type: Boolean, default: true }, { name: 'welcome', type: Boolean, @@ -30,27 +31,27 @@ module.exports = Command.extend({ }, { name: 'yarn', type: Boolean }, // no default means use yarn if the blueprint has a yarn.lock { name: 'name', type: String, default: '', aliases: ['n'] }, + { + name: 'lang', + type: String, + description: 'Sets the base human language of the application via index.html', + }, + { name: 'embroider', type: Boolean, default: false, description: 'Enables the build system to use Embroider' }, ], anonymousOptions: [''], _defaultBlueprint() { if (this.project.isEmberCLIAddon()) { - if (this.project.isModuleUnification()) { - return 'module-unification-addon'; - } return 'addon'; } else { - if (this.project.isModuleUnification()) { - return 'module-unification-app'; - } return 'app'; } }, beforeRun: mergeBlueprintOptions, - run(commandOptions, rawArgs) { + async run(commandOptions, rawArgs) { if (commandOptions.dryRun) { commandOptions.skipNpm = true; commandOptions.skipBower = true; @@ -68,6 +69,10 @@ module.exports = Command.extend({ return Promise.reject(new SilentError(message)); } + if (commandOptions.lang) { + commandOptions.lang = getLangArg(commandOptions.lang, this.ui); + } + let blueprintOpts = clone(commandOptions); if (blueprintOpts.yarn === undefined) { @@ -81,45 +86,40 @@ module.exports = Command.extend({ blueprint: normalizeBlueprint(blueprintOpts.blueprint || this._defaultBlueprint()), }); - if (!validProjectName(packageName)) { + if (!isValidProjectName(packageName)) { return Promise.reject(new SilentError(`We currently do not support a name of \`${packageName}\`.`)); } - return this.runTask('InstallBlueprint', blueprintOpts) - .then(() => { - if (!commandOptions.skipNpm) { - return this.runTask('NpmInstall', { - verbose: commandOptions.verbose, - useYarn: commandOptions.yarn, - }); - } - }) - .then(() => { - if (!commandOptions.skipBower) { - return this.runTask('BowerInstall', { - verbose: commandOptions.verbose, - }); - } - }) - .then(() => { - if (commandOptions.skipGit === false) { - return this.runTask('GitInit', commandOptions, rawArgs); - } - }) - .then(() => { - const projectName = this.project.name(); - const prependEmoji = require('../../lib/utilities/prepend-emoji'); - - this.ui.writeLine(''); - this.ui.writeLine(prependEmoji('🎉', `Successfully created project ${chalk.yellow(projectName)}.`)); - this.ui.writeLine(prependEmoji('👉 ', 'We suggest you that you get started by typing:')); - this.ui.writeLine(''); - const command = `cd ${projectName}`; - this.ui.writeLine(` ${chalk.gray('$')} ${chalk.cyan(command)}`); - const packageManager = commandOptions.yarn ? 'yarn' : 'npm'; - this.ui.writeLine(` ${chalk.gray('$')} ${chalk.cyan(`${packageManager} start`)}`); - this.ui.writeLine(''); - this.ui.writeLine('Happy coding!'); + await this.runTask('InstallBlueprint', blueprintOpts); + + if (!commandOptions.skipNpm) { + await this.runTask('NpmInstall', { + verbose: commandOptions.verbose, + useYarn: commandOptions.yarn, }); + } + + if (!commandOptions.skipBower) { + await this.runTask('BowerInstall', { + verbose: commandOptions.verbose, + }); + } + + if (commandOptions.skipGit === false) { + await this.runTask('GitInit', commandOptions, rawArgs); + } + const projectName = this.project.name(); + const prependEmoji = require('../../lib/utilities/prepend-emoji'); + + this.ui.writeLine(''); + this.ui.writeLine(prependEmoji('🎉', `Successfully created project ${chalk.yellow(projectName)}.`)); + this.ui.writeLine(prependEmoji('👉 ', 'Get started by typing:')); + this.ui.writeLine(''); + const command = `cd ${projectName}`; + this.ui.writeLine(` ${chalk.gray('$')} ${chalk.cyan(command)}`); + const packageManager = commandOptions.yarn ? 'yarn' : 'npm'; + this.ui.writeLine(` ${chalk.gray('$')} ${chalk.cyan(`${packageManager} start`)}`); + this.ui.writeLine(''); + this.ui.writeLine('Happy coding!'); }, }); diff --git a/lib/commands/install.js b/lib/commands/install.js index 3b6aa1bd49..5a938ed1f6 100644 --- a/lib/commands/install.js +++ b/lib/commands/install.js @@ -2,7 +2,6 @@ const Command = require('../models/command'); const SilentError = require('silent-error'); -const Promise = require('rsvp').Promise; module.exports = Command.extend({ name: 'install', diff --git a/lib/commands/new.js b/lib/commands/new.js index 571309a62c..148c43df04 100644 --- a/lib/commands/new.js +++ b/lib/commands/new.js @@ -3,16 +3,11 @@ const fs = require('fs-extra'); const chalk = require('chalk'); const Command = require('../models/command'); -const RSVP = require('rsvp'); const Project = require('../models/project'); const SilentError = require('silent-error'); -const validProjectName = require('../utilities/valid-project-name'); +const isValidProjectName = require('../utilities/valid-project-name'); const normalizeBlueprint = require('../utilities/normalize-blueprint-option'); const mergeBlueprintOptions = require('../utilities/merge-blueprint-options'); -const { isExperimentEnabled } = require('../experiments'); - -const rmdir = RSVP.denodeify(fs.remove); -const Promise = RSVP.Promise; module.exports = Command.extend({ name: 'new', @@ -34,30 +29,26 @@ module.exports = Command.extend({ }, { name: 'yarn', type: Boolean }, // no default means use yarn if the blueprint has a yarn.lock { name: 'directory', type: String, aliases: ['dir'] }, + { + name: 'lang', + type: String, + description: 'Sets the base human language of the application via index.html', + }, + { name: 'embroider', type: Boolean, default: false, description: 'Enables the build system to use Embroider' }, ], anonymousOptions: [''], beforeRun: mergeBlueprintOptions, - run(commandOptions, rawArgs) { - if (isExperimentEnabled('MODULE_UNIFICATION')) { - if (commandOptions.blueprint === 'app') { - commandOptions.blueprint = 'module-unification-app'; - } else if (commandOptions.blueprint === 'addon') { - commandOptions.blueprint = 'module-unification-addon'; - } - } - - let packageName = rawArgs[0], + async run(commandOptions, rawArgs) { + let projectName = rawArgs[0], message; commandOptions.name = rawArgs.shift(); - if (!packageName) { - message = `The \`ember ${ - this.name - }\` command requires a name to be specified. For more details, use \`ember help\`.`; + if (!projectName) { + message = `The \`ember ${this.name}\` command requires a name to be specified. For more details, use \`ember help\`.`; return Promise.reject(new SilentError(message)); } @@ -66,25 +57,21 @@ module.exports = Command.extend({ commandOptions.skipGit = true; } - if (packageName === '.') { + if (projectName === '.') { let blueprintName = commandOptions.blueprint === 'app' ? 'application' : commandOptions.blueprint; message = `Trying to generate an ${blueprintName} structure in this directory? Use \`ember init\` instead.`; return Promise.reject(new SilentError(message)); } - if (!validProjectName(packageName)) { - message = `We currently do not support a name of \`${packageName}\`.`; + if (!isValidProjectName(projectName)) { + message = `We currently do not support a name of \`${projectName}\`.`; return Promise.reject(new SilentError(message)); } commandOptions.blueprint = normalizeBlueprint(commandOptions.blueprint); - if (!commandOptions.directory) { - commandOptions.directory = packageName; - } - let InitCommand = this.commands.Init; let initCommand = new InitCommand({ @@ -94,20 +81,25 @@ module.exports = Command.extend({ project: Project.nullProject(this.ui, this.cli), }); - return this.runTask('CreateAndStepIntoDirectory', { + let opts = await this.runTask('CreateAndStepIntoDirectory', { + projectName, directoryName: commandOptions.directory, dryRun: commandOptions.dryRun, - }).then(opts => { - initCommand.project.root = process.cwd(); - - return initCommand.run(commandOptions, rawArgs).catch(err => { - let dirName = commandOptions.directory; - process.chdir(opts.initialDirectory); - return rmdir(dirName).then(() => { - console.log(chalk.red(`Error creating new application. Removing generated directory \`./${dirName}\``)); - throw err; - }); - }); }); + + initCommand.project.root = process.cwd(); + + try { + let response = await initCommand.run(commandOptions, rawArgs); + return response; + } catch (err) { + let { initialDirectory, projectDirectory } = opts; + + process.chdir(initialDirectory); + await fs.remove(projectDirectory); + + console.log(chalk.red(`Error creating new application. Removing generated directory \`./${projectDirectory}\``)); + throw err; + } }, }); diff --git a/lib/commands/serve.js b/lib/commands/serve.js index b7f1f3da5e..b65906f8c4 100644 --- a/lib/commands/serve.js +++ b/lib/commands/serve.js @@ -1,17 +1,15 @@ 'use strict'; const Command = require('../models/command'); -const RSVP = require('rsvp'); +const util = require('util'); const SilentError = require('silent-error'); const PortFinder = require('portfinder'); const Win = require('../utilities/windows-admin'); const EOL = require('os').EOL; -const Promise = RSVP.Promise; - PortFinder.basePort = 7020; -let getPort = RSVP.denodeify(PortFinder.getPort); +let getPort = util.promisify(PortFinder.getPort); let defaultPort = process.env.PORT || 4200; module.exports = Command.extend({ @@ -92,35 +90,42 @@ module.exports = Command.extend({ { name: 'path', type: 'Path', description: 'Reuse an existing build at given path.' }, ], - run(commandOptions) { + async run(commandOptions) { commandOptions.liveReloadHost = commandOptions.liveReloadHost || commandOptions.host; - return this._checkExpressPort(commandOptions).then(commandOptions => { - if (commandOptions.proxy) { - if (!/^(http:|https:)/.test(commandOptions.proxy)) { - let message = `You need to include a protocol with the proxy URL.${EOL}Try --proxy http://${ - commandOptions.proxy - }`; + let wrappedCommandOptions = await this._checkExpressPort(commandOptions); + if (wrappedCommandOptions.proxy) { + if (!/^(http:|https:)/.test(wrappedCommandOptions.proxy)) { + let message = `You need to include a protocol with the proxy URL.${EOL}Try --proxy http://${wrappedCommandOptions.proxy}`; - return Promise.reject(new SilentError(message)); - } + return Promise.reject(new SilentError(message)); } + } - return Win.checkIfSymlinksNeedToBeEnabled(this.ui).then(() => this.runTask('Serve', commandOptions)); - }); + await Win.checkIfSymlinksNeedToBeEnabled(this.ui); + await this.runTask('Serve', commandOptions); }, - _checkExpressPort(commandOptions) { - return getPort({ port: commandOptions.port, host: commandOptions.host }).then(foundPort => { - if (commandOptions.port !== foundPort && commandOptions.port !== 0) { - let message = `Port ${commandOptions.port} is already in use.`; - return Promise.reject(new SilentError(message)); - } - - // otherwise, our found port is good + async _checkExpressPort(commandOptions) { + let portOptions = { port: commandOptions.port, host: commandOptions.host }; + if (commandOptions.port !== 0) { + portOptions.stopPort = commandOptions.port; + } + try { + let foundPort = await getPort(portOptions); commandOptions.port = foundPort; commandOptions.liveReloadPort = commandOptions.liveReloadPort || commandOptions.port; return commandOptions; - }); + } catch (err) { + let message; + if (commandOptions.port === 0) { + message = `No open port found above ${commandOptions.port}`; + } else if (commandOptions.port < 1024) { + message = `Port ${commandOptions.port} is already in use or you do not have permissions to use this port.`; + } else { + message = `Port ${commandOptions.port} is already in use.`; + } + throw new SilentError(message); + } }, }); diff --git a/lib/commands/test.js b/lib/commands/test.js index c168f3d2e3..49f89a0dbb 100644 --- a/lib/commands/test.js +++ b/lib/commands/test.js @@ -145,7 +145,7 @@ module.exports = Command.extend({ return params.join('&'); }, - run(commandOptions) { + async run(commandOptions) { let hasBuild = !!commandOptions.path; let outputPath; @@ -175,34 +175,31 @@ module.exports = Command.extend({ this._generateCustomConfigs(commandOptions) ); - return Win.checkIfSymlinksNeedToBeEnabled(this.ui).then(() => { - let session; - - if (commandOptions.server) { - if (hasBuild) { - session = this.runTask('TestServer', testOptions); - } else { - let builder = new this.Builder(testOptions); - - testOptions.watcher = new this.Watcher( - Object.assign(this._env(), { - builder, - verbose: false, - options: commandOptions, - }) - ); - session = this.runTask('TestServer', testOptions).finally(() => builder.cleanup()); - } - } else if (hasBuild) { - session = this.runTask('Test', testOptions); + await Win.checkIfSymlinksNeedToBeEnabled(this.ui); + let session; + if (commandOptions.server) { + if (hasBuild) { + session = this.runTask('TestServer', testOptions); } else { - session = this.runTask('Build', { - environment: commandOptions.environment, - outputPath, - }).then(() => this.runTask('Test', testOptions)); + let builder = new this.Builder(testOptions); + testOptions.watcher = new this.Watcher( + Object.assign(this._env(), { + builder, + verbose: false, + options: commandOptions, + }) + ); + session = this.runTask('TestServer', testOptions).finally(() => builder.cleanup()); } - - return session; - }); + } else if (hasBuild) { + session = this.runTask('Test', testOptions); + } else { + await this.runTask('Build', { + environment: commandOptions.environment, + outputPath, + }); + session = await this.runTask('Test', testOptions); + } + return session; }, }); diff --git a/lib/commands/uninstall-npm.js b/lib/commands/uninstall-npm.js index 3b65b93be8..6633777d28 100644 --- a/lib/commands/uninstall-npm.js +++ b/lib/commands/uninstall-npm.js @@ -2,7 +2,6 @@ const Command = require('../models/command'); const SilentError = require('silent-error'); -const Promise = require('rsvp').Promise; module.exports = Command.extend({ name: 'uninstall:npm', diff --git a/lib/experiments/index.js b/lib/experiments/index.js index 135b02ad12..6cbbc155da 100644 --- a/lib/experiments/index.js +++ b/lib/experiments/index.js @@ -1,26 +1,44 @@ 'use strict'; -const availableExperiments = Object.freeze([ - 'PACKAGER', - 'MODULE_UNIFICATION', - 'DELAYED_TRANSPILATION', - 'SYSTEM_TEMP', - 'BROCCOLI_WATCHER', -]); +const chalk = require('chalk'); +const availableExperiments = Object.freeze(['PACKAGER', 'EMBROIDER', 'CLASSIC']); -const enabledExperiments = Object.freeze(['SYSTEM_TEMP']); +const deprecatedExperiments = Object.freeze(['BROCCOLI_WATCHER', 'PACKAGER']); +const enabledExperiments = Object.freeze([]); +const deprecatedExperimentsDeprecationsIssued = []; function isExperimentEnabled(experimentName) { if (!availableExperiments.includes(experimentName)) { return false; } + if (process.env.EMBER_CLI_ENABLE_ALL_EXPERIMENTS && deprecatedExperiments.includes(experimentName)) { + return false; + } + if (process.env.EMBER_CLI_ENABLE_ALL_EXPERIMENTS) { return true; } + if (process.env.EMBER_CLI_CLASSIC && experimentName === 'EMBROIDER') { + return false; + } + let experimentEnvironmentVariable = `EMBER_CLI_${experimentName}`; let experimentValue = process.env[experimentEnvironmentVariable]; + + if (deprecatedExperiments.includes(experimentName)) { + let deprecationPreviouslyIssued = deprecatedExperimentsDeprecationsIssued.includes(experimentName); + let isSpecifiedByUser = experimentValue !== undefined; + + if (!deprecationPreviouslyIssued && isSpecifiedByUser) { + console.warn( + chalk.yellow(`The ${experimentName} experiment in ember-cli has been deprecated and will be removed.`) + ); + deprecatedExperimentsDeprecationsIssued.push(experimentName); + } + } + if (enabledExperiments.includes(experimentName)) { return experimentValue !== 'false'; } else { @@ -28,4 +46,9 @@ function isExperimentEnabled(experimentName) { } } -module.exports = { isExperimentEnabled }; +module.exports = { + isExperimentEnabled, + + // exported for testing purposes + _deprecatedExperimentsDeprecationsIssued: deprecatedExperimentsDeprecationsIssued, +}; diff --git a/lib/models/addon.js b/lib/models/addon.js index c0e0748b16..dabc6b9127 100644 --- a/lib/models/addon.js +++ b/lib/models/addon.js @@ -11,7 +11,6 @@ const heimdallLogger = require('heimdalljs-logger'); const logger = heimdallLogger('ember-cli:addon'); const treeCacheLogger = heimdallLogger('ember-cli:addon:tree-cache'); const cacheKeyLogger = heimdallLogger('ember-cli:addon:cache-key-for-tree'); -const { isExperimentEnabled } = require('../experiments'); const PackageInfoCache = require('../models/package-info-cache'); const p = require('ember-cli-preprocess-registry/preprocessors'); @@ -57,7 +56,6 @@ if (!heimdall.hasMonitor('cache-key-for-tree')) { let DEFAULT_TREE_FOR_METHODS = { app: 'treeForApp', - src: 'treeForSrc', addon: 'treeForAddon', 'addon-styles': 'treeForAddonStyles', 'addon-templates': 'treeForAddonTemplates', @@ -72,7 +70,6 @@ let DEFAULT_TREE_FOR_METHODS = { let GLOBAL_TREE_FOR_METHOD_METHODS = ['treeFor', '_treeFor', 'treeGenerator']; let DEFAULT_TREE_FOR_METHOD_METHODS = { app: ['treeForApp'], - src: ['treeForSrc'], addon: [ 'treeForAddon', 'treeForAddonStyles', @@ -291,7 +288,6 @@ let addonProto = { this.treePaths = { app: 'app', - src: 'src', styles: 'app/styles', templates: 'app/templates', addon: 'addon', @@ -321,16 +317,9 @@ let addonProto = { this._packageInfo = this.packageInfoCache.loadAddon(this); - // force us to use the real path as the root. - this.root = this._packageInfo.realPath; - p.setupRegistry(this); this._initDefaultBabelOptions(); - - if (isExperimentEnabled('DELAYED_TRANSPILATION')) { - this.registry.remove('template', 'ember-cli-htmlbars'); - } }, _initDefaultBabelOptions() { @@ -338,19 +327,9 @@ let addonProto = { babel: this[BUILD_BABEL_OPTIONS_FOR_PREPROCESSORS](), }); - let defaultEmberCLIBabelOptions; - if (isExperimentEnabled('DELAYED_TRANSPILATION')) { - defaultEmberCLIBabelOptions = { - compileModules: false, - disablePresetEnv: true, - disableDebugTooling: true, - disableEmberModulesAPIPolyfill: true, - }; - } else { - defaultEmberCLIBabelOptions = { - compileModules: true, - }; - } + let defaultEmberCLIBabelOptions = { + compileModules: true, + }; let emberCLIBabelConfigKey = this._emberCLIBabelConfigKey(); this.__originalOptions[emberCLIBabelConfigKey] = this.options[emberCLIBabelConfigKey] = defaultsDeep( this.options[emberCLIBabelConfigKey], @@ -358,16 +337,6 @@ let addonProto = { ); }, - /** - Returns whether this is using a module unification format. - @private - @method isModuleUnification - @return {Boolean} Whether this is using a module unification format. - */ - isModuleUnification() { - return false; - }, - /** * Find an addon of the current addon. * @@ -389,7 +358,7 @@ let addonProto = { * @method findOwnAddonByName */ findOwnAddonByName(name) { - return this.addons.find(addon => addon.name === name); + return this.addons.find((addon) => addon.name === name); }, /** @@ -401,7 +370,7 @@ let addonProto = { */ hintingEnabled() { let isProduction = process.env.EMBER_ENV === 'production'; - let testsEnabledDefault = process.env.EMBER_CLI_TEST_COMMAND || !isProduction; + let testsEnabledDefault = process.env.EMBER_CLI_TEST_COMMAND === 'true' || !isProduction; let explicitlyDisabled = this.app && this.app.options && this.app.options.hinting === false; return testsEnabledDefault && !explicitlyDisabled; @@ -446,10 +415,8 @@ let addonProto = { let pathToDisplay = process.cwd() === this.root ? process.cwd() : path.relative(process.cwd(), this.root); throw new SilentError( - 'Your names in package.json and index.js should match. ' + - `The addon in ${pathToDisplay} currently have '${parentName}' in package.json and '${ - this.name - }' in index.js. ` + + 'ember-cli: Your names in package.json and index.js should match. ' + + `The addon in ${pathToDisplay} currently have '${parentName}' in package.json and '${this.name}' in index.js. ` + 'Until ember-cli v3.9, this error can be disabled by setting env variable EMBER_CLI_IGNORE_ADDON_NAME_MISMATCH to "true". ' + 'For more information about this workaround, see: https://github.com/ember-cli/ember-cli/pull/7950.' ); @@ -474,13 +441,16 @@ let addonProto = { * @method discoverAddons */ discoverAddons() { - let pkgInfo = this.packageInfoCache.getEntry(this.root); + // prefer `packageRoot`, fallback to `root`; this is to maintain backwards compatibility for + // consumers who create a new instance of the base addon model class directly and don't set + // `packageRoot` + let pkgInfo = this.packageInfoCache.getEntry(this.packageRoot || this.root); if (pkgInfo) { let addonPackageList = pkgInfo.discoverAddonAddons(); this.addonPackages = pkgInfo.generateAddonPackages( addonPackageList, - addonInfo => this.shouldIncludeChildAddon && !this.shouldIncludeChildAddon(addonInfo) + (addonInfo) => this.shouldIncludeChildAddon && !this.shouldIncludeChildAddon(addonInfo) ); // in case any child addons are invalid, dump to the console about them. @@ -505,7 +475,7 @@ let addonProto = { this.discoverAddons(); this.addons = instantiateAddons(this, this.project, this.addonPackages); - this.addons.forEach(addon => logger.info('addon: %s', addon.name)); + this.addons.forEach((addon) => logger.info('addon: %s', addon.name)); }, /** @@ -578,9 +548,7 @@ let addonProto = { if (!this.project) { this._warn( - `Addon: \`${ - this.name - }\` is missing addon.project, this may be the result of an addon forgetting to invoke \`super\` in its init.` + `Addon: \`${this.name}\` is missing addon.project, this may be the result of an addon forgetting to invoke \`super\` in its init.` ); } // TODO: fix law of demeter `_watchmanInfo.canNestRoots` is obviously a poor idea @@ -724,7 +692,7 @@ let addonProto = { let cacheKeyStats = heimdall.statsFor('cache-key-for-tree'); // determine if treeFor* (or other methods for tree type) overrides for the given tree - let modifiedMethods = methodsToValidate.filter(methodName => this[methodName] !== addonProto[methodName]); + let modifiedMethods = methodsToValidate.filter((methodName) => this[methodName] !== addonProto[methodName]); if (modifiedMethods.length) { cacheKeyStats.modifiedMethods++; @@ -830,26 +798,6 @@ let addonProto = { @return {Tree} App file tree */ treeForApp(tree) { - if (!isExperimentEnabled('MODULE_UNIFICATION')) { - return tree; - } else if (this.project.isModuleUnification() && this.isModuleUnification()) { - return null; - } - - let srcTreePath = path.resolve(this.root, this.treePaths.src); - - if (fs.existsSync(srcTreePath)) { - const MUReexporter = require('broccoli-module-unification-reexporter'); - - let srcTree = this.treeGenerator(srcTreePath); - - let reexportedOutput = new MUReexporter(srcTree, { - namespace: this.name, - }); - - return reexportedOutput; - } - return tree; }, @@ -865,41 +813,6 @@ let addonProto = { return tree; }, - /** - @private - @method treeForSrc - @return - */ - treeForSrc(rawSrcTree) { - if (!isExperimentEnabled('MODULE_UNIFICATION')) { - return null; - } - if (!rawSrcTree) { - return null; - } - - let srcNamespacedTree = new Funnel(rawSrcTree, { - destDir: `${this.name}/src`, - annotation: `Addon#treeForSrc(${this.name}) namespace`, - }); - - let srcAfterPreprocessTreeHook = this._addonPreprocessTree('src', srcNamespacedTree); - - let srcAfterTemplatePreprocessing = srcAfterPreprocessTreeHook; - if (!isExperimentEnabled('DELAYED_TRANSPILATION') || registryHasPreprocessor(this.registry, 'template')) { - srcAfterTemplatePreprocessing = preprocessTemplates(srcAfterPreprocessTreeHook, { - registry: this.registry, - annotation: `Addon#treeForSrc(${this.name})`, - }); - } - - let srcAfterJsPreprocessing = preprocessJs(srcAfterTemplatePreprocessing, '/', this.name, { - registry: this.registry, - }); - - return this._addonPostprocessTree('src', srcAfterJsPreprocessing); - }, - /** Returns the tree for this addon's templates @@ -943,9 +856,7 @@ let addonProto = { let addonTree = this.compileAddon(tree); let stylesTree = this.compileStyles(this._treeFor('addon-styles')); - return mergeTrees([addonTree, stylesTree], { - annotation: `Addon#treeForAddon(${this.name})`, - }); + return mergeTrees([addonTree, stylesTree], { annotation: `Addon#treeForAddon(${this.name})` }); }, /** @@ -1033,6 +944,7 @@ let addonProto = { if (registryHasPreprocessor(this.registry, 'js')) { return this.preprocessJs(namespacedTree, '/', this.name, { registry: this.registry, + treeType: 'addon-test-support', }); } else { this._warn( @@ -1061,6 +973,10 @@ let addonProto = { let processedStylesTree = preprocessCss(preprocessedStylesTree, '/', '/', { outputPaths: { addon: `${this.name}.css` }, registry: this.registry, + treeType: 'addon-styles', + }); + processedStylesTree = new Funnel(processedStylesTree, { + destDir: `${this.name}/__COMPILED_STYLES__`, }); return this._addonPostprocessTree('css', processedStylesTree); @@ -1069,7 +985,7 @@ let addonProto = { /** Looks in the addon/ and addon/templates trees to determine if template files - exists that need to be precompiled. + exist that need to be precompiled. This is executed once when building, but not on rebuilds. @@ -1083,7 +999,7 @@ let addonProto = { /** Looks in the addon/ and addon/templates trees to determine if template files - exists in the pods format that need to be precompiled. + exist in the pods format that need to be precompiled. This is executed once when building, but not on rebuilds. @@ -1111,8 +1027,8 @@ let addonProto = { let addonTemplatesRelativeToAddonPath = addonTemplatesTreeInAddonTree && addonTemplatesTreePath.replace(`${addonTreePath}/`, ''); let podTemplateMatcher = new RegExp(`template.(${templateExtensions.join('|')})$`); - let hasPodTemplates = files.some(file => { - // short circuit if this is actually a `addon/templates` file + let hasPodTemplates = files.some((file) => { + // short circuit if this is actually an `addon/templates` file if (addonTemplatesTreeInAddonTree && file.indexOf(addonTemplatesRelativeToAddonPath) === 0) { return false; } @@ -1121,14 +1037,14 @@ let addonProto = { }); let jsMatcher = new RegExp(`(${jsExtensions.join('|')})$`); - let hasJSFiles = files.some(file => jsMatcher.test(file)); + let hasJSFiles = files.some((file) => jsMatcher.test(file)); if (!addonTemplatesTreeInAddonTree) { files = files.concat(this._getAddonTemplatesTreeFiles()); } let extensionMatcher = new RegExp(`(${templateExtensions.join('|')})$`); - let hasTemplates = files.some(file => extensionMatcher.test(file)); + let hasTemplates = files.some((file) => extensionMatcher.test(file)); this._cachedFileSystemInfo = { hasJSFiles, @@ -1179,7 +1095,9 @@ let addonProto = { } if (this._shouldCompilePodTemplates()) { - let includePatterns = this.registry.extensionsForType('template').map(extension => `**/*/template.${extension}`); + let includePatterns = this.registry + .extensionsForType('template') + .map((extension) => `**/*/template.${extension}`); let podTemplates = new Funnel(addonTree, { include: includePatterns, @@ -1207,7 +1125,7 @@ let addonProto = { */ compileTemplates(addonTree) { if (this.shouldCompileTemplates()) { - if (!isExperimentEnabled('DELAYED_TRANSPILATION') && !registryHasPreprocessor(this.registry, 'template')) { + if (!registryHasPreprocessor(this.registry, 'template')) { throw new SilentError( `Addon templates were detected, but there are no template compilers registered for \`${this.name}\`. ` + `Please make sure your template precompiler (commonly \`ember-cli-htmlbars\`) is listed in \`dependencies\` ` + @@ -1217,15 +1135,11 @@ let addonProto = { let preprocessedTemplateTree = this._addonPreprocessTree('template', addonTree); - let processedTemplateTree; - if (!isExperimentEnabled('DELAYED_TRANSPILATION') || registryHasPreprocessor(this.registry, 'template')) { - processedTemplateTree = preprocessTemplates(preprocessedTemplateTree, { - annotation: `compileTemplates(${this.name})`, - registry: this.registry, - }); - } else { - processedTemplateTree = preprocessedTemplateTree; - } + let processedTemplateTree = preprocessTemplates(preprocessedTemplateTree, { + annotation: `compileTemplates(${this.name})`, + registry: this.registry, + treeType: 'addon-templates', + }); let postprocessedTemplateTree = this._addonPostprocessTree('template', processedTemplateTree); @@ -1270,9 +1184,7 @@ let addonProto = { if (!this.options[emberCLIBabelConfigKey]) { this._warn( `Ember CLI addons manage their own module transpilation during the \`treeForAddon\` processing. ` + - `\`${this.name}\` (found at \`${ - this.root - }\`) has overridden the \`this.options.${emberCLIBabelConfigKey}\` ` + + `\`${this.name}\` (found at \`${this.root}\`) has overridden the \`this.options.${emberCLIBabelConfigKey}\` ` + `options which conflicts with the addons ability to transpile its \`addon/\` files properly. ` + `Falling back to default babel configuration options.` ); @@ -1292,7 +1204,7 @@ let addonProto = { }, /** - Returns a tree with JSHhint output for all addon JS. + Returns a tree with JSHint output for all addon JS. @private @method jshintAddonTree @@ -1399,6 +1311,7 @@ let addonProto = { let processedAddonJS = this.preprocessJs(preprocessedAddonJS, '/', this.name, { annotation: `processedAddonJsFiles(${this.name})`, registry: this.registry, + treeType: 'addon', }); let postprocessedAddonJs = this._addonPostprocessTree('js', processedAddonJS); @@ -1429,7 +1342,7 @@ let addonProto = { */ moduleName() { if (!this.modulePrefix) { - this.modulePrefix = (this.modulePrefix || this.name).toLowerCase().replace(/\s/g, '-'); + this.modulePrefix = this.name.toLowerCase().replace(/\s/g, '-'); } return this.modulePrefix; @@ -1454,7 +1367,7 @@ let addonProto = { }, /** - Augments the applications configuration settings. + Augments the application's configuration settings. Object returned from this hook is merged with the application's configuration object. @@ -1621,7 +1534,7 @@ let addonProto = { @public @method preprocessTree - @param {String} type What kind of tree (eg. 'js', 'css', 'src', 'template') + @param {String} type What kind of tree (eg. 'js', 'css', 'template') @param {Tree} tree Tree to process @return {Tree} Processed tree */ @@ -1631,7 +1544,7 @@ let addonProto = { @public @method postprocessTree - @param {String} type What kind of tree (eg. 'js', 'css', 'src', 'template') + @param {String} type What kind of tree (eg. 'js', 'css', 'template') @param {Tree} tree Tree to process @return {Tree} Processed tree @@ -1836,7 +1749,7 @@ function methodsForTreeType(treeType) { let Addon = CoreObject.extend(addonProto); -Addon.prototype[BUILD_BABEL_OPTIONS_FOR_PREPROCESSORS] = function() { +Addon.prototype[BUILD_BABEL_OPTIONS_FOR_PREPROCESSORS] = function () { let emberCLIBabelInstance = findAddonByName(this.addons, 'ember-cli-babel'); let version; if (emberCLIBabelInstance) { diff --git a/lib/models/asset-size-printer.js b/lib/models/asset-size-printer.js index 7de6d10214..4053ea98cc 100644 --- a/lib/models/asset-size-printer.js +++ b/lib/models/asset-size-printer.js @@ -2,8 +2,8 @@ const chalk = require('chalk'); const path = require('path'); -const RSVP = require('rsvp'); const walkSync = require('walk-sync'); +const workerpool = require('workerpool'); module.exports = class AssetPrinterSize { constructor(options) { @@ -14,10 +14,10 @@ module.exports = class AssetPrinterSize { const filesize = require('filesize'); let ui = this.ui; - return this.makeAssetSizesObject().then(files => { + return this.makeAssetSizesObject().then((files) => { if (files.length !== 0) { ui.writeLine(chalk.green('File sizes:')); - return files.forEach(file => { + return files.forEach((file) => { let sizeOutput = filesize(file.size); if (file.showGzipped) { sizeOutput += ` (${filesize(file.gzipSize)} gzipped)`; @@ -33,9 +33,9 @@ module.exports = class AssetPrinterSize { printJSON() { let ui = this.ui; - return this.makeAssetSizesObject().then(files => { + return this.makeAssetSizesObject().then((files) => { if (files.length !== 0) { - let entries = files.map(file => ({ + let entries = files.map((file) => ({ name: file.name, size: file.size, gzipSize: file.gzipSize, @@ -47,33 +47,29 @@ module.exports = class AssetPrinterSize { }); } - makeAssetSizesObject() { - return new Promise(resolve => { - const fs = require('fs'); - const zlib = require('zlib'); - let gzip = RSVP.denodeify(zlib.gzip); - let files = this.findFiles(); - let testFileRegex = /(test-(loader|support))|(testem)/i; + async makeAssetSizesObject() { + let files = this.findFiles(); + let testFileRegex = /(test-(loader|support))|(testem)/i; + // create a dedicated worker + const pool = workerpool.pool(`${__dirname}/worker.js`); + + try { let assets = files // Skip test files - .filter(file => { + .filter((file) => { let filename = path.basename(file); return !testFileRegex.test(filename); }) // Print human-readable file sizes (including gzipped) - .map(file => { - let contentsBuffer = fs.readFileSync(file); - return gzip(contentsBuffer).then(buffer => ({ - name: file, - size: contentsBuffer.length, - gzipSize: buffer.length, - showGzipped: contentsBuffer.length > 0, - })); + .map((file) => { + return pool.exec('gzipStats', [file]); }); - return resolve(Promise.all(assets)); - }); + return await Promise.all(assets); + } finally { + pool.terminate(); // terminate all workers when done + } } findFiles() { @@ -83,8 +79,8 @@ module.exports = class AssetPrinterSize { return walkSync(outputPath, { directories: false, }) - .filter(x => x.endsWith('.css') || x.endsWith('.js')) - .map(x => path.join(outputPath, x)); + .filter((x) => x.endsWith('.css') || x.endsWith('.js')) + .map((x) => path.join(outputPath, x)); } catch (e) { if (e !== null && typeof e === 'object' && e.code === 'ENOENT') { throw new Error(`No asset files found in the path provided: ${outputPath}`); diff --git a/lib/models/blueprint.js b/lib/models/blueprint.js index 5fff7675ce..0736c9753f 100644 --- a/lib/models/blueprint.js +++ b/lib/models/blueprint.js @@ -4,7 +4,6 @@ @module ember-cli */ const FileInfo = require('./file-info'); -const RSVP = require('rsvp'); const chalk = require('chalk'); const MarkdownColor = require('../utilities/markdown-color'); const sequence = require('../utilities/sequence'); @@ -26,10 +25,6 @@ const logger = require('heimdalljs-logger')('ember-cli:blueprint'); const normalizeEntityName = require('ember-cli-normalize-entity-name'); const isAddon = require('../utilities/is-addon'); -const Promise = RSVP.Promise; -const stat = RSVP.denodeify(fs.stat); -const writeFile = RSVP.denodeify(fs.outputFile); - const initialIgnoredFiles = ['.DS_Store']; /** @@ -167,7 +162,7 @@ const initialIgnoredFiles = ['.DS_Store']; ### beforeInstall & beforeUninstall Called before any of the template files are processed and receives - the the `options` and `locals` hashes as parameters. Typically used for + the `options` and `locals` hashes as parameters. Typically used for validating any additional command line options or for any asynchronous setup that is needed. As an example, the `controller` blueprint validates its `--type` option in this hook. If you need to run any asynchronous code, @@ -295,9 +290,9 @@ let Blueprint = CoreObject.extend({ @param {Object} info @return {Promise} */ - _writeFile(info) { + async _writeFile(info) { if (!this.dryRun) { - return writeFile(info.outputPath, info.render()); + return fs.outputFile(info.outputPath, await info.render()); } }, @@ -422,16 +417,22 @@ let Blueprint = CoreObject.extend({ @param {Function} process @param {Function} afterHook */ - _process(options, beforeHook, process, afterHook) { + async _process(options, beforeHook, process, afterHook) { let intoDir = options.target; - return this._locals(options).then(locals => - Promise.resolve() - .then(beforeHook.bind(this, options, locals)) - .then(process.bind(this, intoDir, locals)) - .then(promises => RSVP.map(promises, this._commit.bind(this))) - .then(afterHook.bind(this, options)) - ); + let locals = await this._locals(options); + + // run beforeInstall/beforeUninstall userland hooks + await beforeHook.call(this, options, locals); + + // gather fileInfos to be processed + let fileInfos = await process.call(this, intoDir, locals); + + // commit changes for each FileInfo (with prompting as needed) + await Promise.all(fileInfos.map((fi) => this._commit(fi))); + + // run afterInstall/afterUninstall userland hooks + await afterHook.call(this, options); }, /** @@ -525,7 +526,7 @@ let Blueprint = CoreObject.extend({ When the following is called on the command line: ```sh - ember generate controller foo --type=array --dry-run + ember generate controller foo --type=array --dry-run isAdmin:true ``` The object passed to `locals` looks like this: @@ -535,10 +536,12 @@ let Blueprint = CoreObject.extend({ entity: { name: 'foo', options: { - type: 'array' + isAdmin: true } }, dryRun: true + type: "array" + // more keys } ``` @@ -646,7 +649,7 @@ let Blueprint = CoreObject.extend({ generateFileMap(fileMapVariables) { let tokens = this._fileMapTokens(fileMapVariables); let fileMapValues = _.values(tokens); - let tokenValues = fileMapValues.map(token => token(fileMapVariables)); + let tokenValues = fileMapValues.map((token) => token(fileMapVariables)); let tokenKeys = Object.keys(tokens); return _.zipObject(tokenKeys, tokenValues); }, @@ -738,6 +741,7 @@ let Blueprint = CoreObject.extend({ @method processFiles @param {String} intoDir @param {Object} templateVariables + @return {Promise} */ processFiles(intoDir, templateVariables) { let files = this._getFilesForInstall(templateVariables.targetFiles); @@ -750,10 +754,9 @@ let Blueprint = CoreObject.extend({ fileInfosToRemove = finishProcessingForUninstall(fileInfosToRemove); - return RSVP.filter(fileInfos, isValidFile) - .then(promises => RSVP.map(promises, prepareConfirm)) + return Promise.all(fileInfos.filter(isValidFile).map(prepareConfirm)) .then(finishProcessingForInstall) - .then(fileInfos => fileInfos.concat(fileInfosToRemove)); + .then((fileInfos) => fileInfos.concat(fileInfosToRemove)); }, /** @@ -766,7 +769,7 @@ let Blueprint = CoreObject.extend({ this._ignoreUpdateFiles(); - return RSVP.filter(fileInfos, isValidFile).then(finishProcessingForUninstall); + return finishProcessingForUninstall(fileInfos.filter(isValidFile)); }, /** @@ -839,9 +842,9 @@ let Blueprint = CoreObject.extend({ let moduleName = (options.entity && options.entity.name) || packageName; let sanitizedModuleName = moduleName.replace(/\//g, '-'); - return new Promise(resolve => { + return new Promise((resolve) => { resolve(this.locals(options)); - }).then(customLocals => { + }).then((customLocals) => { let fileMapVariables = this._generateFileMapVariables(moduleName, customLocals, options); let fileMap = this.generateFileMap(fileMapVariables); let standardLocals = { @@ -1041,7 +1044,7 @@ let Blueprint = CoreObject.extend({ let installText = packages.length > 1 ? 'install bower packages' : 'install bower package'; let packageNames = []; let packageNamesAndVersions = packages - .map(pkg => { + .map((pkg) => { pkg.source = pkg.source || pkg.name; packageNames.push(pkg.name); return pkg; @@ -1098,7 +1101,7 @@ let Blueprint = CoreObject.extend({ let packages = options.packages; if (packages && packages.length) { - taskOptions.packages = packages.map(pkg => { + taskOptions.packages = packages.map((pkg) => { if (typeof pkg === 'string') { return pkg; } @@ -1158,13 +1161,13 @@ let Blueprint = CoreObject.extend({ Example: ``` // app/router.js - Router.map(function() { + Router.map(function () { }); ``` ``` insertIntoFile('app/router.js', ' this.route("admin");', { - after: 'Router.map(function() {' + EOL + after: 'Router.map(function () {' + EOL }).then(function() { // file has been inserted into! }); @@ -1174,7 +1177,7 @@ let Blueprint = CoreObject.extend({ ``` // app/router.js - Router.map(function() { + Router.map(function () { this.route("admin"); }); ``` @@ -1222,11 +1225,11 @@ let Blueprint = CoreObject.extend({ getJson(verbose) { let json = {}; - this._printableProperties.forEach(key => { + this._printableProperties.forEach((key) => { let value = this[key]; if (key === 'availableOptions') { value = _.cloneDeep(value); - value.forEach(option => { + value.forEach((option) => { if (typeof option.type === 'function') { option.type = option.type.name; } @@ -1273,7 +1276,7 @@ let Blueprint = CoreObject.extend({ matching Blueprint could not be found @return {Blueprint} */ -Blueprint.lookup = function(name, options) { +Blueprint.lookup = function (name, options) { options = options || {}; let lookupPaths = generateLookupPaths(options.paths); @@ -1300,7 +1303,7 @@ Blueprint.lookup = function(name, options) { @param {String} blueprintPath @return {Blueprint} blueprint instance */ -Blueprint.load = function(blueprintPath) { +Blueprint.load = function (blueprintPath) { if (fs.lstatSync(blueprintPath).isDirectory()) { let Constructor = Blueprint; @@ -1327,13 +1330,13 @@ Blueprint.load = function(blueprintPath) { @param {Array} [options.paths] Extra paths to search for blueprints @return {Array} */ -Blueprint.list = function(options) { +Blueprint.list = function (options) { options = options || {}; let lookupPaths = generateLookupPaths(options.paths); let seen = []; - return lookupPaths.map(lookupPath => { + return lookupPaths.map((lookupPath) => { let source; let packagePath = path.join(lookupPath, '../package.json'); if (Blueprint._existsSync(packagePath)) { @@ -1342,7 +1345,7 @@ Blueprint.list = function(options) { source = path.basename(path.join(lookupPath, '..')); } - let blueprints = dir(lookupPath).map(blueprintPath => { + let blueprints = dir(lookupPath).map((blueprintPath) => { let blueprint = Blueprint.load(blueprintPath); if (blueprint) { let name = blueprint.name; @@ -1360,11 +1363,11 @@ Blueprint.list = function(options) { }); }; -Blueprint._existsSync = function(path, parent) { +Blueprint._existsSync = function (path, parent) { return fs.existsSync(path, parent); }; -Blueprint._readdirSync = function(path) { +Blueprint._readdirSync = function (path) { return fs.readdirSync(path); }; @@ -1399,7 +1402,7 @@ Blueprint.ignoredUpdateFiles = ['.gitkeep', 'app.css', 'LICENSE.md']; @static @property defaultLookupPaths */ -Blueprint.defaultLookupPaths = function() { +Blueprint.defaultLookupPaths = function () { return [path.resolve(__dirname, '..', '..', 'blueprints')]; }; @@ -1410,7 +1413,7 @@ Blueprint.defaultLookupPaths = function() { @return {Promise} */ function prepareConfirm(info) { - return info.checkForConflict().then(resolution => { + return info.checkForConflict().then((resolution) => { info.resolution = resolution; return info; }); @@ -1450,16 +1453,6 @@ function gatherConfirmationMessages(collection, info) { return collection; } -/** - @private - @method isFile - @param {FileInfo} info - @return {Promise} -*/ -function isFile(info) { - return stat(info.inputPath).then(it => it.isFile()); -} - /** @private @method isIgnored @@ -1469,7 +1462,7 @@ function isFile(info) { function isIgnored(info) { let fn = info.inputPath; - return Blueprint.ignoredFiles.some(ignoredFile => minimatch(fn, ignoredFile, { matchBase: true })); + return Blueprint.ignoredFiles.some((ignoredFile) => minimatch(fn, ignoredFile, { matchBase: true })); } /** @@ -1501,13 +1494,13 @@ function hasPathToken(files) { } function findAddonByName(addonOrProject, name) { - let addon = addonOrProject.addons.find(addon => addon.name === name); + let addon = addonOrProject.addons.find((addon) => addon.name === name); if (addon) { return addon; } - return addonOrProject.addons.find(addon => findAddonByName(addon, name)); + return addonOrProject.addons.find((addon) => findAddonByName(addon, name)); } function ensureTargetDirIsAddon(addonPath) { @@ -1534,9 +1527,9 @@ function ensureTargetDirIsAddon(addonPath) { */ function isValidFile(fileInfo) { if (isIgnored(fileInfo)) { - return Promise.resolve(false); + return false; } else { - return isFile(fileInfo); + return isFilePath(fileInfo); } } @@ -1557,7 +1550,7 @@ function isFilePath(fileInfo) { */ function dir(fullPath) { if (Blueprint._existsSync(fullPath)) { - return Blueprint._readdirSync(fullPath).map(fileName => path.join(fullPath, fileName)); + return Blueprint._readdirSync(fullPath).map((fileName) => path.join(fullPath, fileName)); } else { return []; } @@ -1582,7 +1575,7 @@ function finishProcessingForInstall(infos) { } function finishProcessingForUninstall(infos) { - let validInfos = infos.filter(info => fs.existsSync(info.outputPath)); + let validInfos = infos.filter((info) => fs.existsSync(info.outputPath)); validInfos.forEach(markToBeRemoved); return validInfos; diff --git a/lib/models/builder.js b/lib/models/builder.js index 7d2a23201f..12e7562030 100644 --- a/lib/models/builder.js +++ b/lib/models/builder.js @@ -2,16 +2,13 @@ const onProcessInterrupt = require('../utilities/will-interrupt-process'); const fs = require('fs-extra'); const path = require('path'); -const Promise = require('rsvp').Promise; const CoreObject = require('core-object'); const SilentError = require('silent-error'); const chalk = require('chalk'); -const attemptNeverIndex = require('../utilities/attempt-never-index'); const findBuildFile = require('../utilities/find-build-file'); const _resetTreeCache = require('./addon')._resetTreeCache; const Sync = require('tree-sync'); const heimdall = require('heimdalljs'); -const { isExperimentEnabled } = require('../experiments'); const progress = require('../utilities/heimdall-progress'); /** @@ -34,7 +31,7 @@ class Builder extends CoreObject { this._instantiationStack = new Error().stack.replace(/[^\n]*\n/, ''); this._cleanup = this.cleanup.bind(this); - this._cleanupPromise = null; + this._cleanupStarted = false; this._onProcessInterrupt = options.onProcessInterrupt || onProcessInterrupt; this._onProcessInterrupt.addHandler(this._cleanup); @@ -67,21 +64,8 @@ class Builder extends CoreObject { let broccoli = require('broccoli'); - // If not using system temp dir, compatibility mode with broccoli-builder, tmp in root - let tmpDir; - if (!isExperimentEnabled('SYSTEM_TEMP')) { - tmpDir = `${this.project.root}/tmp`; - if (!fs.existsSync(tmpDir)) { - fs.mkdir(tmpDir); - } - } - - let options = { - tmpdir: tmpDir, - }; - try { - this.builder = new broccoli.Builder(this.tree, options); + this.builder = new broccoli.Builder(this.tree); return; } catch (e) { // Catch here to trap InvalidNodeError. If this is thrown, it's because the node provided is not valid @@ -103,7 +87,7 @@ class Builder extends CoreObject { broccoli = require('broccoli-builder'); this.broccoliBuilderFallback = true; - this.builder = new broccoli.Builder(this.tree, options); + this.builder = new broccoli.Builder(this.tree); } /** @@ -149,22 +133,7 @@ class Builder extends CoreObject { let changes = sync.sync(); - return changes.map(op => op[1]); - } - - /** - * @private - * @method processBuildResult - * @param results - * @return {Promise} - */ - processBuildResult(results) { - return Promise.resolve() - .then(() => this.copyToOutputPath(results.directory)) - .then(syncResult => { - results.outputChanges = syncResult; - return results; - }); + return changes.map((op) => op[1]); } /** @@ -197,51 +166,72 @@ class Builder extends CoreObject { * @method build * @return {Promise} */ - build(addWatchDirCallback, resultAnnotation) { - this.project._instrumentation.start('build'); + async build(addWatchDirCallback, resultAnnotation) { + let buildResults, uiProgressIntervalID; - if (!isExperimentEnabled('SYSTEM_TEMP')) { - attemptNeverIndex('tmp'); - } + try { + this.project._instrumentation.start('build'); - if (addWatchDirCallback && !this.broccoliBuilderFallback) { - for (let path of this.builder.watchedPaths) { - addWatchDirCallback(path); + if (addWatchDirCallback && !this.broccoliBuilderFallback) { + for (let path of this.builder.watchedPaths) { + addWatchDirCallback(path); + } } - } - this.ui.startProgress(progress.format(progress())); - - const intervalID = setInterval(() => { - this.ui.spinner.text = progress.format(progress()); - }, this.ui.spinner.interval); - - return this.processAddonBuildSteps('preBuild') - .then(() => this.builder.build(this.broccoliBuilderFallback ? addWatchDirCallback : null)) - .then(this.compatNode.bind(this), this.compatBroccoliPayload.bind(this)) - .then(this.processAddonBuildSteps.bind(this, 'postBuild')) - .then(this.processBuildResult.bind(this)) - .then(this.processAddonBuildSteps.bind(this, 'outputReady')) - .then( - result => { - this.project._instrumentation.stopAndReport('build', result, resultAnnotation); - return result; - }, - reason => { - this.project._instrumentation.stopAndReport('build', null, resultAnnotation); - throw reason; + this.ui.startProgress(progress.format(progress())); + + uiProgressIntervalID = setInterval(() => { + this.ui.spinner.text = progress.format(progress()); + }, this.ui.spinner.interval); + + await this.processAddonBuildSteps('preBuild'); + + if (this.broccoliBuilderFallback) { + try { + buildResults = await this.builder.build(addWatchDirCallback); + } catch (error) { + this.throwFormattedBroccoliError(error); + } + } else { + try { + await this.builder.build(); + + // build legacy style results object (this is passed to various addon APIs) + buildResults = { + directory: this.builder.outputPath, + graph: this.builder.outputNodeWrapper, + }; + } catch (error) { + this.throwFormattedBroccoliError(error); } - ) - .then(this.checkForPostBuildEnvironmentIssues.bind(this)) - .finally(() => { - clearInterval(intervalID); - this.ui.stopProgress(); - }) - .catch(error => { - this.processAddonBuildSteps('buildError', error); - throw error; - }) - .finally(this.finalizeBuild.bind(this)); + } + + await this.processAddonBuildSteps('postBuild', buildResults); + + let outputChanges = await this.copyToOutputPath(buildResults.directory); + + await this.processAddonBuildSteps( + 'outputReady', + Object.assign({}, buildResults, { outputChanges, directory: this.outputPath }) + ); + + this.checkForPostBuildEnvironmentIssues(); + + return buildResults; + } catch (error) { + await this.processAddonBuildSteps('buildError', error); + + // Mark this as a builder error so the watcher knows it has been handled + // and won't re-throw it + error.isBuilderError = true; + + throw error; + } finally { + clearInterval(uiProgressIntervalID); + this.ui.stopProgress(); + this.project._instrumentation.stopAndReport('build', buildResults, resultAnnotation); + this.project.configCache.clear(); + } } /** @@ -251,8 +241,9 @@ class Builder extends CoreObject { * @method cleanup * @return {Promise} */ - cleanup() { - if (!this._cleanupPromise) { + async cleanup() { + if (!this._cleanupStarted) { + this._cleanupStarted = true; let ui = this.project.ui; ui.startProgress('cleaning up'); ui.writeLine('cleaning up...'); @@ -263,20 +254,16 @@ class Builder extends CoreObject { this._onProcessInterrupt.removeHandler(this._cleanup); let node = heimdall.start({ name: 'Builder Cleanup' }); - - this._cleanupPromise = Promise.resolve() - .then(() => this.builder.cleanup()) - .finally(() => { - ui.stopProgress(); - node.stop(); - }) - .catch(err => { - ui.writeLine(chalk.red('Cleanup error.')); - ui.writeError(err); - }); + try { + await this.builder.cleanup(); + } catch (error) { + ui.writeLine(chalk.red('Cleanup error.')); + ui.writeError(error); + } finally { + ui.stopProgress(); + node.stop(); + } } - - return this._cleanupPromise; } /** @@ -299,33 +286,7 @@ class Builder extends CoreObject { return value; } - /** - * @private - * @method finalizeBuild - */ - finalizeBuild() { - this.project.configCache.clear(); - } - - /** - * broccoli-builder reformats the response into {directory, graph}, this method is a backwards - * compatible shim for broccoli 1.x - * @private - * @method compatNode - * @param node The node returned from Broccoli builder - */ - compatNode(node) { - if (!this.broccoliBuilderFallback) { - return { - directory: this.builder.outputPath, - graph: this.builder.outputNodeWrapper, - }; - } - - return node; - } - - compatBroccoliPayload(err) { + throwFormattedBroccoliError(err) { // TODO fix ember-cli/console-ui to handle current broccoli broccoliPayload let broccoliPayload = err && err.broccoliPayload; if (broccoliPayload) { diff --git a/lib/models/command.js b/lib/models/command.js index 5547521f16..9dec3e64af 100644 --- a/lib/models/command.js +++ b/lib/models/command.js @@ -7,7 +7,6 @@ const isGitRepo = require('is-git-url'); const camelize = require('ember-cli-string-utils').camelize; const getCallerFile = require('get-caller-file'); const printCommand = require('../utilities/print-command'); -const Promise = require('rsvp').Promise; const _ = require('ember-cli-lodash-subset'); const EOL = require('os').EOL; const CoreObject = require('core-object'); @@ -237,7 +236,7 @@ let Command = CoreObject.extend({ return Promise.resolve() .then(() => task.run(options)) - .catch(error => { + .catch((error) => { logger.info(`An error occurred running \`${name}\` from the \`${this.name}\` command.`, error.stack); throw error; @@ -293,7 +292,9 @@ let Command = CoreObject.extend({ if (isYarnProject(this.project.root)) { installInstuction = '`yarn install`'; } - throw new SilentError(`node_modules appears empty, you may need to run ${installInstuction}`); + throw new SilentError( + `Required packages are missing, run ${installInstuction} from this directory to install them.` + ); } } @@ -350,8 +351,8 @@ let Command = CoreObject.extend({ // TODO: warn on duplicates and overwriting mergedAliases = []; - _.map(duplicateOptions, 'aliases').map(alias => { - alias.map(a => { + _.map(duplicateOptions, 'aliases').map((alias) => { + alias.map((a) => { mergedAliases.push(a); }); }); @@ -360,7 +361,7 @@ let Command = CoreObject.extend({ mergedOption = Object.assign.apply(null, duplicateOptions); // replace aliases with unique aliases - mergedOption.aliases = _.uniqBy(mergedAliases, alias => { + mergedOption.aliases = _.uniqBy(mergedAliases, (alias) => { if (typeof alias === 'object') { return alias[Object.keys(alias)[0]]; } @@ -550,13 +551,13 @@ let Command = CoreObject.extend({ this.registerOptions(); - let assembleAndValidateOption = function(option) { + let assembleAndValidateOption = function (option) { return this.assignOption(option, parsedOptions, commandOptions); }; - let validateParsed = function(key) { + let validateParsed = function (key) { // ignore 'argv', 'h', and 'help' - if (!commandOptions.hasOwnProperty(key) && key !== 'argv' && key !== 'h' && key !== 'help') { + if (!(key in commandOptions) && key !== 'argv' && key !== 'h' && key !== 'help') { this.ui.writeLine( chalk.yellow( `The option '--${key}' is not registered with the '${this.name}' command. ` + @@ -569,7 +570,7 @@ let Command = CoreObject.extend({ } }; - this.availableOptions.forEach(option => { + this.availableOptions.forEach((option) => { if (typeof option.type !== 'string') { knownOpts[option.name] = option.type; } else if (option.type === 'Path') { @@ -653,7 +654,7 @@ let Command = CoreObject.extend({ let json = {}; this.registerOptions(options); - this._printableProperties.forEach(key => (json[key] = this[key])); + this._printableProperties.forEach((key) => (json[key] = this[key])); if (this.addAdditionalJsonForHelp) { this.addAdditionalJsonForHelp(json, options); diff --git a/lib/models/edit-file-diff.js b/lib/models/edit-file-diff.js index 90a0702e44..8d084c7c3a 100644 --- a/lib/models/edit-file-diff.js +++ b/lib/models/edit-file-diff.js @@ -1,15 +1,16 @@ 'use strict'; const fs = require('fs'); -const RSVP = require('rsvp'); +const util = require('util'); const jsdiff = require('diff'); const quickTemp = require('quick-temp'); const path = require('path'); const SilentError = require('silent-error'); const openEditor = require('../utilities/open-editor'); +const hash = require('promise.hash.helper'); -const readFile = RSVP.denodeify(fs.readFile); -const writeFile = RSVP.denodeify(fs.writeFile); +const readFile = util.promisify(fs.readFile); +const writeFile = util.promisify(fs.writeFile); class EditFileDiff { constructor(options) { @@ -19,7 +20,7 @@ class EditFileDiff { } edit() { - return RSVP.hash({ + return hash({ input: this.info.render(), output: readFile(this.info.outputPath), }) @@ -33,10 +34,10 @@ class EditFileDiff { } applyPatch(resultHash) { - return RSVP.hash({ + return hash({ diffString: readFile(resultHash.diffPath), currentString: readFile(resultHash.outputPath), - }).then(result => { + }).then((result) => { let appliedDiff = jsdiff.applyPatch(result.currentString.toString(), result.diffString.toString()); if (!appliedDiff) { diff --git a/lib/models/file-info.js b/lib/models/file-info.js index 19c227173d..df350bb859 100644 --- a/lib/models/file-info.js +++ b/lib/models/file-info.js @@ -1,17 +1,18 @@ 'use strict'; const fs = require('fs'); -const RSVP = require('rsvp'); +const util = require('util'); const chalk = require('chalk'); const EditFileDiff = require('./edit-file-diff'); const EOL = require('os').EOL; const rxEOL = new RegExp(EOL, 'g'); -const isBinaryFile = require('isbinaryfile').sync; +const isBinaryFile = require('isbinaryfile').isBinaryFileSync; +const hash = require('promise.hash.helper'); const canEdit = require('../utilities/open-editor').canEdit; const processTemplate = require('../utilities/process-template'); -const readFile = RSVP.denodeify(fs.readFile); -const lstat = RSVP.denodeify(fs.stat); +const readFile = util.promisify(fs.readFile); +const lstat = util.promisify(fs.stat); function diffHighlight(line) { if (line[0] === '+') { @@ -25,7 +26,7 @@ function diffHighlight(line) { } } -const NOOP = _ => _; +const NOOP = (_) => _; class FileInfo { constructor(options) { this.action = options.action; @@ -67,16 +68,16 @@ class FileInfo { } } - return this.ui.prompt(promptOptions).then(response => response.answer); + return this.ui.prompt(promptOptions).then((response) => response.answer); } displayDiff() { let info = this, jsdiff = require('diff'); - return RSVP.hash({ + return hash({ input: this.render(), output: readFile(info.outputPath), - }).then(result => { + }).then((result) => { let diff = jsdiff.createPatch( info.outputPath, result.output.toString().replace(rxEOL, '\n'), @@ -90,9 +91,10 @@ class FileInfo { }); } - render() { + async render() { if (!this.rendered) { - this.rendered = this._render().then(result => this.replacer(result, this)); + let result = await this._render(); + this.rendered = this.replacer(result, this); } return this.rendered; @@ -102,8 +104,8 @@ class FileInfo { let path = this.inputPath; let context = this.templateVariables; - return readFile(path).then(content => - lstat(path).then(fileStat => { + return readFile(path).then((content) => + lstat(path).then((fileStat) => { if (isBinaryFile(content, fileStat.size)) { return content; } else { @@ -119,16 +121,16 @@ class FileInfo { } checkForConflict() { - return this.render().then(input => { + return this.render().then((input) => { input = input.toString().replace(rxEOL, '\n'); return readFile(this.outputPath) - .then(output => { + .then((output) => { output = output.toString().replace(rxEOL, '\n'); return input === output ? 'identical' : 'confirm'; }) - .catch(e => { + .catch((e) => { if (e.code === 'ENOENT') { return 'none'; } @@ -141,9 +143,9 @@ class FileInfo { confirmOverwriteTask() { let info = this; - return function() { + return function () { function doConfirm() { - return info.confirmOverwrite(info.displayPath).then(action => { + return info.confirmOverwrite(info.displayPath).then((action) => { if (action === 'diff') { return info.displayDiff().then(doConfirm); } else if (action === 'edit') { diff --git a/lib/models/gzipStats.js b/lib/models/gzipStats.js new file mode 100644 index 0000000000..1f3d4e2e46 --- /dev/null +++ b/lib/models/gzipStats.js @@ -0,0 +1,16 @@ +'use strict'; + +const fs = require('fs'); +const zlib = require('zlib'); +const util = require('util'); +const gzip = util.promisify(zlib.gzip); + +module.exports = async function (file) { + let contentsBuffer = fs.readFileSync(file); + return gzip(contentsBuffer).then((buffer) => ({ + name: file, + size: contentsBuffer.length, + gzipSize: buffer.length, + showGzipped: contentsBuffer.length > 0, + })); +}; diff --git a/lib/models/hardware-info.js b/lib/models/hardware-info.js index a2338fc32c..cfb1a4aa1d 100644 --- a/lib/models/hardware-info.js +++ b/lib/models/hardware-info.js @@ -10,7 +10,7 @@ function isUsingBatteryAcpi() { const { stdout } = execa.sync('acpi', ['--ac-adapter']); const lines = stdout.split('\n').filter(Boolean); - return lines.every(line => /off-line/.test(line)); + return lines.every((line) => /off-line/.test(line)); } catch (ex) { logger.warn(`Could not get battery status from acpi: ${ex}`); logger.warn(ex.stack); @@ -89,7 +89,7 @@ function isUsingBatteryUpower() { const { stdout } = execa.sync('upower', ['--enumerate']); const devices = stdout.split('\n').filter(Boolean); - return devices.some(device => { + return devices.some((device) => { const { stdout } = execa.sync('upower', ['--show-info', device]); return /\bpower supply:\s+yes\b/.test(stdout) && /\bstate:\s+discharging\b/.test(stdout); @@ -230,9 +230,10 @@ function memorySwapUsedWindows() { const hwinfo = { /** - * Inidcates whether the host is running on battery power. This can cause + * Indicates whether the host is running on battery power. This can cause * performance degredation. * + * @private * @method isUsingBattery * @for HardwareInfo * @param {String=process.platform} platform The current hardware platform. @@ -265,6 +266,7 @@ const hwinfo = { /** * Determines the amount of swap/virtual memory currently in use. * + * @private * @method memorySwapUsed * @param {String=process.platform} platform The current hardware platform. * USED FOR TESTING ONLY. @@ -296,6 +298,7 @@ const hwinfo = { * Determines the total amount of memory available to the host, as from * `os.totalmem`. * + * @private * @method memoryTotal * @return {Number} The total memory in bytes. */ @@ -307,6 +310,7 @@ const hwinfo = { * Determines the amount of memory currently being used by the current Node * process, as from `process.memoryUsage`. * + * @private * @method memoryUsed * @return {Object} The Resident Set Size, as reported by * `process.memoryUsage`. @@ -319,6 +323,7 @@ const hwinfo = { * Determines the number of logical processors available to the host, as from * `os.cpus`. * + * @private * @method processorCount * @return {Number} The number of logical processors. */ @@ -331,6 +336,7 @@ const hwinfo = { * expressed as a fractional number between 0 and the number of logical * processors. * + * @private * @method processorLoad * @param {String=process.platform} platform The current hardware platform. * USED FOR TESTING ONLY. @@ -354,6 +360,7 @@ const hwinfo = { * * If more than one processor is found, the average of their speeds is taken. * + * @private * @method processorSpeed * @return {Number} The average processor speed in MHz. */ @@ -366,6 +373,7 @@ const hwinfo = { /** * Determines the time since the host was started, as from `os.uptime`. * + * @private * @method uptime * @return {Number} The number of seconds since the host was started. */ diff --git a/lib/models/host-info-cache.js b/lib/models/host-info-cache.js new file mode 100644 index 0000000000..53bae7b2b6 --- /dev/null +++ b/lib/models/host-info-cache.js @@ -0,0 +1,334 @@ +'use strict'; + +function allPkgInfosEqualAtIndex(paths, index) { + const itemToCheck = paths[0][index]; + return paths.every((pathToLazyEngine) => pathToLazyEngine[index] === itemToCheck); +} + +class HostInfoCache { + constructor(project) { + this.project = project; + this._bundledPackageInfoCache = new Map(); + this._hostAddonInfoCache = new Map(); + this._lcaHostCache = new Map(); + } + + /** + * Given a path (calculated as part of `getHostAddonInfo`), return the correct + * "bundle host". A bundle host is considered the project or lazy engine. + * + * For example, given the following package structure: + * + * --Project-- + * / \ + * / \ + * Lazy Engine A \ + * Addon A + * | + * | + * Lazy Engine B + * / \ + * / \ + * Lazy Engine A Lazy Engine C + * + * The provided paths for lazy engine A would look like: + * + * - [Project] + * - [Project, Addon A, Lazy Engine B] + * + * For this project structure, this function would return [Project, [Project]] + * + * Similarly, given the following project structure: + * + * --Project-- + * / \ + * / \ + * Lazy Engine A \ + * / Lazy Engine B + * / | + * / | + * Lazy Engine C Lazy Engine C + * + * The provided paths for lazy engine C would look like: + * + * - [Project, Lazy Engine A] + * - [Project, Lazy Engine B] + * + * In this case, the host is the project and would also return [Project, [Project]] + * + * @method _findNearestBundleHost + * @param {Array} paths The found paths to a given bundle host + * @return {[PackageInfo, PackageInfo[]]} + * @private + */ + _findNearestBundleHost(paths, pkgInfoForLazyEngine) { + // building an engine in isolation (it's considered the project, but it's + // also added as a dependency to the project by `ember-cli`) + if (this.project._packageInfo === pkgInfoForLazyEngine) { + return [this.project._packageInfo, [this.project._packageInfo]]; + } + + const shortestPath = paths.reduce( + (acc, pathToLazyEngine) => Math.min(acc, pathToLazyEngine.length), + Number.POSITIVE_INFINITY + ); + + const pathsEqualToShortest = paths.filter((pathToLazyEngine) => pathToLazyEngine.length === shortestPath); + const [firstPath] = pathsEqualToShortest; + + for (let i = firstPath.length - 1; i >= 0; i--) { + const pkgInfo = firstPath[i]; + + if (pkgInfo.isForBundleHost() && allPkgInfosEqualAtIndex(pathsEqualToShortest, i)) { + return [pkgInfo, firstPath.slice(0, i + 1)]; + } + } + + // this should _never_ be triggered + throw new Error( + `[ember-cli] Could not find a common host for: \`${pkgInfoForLazyEngine.name}\` (located at \`${pkgInfoForLazyEngine.realPath}\`)` + ); + } + + /** + * Returns a `Set` of package-info objects that a given bundle host is + * _directly_ responsible for bundling (i.e., it excludes other bundle + * hosts/lazy engines when it encounters these) + * + * @method _getBundledPackageInfos + * @param {PackageInfo} pkgInfoToStartAt + * @return {Set} + * @private + */ + _getBundledPackageInfos(pkgInfoToStartAt) { + let pkgInfos = this._bundledPackageInfoCache.get(pkgInfoToStartAt); + + if (pkgInfos) { + return pkgInfos; + } + + if (!pkgInfoToStartAt.isForBundleHost()) { + throw new Error( + `[ember-cli] \`${pkgInfoToStartAt.name}\` is not a bundle host; \`getBundledPackageInfos\` should only be used to find bundled package infos for a project or lazy engine` + ); + } + + pkgInfos = new Set(); + this._bundledPackageInfoCache.set(pkgInfoToStartAt, pkgInfos); + + let findAddons = (currentPkgInfo) => { + if (!currentPkgInfo.valid || !currentPkgInfo.addonMainPath) { + return; + } + + if (pkgInfos.has(currentPkgInfo)) { + return; + } + + if (currentPkgInfo.isForBundleHost()) { + return; + } + + pkgInfos.add(currentPkgInfo); + + let addonPackageList = currentPkgInfo.discoverAddonAddons(); + addonPackageList.forEach((pkgInfo) => findAddons(pkgInfo)); + }; + + let addonPackageList = pkgInfoToStartAt.project + ? pkgInfoToStartAt.discoverProjectAddons() + : pkgInfoToStartAt.discoverAddonAddons(); + + addonPackageList.forEach((pkgInfo) => findAddons(pkgInfo)); + + return pkgInfos; + } + + /** + * This function intends to return a common host for a bundle host (lazy engine). The root + * package info should be the starting point (i.e., the project's package info). We do this + * by performing a breadth-first traversal until we find the intended lazy engine (represented + * as a package-info & the 1st argument passed to this function). As part of the traversal, we keep + * track of all paths to said engine; then, once we find the intended engine we use this to determine + * the nearest common host amongst all shortest paths. + * + * Some context: + * + * For a given engine/bundle host, this finds the lowest common ancestor that is considered a + * host amongst _all_ engines by the same name in the project. + * + * For example, given the following package structure: + * + * --Project-- + * / \ + * / \ + * Lazy Engine A \ + * Addon A + * | + * | + * Lazy Engine B + * / \ + * / \ + * Lazy Engine A Lazy Engine C + * + * - The LCA host for Lazy Engine A is the project + * - The LCA host for Lazy Engine B is the project + * - The LCA host for Lazy Engine C is Lazy Engine B + * + * This also returns `hostAndAncestorBundledPackageInfos`, which are all bundled addons above a given host: + * + * - `hostAndAncestorBundledPackageInfos` for lazy engine A includes all non-lazy dependencies of its LCA host & above (in this case, just the project) + * - `hostAndAncestorBundledPackageInfos` for lazy engine B includes all non-lazy dependencies of its LCA host & above (in this case, just the project) + * - `hostAndAncestorBundledPackageInfos` for lazy engine C includes non-lazy deps of lazy engine B & non-lazy deps of the project (LCA host & above) + * + * This is intended to mimic the behavior of `ancestorHostAddons` in `ember-engines`: + * https://github.com/ember-engines/ember-engines/blob/master/packages/ember-engines/lib/engine-addon.js#L333 + * + * Unfortunately, we can't easily repurpose the logic in `ember-engines` since the algorithm has to be different; + * in `ember-engines` we need access to the actual addon instance, however, this is intended to be used _during_ + * addon instantiation, so we only have access to package-info objects. In having said this, we _can_ repurpose + * the `hostPackageInfo` to determine the LCA host; see below `findLCAHost`. + * + * @method getHostAddonInfo + * @param {PackageInfo} packageInfoForLazyEngine + * @return {{ hostPackageInfo: PackageInfo, hostAndAncestorBundledPackageInfos: Set }} + */ + getHostAddonInfo(packageInfoForLazyEngine) { + const cacheKey = `${this.project._packageInfo.realPath}-${packageInfoForLazyEngine.realPath}`; + + let hostInfoCacheEntry = this._hostAddonInfoCache.get(cacheKey); + + if (hostInfoCacheEntry) { + return hostInfoCacheEntry; + } + + if (!packageInfoForLazyEngine.isForEngine()) { + throw new Error( + `[ember-cli] \`${packageInfoForLazyEngine.name}\` is not an engine; \`getHostAddonInfo\` should only be used to find host information about engines` + ); + } + + const queue = [{ pkgInfo: this.project._packageInfo, path: [] }]; + const visited = new Set(); + const foundPaths = []; + + while (queue.length) { + const { pkgInfo: currentPackageInfo, path } = queue.shift(); + + const { + addonMainPath, + inRepoAddons = [], + dependencyPackages = {}, + devDependencyPackages = {}, + } = currentPackageInfo; + + const isCurrentPackageInfoProject = this.project._packageInfo === currentPackageInfo; + + // don't process non-ember addons + if (!isCurrentPackageInfoProject && typeof addonMainPath !== 'string') { + continue; + } + + // store found paths + if (currentPackageInfo === packageInfoForLazyEngine) { + foundPaths.push([...path]); + } + + // don't process a given `PackageInfo` object more than once + if (!visited.has(currentPackageInfo)) { + visited.add(currentPackageInfo); + + // add current package info to current path + path.push(currentPackageInfo); + + queue.push( + ...[ + ...inRepoAddons, + ...Object.values(dependencyPackages), + ...Object.values(devDependencyPackages), + ].map((pkgInfo) => ({ pkgInfo, path: [...path] })) + ); + } + } + + const [hostPackageInfo, foundPath] = this._findNearestBundleHost(foundPaths, packageInfoForLazyEngine); + + const hostAndAncestorBundledPackageInfos = foundPath + .filter((pkgInfo) => pkgInfo.isForBundleHost()) + .reduce((acc, curr) => { + acc.push(...this._getBundledPackageInfos(curr)); + return acc; + }, []); + + hostInfoCacheEntry = { + hostPackageInfo, + hostAndAncestorBundledPackageInfos: new Set(hostAndAncestorBundledPackageInfos), + }; + + this._hostAddonInfoCache.set(cacheKey, hostInfoCacheEntry); + return hostInfoCacheEntry; + } + + /** + * This returns the LCA host for a given engine; we use the associated package info + * to compute this (see `getHostAddonInfo` above); this finds the lowest common ancestor + * that is considered a host amongst _all_ engines by the same name in the project. This + * function is intended to replace the original behavior in `ember-engines`. + * + * For more info, see the original implementation here: + * + * https://github.com/ember-engines/ember-engines/blob/master/packages/ember-engines/lib/utils/find-lca-host.js + * + * @method findLCAHost + * @param {EngineAddon} engineInstance + * @return {EngineAddon|EmberApp} + */ + findLCAHost(engineInstance) { + // only compute once for a given engine + // we're using the engine name as the cache key here because regardless of its + // version, lazy engines will always get output to: `engines-dist/${engineName}` + let lcaHost = this._lcaHostCache.get(engineInstance.name); + + if (lcaHost) { + return lcaHost; + } + + if (!engineInstance._packageInfo.isForEngine()) { + throw new Error( + `[ember-cli] \`findLCAHost\` should only be used for engines; \`${engineInstance.name}\` is not an engine` + ); + } + + const { hostPackageInfo } = this.getHostAddonInfo(engineInstance._packageInfo); + + let curr = engineInstance; + + while (curr && curr.parent) { + if (curr.app) { + lcaHost = curr.app; + break; + } + + if (curr._packageInfo === hostPackageInfo) { + lcaHost = curr; + break; + } + + curr = curr.parent; + } + + if (lcaHost) { + this._lcaHostCache.set(engineInstance.name, lcaHost); + return lcaHost; + } + + // this should _never_ be triggered + throw new Error( + `[ember-cli] Could not find an LCA host for: \`${engineInstance.name}\` (located at \`${ + engineInstance.packageRoot || engineInstance.root + }\`)` + ); + } +} + +module.exports = HostInfoCache; diff --git a/lib/models/instantiate-addons.js b/lib/models/instantiate-addons.js index 24f3b79eb2..e7a820a4c7 100644 --- a/lib/models/instantiate-addons.js +++ b/lib/models/instantiate-addons.js @@ -41,7 +41,7 @@ function instantiateAddons(parent, project, addonPackages) { let graph = new DAGMap(); let addonInfo, emberAddonConfig; - addonNames.forEach(name => { + addonNames.forEach((name) => { addonInfo = addonPackages[name]; emberAddonConfig = addonInfo.pkg['ember-addon']; @@ -49,6 +49,8 @@ function instantiateAddons(parent, project, addonPackages) { }); let addons = []; + let timings = new Map(); + graph.each((key, value) => { let addonInfo = value; if (addonInfo) { @@ -68,28 +70,11 @@ function instantiateAddons(parent, project, addonPackages) { ); } - let AddonConstructor = pkgInfo.getAddonConstructor(); - - let addon; + // get an instance of the addon. If that fails it will throw. + let addon = pkgInfo.getAddonInstance(parent, project); - try { - addon = new AddonConstructor(parent, project); - } catch (e) { - if (parent && parent.ui) { - parent.ui.writeError(e); - } - const SilentError = require('silent-error'); - throw new SilentError(`An error occurred in the constructor for ${addonInfo.name} at ${addonInfo.path}`); - } - - if (addon.initializeAddons) { - addon.initializeAddons(); - } else { - addon.addons = []; - } + timings.set(addon, Date.now() - start); - AddonConstructor._meta_.initializeIn = Date.now() - start; - addon.constructor = AddonConstructor; initializeAddonToken.stop(); addons.push(addon); @@ -98,12 +83,9 @@ function instantiateAddons(parent, project, addonPackages) { logger.info( ' addon info %o', - addons.map(addon => ({ + addons.map((addon) => ({ name: addon.name, - times: { - initialize: addon.constructor._meta_.initializeIn, - lookup: addon.constructor._meta_.lookupIn, - }, + initializeTotalMillis: timings.get(addon), })) ); diff --git a/lib/models/instrumentation.js b/lib/models/instrumentation.js index e5f96115e0..3ae48cdc0e 100644 --- a/lib/models/instrumentation.js +++ b/lib/models/instrumentation.js @@ -3,6 +3,7 @@ const fs = require('fs-extra'); const chalk = require('chalk'); const heimdallGraph = require('heimdalljs-graph'); +const getConfig = require('../utilities/get-config'); const utilsInstrumentation = require('../utilities/instrumentation'); const logger = require('heimdalljs-logger')('ember-cli:instrumentation'); const hwinfo = require('./hardware-info'); @@ -25,7 +26,7 @@ _enableFSMonitorIfInstrumentationEnabled(); function _getHardwareInfo(platform) { const startTime = process.hrtime(); - Object.getOwnPropertyNames(hwinfo).forEach(metric => (platform[metric] = hwinfo[metric]())); + Object.getOwnPropertyNames(hwinfo).forEach((metric) => (platform[metric] = hwinfo[metric]())); const collectionTime = process.hrtime(startTime); @@ -76,6 +77,8 @@ class Instrumentation { this._heimdall = null; + this.config = getConfig(); + if (!options.initInstrumentation && this.isEnabled()) { this.instrumentations.init = { token: null, @@ -208,7 +211,7 @@ class Instrumentation { _invokeAddonHook(name, instrumentationInfo) { if (this.project && this.project.addons.length) { - this.project.addons.forEach(addon => { + this.project.addons.forEach((addon) => { if (typeof addon.instrumentation === 'function') { addon.instrumentation(name, instrumentationInfo); } @@ -236,7 +239,7 @@ class Instrumentation { } start(name) { - if (!instrumentationEnabled()) { + if (!instrumentationEnabled(this.config)) { return; } @@ -255,7 +258,7 @@ class Instrumentation { } stopAndReport(name) { - if (!instrumentationEnabled()) { + if (!instrumentationEnabled(this.config)) { return; } @@ -332,7 +335,5 @@ function totalTime(tree) { // exported for testing Instrumentation._enableFSMonitorIfInstrumentationEnabled = _enableFSMonitorIfInstrumentationEnabled; -Instrumentation._vizEnabled = vizEnabled(); -Instrumentation._instrumentationEnabled = instrumentationEnabled(); module.exports = Instrumentation; diff --git a/lib/models/package-info-cache/index.js b/lib/models/package-info-cache/index.js index e032345bb0..295c45bbe9 100644 --- a/lib/models/package-info-cache/index.js +++ b/lib/models/package-info-cache/index.js @@ -55,7 +55,7 @@ class PackageInfoCache { hasErrors() { let paths = Object.keys(this.entries); - if (paths.find(entryPath => this.getEntry(entryPath).hasErrors())) { + if (paths.find((entryPath) => this.getEntry(entryPath).hasErrors())) { return true; } @@ -72,7 +72,7 @@ class PackageInfoCache { showErrors() { let paths = Object.keys(this.entries).sort(); - paths.forEach(entryPath => { + paths.forEach((entryPath) => { this._showObjErrors(this.getEntry(entryPath)); }); } @@ -110,7 +110,7 @@ class PackageInfoCache { rootPath = obj.realPath; } - errorEntries.forEach(errorEntry => { + errorEntries.forEach((errorEntry) => { switch (errorEntry.type) { case Errors.ERROR_PACKAGE_JSON_MISSING: logger.info(` does not exist`); @@ -131,7 +131,7 @@ class PackageInfoCache { logger.info(` specifies a missing dependency '${errorEntry.data[0]}'`); } else { logger.info(` specifies some missing dependencies:`); - errorEntry.data.forEach(dependencyName => { + errorEntry.data.forEach((dependencyName) => { logger.info(` ${dependencyName}`); }); } @@ -187,7 +187,7 @@ class PackageInfoCache { // create a PackageInfo unless there is really a directory at the // suggested location. The created addon may internally have errors, // as with any other PackageInfo. - projectInstance.supportedInternalAddonPaths().forEach(internalAddonPath => { + projectInstance.supportedInternalAddonPaths().forEach((internalAddonPath) => { if (getRealDirectoryPath(internalAddonPath)) { logger.info('Reading package for internal addon: %o', internalAddonPath); pkgInfo.addInternalAddon(this._readPackage(internalAddonPath)); @@ -222,7 +222,7 @@ class PackageInfoCache { reloadProjects() { let projects = this.projects.slice(); this._clear(); - projects.forEach(project => this.loadProject(project)); + projects.forEach((project) => this.loadProject(project)); } /** @@ -244,7 +244,9 @@ class PackageInfoCache { * No copy is made. */ loadAddon(addonInstance) { - let pkgInfo = this._readPackage(addonInstance.root, addonInstance.pkg); + // to maintain backwards compatibility for consumers who create a new instance + // of the base addon model class directly and don't set `packageRoot` + let pkgInfo = this._readPackage(addonInstance.packageRoot || addonInstance.root, addonInstance.pkg); // NOTE: the returned pkgInfo may contain errors, or may contain // other packages that have errors. We will try to process @@ -274,7 +276,7 @@ class PackageInfoCache { logger.info('Resolving dependencies...'); let packageInfos = this._getPackageInfos(); - packageInfos.forEach(pkgInfo => { + packageInfos.forEach((pkgInfo) => { if (!pkgInfo.processed) { let pkgs = pkgInfo.addDependencies(pkgInfo.pkg.dependencies); if (pkgs) { @@ -331,7 +333,7 @@ class PackageInfoCache { _getPackageInfos() { let result = []; - Object.keys(this.entries).forEach(path => { + Object.keys(this.entries).forEach((path) => { let entry = this.entries[path]; if (entry instanceof PackageInfo) { result.push(entry); @@ -511,7 +513,7 @@ class PackageInfoCache { // If we have an ember-addon, check that the main exists and points // to a valid file. - if (pkgInfo.isAddon()) { + if (pkgInfo.isForAddon()) { logger.info('%s is an addon', pkg.name); // Note: when we have both 'main' and ember-addon:main, the latter takes precedence @@ -549,7 +551,7 @@ class PackageInfoCache { let paths = emberAddonInfo.paths; if (paths) { - paths.forEach(p => { + paths.forEach((p) => { let addonPath = path.join(realPath, p); // real path, though may not exist. logger.info('Adding in-repo-addon at %o', addonPath); let addonPkgInfo = this._readPackage(addonPath); // may have errors in the addon package. @@ -636,17 +638,24 @@ class PackageInfoCache { logger.info('Creating new NodeModulesList instance for %o', realPath); nodeModulesList = new NodeModulesList(realPath, this); - let entries = fs.readdirSync(realPath); // should not fail because getRealDirectoryPath passed - - entries.forEach(entryName => { - // entries should be either a package or a scoping directory. I think - // there can also be files, but we'll ignore those. - - if (entryName.startsWith('.') || entryName.startsWith('_')) { + const entries = fs.readdirSync(realPath).filter((fileName) => { + if (fileName.startsWith('.') || fileName.startsWith('_')) { // we explicitly want to ignore these, according to the // definition of a valid package name. - return; + return false; + } else if (fileName.startsWith('@')) { + return true; + } else if (!fs.existsSync(`${realPath}/${fileName}/package.json`)) { + // a node_module is only valid if it contains a package.json + return false; + } else { + return true; } + }); // should not fail because getRealDirectoryPath passed + + entries.forEach((entryName) => { + // entries should be either a package or a scoping directory. I think + // there can also be files, but we'll ignore those. let entryPath = path.join(realPath, entryName); diff --git a/lib/models/package-info-cache/package-info.js b/lib/models/package-info-cache/package-info.js index 148301b81b..4ec686024a 100644 --- a/lib/models/package-info-cache/package-info.js +++ b/lib/models/package-info-cache/package-info.js @@ -5,7 +5,10 @@ const ErrorList = require('./error-list'); const Errors = require('./errors'); const AddonInfo = require('../addon-info'); const isAddon = require('../../utilities/is-addon'); +const isEngine = require('../../utilities/is-engine'); +const isLazyEngine = require('../../utilities/is-lazy-engine'); const logger = require('heimdalljs-logger')('ember-cli:package-info-cache:package-info'); +const PerBundleAddonCache = require('../per-bundle-addon-cache'); function lexicographically(a, b) { const aIsString = typeof a.name === 'string'; @@ -80,7 +83,7 @@ class PackageInfo { // not actually be used. this.valid = true; - this.mayHaveAddons = isRoot || this.isAddon(); // mayHaveAddons used in index.js + this.mayHaveAddons = isRoot || this.isForAddon(); // mayHaveAddons used in index.js this._hasDumpedInvalidAddonPackages = false; } @@ -162,7 +165,7 @@ class PackageInfo { * been added to the cache. * * Note: this is for ALL dependencies, not just addons. To get just - * addons, filter the result by calling pkgInfo.isAddon(). + * addons, filter the result by calling pkgInfo.isForAddon(). * * Note: this is only intended for use from PackageInfoCache._resolveDependencies. * It is not to be called directly by anything else. @@ -189,7 +192,7 @@ class PackageInfo { let missingDependencies = []; - dependencyNames.forEach(dependencyName => { + dependencyNames.forEach((dependencyName) => { logger.info('%s: Trying to find dependency %o', this.pkg.name, dependencyName); let dependencyPackage; @@ -220,10 +223,59 @@ class PackageInfo { return packages; } - isAddon() { + /** + * Indicate if this packageInfo is for a project. Should be called only after the project + * has been loaded (see {@link PackageInfoCache#loadProject} for details). + * + * @method isForProject + * @return {Boolean} true if this packageInfo is for a Project, false otherwise. + */ + isForProject() { + return !!this.project && this.project.isEmberCLIProject && this.project.isEmberCLIProject(); + } + + /** + * Indicate if this packageInfo is for an Addon. + * + * @method isForAddon + * @return {Boolean} true if this packageInfo is for an Addon, false otherwise. + */ + isForAddon() { return isAddon(this.pkg.keywords); } + /** + * Indicate if this packageInfo represents an engine. + * + * @method isForEngine + * @return {Boolean} true if this pkgInfo is configured as an engine & false otherwise + */ + isForEngine() { + return isEngine(this.pkg.keywords); + } + + /** + * Indicate if this packageInfo represents a lazy engine. + * + * @method isForLazyEngine + * @return {Boolean} true if this pkgInfo is configured as an engine and the + * module this represents has lazyLoading enabled, false otherwise. + */ + isForLazyEngine() { + return this.isForEngine() && isLazyEngine(this._getAddonEntryPoint()); + } + + /** + * For use with the PerBundleAddonCache, is this packageInfo representing a + * bundle host (for now, a Project or a lazy engine). + * + * @method isForBundleHost + * @return {Boolean} true if this pkgInfo is for a bundle host, false otherwise. + */ + isForBundleHost() { + return this.isForProject() || this.isForLazyEngine(); + } + /** * Add to a list of child addon PackageInfos for this packageInfo. * @@ -244,13 +296,13 @@ class PackageInfo { let result = []; if (Array.isArray(packageInfoList)) { if (excludeFn) { - packageInfoList = packageInfoList.filter(pkgInfo => !excludeFn(pkgInfo)); + packageInfoList = packageInfoList.filter((pkgInfo) => !excludeFn(pkgInfo)); } - packageInfoList.forEach(pkgInfo => result.push(pkgInfo)); + packageInfoList.forEach((pkgInfo) => result.push(pkgInfo)); } else { // We're going to assume we have a map of name to packageInfo - Object.keys(packageInfoList).forEach(name => { + Object.keys(packageInfoList).forEach((name) => { let pkgInfo = packageInfoList[name]; if (!excludeFn || !excludeFn(pkgInfo)) { result.push(pkgInfo); @@ -258,7 +310,7 @@ class PackageInfo { }); } - result.sort(lexicographically).forEach(pkgInfo => pushUnique(addonPackageList, pkgInfo)); + result.sort(lexicographically).forEach((pkgInfo) => pushUnique(addonPackageList, pkgInfo)); return addonPackageList; } @@ -274,7 +326,7 @@ class PackageInfo { this.addPackages( addonPackageList, this.dependencyPackages, - pkgInfo => !pkgInfo.isAddon() || pkgInfo.name === 'ember-cli' + (pkgInfo) => !pkgInfo.isForAddon() || pkgInfo.name === 'ember-cli' ); this.addPackages(addonPackageList, this.inRepoAddons); @@ -294,8 +346,8 @@ class PackageInfo { this.addPackages(addonPackageList, project.isEmberCLIAddon() ? [this] : null); this.addPackages(addonPackageList, this.cliInfo ? this.cliInfo.inRepoAddons : null); this.addPackages(addonPackageList, this.internalAddons); - this.addPackages(addonPackageList, this.devDependencyPackages, pkgInfo => !pkgInfo.isAddon()); - this.addPackages(addonPackageList, this.dependencyPackages, pkgInfo => !pkgInfo.isAddon()); + this.addPackages(addonPackageList, this.devDependencyPackages, (pkgInfo) => !pkgInfo.isForAddon()); + this.addPackages(addonPackageList, this.dependencyPackages, (pkgInfo) => !pkgInfo.isForAddon()); this.addPackages(addonPackageList, this.inRepoAddons); return addonPackageList; @@ -317,7 +369,7 @@ class PackageInfo { let packageMap = Object.create(null); - validPackages.forEach(pkgInfo => { + validPackages.forEach((pkgInfo) => { let addonInfo = new AddonInfo(pkgInfo.name, pkgInfo.realPath, pkgInfo.pkg); if (!excludeFn || !excludeFn(addonInfo)) { packageMap[pkgInfo.name] = addonInfo; @@ -328,11 +380,11 @@ class PackageInfo { } getValidPackages(addonPackageList) { - return addonPackageList.filter(pkgInfo => pkgInfo.valid); + return addonPackageList.filter((pkgInfo) => pkgInfo.valid); } getInvalidPackages(addonPackageList) { - return addonPackageList.filter(pkgInfo => !pkgInfo.valid); + return addonPackageList.filter((pkgInfo) => !pkgInfo.valid); } dumpInvalidAddonPackages(addonPackageList) { @@ -346,21 +398,25 @@ class PackageInfo { if (invalidPackages.length > 0) { let typeName = this.project ? 'project' : 'addon'; - logger.info(''); - logger.info(`The 'package.json' file for the ${typeName} at ${this.realPath}`); + let msg = `The 'package.json' file for the ${typeName} at ${this.realPath}`; let relativePath; - if (invalidPackages.length === 1) { relativePath = path.relative(this.realPath, invalidPackages[0].realPath); - logger.info(` specifies an invalid, malformed or missing addon at relative path '${relativePath}'`); + msg = `${msg}\n specifies an invalid, malformed or missing addon at relative path '${relativePath}'`; } else { - logger.info(' specifies invalid, malformed or missing addons at relative paths'); - invalidPackages.forEach(packageInfo => { + msg = `${msg}\n specifies invalid, malformed or missing addons at relative paths`; + invalidPackages.forEach((packageInfo) => { let relativePath = path.relative(this.realPath, packageInfo.realPath); - logger.info(` '${relativePath}'`); + msg = `${msg}\n '${relativePath}'`; }); } + + if (process.env.EMBER_CLI_ERROR_ON_INVALID_ADDON) { + throw msg; + } else { + this.cache.ui.writeWarnLine(msg); + } } } @@ -369,6 +425,7 @@ class PackageInfo { * Also, the assumption here is that this PackageInfo really is for an * Addon, so we don't need to check each time. * + * @private * @method getAddonConstructor * @return {AddonConstructor} an instance of a constructor function for the Addon class * whose package information is stored in this object. @@ -378,10 +435,7 @@ class PackageInfo { return this.addonConstructor; } - // Load the addon. - // TODO: Future work - allow a time budget for loading each addon and warn - // or error for those that take too long. - let module = require(this.addonMainPath); + let module = this._getAddonEntryPoint(); let mainDir = path.dirname(this.addonMainPath); let ctor; @@ -389,24 +443,123 @@ class PackageInfo { if (typeof module === 'function') { ctor = module; ctor.prototype.root = ctor.prototype.root || mainDir; + ctor.prototype.packageRoot = ctor.prototype.packageRoot || this.realPath; ctor.prototype.pkg = ctor.prototype.pkg || this.pkg; } else { const Addon = require('../addon'); // done here because of circular dependency - ctor = Addon.extend(Object.assign({ root: mainDir, pkg: this.pkg }, module)); + ctor = Addon.extend(Object.assign({ root: mainDir, packageRoot: this.realPath, pkg: this.pkg }, module)); } - // XXX Probably want to store the timings here in PackageInfo, - // rather than adding _meta_ to the constructor. - // XXX Will also need to remove calls to it from various places. ctor._meta_ = { modulePath: this.addonMainPath, - lookupDuration: 0, - initializeIn: 0, }; return (this.addonConstructor = ctor); } + + /** + * Construct an addon instance. + * + * NOTE: this does NOT call constructors for the child addons. That is left to + * the caller to do, so they can insert any other logic they want. + * + * @private + * @method constructAddonInstance + * @param {Project|Addon} parent the parent that directly contains this addon + * @param {Project} project the project that is/contains this addon + */ + constructAddonInstance(parent, project) { + let start = Date.now(); + + let AddonConstructor = this.getAddonConstructor(); + + let addonInstance; + + try { + addonInstance = new AddonConstructor(parent, project); + } catch (e) { + if (parent && parent.ui) { + parent.ui.writeError(e); + } + + const SilentError = require('silent-error'); + throw new SilentError(`An error occurred in the constructor for ${this.name} at ${this.realPath}`); + } + + AddonConstructor._meta_.initializeIn = Date.now() - start; + addonInstance.constructor = AddonConstructor; + + return addonInstance; + } + + /** + * Create an instance of the addon represented by this packageInfo or (if we + * are supporting per-bundle caching and this is an allow-caching-per-bundle addon) + * check if we should be creating a proxy instead. + * + * NOTE: we assume that the value of 'allowCachingPerBundle' does not change between + * calls to the constructor! A given addon is either allowing or not allowing caching + * for an entire run. + * + * @method getAddonInstance + * @param {} parent the addon/project that is to be the direct parent of the + * addon instance created here + * @param {*} project the project that is to contain this addon instance + * @return {Object} the constructed instance of the addon + */ + getAddonInstance(parent, project) { + let addonEntryPointModule = this._getAddonEntryPoint(); + let addonInstance; + + if ( + PerBundleAddonCache.isEnabled() && + project && + project.perBundleAddonCache.allowCachingPerBundle(addonEntryPointModule) + ) { + addonInstance = project.perBundleAddonCache.getAddonInstance(parent, this); + } else { + addonInstance = this.constructAddonInstance(parent, project); + this.initChildAddons(addonInstance); + } + + return addonInstance; + } + + /** + * Initialize the child addons array of a newly-created addon instance. Normally when + * an addon derives from Addon, child addons will be created during 'setupRegistry' and + * this code is essentially unnecessary. But if an addon is created with custom constructors + * that don't call 'setupRegistry', any child addons may not yet be initialized. + * + * @method initChildAddons + * @param {Addon} addonInstance + */ + initChildAddons(addonInstance) { + if (addonInstance.initializeAddons) { + addonInstance.initializeAddons(); + } else { + addonInstance.addons = []; + } + } + + /** + * Gets the addon entry point + * + * @method _getAddonEntryPoint + * @return {Object|Function} The exported addon entry point + * @private + */ + _getAddonEntryPoint() { + if (!this.addonMainPath) { + throw new Error(`${this.pkg.name} at ${this.realPath} is missing its addon main file`); + } + + // Load the addon. + // TODO: Future work - allow a time budget for loading each addon and warn + // or error for those that take too long. + return require(this.addonMainPath); + } } module.exports = PackageInfo; diff --git a/lib/models/per-bundle-addon-cache/addon-proxy.js b/lib/models/per-bundle-addon-cache/addon-proxy.js new file mode 100644 index 0000000000..ce1ccd1cef --- /dev/null +++ b/lib/models/per-bundle-addon-cache/addon-proxy.js @@ -0,0 +1,158 @@ +'use strict'; + +const { TARGET_INSTANCE } = require('./target-instance'); + +const CACHE_KEY_FOR_TREE_TRACKER = Symbol('CACHE_KEY_FOR_TREE_TRACKER'); + +/** + * Validates that a new cache key for a given tree type matches the previous + * cache key for the same tree type. To opt-in to bundle addon caching for + * a given addon it's assumed that it returns stable cache keys; specifically + * this is because the interplay between bundle addon caching and `ember-engines` + * when transitive deduplication is enabled assumes stable cache keys, so we validate + * for this case here. + * + * @method validateCacheKey + * @param {Addon} realAddonInstance The real addon instance + * @param {string} treeType + * @param {string} newCacheKey + * @throws {Error} If the new cache key doesn't match the previous cache key + */ +function validateCacheKey(realAddonInstance, treeType, newCacheKey) { + let cacheKeyTracker = realAddonInstance[CACHE_KEY_FOR_TREE_TRACKER]; + + if (!cacheKeyTracker) { + cacheKeyTracker = {}; + realAddonInstance[CACHE_KEY_FOR_TREE_TRACKER] = cacheKeyTracker; + } + + cacheKeyTracker[treeType] = treeType in cacheKeyTracker ? cacheKeyTracker[treeType] : newCacheKey; + + if (cacheKeyTracker[treeType] !== newCacheKey) { + throw new Error( + `[ember-cli] addon bundle caching can only be used on addons that have stable cache keys (previously \`${ + cacheKeyTracker[treeType] + }\`, now \`${newCacheKey}\`; for addon \`${realAddonInstance.name}\` located at \`${ + realAddonInstance.packageRoot || realAddonInstance.root + }\`)` + ); + } +} + +/** + * Returns a proxy to a target with specific handling for the + * `parent` property, as well has to handle the `app` property; + * that is, the proxy should maintain correct local state in + * closure scope for the `app` property if it happens to be set + * by `ember-cli`. Other than `parent` & `app`, this function also + * proxies _almost_ everything to `target[TARGET_INSTANCE] with a few + * exceptions: we trap & return `[]` for `addons`, and we don't return + * the original `included` (it's already called on the "real" addon + * by `ember-cli`). + * + * Note: the target is NOT the per-bundle cacheable instance of the addon. Rather, + * it is a cache entry POJO from PerBundleAddonCache. + * + * @method getAddonProxy + * @param targetCacheEntry the PerBundleAddonCache cache entry we are to proxy. It + * has one interesting property, the real addon instance the proxy is forwarding + * calls to (that property is not globally exposed). + * @param parent the parent object of the proxy being created (the same as + * the 'parent' property of a normal addon instance) + * @return Proxy + */ +function getAddonProxy(targetCacheEntry, parent) { + let _app; + + // handle `preprocessJs` separately for Embroider + // + // some context here: + // + // Embroider patches `preprocessJs`, so we want to maintain local state within the + // proxy rather than allowing a patched `preprocessJs` set on the original addon + // instance itself + // + // for more info as to where this happens, see: + // https://github.com/embroider-build/embroider/blob/master/packages/compat/src/v1-addon.ts#L634 + let _preprocessJs; + + return new Proxy(targetCacheEntry, { + get(targetCacheEntry, property) { + if (property === 'parent') { + return parent; + } + + if (property === 'app') { + return _app; + } + + // only return `_preprocessJs` here if it was previously set to a patched version + if (property === 'preprocessJs' && _preprocessJs) { + return _preprocessJs; + } + + // keep proxies from even trying to set or initialize addons + if (property === 'initializeAddons') { + return undefined; + } + + // See the {@link index.js} file for a discussion of why the proxy 'addons' + // property returns an empty array. + if (property === 'addons') { + return []; + } + + // allow access to the property pointing to the real instance. + if (property === TARGET_INSTANCE) { + return targetCacheEntry[TARGET_INSTANCE]; + } + + // `included` will be called on the "real" addon, so there's no need for it to be + // called again; instead we return a no-op implementation here + if (property === 'included') { + return () => undefined; + } + + if (targetCacheEntry[TARGET_INSTANCE]) { + if (property !== 'constructor' && typeof targetCacheEntry[TARGET_INSTANCE][property] === 'function') { + // If we fall through to the Reflect.get just below, the 'this' context of the function when + // invoked is the proxy, not the original instance (so its local state is incorrect). + // Wrap the original methods to maintain the correct 'this' context. + return function _originalAddonPropMethodWrapper() { + let originalReturnValue = targetCacheEntry[TARGET_INSTANCE][property](...arguments); + + if (property === 'cacheKeyForTree') { + const treeType = arguments[0]; + validateCacheKey(targetCacheEntry[TARGET_INSTANCE], treeType, originalReturnValue); + } + + return originalReturnValue; + }; + } + + return Reflect.get(targetCacheEntry[TARGET_INSTANCE], property); + } + + return Reflect.get(targetCacheEntry, property); + }, + set(targetCacheEntry, property, value) { + if (property === 'app') { + _app = value; + return true; + } + + if (property === 'preprocessJs') { + _preprocessJs = value; + return true; + } + + if (targetCacheEntry[TARGET_INSTANCE]) { + return Reflect.set(targetCacheEntry[TARGET_INSTANCE], property, value); + } + + return Reflect.set(targetCacheEntry, property, value); + }, + }); +} + +module.exports = { getAddonProxy }; diff --git a/lib/models/per-bundle-addon-cache/index.js b/lib/models/per-bundle-addon-cache/index.js new file mode 100644 index 0000000000..064a0cc748 --- /dev/null +++ b/lib/models/per-bundle-addon-cache/index.js @@ -0,0 +1,391 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const isLazyEngine = require('../../utilities/is-lazy-engine'); +const { getAddonProxy } = require('./addon-proxy'); +const logger = require('heimdalljs-logger')('ember-cli:per-bundle-addon-cache'); +const { TARGET_INSTANCE } = require('./target-instance'); + +function defaultAllowCachingPerBundle({ addonEntryPointModule }) { + return ( + addonEntryPointModule.allowCachingPerBundle || + (addonEntryPointModule.prototype && addonEntryPointModule.prototype.allowCachingPerBundle) + ); +} + +/** + * Resolves the perBundleAddonCacheUtil; this prefers the custom provided version by + * the consuming application, and defaults to an internal implementation here. + * + * @method resolvePerBundleAddonCacheUtil + * @param {Project} project + * @return {{allowCachingPerBundle: Function}} + */ +function resolvePerBundleAddonCacheUtil(project) { + const relativePathToUtil = + project.pkg && project.pkg['ember-addon'] && project.pkg['ember-addon'].perBundleAddonCacheUtil; + + if (typeof relativePathToUtil === 'string') { + const absolutePathToUtil = path.resolve(project.root, relativePathToUtil); + + if (!fs.existsSync(absolutePathToUtil)) { + throw new Error( + `[ember-cli] the provided \`${relativePathToUtil}\` for \`ember-addon.perBundleAddonCacheUtil\` does not exist` + ); + } + + return require(absolutePathToUtil); + } + + return { + allowCachingPerBundle: defaultAllowCachingPerBundle, + }; +} + +/** + * For large applications with many addons (and many instances of each, resulting in + * potentially many millions of addon instances during a build), the build can become + * very, very slow (tens of minutes) partially due to the sheer number of addon instances. + * The PerBundleAddonCache deals with this slowness by doing 3 things: + * + * (1) Making only a single copy of each of certain addons and their dependent addons + * (2) Replacing any other instances of those addons with Proxy copies to the single instance + * (3) Having the Proxies return an empty array for their dependent addons, rather + * than proxying to the contents of the single addon instance. This gives up the + * ability of the Proxies to traverse downward into their child addons, + * something that many addons do not do anyway, for the huge reduction in duplications + * of those child addons. For applications that enable `ember-engines` dedupe logic, + * that logic is stateful, and having the Proxies allow access to the child addons array + * just breaks everything, because that logic will try multiple times to remove items + * it thinks are duplicated, messing up the single copy of the child addon array. + * See the explanation of the dedupe logic in + * {@link https://github.com/ember-engines/ember-engines/blob/master/packages/ember-engines/lib/utils/deeply-non-duplicated-addon.js} + * + * What follows are the more technical details of how the PerBundleAddonCache implements + * the above 3 behaviors. + * + * This class supports per-bundle-host (bundle host = project or lazy engine) + * caching of addon instances. During addon initialization we cannot add a + * cache to each bundle host object AFTER it is instantiated because running the + * addon constructor ultimately causes Addon class `setupRegistry` code to + * run which instantiates child addons, which need the cache to already be + * in place for the parent bundle host. + * We handle this by providing a global cache that exists independent of the + * bundle host objects. That is this object. + * + * There are a number of "behaviors" being implemented by this object and + * its contents. They are: + * (1) Any addon that is a lazy engine has only a single real instance per + * project - all other references to the lazy engine are to be proxies. These + * lazy engines are compared by name, not by packageInfo.realPath. + * (2) Any addon that is not a lazy engine, there is only a single real instance + * of the addon per "bundle host" (i.e. lazy engine or project). + * (3) An optimization - any addon that is in a lazy engine but that is also + * in bundled by its LCA host - the single instance is the one bundled by this + * host. All other instances (in any lazy engine) are proxies. + * + * NOTE: the optimization is only enabled if the environment variable that controls + * `ember-engines` transitive deduplication (process.env.EMBER_ENGINES_ADDON_DEDUPE) + * is set to a truthy value. For more info, see: + * https://github.com/ember-engines/ember-engines/blob/master/packages/ember-engines/lib/engine-addon.js#L396 + * + * @public + * @class PerBundleAddonCache { + */ +class PerBundleAddonCache { + constructor(project) { + this.project = project; + + // The cache of bundle-host package infos and their individual addon caches. + // The cache is keyed by package info (representing a bundle host (project or + // lazy engine)) and an addon instance cache to bundle with that bundle host. + this.bundleHostCache = new Map(); + + // Indicate if ember-engines transitive dedupe is enabled. + this.engineAddonTransitiveDedupeEnabled = !!process.env.EMBER_ENGINES_ADDON_DEDUPE; + this._perBundleAddonCacheUtil = resolvePerBundleAddonCacheUtil(this.project); + + // For stats purposes, counts on the # addons and proxies created. Addons we + // can compare against the bundleHostCache addon caches. Proxies, not so much, + // but we'll count them here. + this.numAddonInstances = 0; + this.numProxies = 0; + } + + /** + * The default implementation here is to indicate if the original addon entry point has + * the `allowCachingPerBundle` flag set either on itself or on its prototype. + * + * If a consuming application specifies a relative path to a custom utility via the + * `ember-addon.perBundleAddonCacheUtil` configuration, we prefer the custom implementation + * provided by the consumer. + * + * @method allowCachingPerBundle + * @param {Object|Function} addonEntryPointModule + * @return {Boolean} true if the given constructor function or class supports caching per bundle, false otherwise + */ + allowCachingPerBundle(addonEntryPointModule) { + return this._perBundleAddonCacheUtil.allowCachingPerBundle({ addonEntryPointModule }); + } + + /** + * Creates a cache entry for the bundleHostCache. Because we want to use the same sort of proxy + * for both bundle hosts and for 'regular' addon instances (though their cache entries have + * slightly different structures) we'll use the Symbol from getAddonProxy. + * + * @method createBundleHostCacheEntry + * @param {PackageInfo} bundleHostPkgInfo bundle host's pkgInfo.realPath + * @return {Object} an object in the form of a bundle-host cache entry + */ + createBundleHostCacheEntry(bundleHostPkgInfo) { + return { [TARGET_INSTANCE]: null, realPath: bundleHostPkgInfo.realPath, addonInstanceCache: new Map() }; + } + + /** + * Create a cache entry object for a given (non-bundle-host) addon to put into + * an addon cache. + * + * @method createAddonCacheEntry + * @param {Addon} addonInstance the addon instance to cache + * @param {String} addonRealPath the addon's pkgInfo.realPath + * @return {Object} an object in the form of an addon-cache entry + */ + createAddonCacheEntry(addonInstance, addonRealPath) { + return { [TARGET_INSTANCE]: addonInstance, realPath: addonRealPath }; + } + + /** + * Given a parent object of a potential addon (another addon or the project), + * go up the 'parent' chain to find the potential addon's bundle host object + * (i.e. lazy engine or project.) Because Projects are always bundle hosts, + * this should always pass, but we'll throw if somehow it doesn't work. + * + * @method findBundleHost + * @param {Project|Addon} addonParent the direct parent object of a (potential or real) addon. + * @param {PackageInfo} addonPkgInfo the PackageInfo for an addon being instantiated. This is only + * used for information if an error is going to be thrown. + * @return {Object} the object in the 'parent' chain that is a bundle host. + * @throws {Error} if there is not bundle host + */ + findBundleHost(addonParent, addonPkgInfo) { + let curr = addonParent; + + while (curr) { + if (curr === this.project) { + return curr; + } + + if (isLazyEngine(curr)) { + // if we're building a lazy engine in isolation, prefer that the bundle host is + // the project, not the lazy engine addon instance + if (curr.parent === this.project && curr._packageInfo === this.project._packageInfo) { + return this.project; + } + + return curr; + } + + curr = curr.parent; + } + + // the following should not be able to happen given that Projects are always + // bundle hosts, but just in case, throw an error if we didn't find one. + throw new Error(`Addon at path\n ${addonPkgInfo.realPath}\n has 'allowCachingPerBundle' but has no bundleHost`); + } + + /** + * An optimization we support from lazy engines is the following: + * + * If an addon instance is supposed to be bundled with a particular lazy engine, and + * same addon is also to be bundled by a common LCA host, prefer the one bundled by the + * host (since it's ultimately going to be deduped later by `ember-engines`). + * + * NOTE: this only applies if this.engineAddonTransitiveDedupeEnabled is truthy. If it is not, + * the bundle host always "owns" the addon instance. + * + * If deduping is enabled and the LCA host also depends on the same addon, + * the lazy-engine instances of the addon will all be proxies to the one in + * the LCA host. This function indicates whether the bundle host passed in + * (either the project or a lazy engine) is really the bundle host to "own" the + * new addon. + * + * @method bundleHostOwnsInstance + * @param (Object} bundleHost the project or lazy engine that is trying to "own" + * the new addon instance specified by addonPkgInfo + * @param {PackageInfo} addonPkgInfo the PackageInfo of the potential new addon instance + * @return {Boolean} true if the bundle host is to "own" the instance, false otherwise. + */ + bundleHostOwnsInstance(bundleHost, addonPkgInfo) { + if (isLazyEngine(bundleHost)) { + return ( + !this.engineAddonTransitiveDedupeEnabled || + !this.project.hostInfoCache + .getHostAddonInfo(bundleHost._packageInfo) + .hostAndAncestorBundledPackageInfos.has(addonPkgInfo) + ); + } + + return true; + } + + findBundleOwner(bundleHost, addonPkgInfo) { + if (bundleHost === this.project._packageInfo) { + return bundleHost; + } + + let { hostPackageInfo, hostAndAncestorBundledPackageInfos } = this.project.hostInfoCache.getHostAddonInfo( + bundleHost + ); + + if (!hostAndAncestorBundledPackageInfos.has(addonPkgInfo)) { + return bundleHost; + } + + return this.findBundleOwner(hostPackageInfo, addonPkgInfo); + } + + /** + * Called from PackageInfo.getAddonInstance(), return an instance of the requested + * addon or a Proxy, based on the type of addon and its bundle host. + * + * @method getAddonInstance + * @param {Addon|Project} parent the parent Addon or Project this addon instance is + * a child of. + * @param {*} addonPkgInfo the PackageInfo for the addon being created. + * @return {Addon|Proxy} An addon instance (for the first copy of the addon) or a Proxy. + * An addon that is a lazy engine will only ever have a single copy in the cache. + * An addon that is not will have 1 copy per bundle host (Project or lazy engine), + * except if it is an addon that's also owned by a given LCA host and transitive + * dedupe is enabled (`engineAddonTransitiveDedupeEnabled`), in which case it will + * only have a single copy in the project's addon cache. + */ + getAddonInstance(parent, addonPkgInfo) { + // If the new addon is itself a bundle host (i.e. lazy engine), there is only one + // instance of the bundle host, and it's in the entries of the bundleHostCache, outside + // of the 'regular' addon caches. Because 'setupBundleHostCache' ran during construction, + // we know that an entry is in the cache with this engine name. + if (addonPkgInfo.isForBundleHost()) { + let cacheEntry = this._getBundleHostCacheEntry(addonPkgInfo); + + if (cacheEntry[TARGET_INSTANCE]) { + logger.debug(`About to construct BR PROXY to cache entry for addon at: ${addonPkgInfo.realPath}`); + this.numProxies++; + return getAddonProxy(cacheEntry, parent); + } else { + // create an instance, put it in the pre-existing cache entry, then + // return it (as the first instance of the lazy engine.) + logger.debug(`About to fill in BR EXISTING cache entry for addon at: ${addonPkgInfo.realPath}`); + this.numAddonInstances++; + let addon = addonPkgInfo.constructAddonInstance(parent, this.project); + cacheEntry[TARGET_INSTANCE] = addon; // cache BEFORE initializing child addons + addonPkgInfo.initChildAddons(addon); + return addon; + } + } + + // We know now we're asking for a 'regular' (non-bundle-host) addon instance. + + let bundleHost = this.findBundleHost(parent, addonPkgInfo); + + // if the bundle host "owns" the new addon instance + // * Do we already have an instance of the addon cached? + // * If so, make a proxy for it. + // * If not, make a new instance of the addon and cache it in the + // bundle host's addon cache. + // If not, it means the bundle host is a lazy engine but the LCA host also uses + // the addon and deduping is enabled + // * If the LCA host already has a cached entry, return a proxy to that + // * If it does not, create a 'blank' cache entry and return a proxy to that. + // When the addon is encountered later when processing the LCA host's addons, + // fill in the instance. + if (this.bundleHostOwnsInstance(bundleHost, addonPkgInfo)) { + let bundleHostCacheEntry = this._getBundleHostCacheEntry(bundleHost._packageInfo); + let addonInstanceCache = bundleHostCacheEntry.addonInstanceCache; + let addonCacheEntry = addonInstanceCache.get(addonPkgInfo.realPath); + let addonInstance; + + if (addonCacheEntry) { + if (addonCacheEntry[TARGET_INSTANCE]) { + logger.debug(`About to construct REGULAR ADDON PROXY for addon at: ${addonPkgInfo.realPath}`); + this.numProxies++; + return getAddonProxy(addonCacheEntry, parent); + } else { + // the cache entry was created 'empty' by an earlier call, indicating + // an addon that is used in a lazy engine but also used by its LCA host, + // and we're now creating the instance for the LCA host. + // Fill in the entry and return the new instance. + logger.debug(`About to fill in REGULAR ADDON EXISTING cache entry for addon at: ${addonPkgInfo.realPath}`); + this.numAddonInstances++; + addonInstance = addonPkgInfo.constructAddonInstance(parent, this.project); + addonCacheEntry[TARGET_INSTANCE] = addonInstance; // cache BEFORE initializing child addons + addonPkgInfo.initChildAddons(addonInstance); + return addonInstance; + } + } + + // There is no entry for this addon in the bundleHost's addon cache. Create a new + // instance, cache it in the addon cache, and return it. + logger.debug(`About to construct REGULAR ADDON NEW cache entry for addon at: ${addonPkgInfo.realPath}`); + this.numAddonInstances++; + addonInstance = addonPkgInfo.constructAddonInstance(parent, this.project); + addonCacheEntry = this.createAddonCacheEntry(addonInstance, addonPkgInfo.realPath); + addonInstanceCache.set(addonPkgInfo.realPath, addonCacheEntry); // cache BEFORE initializing child addons + addonPkgInfo.initChildAddons(addonInstance); + return addonInstance; + } else { + // The bundleHost is not the project but the some ancestor bundles the addon and + // deduping is enabled, so the cache entry needs to go in the bundle owner's cache. + // Get/create an empty cache entry and return a proxy to it. The bundle owner will + // set the instance later (see above). + let bundleHostCacheEntry = this._getBundleHostCacheEntry( + this.findBundleOwner(bundleHost._packageInfo, addonPkgInfo) + ); + let addonCacheEntry = bundleHostCacheEntry.addonInstanceCache.get(addonPkgInfo.realPath); + + if (!addonCacheEntry) { + logger.debug(`About to construct REGULAR ADDON EMPTY cache entry for addon at: ${addonPkgInfo.realPath}`); + addonCacheEntry = this.createAddonCacheEntry(null, addonPkgInfo.realPath); + bundleHostCacheEntry.addonInstanceCache.set(addonPkgInfo.realPath, addonCacheEntry); + } + + logger.debug(`About to construct REGULAR ADDON PROXY for EMPTY addon at: ${addonPkgInfo.realPath}`); + this.numProxies++; + return getAddonProxy(addonCacheEntry, parent); + } + } + + getPathsToAddonsOptedIn() { + const addonSet = new Set(); + + for (const [, { addonInstanceCache }] of this.bundleHostCache) { + Array.from(addonInstanceCache.keys()).forEach((realPath) => { + addonSet.add(realPath); + }); + } + + return Array.from(addonSet); + } + + _getBundleHostCacheEntry(pkgInfo) { + let cacheEntry = this.bundleHostCache.get(pkgInfo); + + if (!cacheEntry) { + cacheEntry = this.createBundleHostCacheEntry(pkgInfo); + this.bundleHostCache.set(pkgInfo, cacheEntry); + } + + return cacheEntry; + } + + // Support for per-bundle addon caching is GLOBAL opt OUT (unless you explicitly set + // EMBER_CLI_ADDON_INSTANCE_CACHING to false, it will be enabled.) If you opt out, that + // overrides setting `allowCachingPerBundle` for any particular addon type to true. + // To help make testing easier, we'll expose the setting as a function so it can be + // called multiple times and evaluate each time. + static isEnabled() { + return process.env.EMBER_CLI_ADDON_INSTANCE_CACHING !== 'false'; + } +} + +module.exports = PerBundleAddonCache; diff --git a/lib/models/per-bundle-addon-cache/target-instance.js b/lib/models/per-bundle-addon-cache/target-instance.js new file mode 100644 index 0000000000..ed3f23eb53 --- /dev/null +++ b/lib/models/per-bundle-addon-cache/target-instance.js @@ -0,0 +1,14 @@ +'use strict'; + +/** + * A Symbol constant for sharing between index.js and addon-proxy.js rather than + * putting the symbol into the Symbol global cache. The symbol is used in per-bundle + * cache entries to refer to the field that points at the real instance that a Proxy + * refers to. + * @property + * @type Symbol + * @final + */ +const TARGET_INSTANCE = Symbol('_targetInstance_'); + +module.exports.TARGET_INSTANCE = TARGET_INSTANCE; diff --git a/lib/models/project.js b/lib/models/project.js index b9b3427766..d1c53a11f1 100644 --- a/lib/models/project.js +++ b/lib/models/project.js @@ -3,10 +3,10 @@ /** @module ember-cli */ -const RSVP = require('rsvp'); +const util = require('util'); const path = require('path'); const findup = require('find-up'); -const resolve = RSVP.denodeify(require('resolve')); +const resolve = util.promisify(require('resolve')); const fs = require('fs-extra'); const _ = require('ember-cli-lodash-subset'); const logger = require('heimdalljs-logger')('ember-cli:project'); @@ -14,9 +14,10 @@ const versionUtils = require('../utilities/version-utils'); const emberCLIVersion = versionUtils.emberCLIVersion; const findAddonByName = require('../utilities/find-addon-by-name'); const heimdall = require('heimdalljs'); -const PackageInfoCache = require('../models/package-info-cache'); - -const instantiateAddons = require('../models/instantiate-addons'); +const PackageInfoCache = require('./package-info-cache'); +const PerBundleAddonCache = require('./per-bundle-addon-cache'); +const instantiateAddons = require('./instantiate-addons'); +const HostInfoCache = require('./host-info-cache'); let processCwd = process.cwd(); @@ -48,6 +49,7 @@ class Project { this.liveReloadFilterPatterns = []; this.setupBowerDirectory(); this.configCache = new Map(); + this.hostInfoCache = new HostInfoCache(this); /** Set when the `Watcher.detectWatchman` helper method finishes running, @@ -96,6 +98,10 @@ class Project { if (this.packageInfoCache.hasErrors()) { this.packageInfoCache.showErrors(); } + + if (PerBundleAddonCache.isEnabled()) { + this.perBundleAddonCache = new PerBundleAddonCache(this); + } } /** @@ -154,19 +160,15 @@ class Project { NULL_PROJECT = new Project(processCwd, {}, ui, cli); - NULL_PROJECT.isEmberCLIProject = function() { + NULL_PROJECT.isEmberCLIProject = function () { return false; }; - NULL_PROJECT.isEmberCLIAddon = function() { + NULL_PROJECT.isEmberCLIAddon = function () { return false; }; - NULL_PROJECT.isModuleUnification = function() { - return false; - }; - - NULL_PROJECT.name = function() { + NULL_PROJECT.name = function () { return path.basename(process.cwd()); }; @@ -207,22 +209,7 @@ class Project { @return {Boolean} Whether or not this is an Ember CLI Addon. */ isEmberCLIAddon() { - return !!this.pkg.keywords && this.pkg.keywords.indexOf('ember-addon') > -1; - } - - /** - Returns whether this is using a module unification format. - @private - @method isModuleUnification - @return {Boolean} Whether this is using a module unification format. - */ - isModuleUnification() { - const { isExperimentEnabled } = require('../experiments'); - if (isExperimentEnabled('MODULE_UNIFICATION')) { - return this.has('src'); - } else { - return false; - } + return !!this.pkg && !!this.pkg.keywords && this.pkg.keywords.indexOf('ember-addon') > -1; } /** @@ -448,13 +435,11 @@ class Project { return; } - let pkgInfo = this.packageInfoCache.getEntry(this.root); - - let addonPackageList = pkgInfo.discoverProjectAddons(); - this.addonPackages = pkgInfo.generateAddonPackages(addonPackageList); + let addonPackageList = this._packageInfo.discoverProjectAddons(); + this.addonPackages = this._packageInfo.generateAddonPackages(addonPackageList); // in case any child addons are invalid, dump to the console about them. - pkgInfo.dumpInvalidAddonPackages(addonPackageList); + this._packageInfo.dumpInvalidAddonPackages(addonPackageList); } /** @@ -474,7 +459,7 @@ class Project { this.discoverAddons(); this.addons = instantiateAddons(this, this, this.addonPackages); - this.addons.forEach(addon => logger.info('addon: %s', addon.name)); + this.addons.forEach((addon) => logger.info('addon: %s', addon.name)); } /** @@ -488,7 +473,7 @@ class Project { addonCommands() { const Command = require('../models/command'); let commands = Object.create(null); - this.addons.forEach(addon => { + this.addons.forEach((addon) => { if (!addon.includedCommands) { return; } @@ -594,7 +579,8 @@ class Project { } /** - Reloads package.json + Reloads package.json of the project. Clears and reloads the packageInfo and + per-bundle addon cache, too. @private @method reloadPkg @@ -608,6 +594,15 @@ class Project { this.packageInfoCache.reloadProjects(); + // update `_packageInfo` after reloading projects from the `PackageInfoCache` instance + // if we don't do this we get into a state where `_packageInfo` is referencing the old + // pkginfo object that hasn't been updated/reloaded + this._packageInfo = this.packageInfoCache.loadProject(this); + + if (PerBundleAddonCache.isEnabled()) { + this.perBundleAddonCache = new PerBundleAddonCache(this); + } + return this.pkg; } @@ -662,9 +657,13 @@ class Project { } /** - Returns a new project based on the first package.json that is found + Returns a new project based on the first `package.json` that is found in `pathName`. + If the above `package.json` specifies `ember-addon.projectRoot`, we load + the project based on the relative path between this directory and the + specified `projectRoot`. + @private @static @method closestSync @@ -678,15 +677,26 @@ class Project { let ui = ensureUI(_ui); let directory = findupPath(pathName); + let pkg = fs.readJsonSync(path.join(directory, 'package.json')); logger.info('found package.json at %s', directory); + // allow `package.json` files to specify where the actual project lives + if (pkg && pkg['ember-addon'] && typeof pkg['ember-addon'].projectRoot === 'string') { + if (fs.existsSync(path.join(directory, 'ember-cli-build.js'))) { + throw new Error( + `Both \`ember-addon.projectRoot\` and \`ember-cli-build.js\` exist as part of \`${directory}\`` + ); + } + + return Project.closestSync(path.join(directory, pkg['ember-addon'].projectRoot), _ui, _cli); + } + let relative = path.relative(directory, pathName); if (relative.indexOf('tmp') === 0) { logger.info('ignoring parent project since we are in the tmp folder of the project'); return Project.nullProject(_ui, _cli); } - let pkg = fs.readJsonSync(path.join(directory, 'package.json')); logger.info('project name: %s', pkg && pkg.name); if (!isEmberCliProject(pkg)) { diff --git a/lib/models/server-watcher.js b/lib/models/server-watcher.js index ff5db350d0..fe53c2e945 100644 --- a/lib/models/server-watcher.js +++ b/lib/models/server-watcher.js @@ -10,11 +10,6 @@ module.exports = class ServerWatcher extends Watcher { this.watcher.on('delete', this.didDelete.bind(this)); } - // This branch can be removed once BROCCOLI_WATCHER is enabled by default - constructWatcher(options) { - return this.constructBroccoliWatcher(options); - } - constructBroccoliWatcher(options) { return new (require('sane'))(this.watchedDir, options); } diff --git a/lib/models/watcher.js b/lib/models/watcher.js index 9ce6bab773..380867687b 100644 --- a/lib/models/watcher.js +++ b/lib/models/watcher.js @@ -4,9 +4,7 @@ const chalk = require('chalk'); const logger = require('heimdalljs-logger')('ember-cli:watcher'); const CoreObject = require('core-object'); const serveURL = require('../utilities/get-serve-url'); -const { isExperimentEnabled } = require('../experiments'); const printSlowTrees = require('broccoli-slow-trees'); -const promiseFinally = require('promise.prototype.finally'); const eventTypeNormalization = { add: 'added', @@ -26,31 +24,25 @@ module.exports = class Watcher extends CoreObject { this.serving = _options.serving; - if (isExperimentEnabled('BROCCOLI_WATCHER')) { - this.watcher = this.watcher || this.constructBroccoliWatcher(options); + this.watcher = this.watcher || this.constructBroccoliWatcher(options); - this.setupBroccoliChangeEvent(); - this.watcher.on('buildStart', this._setupBroccoliWatcherBuild.bind(this)); - this.watcher.on('buildSuccess', this.didChange.bind(this)); - } else { - // This branch can be removed once BROCCOLI_WATCHER is enabled by default - this.watcher = this.watcher || this.constructWatcher(options); - this.watcher.on('change', this.didChange.bind(this)); - } + this.setupBroccoliChangeEvent(); + this.watcher.on('buildStart', this._setupBroccoliWatcherBuild.bind(this)); + this.watcher.on('buildSuccess', this.didChange.bind(this)); + // build errors arriving via watcher + this.watcher.on('buildFailure', this.didError.bind(this)); + // watcher specific errors this.watcher.on('error', this.didError.bind(this)); - this.serveURL = serveURL; - } - constructWatcher(options) { - return new (require('ember-cli-broccoli-sane-watcher'))(this.builder, options); + this.serveURL = serveURL; } constructBroccoliWatcher(options) { const { Watcher } = require('broccoli'); const { watchedSourceNodeWrappers } = this.builder.builder; - let watcher = new Watcher(this.builder, watchedSourceNodeWrappers, { saneOptions: options }); + let watcher = new Watcher(this.builder, watchedSourceNodeWrappers, { saneOptions: options, ignored: this.ignored }); watcher.start(); @@ -67,35 +59,40 @@ module.exports = class Watcher extends CoreObject { } } - _setupBroccoliWatcherBuild() { - let heimdallNode; - - promiseFinally( - this.watcher.currentBuild.then(hash => { - heimdallNode = hash.graph.__heimdall__; - return hash; - }), - () => { - // guard against `build` rejecting - if (heimdallNode) { - // remove the heimdall subtree for this build so we don't leak. If - // BROCCOLI_VIZ=1 then we have already output the json in `verboseOutput`. - heimdallNode.remove(); - } + async _setupBroccoliWatcherBuild() { + try { + const hash = await this.watcher.currentBuild; + hash.graph.__heimdall__.remove(); + } catch (e) { + if (e !== null && typeof e === 'object' && e.isBuilderError === true) { + // e must be a builder error which we can safely ignore. + // + // Builder errors are expected failures which are handled and presented + // to the user else-where. To prevent double reporting faliures to the + // user, it is important that we ignore them here as this functions + // single responsibilty is to reset the himedall state after each build. + // + // exists to prune heimdall data after a build. Those errors are + // reported elsewhere. + // + } else { + // this error is unexpected, we must re-throw as we cannot be sure it + // has been handled else-where + throw e; } - ); + } } _totalTime(hash) { const sumNodes = (node, cb) => { let total = 0; - node.visitPreOrder(node => { + node.visitPreOrder((node) => { total += cb(node); }); return total; }; - return sumNodes(hash.graph.__heimdall__, node => node.stats.time.self); + return sumNodes(hash.graph.__heimdall__, (node) => node.stats.time.self); } didError(error) { @@ -109,9 +106,7 @@ module.exports = class Watcher extends CoreObject { didChange(results) { logger.info('didChange %o', results); - if (isExperimentEnabled('BROCCOLI_WATCHER')) { - results.totalTime = this._totalTime(results); - } + results.totalTime = this._totalTime(results); let totalTime = results.totalTime / 1e6; let message = chalk.green(`Build successful (${Math.round(totalTime)}ms)`); @@ -141,7 +136,7 @@ module.exports = class Watcher extends CoreObject { value: Number(totalTime), }); - if (isExperimentEnabled('BROCCOLI_WATCHER') && this.verbose) { + if (this.verbose) { printSlowTrees(results.graph.__heimdall__); } } @@ -162,16 +157,12 @@ module.exports = class Watcher extends CoreObject { } then() { - if (isExperimentEnabled('BROCCOLI_WATCHER')) { - return this.watcher.currentBuild.then.apply(this.watcher.currentBuild, arguments); - } - - return this.watcher.then.apply(this.watcher, arguments); + return this.watcher.currentBuild.then.apply(this.watcher.currentBuild, arguments); } on() { const args = arguments; - if (isExperimentEnabled('BROCCOLI_WATCHER') && args[0] === 'change' && !this.watchedDir) { + if (args[0] === 'change' && !this.watchedDir) { args[0] = 'buildSuccess'; } this.watcher.on.apply(this.watcher, args); @@ -179,7 +170,7 @@ module.exports = class Watcher extends CoreObject { off() { const args = arguments; - if (isExperimentEnabled('BROCCOLI_WATCHER') && args[0] === 'change' && !this.watchedDir) { + if (args[0] === 'change' && !this.watchedDir) { args[0] = 'buildSuccess'; } this.watcher.off.apply(this.watcher, args); diff --git a/lib/models/worker.js b/lib/models/worker.js new file mode 100644 index 0000000000..051bca2b89 --- /dev/null +++ b/lib/models/worker.js @@ -0,0 +1,9 @@ +'use strict'; + +const workerpool = require('workerpool'); +const gzipStats = require('./gzipStats'); + +// create worker and register public functions +workerpool.worker({ + gzipStats, +}); diff --git a/lib/tasks/addon-install.js b/lib/tasks/addon-install.js index 19d04f2474..3cf631bf01 100644 --- a/lib/tasks/addon-install.js +++ b/lib/tasks/addon-install.js @@ -4,7 +4,6 @@ const Task = require('../models/task'); const SilentError = require('silent-error'); const merge = require('ember-cli-lodash-subset').merge; const getPackageBaseName = require('../utilities/get-package-base-name'); -const Promise = require('rsvp').Promise; class AddonInstallTask extends Task { constructor(options) { @@ -17,7 +16,7 @@ class AddonInstallTask extends Task { run(options) { const chalk = require('chalk'); let ui = this.ui; - let packageNames = options.packages; + let packageNames = options.packages || []; let blueprintOptions = options.blueprintOptions || {}; let commandOptions = blueprintOptions; diff --git a/lib/tasks/bower-install.js b/lib/tasks/bower-install.js index 7d6a56c29d..e4f6a1e6b6 100644 --- a/lib/tasks/bower-install.js +++ b/lib/tasks/bower-install.js @@ -4,16 +4,14 @@ const fs = require('fs-extra'); const path = require('path'); const execa = require('../utilities/execa'); -const RSVP = require('rsvp'); +const util = require('util'); const SilentError = require('silent-error'); const Task = require('../models/task'); const formatPackageList = require('../utilities/format-package-list'); const logger = require('heimdalljs-logger')('ember-cli:tasks:bower-install'); -const Promise = RSVP.Promise; -const resolve = RSVP.denodeify(require('resolve')); -const writeJson = RSVP.denodeify(fs.writeJson); +const resolve = util.promisify(require('resolve')); const cliPath = path.resolve(`${__dirname}/../..`); @@ -34,14 +32,14 @@ class BowerInstallTask extends Task { } return this.resolveBower() - .catch(error => { + .catch((error) => { if (error.message.indexOf("Cannot find module 'bower'") === -1) { throw error; } return this.installBower().then(() => this.resolveBower()); }) - .then(bowerPath => this.importBower(bowerPath)); + .then((bowerPath) => this.importBower(bowerPath)); } installBower() { @@ -54,7 +52,7 @@ class BowerInstallTask extends Task { return execa('npm', ['install', 'bower@^1.3.12'], { cwd: cliPath }) .finally(() => ui.stopProgress()) - .catch(error => this.handleInstallBowerError(error)) + .catch((error) => this.handleInstallBowerError(error)) .then(() => ui.writeLine(chalk.green('npm: Installed bower'))); } @@ -98,7 +96,7 @@ class BowerInstallTask extends Task { logger.info('Creating "bower.json" for: %s at: %s', projectName, bowerJsonPath); - return writeJson(bowerJsonPath, { name: projectName }, { spaces: 2 }); + return fs.writeJson(bowerJsonPath, { name: projectName }, { spaces: 2 }); } // Options: Boolean verbose diff --git a/lib/tasks/build-watch.js b/lib/tasks/build-watch.js index d3e27550e2..c390088981 100644 --- a/lib/tasks/build-watch.js +++ b/lib/tasks/build-watch.js @@ -1,6 +1,7 @@ 'use strict'; const chalk = require('chalk'); +const path = require('path'); const Task = require('../models/task'); const Watcher = require('../models/watcher'); const Builder = require('../models/builder'); @@ -39,6 +40,7 @@ class BuildWatchTask extends Task { builder, analytics: this.analytics, options, + ignored: [path.resolve(this.project.root, options.outputPath)], }); await watcher; diff --git a/lib/tasks/build.js b/lib/tasks/build.js index fd7996a5e1..465e557008 100644 --- a/lib/tasks/build.js +++ b/lib/tasks/build.js @@ -6,12 +6,10 @@ const Builder = require('../models/builder'); module.exports = class BuildTask extends Task { // Options: String outputPath - run(options) { + async run(options) { let ui = this.ui; let analytics = this.analytics; - ui.startProgress(chalk.green('Building'), chalk.green('.')); - let builder = new Builder({ ui, outputPath: options.outputPath, @@ -19,42 +17,43 @@ module.exports = class BuildTask extends Task { project: this.project, }); - ui.writeLine(`Environment: ${options.environment}`); - - let annotation = { - type: 'initial', - reason: 'build', - primaryFile: null, - changedFiles: [], - }; - - return builder - .build(null, annotation) - .then(results => { - let totalTime = results.totalTime / 1e6; - - analytics.track({ - name: 'ember build', - message: `${totalTime}ms`, - }); - - /* - * We use the `rebuild` category in our analytics setup for both builds - * and rebuilds. This is a bit confusing, but the actual thing we - * delineate on in the reports is the `variable` value below. This is - * used both here and in `lib/models/watcher.js`. - */ - analytics.trackTiming({ - category: 'rebuild', - variable: 'build time', - label: 'broccoli build time', - value: parseInt(totalTime, 10), - }); - }) - .finally(() => { - ui.stopProgress(); - return builder.cleanup(); - }) - .then(() => ui.writeLine(chalk.green(`Built project successfully. Stored in "${options.outputPath}".`))); + try { + ui.startProgress(chalk.green('Building'), chalk.green('.')); + + ui.writeLine(`Environment: ${options.environment}`); + + let annotation = { + type: 'initial', + reason: 'build', + primaryFile: null, + changedFiles: [], + }; + + let results = await builder.build(null, annotation); + let totalTime = results.totalTime / 1e6; + + analytics.track({ + name: 'ember build', + message: `${totalTime}ms`, + }); + + /* + * We use the `rebuild` category in our analytics setup for both builds + * and rebuilds. This is a bit confusing, but the actual thing we + * delineate on in the reports is the `variable` value below. This is + * used both here and in `lib/models/watcher.js`. + */ + analytics.trackTiming({ + category: 'rebuild', + variable: 'build time', + label: 'broccoli build time', + value: parseInt(totalTime, 10), + }); + } finally { + ui.stopProgress(); + await builder.cleanup(); + } + + ui.writeLine(chalk.green(`Built project successfully. Stored in "${options.outputPath}".`)); } }; diff --git a/lib/tasks/create-and-step-into-directory.js b/lib/tasks/create-and-step-into-directory.js index ba2b820235..6908360b67 100644 --- a/lib/tasks/create-and-step-into-directory.js +++ b/lib/tasks/create-and-step-into-directory.js @@ -4,23 +4,50 @@ // this directory. const fs = require('fs-extra'); +const path = require('path'); const Task = require('../models/task'); const SilentError = require('silent-error'); +// used in order to infer the directory to use, if `--directory` was specified +// to `ember new`/`ember addon` it will **always** be used directly (without +// modification) +function chooseDirectoryName(projectName) { + let isScoped = projectName[0] === '@' && projectName.includes('/'); + + if (isScoped) { + let slashIndex = projectName.indexOf('/'); + let scopeName = projectName.substring(1, slashIndex); + let packageNameWithoutScope = projectName.substring(slashIndex + 1); + let pathParts = process.cwd().split(path.sep); + + let parentDirectoryContainsScopeName = pathParts.includes(scopeName); + + if (parentDirectoryContainsScopeName) { + return packageNameWithoutScope; + } else { + return `${scopeName}-${packageNameWithoutScope}`; + } + } else { + return projectName; + } +} + class CreateTask extends Task { // Options: String directoryName, Boolean: dryRun - warnDirectoryAlreadyExists() { - let message = `Directory '${this.directoryName}' already exists.`; + warnDirectoryAlreadyExists(directoryName) { + let message = `Directory '${directoryName}' already exists.`; return new SilentError(message); } async run(options) { - let directoryName = (this.directoryName = options.directoryName); + let directoryName = options.directoryName ? options.directoryName : chooseDirectoryName(options.projectName); + if (options.dryRun) { if (fs.existsSync(directoryName) && fs.readdirSync(directoryName).length) { - throw this.warnDirectoryAlreadyExists(); + throw this.warnDirectoryAlreadyExists(directoryName); } + return; } @@ -30,7 +57,7 @@ class CreateTask extends Task { if (err.code === 'EEXIST') { // Allow using directory if it is empty. if (fs.readdirSync(directoryName).length) { - throw this.warnDirectoryAlreadyExists(); + throw this.warnDirectoryAlreadyExists(directoryName); } } else { throw err; @@ -38,7 +65,8 @@ class CreateTask extends Task { } let cwd = process.cwd(); process.chdir(directoryName); - return { initialDirectory: cwd }; + + return { initialDirectory: cwd, projectDirectory: directoryName }; } } diff --git a/lib/tasks/generate-from-blueprint.js b/lib/tasks/generate-from-blueprint.js index ef6bb08689..2dff8a1428 100644 --- a/lib/tasks/generate-from-blueprint.js +++ b/lib/tasks/generate-from-blueprint.js @@ -1,10 +1,11 @@ 'use strict'; -const Promise = require('rsvp').Promise; const Blueprint = require('../models/blueprint'); const Task = require('../models/task'); const parseOptions = require('../utilities/parse-options'); const merge = require('ember-cli-lodash-subset').merge; +const logger = require('heimdalljs-logger')('ember-cli:generate-from-blueprint'); +const lintFix = require('../utilities/lint-fix'); class GenerateTask extends Task { constructor(options) { @@ -12,31 +13,39 @@ class GenerateTask extends Task { this.blueprintFunction = 'install'; } - run(options) { + async run(options) { + await this.createBlueprints(options); + + if (options.lintFix) { + try { + await lintFix.run(this.ui); + } catch (error) { + logger.error('Lint fix failed: %o', error); + } + } + } + + async createBlueprints(options) { let name = options.args[0]; - let isNotModuleUnification = !this.project.isModuleUnification(); let noAddonBlueprint = ['mixin', 'blueprint-test']; let mainBlueprint = this.lookupBlueprint(name, options.ignoreMissingMain); let testBlueprint = this.lookupBlueprint(`${name}-test`, true); // lookup custom addon blueprint - let addonBlueprint; - if (isNotModuleUnification) { - addonBlueprint = this.lookupBlueprint(`${name}-addon`, true); + let addonBlueprint = this.lookupBlueprint(`${name}-addon`, true); - // otherwise, use default addon-import - if (noAddonBlueprint.indexOf(name) < 0 && !addonBlueprint && options.args[1]) { - let mainBlueprintSupportsAddon = mainBlueprint && mainBlueprint.supportsAddon() && isNotModuleUnification; + // otherwise, use default addon-import + if (noAddonBlueprint.indexOf(name) < 0 && !addonBlueprint && options.args[1]) { + let mainBlueprintSupportsAddon = mainBlueprint && mainBlueprint.supportsAddon(); - if (mainBlueprintSupportsAddon) { - addonBlueprint = this.lookupBlueprint('addon-import', true); - } + if (mainBlueprintSupportsAddon) { + addonBlueprint = this.lookupBlueprint('addon-import', true); } } if (options.ignoreMissingMain && !mainBlueprint) { - return Promise.resolve(); + return; } if (options.dummy) { @@ -66,40 +75,34 @@ class GenerateTask extends Task { blueprintOptions = merge(blueprintOptions, options || {}); - return mainBlueprint[this.blueprintFunction](blueprintOptions) - .then(() => { - if (!testBlueprint) { - return; - } - - if (testBlueprint.locals === Blueprint.prototype.locals) { - testBlueprint.locals = function(options) { - return mainBlueprint.locals(options); - }; - } - - let testBlueprintOptions = merge({}, blueprintOptions, { installingTest: true }); - - return testBlueprint[this.blueprintFunction](testBlueprintOptions); - }) - .then(() => { - if (!addonBlueprint || name.match(/-addon/)) { - return; - } - if (!this.project.isEmberCLIAddon() && blueprintOptions.inRepoAddon === null) { - return; - } - - if (addonBlueprint.locals === Blueprint.prototype.locals) { - addonBlueprint.locals = function(options) { - return mainBlueprint.locals(options); - }; - } - - let addonBlueprintOptions = merge({}, blueprintOptions, { installingAddon: true }); - - return addonBlueprint[this.blueprintFunction](addonBlueprintOptions); - }); + await mainBlueprint[this.blueprintFunction](blueprintOptions); + if (testBlueprint) { + if (testBlueprint.locals === Blueprint.prototype.locals) { + testBlueprint.locals = function (options) { + return mainBlueprint.locals(options); + }; + } + + let testBlueprintOptions = merge({}, blueprintOptions, { installingTest: true }); + await testBlueprint[this.blueprintFunction](testBlueprintOptions); + } + + if (!addonBlueprint || name.match(/-addon/)) { + return; + } + if (!this.project.isEmberCLIAddon() && blueprintOptions.inRepoAddon === null) { + return; + } + + if (addonBlueprint.locals === Blueprint.prototype.locals) { + addonBlueprint.locals = function (options) { + return mainBlueprint.locals(options); + }; + } + + let addonBlueprintOptions = merge({}, blueprintOptions, { installingAddon: true }); + + return addonBlueprint[this.blueprintFunction](addonBlueprintOptions); } lookupBlueprint(name, ignoreMissing) { diff --git a/lib/tasks/install-blueprint.js b/lib/tasks/install-blueprint.js index b07a427920..2e5cbc7689 100644 --- a/lib/tasks/install-blueprint.js +++ b/lib/tasks/install-blueprint.js @@ -3,14 +3,14 @@ const fs = require('fs-extra'); const Blueprint = require('../models/blueprint'); const Task = require('../models/task'); -const RSVP = require('rsvp'); -const isGitRepo = require('is-git-url'); +const util = require('util'); const temp = require('temp'); const path = require('path'); const merge = require('ember-cli-lodash-subset').merge; const execa = require('../utilities/execa'); const SilentError = require('silent-error'); const npa = require('npm-package-arg'); +const lintFix = require('../utilities/lint-fix'); const logger = require('heimdalljs-logger')('ember-cli:tasks:install-blueprint'); @@ -19,10 +19,10 @@ const NOT_FOUND_REGEXP = /npm ERR! 404 {2}'(\S+)' is not in the npm registry/; // Automatically track and cleanup temp files at exit temp.track(); -let mkdirTemp = RSVP.denodeify(temp.mkdir); +let mkdirTemp = util.promisify(temp.mkdir); class InstallBlueprintTask extends Task { - run(options) { + async run(options) { let cwd = process.cwd(); let name = options.rawName; let blueprintOption = options.blueprint; @@ -44,86 +44,133 @@ class InstallBlueprintTask extends Task { installOptions = merge(installOptions, options || {}); - return this._resolveBlueprint(blueprintOption).then(blueprint => { - logger.info(`Installing blueprint into "${target}" ...`); - return blueprint.install(installOptions); - }); + let blueprint = await this._resolveBlueprint(blueprintOption); + logger.info(`Installing blueprint into "${target}" ...`); + await blueprint.install(installOptions); + + if (options.lintFix) { + try { + await lintFix.run(this.ui); + } catch (error) { + logger.error('Lint fix failed: %o', error); + } + } } - _resolveBlueprint(name) { + async _resolveBlueprint(name) { name = name || 'app'; logger.info(`Resolving blueprint "${name}" ...`); - if (!isGitRepo(name)) { - return this._lookupBlueprint(name).catch(error => this._handleLookupFailure(name, error)); + let blueprint; + try { + blueprint = await this._lookupLocalBlueprint(name); + } catch (error) { + blueprint = await this._handleLocalLookupFailure(name, error); } - return this._createTempFolder().then(pathName => - this._gitClone(name, pathName) - .then(() => this._npmInstall(pathName)) - .then(() => this._loadBlueprintFromPath(pathName)) - ); + return blueprint; } - _lookupBlueprint(name) { - logger.info(`Looking up blueprint "${name}" ...`); - return RSVP.resolve().then(() => - Blueprint.lookup(name, { - paths: this.project.blueprintLookupPaths(), - }) - ); + async _lookupLocalBlueprint(name) { + logger.info(`Looking up blueprint "${name}" locally...`); + return Blueprint.lookup(name, { + paths: this.project.blueprintLookupPaths(), + }); } - _handleLookupFailure(name, error) { - logger.info(`Blueprint lookup for "${name}" failed`); + _handleLocalLookupFailure(name, error) { + logger.info(`Local blueprint lookup for "${name}" failed`); - let parsed; try { - parsed = npa(name); + npa(name); } catch (err) { - logger.info(`"${name} is not a valid npm package name -> rethrowing original error`); + logger.info(`"${name} is not a valid npm package specifier -> rethrowing original error`); throw error; } - logger.info(`"${name} is a valid npm package name -> trying npm`); - return this._tryNpmBlueprint(parsed.name, parsed.fetchSpec); + logger.info(`"${name} is a valid npm package specifier -> trying npm`); + return this._tryRemoteBlueprint(name); } - _tryNpmBlueprint(nameWithoutVersion, version) { - return this._createTempFolder() - .then(pathName => - this._npmInstallModule(nameWithoutVersion, version, pathName).catch(error => - this._handleNpmInstallModuleError(error) - ) - ) - .then(modulePath => { - this._validateNpmModule(modulePath, nameWithoutVersion); - return this._loadBlueprintFromPath(modulePath); - }); + async _tryRemoteBlueprint(name) { + let tmpDir = await this._createTempFolder(); + await this._createEmptyPackageJSON(tmpDir, name); + + try { + await this._npmInstall(tmpDir, name); + } catch (error) { + this._handleNpmInstallModuleError(error); // re-throws + } + + let packageName = await this._readSingleDependencyFromPackageJSON(tmpDir); + let blueprintPath = path.resolve(tmpDir, 'node_modules', packageName); + this._validateNpmModule(blueprintPath, packageName); + + return this._loadBlueprintFromPath(blueprintPath); } _createTempFolder() { return mkdirTemp('ember-cli'); } - _gitClone(source, destination) { - logger.info(`Cloning from git (${source}) into "${destination}" ...`); - return execa('git', ['clone', source, destination]); + _resolvePackageJSON(directoryPath) { + return path.resolve(directoryPath, 'package.json'); } - _npmInstall(cwd) { - logger.info(`Running "npm install" in "${cwd}" ...`); + async _createEmptyPackageJSON(tempProjectPath, blueprintName) { + return fs.writeFile( + this._resolvePackageJSON(tempProjectPath), + JSON.stringify({ + name: 'ember-cli-blueprint-install-wrapper', + version: '0.0.0', + description: `This is a transient package used to fetch this blueprint: ${blueprintName}`, + license: 'UNLICENSED', + private: true, + dependencies: {}, + }) + ); + } - this._copyNpmrc(cwd); - return execa('npm', ['install'], { cwd }); + async _readSingleDependencyFromPackageJSON(tempProjectPath) { + let packageJSON = JSON.parse(await fs.readFile(this._resolvePackageJSON(tempProjectPath), 'utf8')); + + if (!packageJSON.dependencies || typeof packageJSON.dependencies !== 'object') { + throw new SilentError(`The transient package.json at '${tempProjectPath}' is missing 'dependencies'.`); + } + + let dependencyNames = Object.keys(packageJSON.dependencies); + + if (dependencyNames.length !== 1) { + throw new SilentError( + `The transient package.json at '${tempProjectPath}' has more than one or no entries in 'dependencies'.` + ); + } + + return dependencyNames[0]; } - _npmInstallModule(nameWithoutVersion, version, cwd) { - let module = `${nameWithoutVersion}@${version}`; - logger.info(`Running "npm install ${module}" in "${cwd}" ...`); + async _createPackageJSON(tempProjectPath, descriptor) { + let packageName = descriptor.name || `ember-cli-blueprint-${Date.now()}`; + let specifier = descriptor.saveSpec || descriptor.fetchSpec; + + await fs.writeFile( + this._resolvePackageJSON(tempProjectPath), + JSON.stringify({ + dependencies: { + [packageName]: specifier, + }, + }) + ); + + return packageName; + } + + _npmInstall(cwd, name) { + logger.info(`Running "npm install" for "${name}" in "${cwd}" ...`); this._copyNpmrc(cwd); - return execa('npm', ['install', module], { cwd }).then(() => path.join(cwd, 'node_modules', nameWithoutVersion)); + + return execa('npm', ['install', '--no-package-lock', '--save', name], { cwd }); } _handleNpmInstallModuleError(error) { @@ -138,15 +185,15 @@ class InstallBlueprintTask extends Task { _validateNpmModule(modulePath, packageName) { logger.info(`Checking for "ember-blueprint" keyword in "${packageName}" module ...`); - let pkg = require(path.join(modulePath, 'package.json')); + let pkg = require(this._resolvePackageJSON(modulePath)); if (!pkg || !pkg.keywords || pkg.keywords.indexOf('ember-blueprint') === -1) { throw new SilentError(`The package '${packageName}' is not a valid Ember CLI blueprint.`); } } - _loadBlueprintFromPath(path) { + async _loadBlueprintFromPath(path) { logger.info(`Loading blueprint from "${path}" ...`); - return RSVP.resolve().then(() => Blueprint.load(path)); + return Blueprint.load(path); } /* diff --git a/lib/tasks/npm-install.js b/lib/tasks/npm-install.js index f4978ea197..6b98374909 100644 --- a/lib/tasks/npm-install.js +++ b/lib/tasks/npm-install.js @@ -5,7 +5,6 @@ const path = require('path'); const fs = require('fs'); const NpmTask = require('./npm-task'); const formatPackageList = require('../utilities/format-package-list'); -const Promise = require('rsvp').Promise; class NpmInstallTask extends NpmTask { constructor(options) { diff --git a/lib/tasks/npm-task.js b/lib/tasks/npm-task.js index 1dbce5414d..2862c43ba7 100644 --- a/lib/tasks/npm-task.js +++ b/lib/tasks/npm-task.js @@ -42,56 +42,72 @@ class NpmTask extends Task { return isYarnProject(this.project.root); } - checkYarn() { - return this.yarn(['--version']) - .then(result => { - let version = result.stdout; + async checkYarn() { + try { + let result = await this.yarn(['--version']); + let version = result.stdout; + + if (semver.gte(version, '2.0.0')) { + logger.warn('yarn --version: %s', version); + this.ui.writeWarnLine(`Yarn v2 is not fully supported. Proceeding with yarn: ${version}`); + } else { logger.info('yarn --version: %s', version); - }) - .catch(error => { - logger.error('yarn --version failed: %s', error); - throw error; - }); + } + + this.useYarn = true; + + return { yarnVersion: version }; + } catch (error) { + logger.error('yarn --version failed: %s', error); + + if (error.code === 'ENOENT') { + throw new SilentError( + 'Ember CLI is now using yarn, but was not able to find it.\n' + + 'Please install yarn using the instructions at https://classic.yarnpkg.com/en/docs/install' + ); + } + + throw error; + } } - checkNpmVersion() { - return this.npm(['--version']) - .then(result => { - let version = result.stdout; - logger.info('npm --version: %s', version); - - let ok = semver.satisfies(version, this.versionConstraints); - if (!ok) { - logger.warn('npm --version is outside of version constraint: %s', this.versionConstraints); - - let below = semver.ltr(version, this.versionConstraints); - if (below) { - throw new SilentError( - 'Ember CLI is now using the global npm, but your npm version is outdated.\n' + - 'Please update your global npm version by running: npm install -g npm' - ); - } - - this.ui.writeWarnLine( - 'Ember CLI is using the global npm, but your npm version has not yet been ' + - 'verified to work with the current Ember CLI release.' - ); - } + async checkNpmVersion() { + try { + let result = await this.npm(['--version']); + let version = result.stdout; + logger.info('npm --version: %s', version); - return { npmVersion: version }; - }) - .catch(error => { - logger.error('npm --version failed: %s', error); + let ok = semver.satisfies(version, this.versionConstraints); + if (!ok) { + logger.warn('npm --version is outside of version constraint: %s', this.versionConstraints); - if (error.code === 'ENOENT') { + let below = semver.ltr(version, this.versionConstraints); + if (below) { throw new SilentError( - 'Ember CLI is now using the global npm, but was not able to find it.\n' + - 'Please install npm using the instructions at https://github.com/npm/npm' + 'Ember CLI is now using the global npm, but your npm version is outdated.\n' + + 'Please update your global npm version by running: npm install -g npm' ); } - throw error; - }); + this.ui.writeWarnLine( + 'Ember CLI is using the global npm, but your npm version has not yet been ' + + 'verified to work with the current Ember CLI release.' + ); + } + + return { npmVersion: version }; + } catch (error) { + logger.error('npm --version failed: %s', error); + + if (error.code === 'ENOENT') { + throw new SilentError( + 'Ember CLI is now using the global npm, but was not able to find it.\n' + + 'Please install npm using the instructions at https://github.com/npm/npm' + ); + } + + throw error; + } } /** @@ -108,17 +124,10 @@ class NpmTask extends Task { * @method findPackageManager * @return {Promise} */ - findPackageManager() { + async findPackageManager() { if (this.useYarn === true) { logger.info('yarn requested -> trying yarn'); - - return this.checkYarn().catch(error => { - if (error.code === 'ENOENT') { - throw new SilentError('Yarn could not be found.'); - } - - throw error; - }); + return this.checkYarn(); } if (this.useYarn === false) { @@ -132,56 +141,55 @@ class NpmTask extends Task { } logger.info('yarn.lock found -> trying yarn'); - return this.checkYarn() - .then(() => { - logger.info('yarn found -> using yarn'); - this.useYarn = true; - }) - .catch(() => { - logger.info('yarn not found -> using npm'); - return this.checkNpmVersion(); - }); + try { + const yarnResult = await this.checkYarn(); + logger.info('yarn found -> using yarn'); + return yarnResult; + } catch (_err) { + logger.info('yarn not found -> using npm'); + return this.checkNpmVersion(); + } } - run(options) { + async run(options) { this.useYarn = options.useYarn; - return this.findPackageManager().then(result => { - let ui = this.ui; - let startMessage = this.formatStartMessage(options.packages); - let completeMessage = this.formatCompleteMessage(options.packages); + let result = await this.findPackageManager(); + let ui = this.ui; + let startMessage = this.formatStartMessage(options.packages); + let completeMessage = this.formatCompleteMessage(options.packages); - const prependEmoji = require('../../lib/utilities/prepend-emoji'); + const prependEmoji = require('../../lib/utilities/prepend-emoji'); - ui.writeLine(''); - ui.writeLine(prependEmoji('🚧', 'Installing packages... This might take a couple of minutes.')); - ui.startProgress(chalk.green(startMessage)); + ui.writeLine(''); + ui.writeLine(prependEmoji('🚧', 'Installing packages... This might take a couple of minutes.')); + ui.startProgress(chalk.green(startMessage)); - let promise; - if (this.useYarn) { - let args = this.toYarnArgs(this.command, options); - promise = this.yarn(args); - } else { - let args = this.toNpmArgs(this.command, options); - promise = this.npm(args); - - // as of 2018-10-09 npm 5 and 6 _break_ the hierarchy of `node_modules` - // after a `npm install foo` (deletes files/folders other than - // what was directly installed) in some circumstances, see: - // - // * https://github.com/npm/npm/issues/16853 - // * https://github.com/npm/npm/issues/17379 - // - // this ensures that we run a full `npm install` **after** any `npm - // install foo` runs to ensure that we have a fully functional - // node_modules hierarchy - if (result.npmVersion && semver.gte(result.npmVersion, '5.0.0')) { - promise = promise.then(() => this.npm(['install'])); - } + let promise; + if (this.useYarn) { + this.yarnVersion = result.yarnVersion; + let args = this.toYarnArgs(this.command, options); + promise = this.yarn(args); + } else { + let args = this.toNpmArgs(this.command, options); + promise = this.npm(args); + + // as of 2018-10-09 npm 5 and 6 _break_ the hierarchy of `node_modules` + // after a `npm install foo` (deletes files/folders other than + // what was directly installed) in some circumstances, see: + // + // * https://github.com/npm/npm/issues/16853 + // * https://github.com/npm/npm/issues/17379 + // + // this ensures that we run a full `npm install` **after** any `npm + // install foo` runs to ensure that we have a fully functional + // node_modules hierarchy + if (result.npmVersion && semver.lt(result.npmVersion, '5.7.1')) { + promise = promise.then(() => this.npm(['install'])); } + } - return promise.finally(() => ui.stopProgress()).then(() => ui.writeLine(chalk.green(completeMessage))); - }); + return promise.finally(() => ui.stopProgress()).then(() => ui.writeLine(chalk.green(completeMessage))); } toNpmArgs(command, options) { @@ -251,7 +259,12 @@ class NpmTask extends Task { args = args.concat(options.packages); } - args.push('--non-interactive'); + // Yarn v2 defaults to non-interactive + // with an optional -i flag + + if (semver.lt(this.yarnVersion, '2.0.0')) { + args.push('--non-interactive'); + } return args; } diff --git a/lib/tasks/serve.js b/lib/tasks/serve.js index 9fe9bfdc1f..6f65eaed6c 100644 --- a/lib/tasks/serve.js +++ b/lib/tasks/serve.js @@ -3,16 +3,16 @@ const fs = require('fs'); const path = require('path'); const ExpressServer = require('./server/express-server'); -const RSVP = require('rsvp'); const Task = require('../models/task'); const Watcher = require('../models/watcher'); const ServerWatcher = require('../models/server-watcher'); const Builder = require('../models/builder'); const SilentError = require('silent-error'); const serveURL = require('../utilities/get-serve-url'); +const pDefer = require('p-defer'); function mockWatcher(distDir) { - let watcher = RSVP.Promise.resolve({ directory: distDir }); + let watcher = Promise.resolve({ directory: distDir }); watcher.on = () => {}; return watcher; } @@ -31,7 +31,7 @@ class ServeTask extends Task { this._builder = null; } - run(options) { + async run(options) { let hasBuild = !!options.path; if (hasBuild) { @@ -61,6 +61,7 @@ class ServeTask extends Task { analytics: this.analytics, options, serving: true, + ignored: [path.resolve(this.project.root, options.outputPath)], }); let serverRoot = './server'; @@ -86,14 +87,15 @@ class ServeTask extends Task { }); /* hang until the user exits */ - this._runDeferred = RSVP.defer(); + this._runDeferred = pDefer(); - return expressServer.start(options).then(() => { - if (hasBuild) { - this.ui.writeLine(`– Serving on ${serveURL(options, this.project)}`); - } - return this._runDeferred.promise; - }); + await expressServer.start(options); + + if (hasBuild) { + this.ui.writeLine(`– Serving on ${serveURL(options, this.project)}`); + } + + return this._runDeferred.promise; } /** @@ -102,8 +104,9 @@ class ServeTask extends Task { * @private * @method onInterrupt */ - onInterrupt() { - return this._builder.cleanup().then(() => this._runDeferred.resolve()); + async onInterrupt() { + await this._builder.cleanup(); + return this._runDeferred.resolve(); } } diff --git a/lib/tasks/server/express-server.js b/lib/tasks/server/express-server.js index 2642f4c0c9..148343e318 100644 --- a/lib/tasks/server/express-server.js +++ b/lib/tasks/server/express-server.js @@ -6,7 +6,6 @@ const chalk = require('chalk'); const fs = require('fs'); const debounce = require('ember-cli-lodash-subset').debounce; const mapSeries = require('promise-map-series'); -const Promise = require('rsvp').Promise; const Task = require('../../models/task'); const SilentError = require('silent-error'); const LiveReloadServer = require('./livereload-server'); @@ -20,7 +19,7 @@ class ExpressServerTask extends Task { this.https = this.https || require('https'); let serverRestartDelayTime = this.serverRestartDelayTime || 100; - this.scheduleServerRestart = debounce(function() { + this.scheduleServerRestart = debounce(function () { this.restartHttpServer(); }, serverRestartDelayTime); } @@ -52,7 +51,7 @@ class ExpressServerTask extends Task { // when we need to restart. this.sockets = {}; this.nextSocketId = 0; - this.httpServer.on('connection', socket => { + this.httpServer.on('connection', (socket) => { let socketId = this.nextSocketId++; this.sockets[socketId] = socket; @@ -103,7 +102,7 @@ class ExpressServerTask extends Task { return mapSeries( this.project.addons, - function(addon) { + function (addon) { if (addon.serverMiddleware) { return addon.serverMiddleware({ app: this.app, @@ -117,18 +116,24 @@ class ExpressServerTask extends Task { processAppMiddlewares(options) { if (this.project.has(this.serverRoot)) { - let server = this.project.require(this.serverRoot); + try { + let server = this.project.require(this.serverRoot); - if (typeof server !== 'function') { - throw new TypeError('ember-cli expected ./server/index.js to be the entry for your mock or proxy server'); - } + if (typeof server !== 'function') { + throw new TypeError('ember-cli expected ./server/index.js to be the entry for your mock or proxy server'); + } - if (server.length === 3) { - // express app is function of form req, res, next - return this.app.use(server); - } + if (server.length === 3) { + // express app is function of form req, res, next + return this.app.use(server); + } - return server(this.app, options); + return server(this.app, options); + } catch (e) { + if (e.code !== 'MODULE_NOT_FOUND') { + throw e; + } + } } } @@ -155,29 +160,28 @@ class ExpressServerTask extends Task { } restartHttpServer() { - if (!this.serverRestartPromise) { - this.serverRestartPromise = this.stopHttpServer() - .then(() => { - this.invalidateCache(this.serverRoot); - return this.startHttpServer(); - }) - .then(() => { - this.emit('restart'); - this.ui.writeLine(''); - this.ui.writeLine(chalk.green('Server restarted.')); - this.ui.writeLine(''); - }) - .catch(err => { - this.ui.writeError(err); - }) - .finally(() => { - this.serverRestartPromise = null; - }); - - return this.serverRestartPromise; - } else { + if (this.serverRestartPromise) { return this.serverRestartPromise.then(() => this.restartHttpServer()); } + + this.serverRestartPromise = (async () => { + try { + await this.stopHttpServer(); + this.invalidateCache(this.serverRoot); + await this.startHttpServer(); + + this.emit('restart'); + this.ui.writeLine(''); + this.ui.writeLine(chalk.green('Server restarted.')); + this.ui.writeLine(''); + } catch (err) { + this.ui.writeError(err); + } finally { + this.serverRestartPromise = null; + } + })(); + + return this.serverRestartPromise; } stopHttpServer() { @@ -185,7 +189,7 @@ class ExpressServerTask extends Task { if (!this.httpServer) { return resolve(); } - this.httpServer.close(err => { + this.httpServer.close((err) => { if (err) { reject(err); return; @@ -202,7 +206,7 @@ class ExpressServerTask extends Task { }); } - startHttpServer() { + async startHttpServer() { this.app = this.express(); const compression = require('compression'); this.app.use( @@ -250,18 +254,16 @@ class ExpressServerTask extends Task { baseURL: config.baseURL || '/', }); - return Promise.resolve() - .then(() => liveReloadServer.setupMiddleware(this.startOptions)) - .then(() => this.processAppMiddlewares(middlewareOptions)) - .then(() => this.processAddonMiddlewares(middlewareOptions)) - .then(() => - this.listen(options.port, options.host).catch(() => { - throw new SilentError( - `Could not serve on http://${this.displayHost(options.host)}:${options.port}. ` + - `It is either in use or you do not have permission.` - ); - }) + await liveReloadServer.setupMiddleware(this.startOptions); + await this.processAppMiddlewares(middlewareOptions); + await this.processAddonMiddlewares(middlewareOptions); + + return this.listen(options.port, options.host).catch(() => { + throw new SilentError( + `Could not serve on http://${this.displayHost(options.host)}:${options.port}. ` + + `It is either in use or you do not have permission.` ); + }); } invalidateCache(serverRoot) { diff --git a/lib/tasks/server/livereload-server.js b/lib/tasks/server/livereload-server.js index cc2eabc923..8eb2c36bf6 100644 --- a/lib/tasks/server/livereload-server.js +++ b/lib/tasks/server/livereload-server.js @@ -6,7 +6,6 @@ const path = require('path'); const FSTree = require('fs-tree-diff'); const logger = require('heimdalljs-logger')('ember-cli:live-reload:'); const fs = require('fs'); -const Promise = require('rsvp').Promise; const cleanBaseUrl = require('clean-base-url'); const isLiveReloadRequest = require('../../utilities/is-live-reload-request'); @@ -70,7 +69,7 @@ module.exports = class LiveReloadServer { if (options.liveReload) { if (options.liveReloadPort && options.port !== options.liveReloadPort) { - return this.createServerforCustomPort(options, Server).catch(error => { + return this.createServerforCustomPort(options, Server).catch((error) => { if (error !== null && typeof error === 'object' && error.code === 'EADDRINUSE') { let url = `http${options.ssl ? 's' : ''}://${this.displayHost(options.liveReloadHost)}:${ options.liveReloadPort @@ -95,7 +94,7 @@ module.exports = class LiveReloadServer { // Reload on file changes this.watcher.on( 'change', - function() { + function () { try { this.didChange.apply(this, arguments); } catch (e) { @@ -142,7 +141,7 @@ module.exports = class LiveReloadServer { createServerforCustomPort(options, Server) { let instance; - Server.prototype.error = function() { + Server.prototype.error = function () { instance.error.apply(instance, arguments); }; let serverOptions = { @@ -185,7 +184,7 @@ module.exports = class LiveReloadServer { if (this.project.liveReloadFilterPatterns.length > 0) { let filePath = path.relative(this.project.root, options.filePath || ''); - result = this.project.liveReloadFilterPatterns.every(pattern => pattern.test(filePath) === false); + result = this.project.liveReloadFilterPatterns.every((pattern) => pattern.test(filePath) === false); if (result === false) { this.writeSkipBanner(filePath); diff --git a/lib/tasks/server/middleware/broccoli-watcher/index.js b/lib/tasks/server/middleware/broccoli-watcher/index.js index 3f474ff650..03669a9930 100644 --- a/lib/tasks/server/middleware/broccoli-watcher/index.js +++ b/lib/tasks/server/middleware/broccoli-watcher/index.js @@ -46,7 +46,7 @@ class WatcherAddon { // Find asset and set response headers, if no file has been found, reset url for proxy stuff // that comes afterwards - middleware(req, res, err => { + middleware(req, res, (err) => { req.url = oldURL; if (err) { logger.error('err', err); diff --git a/lib/tasks/server/middleware/history-support/index.js b/lib/tasks/server/middleware/history-support/index.js index 3426c4fd95..0635fa461a 100644 --- a/lib/tasks/server/middleware/history-support/index.js +++ b/lib/tasks/server/middleware/history-support/index.js @@ -2,7 +2,6 @@ const path = require('path'); const fs = require('fs'); -const promiseFinally = require('promise.prototype.finally'); const cleanBaseURL = require('clean-base-url'); class HistorySupportAddon { @@ -48,11 +47,22 @@ class HistorySupportAddon { let baseURL = options.rootURL === '' ? '/' : cleanBaseURL(options.rootURL || options.baseURL); - app.use((req, res, next) => { - const results = watcher.then(results => { + app.use(async (req, _, next) => { + try { + let results; + try { + results = await watcher; + } catch (e) { + // This means there was a build error, so we won't actually be serving + // index.html, and we have nothing to do. We have to catch it here, + // though, or it will go uncaught and cause the process to exit. + return; + } + if (this.shouldHandleRequest(req, options)) { let assetPath = req.path.slice(baseURL.length); let isFile = false; + try { isFile = fs.statSync(path.join(results.directory, assetPath)).isFile(); } catch (err) { @@ -62,9 +72,9 @@ class HistorySupportAddon { req.serveUrl = `${baseURL}index.html`; } } - }); - - promiseFinally(Promise.resolve(results), next); + } finally { + next(); + } }); } diff --git a/lib/tasks/server/middleware/history-support/package.json b/lib/tasks/server/middleware/history-support/package.json index 953a797f7d..ad739c5053 100644 --- a/lib/tasks/server/middleware/history-support/package.json +++ b/lib/tasks/server/middleware/history-support/package.json @@ -7,7 +7,6 @@ "before": "broccoli-watcher" }, "dependencies": { - "clean-base-url": "*", - "promise.prototype.finally": "*" + "clean-base-url": "*" } } diff --git a/lib/tasks/server/middleware/proxy-server/index.js b/lib/tasks/server/middleware/proxy-server/index.js index 75dddc3a12..14d6f4ebe7 100644 --- a/lib/tasks/server/middleware/proxy-server/index.js +++ b/lib/tasks/server/middleware/proxy-server/index.js @@ -26,7 +26,7 @@ class ProxyServerAddon { timeout: options.proxyInTimeout, }); - proxy.on('error', e => { + proxy.on('error', (e) => { options.ui.writeLine(`Error proxying to ${options.proxy}`); options.ui.writeError(e); }); @@ -47,6 +47,7 @@ class ProxyServerAddon { handleProxiedRequest({ req, socket, head, options, proxy }) { if (!isLiveReloadRequest(req.url, options.liveReloadPrefix)) { options.ui.writeLine(`Proxying websocket to ${req.url}`); + socket.on('error', (e) => proxy.emit('error', e)); proxy.ws(req, socket, head); } } diff --git a/lib/tasks/server/middleware/tests-server/index.js b/lib/tasks/server/middleware/tests-server/index.js index aee289b803..5a87c6a848 100644 --- a/lib/tasks/server/middleware/tests-server/index.js +++ b/lib/tasks/server/middleware/tests-server/index.js @@ -4,9 +4,8 @@ const cleanBaseURL = require('clean-base-url'); const path = require('path'); const fs = require('fs'); const logger = require('heimdalljs-logger')('ember-cli:test-server'); -const promiseFinally = require('promise.prototype.finally'); -class TestsServerAddon { +module.exports = class TestsServerAddon { /** * This addon is used to serve the QUnit or Mocha test runner * at `baseURL + '/tests'`. @@ -27,42 +26,52 @@ class TestsServerAddon { let baseURL = options.rootURL === '' ? '/' : cleanBaseURL(options.rootURL || options.baseURL); let testsPath = `${baseURL}tests`; - app.use((req, res, next) => { - const results = watcher.then(results => { - let acceptHeaders = req.headers.accept || []; - let hasHTMLHeader = acceptHeaders.indexOf('text/html') !== -1; - let hasWildcardHeader = acceptHeaders.indexOf('*/*') !== -1; + app.use(async (req, _, next) => { + let results; + let watcherSuccess; - let isForTests = req.path.indexOf(testsPath) === 0; + try { + results = await watcher; + watcherSuccess = true; + } catch (e) { + watcherSuccess = false; + // the build has failed, the middleware can safely be skipped. + // build error reporting is handled by: + // 1. watcher production stderr + // 2. watcher-middleware serving the error page + } - logger.info('isForTests: %o', isForTests); + if (watcherSuccess) { + rewriteRequestUrlIfBasePageIsNeeded(req, testsPath, baseURL, results.directory); + } - if (isForTests && (hasHTMLHeader || hasWildcardHeader) && req.method === 'GET') { - let assetPath = req.path.slice(baseURL.length); - let filePath = path.join(results.directory, assetPath); + next(); - if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) { - // N.B., `baseURL` will end with a slash as it went through `cleanBaseURL` - let newURL = `${baseURL}tests/index.html`; + if (config.finally) { + config.finally(); + } + }); + } +}; - logger.info( - 'url: %s resolved to path: %s which is not a file. Assuming %s instead', - req.path, - filePath, - newURL - ); - req.url = newURL; - } - } - }); +function rewriteRequestUrlIfBasePageIsNeeded(req, testsPath, baseURL, directory) { + let acceptHeaders = req.headers.accept || []; + let hasHTMLHeader = acceptHeaders.indexOf('text/html') !== -1; + let hasWildcardHeader = acceptHeaders.indexOf('*/*') !== -1; - promiseFinally(promiseFinally(Promise.resolve(results), next), () => { - if (config.finally) { - config.finally(); - } - }); - }); + let isForTests = req.path.indexOf(testsPath) === 0; + + logger.info('isForTests: %o', isForTests); + + if (isForTests && (hasHTMLHeader || hasWildcardHeader) && req.method === 'GET') { + let assetPath = req.path.slice(baseURL.length); + let filePath = path.join(directory, assetPath); + + if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) { + // N.B., `baseURL` will end with a slash as it went through `cleanBaseURL` + let newURL = `${baseURL}tests/index.html`; + logger.info('url: %s resolved to path: %s which is not a file. Assuming %s instead', req.path, filePath, newURL); + req.url = newURL; + } } } - -module.exports = TestsServerAddon; diff --git a/lib/tasks/server/middleware/tests-server/package.json b/lib/tasks/server/middleware/tests-server/package.json index e6c5de51fa..258449ef43 100644 --- a/lib/tasks/server/middleware/tests-server/package.json +++ b/lib/tasks/server/middleware/tests-server/package.json @@ -8,7 +8,6 @@ }, "dependencies": { "clean-base-url": "*", - "heimdalljs-logger": "*", - "promise.prototype.finally": "*" + "heimdalljs-logger": "*" } } diff --git a/lib/tasks/test-server.js b/lib/tasks/test-server.js index b72e957714..2dee6e4b96 100644 --- a/lib/tasks/test-server.js +++ b/lib/tasks/test-server.js @@ -1,7 +1,6 @@ 'use strict'; const TestTask = require('./test'); -const Promise = require('rsvp').Promise; const chalk = require('chalk'); const SilentError = require('silent-error'); diff --git a/lib/tasks/test.js b/lib/tasks/test.js index 41f50e0303..95f623f62b 100644 --- a/lib/tasks/test.js +++ b/lib/tasks/test.js @@ -27,12 +27,14 @@ class TestTask extends Task { }); } - addonMiddlewares() { + addonMiddlewares(options) { this.project.initializeAddons(); return this.project.addons.reduce((addons, addon) => { if (addon.testemMiddleware) { - addons.push(addon.testemMiddleware.bind(addon)); + addons.push(function () { + addon.testemMiddleware(...arguments, options); + }); } return addons; @@ -54,7 +56,7 @@ class TestTask extends Task { port: options.port, debug: options.testemDebug, reporter: options.reporter, - middleware: this.addonMiddlewares(), + middleware: this.addonMiddlewares(options), launch: options.launch, file: options.configFile, /* eslint-disable camelcase */ @@ -67,7 +69,6 @@ class TestTask extends Task { transformed.key = options.sslKey; transformed.cert = options.sslCert; } - return transformed; } diff --git a/lib/tasks/transforms/amd/amd-shim.js b/lib/tasks/transforms/amd/amd-shim.js new file mode 100644 index 0000000000..8477e704cc --- /dev/null +++ b/lib/tasks/transforms/amd/amd-shim.js @@ -0,0 +1,20 @@ +'use strict'; +const stew = require('broccoli-stew'); + +module.exports = function shimAmd(tree, nameMapping) { + return stew.map(tree, (content, relativePath) => { + let name = nameMapping[relativePath]; + let sections = [ + '(function(define){\n', + content, + '\n})((function(){ function newDefine(){ var args = Array.prototype.slice.call(arguments);', + ]; + if (name) { + sections.push(' args.unshift("'); + sections.push(name); + sections.push('");'); + } + sections.push(' return define.apply(null, args); }; newDefine.amd = true; return newDefine; })());'); + return sections.join(''); + }); +}; diff --git a/lib/tasks/transforms/amd/index.js b/lib/tasks/transforms/amd/index.js index 98aa080625..cac1de5667 100644 --- a/lib/tasks/transforms/amd/index.js +++ b/lib/tasks/transforms/amd/index.js @@ -1,6 +1,6 @@ 'use strict'; -const stew = require('broccoli-stew'); +const shimAmd = require('./amd-shim'); class AmdTransformAddon { /** @@ -18,30 +18,16 @@ class AmdTransformAddon { return { amd: { transform: (tree, options) => { - let amdTransform = stew.map(tree, (content, relativePath) => { - const name = options[relativePath].as; - if (name) { - return [ - '(function(define){\n', - content, - '\n})((function(){ function newDefine(){ var args = Array.prototype.slice.call(arguments); args.unshift("', - name, - '"); return define.apply(null, args); }; newDefine.amd = true; return newDefine; })());', - ].join(''); - } else { - return content; - } - }); + let nameMapping = {}; + for (let relativePath in options) { + nameMapping[relativePath] = options[relativePath].as; + } + + let amdTransform = shimAmd(tree, nameMapping); return amdTransform; }, processOptions: (assetPath, entry, options) => { - if (!entry.as) { - throw new Error( - `while importing ${assetPath}: amd transformation requires an \`as\` argument that specifies the desired module name` - ); - } - // If the import is specified to be a different name we must break because of the broccoli rewrite behavior. if (Object.keys(options).indexOf(assetPath) !== -1 && options[assetPath].as !== entry.as) { throw new Error( diff --git a/lib/utilities/attempt-never-index.js b/lib/utilities/attempt-never-index.js deleted file mode 100644 index ac3d3b022d..0000000000 --- a/lib/utilities/attempt-never-index.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -let isDarwin = /darwin/i.test(require('os').type()); -const logger = require('heimdalljs-logger')('ember-cli:utilities/attempt-metadata-index-file'); - -/* - * Writes a `.metadata_never_index` file to the specified folder if running on OS X. - * - * This hints to spotlight that this folder should not be indexed. - * - * @param {String} dir path to the folder that should not be indexed - */ -module.exports = function(dir) { - let path = `${dir}/.metadata_never_index`; - - if (!isDarwin) { - logger.info('not darwin, skipping %s (which hints to spotlight to prevent indexing)', path); - return; - } - - logger.info('creating: %s (to prevent spotlight indexing)', path); - - const fs = require('fs-extra'); - - fs.mkdirsSync(dir); - fs.writeFileSync(path); -}; diff --git a/lib/utilities/clean-remove.js b/lib/utilities/clean-remove.js index ab4f41cb39..275635762f 100644 --- a/lib/utilities/clean-remove.js +++ b/lib/utilities/clean-remove.js @@ -2,52 +2,30 @@ const path = require('path'); const fs = require('fs-extra'); -const RSVP = require('rsvp'); const walkUp = require('./walk-up-path'); -const Promise = RSVP.Promise; -const stat = RSVP.denodeify(fs.stat); -const remove = RSVP.denodeify(fs.remove); -const readdir = RSVP.denodeify(fs.readdir); +async function cleanRemove(fileInfo) { + try { + await fs.stat(fileInfo.outputPath); + await fs.remove(fileInfo.outputPath); + let paths = walkUp(fileInfo.displayPath).map((thePath) => path.join(fileInfo.outputBasePath, thePath)); -function cleanRemove(fileInfo) { - // see if file exists to avoid wasting time - return stat(fileInfo.outputPath) - .then(() => remove(fileInfo.outputPath)) - .then(() => { - let paths = walkUp(fileInfo.displayPath).map(thePath => path.join(fileInfo.outputBasePath, thePath)); - - return paths.reduce( - (chainedPromise, thePath) => - chainedPromise.then(wasShortCircuited => { - if (wasShortCircuited) { - // optimization that says since my child dir wasn't empty, - // I can't be empty, so keep skipping - return true; - } - - // get list of files remaining in this dir - return readdir(thePath).then(paths => { - if (paths.length) { - // don't check parent dirs since this one isn't empty - return true; - } - - return remove(thePath).then(() => false); - }); - }), - Promise.resolve() - ); - }) - .catch(err => { - if (err.code !== 'ENOENT') { - throw err; + for (let thePath of paths) { + let childPaths = await fs.readdir(thePath); + if (childPaths.length > 0) { + return; } - // you tried to destroy a blueprint without first generating it - // instead of trying to read dirs that don't exist - // swallow error and carry on - }); + await fs.remove(thePath); + } + } catch (err) { + // you tried to destroy a blueprint without first generating it + // instead of trying to read dirs that don't exist + // swallow error and carry on + if (err.code !== 'ENOENT') { + throw err; + } + } } module.exports = cleanRemove; diff --git a/lib/utilities/deprecate.js b/lib/utilities/deprecate.js index 4906bd5784..6309970e4b 100644 --- a/lib/utilities/deprecate.js +++ b/lib/utilities/deprecate.js @@ -2,7 +2,7 @@ const chalk = require('chalk'); -module.exports = function(message, test) { +module.exports = function (message, test) { if (test) { console.log(chalk.yellow(`DEPRECATION: ${message}`)); } diff --git a/lib/utilities/ember-app-utils.js b/lib/utilities/ember-app-utils.js index b5fb40d208..f3f31508e7 100644 --- a/lib/utilities/ember-app-utils.js +++ b/lib/utilities/ember-app-utils.js @@ -77,8 +77,6 @@ function calculateBaseTag(baseURL, locationType) { application or not * @param {Boolean} options.storeConfigInMeta Controls whether to include the contents of config - * @param {Boolean} options.isModuleUnification Signifies if the application - supports module unification or not * @return {String} The content. */ function contentFor(config, match, type, options) { @@ -120,7 +118,7 @@ function contentFor(config, match, type, options) { break; case 'app-boot': if (options.autoRun) { - let moduleToRequire = `${config.modulePrefix}/${options.isModuleUnification ? 'src/main' : 'app'}`; + let moduleToRequire = `${config.modulePrefix}/app`; content.push(` if (!runningTests) { require("${moduleToRequire}")["default"].create(${convertObjectToString(config.APP)}); @@ -131,7 +129,13 @@ function contentFor(config, match, type, options) { break; case 'test-body-footer': content.push( - `` + `` ); break; @@ -160,8 +164,6 @@ function contentFor(config, match, type, options) { application or not * @param {Boolean} options.storeConfigInMeta Controls whether to include the contents of config - * @param {Boolean} options.isModuleUnification Signifies if the application - supports module unification or not @return {Array} An array of patterns to match against and replace */ function configReplacePatterns(options) { diff --git a/lib/utilities/execa.js b/lib/utilities/execa.js index 38f6e63f81..47a4fa3cbd 100644 --- a/lib/utilities/execa.js +++ b/lib/utilities/execa.js @@ -1,18 +1,16 @@ 'use strict'; const execa = require('execa'); -const RSVP = require('rsvp'); const logger = require('heimdalljs-logger')('ember-cli:execa'); -function _execa(cmd, args, opts) { +async function _execa(cmd, args, opts) { logger.info('execa(%j, %j)', cmd, args); - return RSVP.resolve(execa(cmd, args, opts)).then(result => { - logger.info('execa(%j, %j) -> code: %d', cmd, args, result.code); - return result; - }); + let result = await execa(cmd, args, opts); + logger.info('execa(%j, %j) -> code: %d', cmd, args, result.code); + return result; } -_execa.sync = function(cmd, args, opts) { +_execa.sync = function (cmd, args, opts) { logger.info('execa.sync(%j, %j)', cmd, args); let result = execa.sync(cmd, args, opts); logger.info('execa.sync(%j, %j) -> code: %d', cmd, args, result.code); diff --git a/lib/utilities/find-addon-by-name.js b/lib/utilities/find-addon-by-name.js index bd659ab8e4..be5098b06b 100644 --- a/lib/utilities/find-addon-by-name.js +++ b/lib/utilities/find-addon-by-name.js @@ -29,13 +29,13 @@ let HAS_FOUND_ADDON_BY_UNSCOPED_NAME = Object.create(null); */ module.exports = function findAddonByName(addons, name) { let unscopedName = unscope(name); - let exactMatchFromPkg = addons.find(addon => addon.pkg && addon.pkg.name === name); + let exactMatchFromPkg = addons.find((addon) => addon.pkg && addon.pkg.name === name); if (exactMatchFromPkg) { return exactMatchFromPkg; } - let exactMatchFromIndex = addons.find(addon => addon.name === name); + let exactMatchFromIndex = addons.find((addon) => addon.name === name); if (exactMatchFromIndex) { let pkg = exactMatchFromIndex.pkg; @@ -51,14 +51,12 @@ module.exports = function findAddonByName(addons, name) { return exactMatchFromIndex; } - let unscopedMatchFromIndex = addons.find(addon => addon.name && unscope(addon.name) === unscopedName); + let unscopedMatchFromIndex = addons.find((addon) => addon.name && unscope(addon.name) === unscopedName); if (unscopedMatchFromIndex) { if (HAS_FOUND_ADDON_BY_UNSCOPED_NAME[name] !== true) { HAS_FOUND_ADDON_BY_UNSCOPED_NAME[name] = true; console.trace( - `Finding a scoped addon via its unscoped name is deprecated. You searched for \`${name}\` which we found as \`${ - unscopedMatchFromIndex.name - }\` in '${unscopedMatchFromIndex.root}'` + `Finding a scoped addon via its unscoped name is deprecated. You searched for \`${name}\` which we found as \`${unscopedMatchFromIndex.name}\` in '${unscopedMatchFromIndex.root}'` ); } @@ -68,7 +66,7 @@ module.exports = function findAddonByName(addons, name) { return null; }; -module.exports._clearCaches = function() { +module.exports._clearCaches = function () { HAS_FOUND_ADDON_BY_NAME = Object.create(null); HAS_FOUND_ADDON_BY_UNSCOPED_NAME = Object.create(null); }; diff --git a/lib/utilities/find-build-file.js b/lib/utilities/find-build-file.js index eb3f497326..1357bbd7e9 100644 --- a/lib/utilities/find-build-file.js +++ b/lib/utilities/find-build-file.js @@ -2,7 +2,7 @@ const findUp = require('find-up'); const path = require('path'); -module.exports = function(file, dir) { +module.exports = function (file, dir) { let buildFilePath = findUp.sync(file, { cwd: dir }); // Note: In the future this should throw diff --git a/lib/utilities/get-lang-arg.js b/lib/utilities/get-lang-arg.js new file mode 100644 index 0000000000..769e215a34 --- /dev/null +++ b/lib/utilities/get-lang-arg.js @@ -0,0 +1,269 @@ +/* + +This utility processes the argument passed with the `lang` option +in ember-cli, i.e. `ember (new||init||addon) app-name --lang=langArg` + +Execution Context (usage, input, output, error handling, etc.): + - called directly by `init` IFF `--lang` flag is used in (new||init||addon) + - receives single input: the argument passed with `lang` (herein `langArg`) + - processes `langArg`: lang code validation + error detection / handling + - DOES emit Warning messages if necessary + - DOES NOT halt execution process / throw errors / disrupt the build + - returns single result as output (to `init`): + - `langArg` (if it is a valid language code) + - `undefined` (otherwise) + - `init` assigns the value of `commandOptions.lang` to the returned result + - downstream, the `lang` attribute is assigned via inline template control: + - file: `blueprints/app/files/app/index.html` + - logic: ` lang="<%= lang %>"<% } %>> + +Internal Mechanics -- the utility processes `langArg` to determine: + - the value to return to `init` (i.e. validated lang code or undefined) + - a descriptive category for the usage: `correct`, `incorrect`, `edge`, etc. + - what message text (if any: category-dependent) to emit before return + +Warning Messages (if necessary): + - An internal instance of `console-ui` is used to emit messages + - IFF there is a message, it will be emitted before returning the result + - Components of all emitted messages -- [Name] (writeLevel): 'example': + - [`HEAD`] (WARNING): 'A warning was generated while processing `--lang`:' + - [`BODY`] (WARNING): 'Invalid language code, `en-UK`' + - [`STATUS`] (WARNING): '`lang` will NOT be set to `en-UK` in `index.html`' + - [`HELP`] (INFO): 'Correct usage of `--lang`: ... ' + +*/ + +'use strict'; + +const getLangCodeInfo = require('is-language-code'); + +// Primary language code validation function (boolean) +function isValidLangCode(langArg) { + return getLangCodeInfo(langArg).res; +} + +// Generates the result to pass back to `init` +function getResult(langArg) { + return isValidLangCode(langArg) ? langArg : undefined; +} + +/* +Misuse case: attempt to set application programming language via `lang` +AND +Edge case: valid language code AND a common programming language abbreviation +------------------------------------------------------------------------------- +It is possible that a user might mis-interpret the type of `language` that is +specified by the `--lang` flag. One notable potential `misuse case` is one in +which the user thinks `--lang` specifies the application's programming +language. For example, the user might call `ember new my-app --lang=typescript` +expecting to achieve an effect similar to the one provided by the +`ember-cli-typescript` addon. + +This misuse case is handled by checking the input `langArg` against an Array +containing notable programming language-related values: language names +(e.g. `JavaScript`), abbreviations (e.g. `js`), file extensions (e.g. `.js`), +or versions (e.g. `ES6`), etc. Specifically, if `langArg` is found within this +reference list, a WARNING message that describes correct `--lang` usage will +be emitted. The `lang` attribute will not be assigned in `index.html`, and the +user will be notified with a corresponding STATUS message. + +There are several edge cases (marked) where `langArg` is both a commonly-used +abbreviation for a programming language AND a valid language code. The behavior +for these cases is to assume the user has used `--lang` correctly and set the +`lang` attribute to the valid code in `index.html`. To cover for potential +misuage, several helpful messages will also be emitted: +- `ts` is a valid language code AND a common programming language abbreviation +- the `lang` attribute will be set to `ts` in the application +- if this is not correct, it can be changed in `app/index.html` directly +- (general `help` information about correct `--lang` usage) +*/ +const PROG_LANGS = [ + 'javascript', + '.js', + 'js', + 'emcascript2015', + 'emcascript6', + 'es6', + 'emcascript2016', + 'emcascript7', + 'es7', + 'emcascript2017', + 'emcascript8', + 'es8', + 'emcascript2018', + 'emcascript9', + 'es9', + 'emcascript2019', + 'emcascript10', + 'es10', + 'typescript', + '.ts', + 'node.js', + 'node', + 'handlebars', + '.hbs', + 'hbs', + 'glimmer', + 'glimmer.js', + 'glimmer-vm', + 'markdown', + 'markup', + 'html5', + 'html4', + '.md', + '.html', + '.htm', + '.xhtml', + '.xml', + '.xht', + 'md', + 'html', + 'htm', + 'xhtml', + '.sass', + '.scss', + '.css', + 'sass', + 'scss', + // Edge Cases + 'ts', // Tsonga + 'TS', // Tsonga (case insensitivity check) + 'xml', // Malaysian Sign Language + 'xht', // Hattic + 'css', // Costanoan +]; + +function isProgLang(langArg) { + return langArg && PROG_LANGS.includes(langArg.toLowerCase().trim()); +} + +function isValidCodeAndProg(langArg) { + return isValidLangCode(langArg) && isProgLang(langArg); +} + +/* +Misuse case: `--lang` called without `langArg` (NB: parser bug workaround) +------------------------------------------------------------------------------- +This is a workaround for handling an existing bug in the ember-cli parser +where the `--lang` option is specified in the command without a corresponding +value for `langArg`. + +As examples, the parser behavior would likely affect the following usages: + 1. `ember new app-name --lang --skip-npm + 2. `ember new app-name --lang` + +In this context, the unintended parser behavior is that `langArg` will be +assingned to the String that immediately follows `--lang` in the command. If +`--lang` is the last explicitly defined part of the command (as in the second +example above), the first of any any `hidden` options pushed onto the command +after the initial parse (e.g. `--disable-analytics`, `--no-watcher`) will be +used when assigning `langArg`. + +In the above examples, `langArg` would likely be assigned as follows: + 1. `ember new app-name --lang --skip-npm => `langArg='--skip-npm'` + 2. `ember new app-name --lang` => `langArg='--disable-analytics'` + +The workaround impelemented herein is to check whether or not the value of +`langArg` starts with a hyphen. The rationale for this approach is based on +the following underlying assumptions: + - ALL CLI options start with (at least one) hyphen + - NO valid language codes start with a hyphen + +If the leading hyphen is detected, the current behavior is to assume `--lang` +was declared without a corresponding specification. A WARNING message that +describes correct `--lang` usage will be emitted. The `lang` attribute will not +be assigned in `index.html`, and the user will be notified with a corresponding +STATUS message. Execution will not be halted. + +Other complications related to this parser behavior are considered out-of-scope +and not handled here. In the first example above, this workaround would ensure +that `lang` is not assigned to `--skip-npm`, but it would not guarantee that +`--skip-npm` is correctly processed as a command option. That is, `npm` may or +may not get skipped during execution. +*/ + +function startsWithHyphen(langArg) { + return langArg && langArg[0] === '-'; +} + +// MESSAGE GENERATION: +// 1. `HEAD` Message: template for all `--lang`-related warnings emitted +const MSG_HEAD = `An issue with the \`--lang\` flag returned the following message:`; + +// 2. `BODY` Messages: category-dependent context information + +// Message context from language code validation (valid: null, invalid: reason) +function getLangCodeMsg(langArg) { + return getLangCodeInfo(langArg).message; +} + +// Edge case: valid language code AND a common programming language abbreviation +function getValidAndProgMsg(langArg) { + return `The \`--lang\` flag has been used with argument \`${langArg}\`, + which is BOTH a valid language code AND an abbreviation for a programming language. + ${getProgLangMsg(langArg)}`; +} + +// Misuse case: attempt to set application programming language via `lang` +function getProgLangMsg(langArg) { + return `Trying to set the app programming language to \`${langArg}\`? + This is not the intended usage of the \`--lang\` flag.`; +} + +// Misuse case: `--lang` called without `langArg` (NB: parser bug workaround) +function getCliMsg() { + return `Detected a \`--lang\` specification starting with command flag \`-\`. + This issue is likely caused by using the \`--lang\` flag without a specification.`; +} + +// 3. `STATUS` message: report if `lang` will be set in `index.html` +function getStatusMsg(langArg, willSet) { + return `The human language of the application will ${willSet ? `be set to ${langArg}` : `NOT be set`} in + the \`\` element's \`lang\` attribute in \`index.html\`.`; +} + +// 4. `HELP` message: template for all `--lang`-related warnings emitted +const MSG_HELP = `If this was not your intention, you may edit the \`\` element's + \`lang\` attribute in \`index.html\` manually after the process is complete. +Information about using the \`--lang\` flag: + The \`--lang\` flag sets the base human language of an app or test app: + - \`app/index.html\` (app) + - \`tests/dummy/app/index.html\` (addon test app) + If used, the lang option must specfify a valid language code. + For default behavior, remove the flag. + See \`ember help\` for more information.`; + +function getBodyMsg(langArg) { + return isValidCodeAndProg(langArg) + ? getValidAndProgMsg(langArg) + : isProgLang(langArg) + ? getProgLangMsg(langArg) + : startsWithHyphen(langArg) + ? getCliMsg(langArg) + : getLangCodeMsg(langArg); +} + +function getFullMsg(langArg) { + return { + head: MSG_HEAD, + body: getBodyMsg(langArg), + status: getStatusMsg(langArg, isValidCodeAndProg(langArg)), + help: MSG_HELP, + }; +} + +function writeFullMsg(fullMsg, ui) { + ui.setWriteLevel('WARNING'); + ui.writeWarnLine(`${fullMsg.head}\n ${fullMsg.body}\``); + ui.writeWarnLine(fullMsg.status); + ui.setWriteLevel('INFO'); + ui.writeInfoLine(fullMsg.help); +} + +module.exports = function getLangArg(langArg, ui) { + let fullMsg = getFullMsg(langArg); + if (fullMsg.body) { + writeFullMsg(fullMsg, ui); + } + return getResult(langArg); +}; diff --git a/lib/utilities/get-option-args.js b/lib/utilities/get-option-args.js index 2442228d06..34198cecef 100644 --- a/lib/utilities/get-option-args.js +++ b/lib/utilities/get-option-args.js @@ -1,6 +1,6 @@ 'use strict'; -module.exports = function(option, commandArgs) { +module.exports = function (option, commandArgs) { let results = [], value, i; diff --git a/lib/utilities/get-package-base-name.js b/lib/utilities/get-package-base-name.js index bd6b6ceaf8..3ff984a76a 100644 --- a/lib/utilities/get-package-base-name.js +++ b/lib/utilities/get-package-base-name.js @@ -2,7 +2,7 @@ const npa = require('npm-package-arg'); -module.exports = function(name) { +module.exports = function (name) { if (!name) { return null; } diff --git a/lib/utilities/get-serve-url.js b/lib/utilities/get-serve-url.js index 1c5de475b6..4c80000841 100644 --- a/lib/utilities/get-serve-url.js +++ b/lib/utilities/get-serve-url.js @@ -2,9 +2,9 @@ const cleanBaseURL = require('clean-base-url'); -module.exports = function(options, project) { +module.exports = function (options, project) { let config = project.config(options.environment); - let baseURL = config.rootURL === '' ? '/' : cleanBaseURL(config.rootURL || (config.baseURL || '/')); + let baseURL = config.rootURL === '' ? '/' : cleanBaseURL(config.rootURL || config.baseURL || '/'); return `http${options.ssl ? 's' : ''}://${options.host || 'localhost'}:${options.port}${baseURL}`; }; diff --git a/lib/utilities/heimdall-progress.js b/lib/utilities/heimdall-progress.js index ee9eb7702b..a47864f878 100644 --- a/lib/utilities/heimdall-progress.js +++ b/lib/utilities/heimdall-progress.js @@ -9,11 +9,11 @@ module.exports = function progress(heimdalljs = require('heimdalljs')) { } return stack - .filter(name => name !== 'heimdall') + .filter((name) => name !== 'heimdall') .reverse() .join(' > '); }; -module.exports.format = function(text) { +module.exports.format = function (text) { return require('chalk').green('building... ') + (text ? `[${text}]` : ''); }; diff --git a/lib/utilities/insert-into-file.js b/lib/utilities/insert-into-file.js index 6cc7e8d358..dff87ddd1f 100644 --- a/lib/utilities/insert-into-file.js +++ b/lib/utilities/insert-into-file.js @@ -2,10 +2,6 @@ const fs = require('fs-extra'); const EOL = require('os').EOL; -const RSVP = require('rsvp'); - -const Promise = RSVP.Promise; -const writeFile = RSVP.denodeify(fs.outputFile); /** Inserts the given content into a file. If the `contentsToInsert` string is already @@ -28,19 +24,19 @@ const writeFile = RSVP.denodeify(fs.outputFile); ``` // app/router.js - Router.map(function() { + Router.map(function () { }); ``` ``` insertIntoFile('app/router.js', ' this.route("admin");', { - after: 'Router.map(function() {' + EOL + after: 'Router.map(function () {' + EOL }); ``` ``` // app/router.js - Router.map(function() { + Router.map(function () { this.route("admin"); }); ``` @@ -51,7 +47,7 @@ const writeFile = RSVP.denodeify(fs.outputFile); @param {Object} providedOptions @return {Promise} */ -function insertIntoFile(fullPath, contentsToInsert, providedOptions) { +async function insertIntoFile(fullPath, contentsToInsert, providedOptions) { let options = providedOptions || {}; let returnValue = { @@ -118,7 +114,8 @@ function insertIntoFile(fullPath, contentsToInsert, providedOptions) { if (contentsToWrite !== originalContents) { returnValue.inserted = true; - return writeFile(fullPath, contentsToWrite).then(() => returnValue); + await fs.outputFile(fullPath, contentsToWrite); + return returnValue; } } diff --git a/lib/utilities/is-engine.js b/lib/utilities/is-engine.js new file mode 100644 index 0000000000..352f7ee5e1 --- /dev/null +++ b/lib/utilities/is-engine.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function isEngine(keywords) { + return !!(Array.isArray(keywords) && keywords.indexOf('ember-engine') >= 0); +}; diff --git a/lib/utilities/is-lazy-engine.js b/lib/utilities/is-lazy-engine.js new file mode 100644 index 0000000000..4b2be5e0af --- /dev/null +++ b/lib/utilities/is-lazy-engine.js @@ -0,0 +1,31 @@ +'use strict'; + +/** + * Indicate if a given object is a constructor function or class or an instance of an Addon. + * + * @module is-lazy-engine + * @param {Object} addonCtorOrInstance the constructor function/class or an instance of an Addon. + * @return {Boolean} True if the addonCtorOrInstance is a lazy engine, False otherwise. + */ +module.exports = function isLazyEngine(addonCtorOrInstance) { + if (!addonCtorOrInstance) { + return false; + } + + if (addonCtorOrInstance.lazyLoading) { + return addonCtorOrInstance.lazyLoading.enabled === true; + } else if (addonCtorOrInstance.options) { + return !!(addonCtorOrInstance.options.lazyLoading && addonCtorOrInstance.options.lazyLoading.enabled === true); + } else if (addonCtorOrInstance.prototype) { + if (addonCtorOrInstance.prototype.lazyLoading) { + return addonCtorOrInstance.prototype.lazyLoading.enabled === true; + } else if (addonCtorOrInstance.prototype.options) { + return !!( + addonCtorOrInstance.prototype.options.lazyLoading && + addonCtorOrInstance.prototype.options.lazyLoading.enabled === true + ); + } + } + + return false; +}; diff --git a/lib/utilities/json-generator.js b/lib/utilities/json-generator.js index d77615fff9..6501685398 100644 --- a/lib/utilities/json-generator.js +++ b/lib/utilities/json-generator.js @@ -29,7 +29,7 @@ class JsonGenerator { json.commands = []; json.addons = []; - Object.keys(this.commands).forEach(function(commandName) { + Object.keys(this.commands).forEach(function (commandName) { this._addCommandHelpToJson(commandName, commandOptions, json); }, this); @@ -41,7 +41,7 @@ class JsonGenerator { addonJson.commands = []; json.addons.push(addonJson); - Object.keys(this.commands).forEach(function(commandName) { + Object.keys(this.commands).forEach(function (commandName) { this._addCommandHelpToJson(commandName, commandOptions, addonJson); }, this); }); diff --git a/lib/utilities/lint-fix.js b/lib/utilities/lint-fix.js new file mode 100644 index 0000000000..4d40dcba08 --- /dev/null +++ b/lib/utilities/lint-fix.js @@ -0,0 +1,32 @@ +'use strict'; + +const fs = require('fs-extra'); +const execa = require('../utilities/execa'); + +async function run(ui) { + let lintFixScriptName = 'lint:fix'; + let cwd = process.cwd(); + let packageJson = fs.readJsonSync('package.json'); + + let hasLintFixScript = !!packageJson.scripts[lintFixScriptName]; + + if (!hasLintFixScript) { + ui.writeWarnLine( + 'Lint fix skipped: "lint:fix" not found in package.json scripts. New files might not pass linting.' + ); + + return; + } + + let usingYarn = fs.existsSync('yarn.lock'); + + if (usingYarn) { + await execa('yarn', [lintFixScriptName], { cwd }); + } else { + await execa('npm', ['run', lintFixScriptName], { cwd }); + } +} + +module.exports = { + run, +}; diff --git a/lib/utilities/markdown-color.js b/lib/utilities/markdown-color.js index cc5c11e8c7..d7cb59fcf1 100644 --- a/lib/utilities/markdown-color.js +++ b/lib/utilities/markdown-color.js @@ -74,7 +74,7 @@ class MarkdownColor { let styles = Object.keys(this.tokens); input = input.replace(/^/gm, indent); - styles.reverse().map(style => { + styles.reverse().map((style) => { input = input.replace(this.tokens[style].pattern, this.tokens[style].render); }); input = input.replace(/~\^(.*)~\^/g, escapeToken); @@ -128,7 +128,7 @@ class MarkdownColor { styles = [checkStyleName(renderer, styleNames)]; } - return function(match, pattern) { + return function (match, pattern) { return styles.reverse().reduce((previous, current) => renderer[current](previous), pattern); }; } diff --git a/lib/utilities/merge-blueprint-options.js b/lib/utilities/merge-blueprint-options.js index 1420638b0b..fc240e8fd0 100644 --- a/lib/utilities/merge-blueprint-options.js +++ b/lib/utilities/merge-blueprint-options.js @@ -14,7 +14,7 @@ const Blueprint = require('../models/blueprint'); * beforeRun: mergeBlueprintOptions * }) */ -module.exports = function(rawArgs) { +module.exports = function (rawArgs) { if (rawArgs.length === 0) { return; } diff --git a/lib/utilities/mk-tmp-dir-in.js b/lib/utilities/mk-tmp-dir-in.js index 5a5840bbe1..7636c78db2 100644 --- a/lib/utilities/mk-tmp-dir-in.js +++ b/lib/utilities/mk-tmp-dir-in.js @@ -2,12 +2,13 @@ const fs = require('fs-extra'); const temp = require('temp'); -const RSVP = require('rsvp'); +const util = require('util'); -const mkdirTemp = RSVP.denodeify(temp.mkdir); +const mkdirTemp = util.promisify(temp.mkdir); -function mkTmpDirIn(dir) { - return fs.ensureDir(dir).then(() => mkdirTemp({ dir })); +async function mkTmpDirIn(dir) { + await fs.ensureDir(dir); + return mkdirTemp({ dir }); } module.exports = mkTmpDirIn; diff --git a/lib/utilities/open-editor.js b/lib/utilities/open-editor.js index 1196166058..99260ff34c 100644 --- a/lib/utilities/open-editor.js +++ b/lib/utilities/open-editor.js @@ -1,6 +1,5 @@ 'use strict'; -const Promise = require('rsvp').Promise; const spawn = require('child_process').spawn; function openEditor(file) { @@ -14,28 +13,31 @@ function openEditor(file) { let editorArgs = openEditor._env().EDITOR.split(' '); let editor = editorArgs.shift(); - let editProcess = openEditor._spawn(editor, [file].concat(editorArgs), { stdio: 'inherit' }); + const args = [file].concat(editorArgs); + let editProcess = openEditor._spawn(editor, args, { stdio: 'inherit' }); return new Promise((resolve, reject) => { - editProcess.on('close', code => { + editProcess.on('close', (code) => { if (code === 0) { resolve(); } else { - reject(); + reject( + new Error(`Spawn('${editor}', [${args.join(',')}]) exited with a non zero error status code: '${code}'`) + ); } }); }); } -openEditor.canEdit = function() { +openEditor.canEdit = function () { return openEditor._env().EDITOR !== undefined; }; -openEditor._env = function() { +openEditor._env = function () { return process.env; }; -openEditor._spawn = function() { +openEditor._spawn = function () { return spawn.apply(this, arguments); }; diff --git a/lib/utilities/platform-checker.js b/lib/utilities/platform-checker.js index c421e59233..5234cc75dd 100644 --- a/lib/utilities/platform-checker.js +++ b/lib/utilities/platform-checker.js @@ -4,24 +4,26 @@ const semver = require('semver'); const logger = require('heimdalljs-logger')('ember-cli:platform-checker:'); const loadConfig = require('./load-config'); -let testedEngines; -if (process.platform === 'win32') { - testedEngines = loadConfig('appveyor.yml') - .environment.matrix.map(element => element.nodejs_version) - .join(' || '); -} else { - let travisConfig = loadConfig('.travis.yml'); - let nodeVersions = new Set(travisConfig.node_js); - - travisConfig.jobs.include.forEach(job => { - if (job.node_js) { - nodeVersions.add(job.node_js); +const ci = loadConfig('.github/workflows/ci.yml'); +const nodeVersions = new Set(); + +for (let jobName in ci.jobs) { + let job = ci.jobs[jobName]; + + job.steps.forEach((step) => { + let isSetupNode = step.uses === 'actions/setup-node@v1'; + if (isSetupNode && step.with['node-version'].includes('${{') === false) { + nodeVersions.add(step.with['node-version']); } }); - testedEngines = Array.from(nodeVersions).join(' || '); + if (job.strategy && job.strategy.matrix && job.strategy.matrix['node-version']) { + job.strategy.matrix['node-version'].forEach((version) => nodeVersions.add(version)); + } } +const testedEngines = Array.from(nodeVersions).join(' || '); + let supportedEngines = loadConfig('package.json').engines.node; module.exports = class PlatformChecker { diff --git a/lib/utilities/print-command.js b/lib/utilities/print-command.js index 9230c8232c..436348a75b 100644 --- a/lib/utilities/print-command.js +++ b/lib/utilities/print-command.js @@ -3,7 +3,7 @@ const chalk = require('chalk'); const EOL = require('os').EOL; -module.exports = function(initialMargin, shouldDescriptionBeGrey) { +module.exports = function (initialMargin, shouldDescriptionBeGrey) { initialMargin = initialMargin || ''; let output = ''; @@ -14,7 +14,7 @@ module.exports = function(initialMargin, shouldDescriptionBeGrey) { if (options.length) { output += ` ${chalk.yellow( options - .map(option => { + .map((option) => { // blueprints we insert brackets, commands already have them if (option.indexOf('<') === 0) { return option; @@ -44,12 +44,12 @@ module.exports = function(initialMargin, shouldDescriptionBeGrey) { // aliases: a b c if (this.aliases && this.aliases.length) { - output += `${EOL + initialMargin} ${chalk.grey(`aliases: ${this.aliases.filter(a => a).join(', ')}`)}`; + output += `${EOL + initialMargin} ${chalk.grey(`aliases: ${this.aliases.filter((a) => a).join(', ')}`)}`; } // --available-option (Required) (Default: value) // ... - options.forEach(option => { + options.forEach((option) => { output += `${EOL + initialMargin} ${chalk.cyan(`--${option.name}`)}`; if (option.values) { @@ -77,7 +77,7 @@ module.exports = function(initialMargin, shouldDescriptionBeGrey) { if (option.aliases && option.aliases.length) { output += `${EOL + initialMargin} ${chalk.grey( `aliases: ${option.aliases - .map(a => { + .map((a) => { if (typeof a === 'string') { return (a.length > 4 ? '--' : '-') + a + (option.type === Boolean ? '' : ' '); } else { diff --git a/lib/utilities/sequence.js b/lib/utilities/sequence.js index a7e3f6db01..23f472877b 100644 --- a/lib/utilities/sequence.js +++ b/lib/utilities/sequence.js @@ -1,6 +1,5 @@ 'use strict'; -const Promise = require('rsvp').Promise; /* * * given an array of functions, that may or may not return promises sequence diff --git a/lib/utilities/symbol.js b/lib/utilities/symbol.js deleted file mode 100644 index f75eabb6b0..0000000000 --- a/lib/utilities/symbol.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -module.exports = function symbol(name) { - let id = `EMBER_CLI${Math.floor(Math.random() * new Date())}`; - return `__${name}__ [id=${id}]`; -}; diff --git a/lib/utilities/valid-project-name.js b/lib/utilities/valid-project-name.js index feaf251e60..36ad71a66b 100644 --- a/lib/utilities/valid-project-name.js +++ b/lib/utilities/valid-project-name.js @@ -1,5 +1,7 @@ 'use strict'; +const INVALID_PROJECT_NAMES = ['test', 'ember', 'ember-cli', 'vendor', 'public', 'app', 'addon', 'application']; + /** * Checks if the string starts with a number. * @@ -10,6 +12,15 @@ function startsWithNumber(name) { return /^\d.*$/.test(name); } +function containsInvalidSlash(name) { + let indexOfFirstSlash = name.indexOf('/'); + let isScoped = name[0] === '@' && indexOfFirstSlash !== -1; + + let containsInvalidSlash = isScoped ? name.indexOf('/', indexOfFirstSlash + 1) > -1 : name.includes('/'); + + return containsInvalidSlash; +} + /** * Checks if project name is valid. * @@ -21,11 +32,15 @@ function startsWithNumber(name) { * @param {String} name The name of Ember CLI project * @return {Boolean} */ -module.exports = function(name) { - let invalidProjectNames = ['test', 'ember', 'ember-cli', 'vendor', 'public', 'app']; - name = name.toLowerCase(); +module.exports = function isValidProjectName(projectName) { + let lowerSanitizedName = projectName.toLowerCase(); - if (invalidProjectNames.indexOf(name) !== -1 || name.indexOf('.') !== -1 || startsWithNumber(name)) { + if ( + INVALID_PROJECT_NAMES.includes(lowerSanitizedName) || + lowerSanitizedName.includes('.') || + containsInvalidSlash(lowerSanitizedName) || + startsWithNumber(lowerSanitizedName) + ) { return false; } diff --git a/lib/utilities/version-utils.js b/lib/utilities/version-utils.js index 6a7564bbb5..fd01d12318 100644 --- a/lib/utilities/version-utils.js +++ b/lib/utilities/version-utils.js @@ -10,10 +10,12 @@ module.exports = { let output = [require('../../package.json').version]; if (fs.existsSync(gitPath)) { - let repoInfo = getRepoInfo(gitPath); + let { branch, abbreviatedSha } = getRepoInfo(gitPath); - output.push(repoInfo.branch); - output.push(repoInfo.abbreviatedSha); + if (branch) { + output.push(branch.replace(/\//g, '-')); + } + output.push(abbreviatedSha); } return output.join('-'); diff --git a/lib/utilities/will-interrupt-process.js b/lib/utilities/will-interrupt-process.js index 170c5add4a..748d670bd9 100644 --- a/lib/utilities/will-interrupt-process.js +++ b/lib/utilities/will-interrupt-process.js @@ -16,12 +16,12 @@ const EventEmitter = require('events'); const handlers = []; -let _process, windowsCtrlCTrap, originalIsRaw; +let _process, _processCapturedLocation, windowsCtrlCTrap, originalIsRaw; module.exports = { capture(outerProcess) { if (_process) { - throw new Error('process already captured'); + throw new Error(`process already captured at: \n\n${_processCapturedLocation.stack}`); } if (outerProcess instanceof EventEmitter === false) { @@ -29,6 +29,7 @@ module.exports = { } _process = outerProcess; + _processCapturedLocation = new Error(); // ember-cli and user apps have many dependencies, many of which require // process.addListener('exit', ....) for cleanup, by default this limit for @@ -62,6 +63,7 @@ module.exports = { } _process = null; + _processCapturedLocation = null; }, /** @@ -163,7 +165,7 @@ function trapWindowsSignals(_process) { // This is required to capture Ctrl + C on Windows stdin.setRawMode(true); - windowsCtrlCTrap = function(data) { + windowsCtrlCTrap = function (data) { if (data.length === 1 && data[0] === 0x03) { _process.emit('SIGINT'); } diff --git a/lib/utilities/windows-admin.js b/lib/utilities/windows-admin.js index 85a1845154..a7cf542c71 100644 --- a/lib/utilities/windows-admin.js +++ b/lib/utilities/windows-admin.js @@ -1,20 +1,19 @@ 'use strict'; -const Promise = require('rsvp').Promise; const chalk = require('chalk'); class WindowsSymlinkChecker { /** * - * On windows users will have a much better experience if symlinks are enabled - * an usable. This object when queried informs windows users, if they can - * improve there build performance, and how. + * On Windows, users will have a much better experience if symlinks are enabled + * and usable. When queried, this object informs Windows users regarding + * improving their build performance, and how. * - * > Windows vista: nothing we can really do, so we fall back to junctions for folders + copying of files - * <= Windows vista: symlinks are available but using them is somewhat tricky - * * if the users is an admin, the process needed to have been started with elevated privs + * > Windows Vista: nothing we can really do, so we fall back to junctions for folders + copying of files + * <= Windows Vista: symlinks are available but using them is somewhat tricky + * * if the user is an admin, the process needs to have been started with elevated privileges * * if the user is not an admin, a specific setting needs to be enabled - * <= Windows 10 Insiders build 14972 + * <= Windows 10 * * if developer mode is enabled, symlinks "just work" * * https://blogs.windows.com/buildingapps/2016/12/02/symlinks-windows-10 * @@ -85,7 +84,7 @@ class WindowsSymlinkChecker { * @return {Promise} Object describing whether we're on windows and if admin rights exist */ checkIfSymlinksNeedToBeEnabled() { - return new Promise(resolve => { + return new Promise((resolve) => { if (!this.isWindows) { resolve({ windows: false, @@ -116,7 +115,7 @@ class WindowsSymlinkChecker { let ui = this.ui; let exec = this.exec; - return new Promise(resolve => { + return new Promise((resolve) => { exec('NET SESSION', (error, stdout, stderr) => { let elevated = !stderr || stderr.length === 0; diff --git a/package.json b/package.json index 1fa6993d95..ae33c31cc9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ember-cli", - "version": "3.13.1", + "version": "3.28.6", "description": "Command line tool for developing ambitious ember.js apps", "keywords": [ "app", @@ -16,156 +16,175 @@ "bugs": { "url": "https://github.com/ember-cli/ember-cli/issues" }, + "repository": { + "type": "git", + "url": "https://github.com/ember-cli/ember-cli.git" + }, "license": "MIT", "author": "Stefan Penner, Robert Jackson and ember-cli contributors", "main": "lib/cli/index.js", "bin": { "ember": "./bin/ember" }, - "repository": { - "type": "git", - "url": "https://github.com/ember-cli/ember-cli.git" - }, "scripts": { "docs": "yuidoc", "lint": "eslint . --cache", - "test": "node tests/runner", - "test:all": "node tests/runner all", - "test:cover": "nyc node tests/runner all", - "test:slow": "node tests/runner slow", - "test:debug": "node debug tests/runner" + "prepack": "yarn docs", + "test": "node --unhandled-rejections=strict tests/runner", + "test:all": "node --unhandled-rejections=strict tests/runner all", + "test:cover": "nyc --all --reporter=text --reporter=lcov node tests/runner all", + "test:debug": "node --unhandled-rejections=strict debug tests/runner", + "test:slow": "node --unhandled-rejections=strict tests/runner slow" }, "dependencies": { - "@babel/core": "^7.5.5", - "@babel/plugin-transform-modules-amd": "^7.5.0", + "@babel/core": "^7.13.8", + "@babel/plugin-transform-modules-amd": "^7.12.1", "amd-name-resolver": "^1.3.1", - "babel-plugin-module-resolver": "^3.2.0", - "bower-config": "^1.4.1", + "babel-plugin-module-resolver": "^4.1.0", + "bower-config": "^1.4.3", "bower-endpoint-parser": "0.2.2", - "broccoli": "^3.2.0", + "broccoli": "^3.5.1", "broccoli-amd-funnel": "^2.0.1", - "broccoli-babel-transpiler": "^7.3.0", + "broccoli-babel-transpiler": "^7.8.0", "broccoli-builder": "^0.18.14", - "broccoli-concat": "^3.7.4", + "broccoli-concat": "^4.2.5", "broccoli-config-loader": "^1.0.1", "broccoli-config-replace": "^1.1.2", "broccoli-debug": "^0.6.5", - "broccoli-funnel": "^2.0.2", + "broccoli-funnel": "^3.0.5", "broccoli-funnel-reducer": "^1.0.0", "broccoli-merge-trees": "^3.0.2", - "broccoli-middleware": "^2.1.0", - "broccoli-module-normalizer": "^1.3.0", - "broccoli-module-unification-reexporter": "^1.0.0", - "broccoli-slow-trees": "^3.0.1", + "broccoli-middleware": "^2.1.1", + "broccoli-slow-trees": "^3.1.0", "broccoli-source": "^3.0.0", "broccoli-stew": "^3.0.0", "calculate-cache-key-for-tree": "^2.0.0", "capture-exit": "^2.0.0", - "chalk": "^2.4.2", + "chalk": "^4.1.0", "ci-info": "^2.0.0", "clean-base-url": "^1.0.0", "compression": "^1.7.4", - "configstore": "^5.0.0", - "console-ui": "^3.1.1", + "configstore": "^5.0.1", + "console-ui": "^3.1.2", "core-object": "^3.1.5", "dag-map": "^2.0.2", - "diff": "^4.0.1", - "ember-cli-broccoli-sane-watcher": "^3.0.0", + "diff": "^5.0.0", "ember-cli-is-package-missing": "^1.0.0", "ember-cli-lodash-subset": "^2.0.1", "ember-cli-normalize-entity-name": "^1.0.0", "ember-cli-preprocess-registry": "^3.3.0", "ember-cli-string-utils": "^1.1.0", - "ember-source-channel-url": "^2.0.1", - "ensure-posix-path": "^1.0.2", - "execa": "^1.0.0", + "ember-source-channel-url": "^3.0.0", + "ensure-posix-path": "^1.1.1", + "execa": "^5.0.0", "exit": "^0.1.2", - "express": "^4.16.4", - "filesize": "^4.1.2", - "find-up": "^4.1.0", - "find-yarn-workspace-root": "^1.2.1", - "fs-extra": "^8.1.0", + "express": "^4.17.1", + "filesize": "^6.1.0", + "find-up": "^5.0.0", + "find-yarn-workspace-root": "^2.0.0", + "fixturify-project": "^2.1.1", + "fs-extra": "^9.1.0", "fs-tree-diff": "^2.0.1", "get-caller-file": "^2.0.5", - "git-repo-info": "^2.1.0", - "glob": "^7.1.4", + "git-repo-info": "^2.1.1", + "glob": "^7.1.6", "heimdalljs": "^0.2.6", - "heimdalljs-fs-monitor": "^0.2.3", + "heimdalljs-fs-monitor": "^1.1.0", "heimdalljs-graph": "^1.0.0", "heimdalljs-logger": "^0.1.10", - "http-proxy": "^1.17.0", + "http-proxy": "^1.18.1", "inflection": "^1.12.0", "is-git-url": "^1.0.0", - "isbinaryfile": "^3.0.3", - "js-yaml": "^3.13.1", + "is-language-code": "^2.0.0", + "isbinaryfile": "^4.0.6", + "js-yaml": "^3.14.0", "json-stable-stringify": "^1.0.1", "leek": "0.0.24", "lodash.template": "^4.5.0", - "markdown-it": "^9.0.1", - "markdown-it-terminal": "0.1.0", + "markdown-it": "^12.0.4", + "markdown-it-terminal": "0.2.1", "minimatch": "^3.0.4", - "morgan": "^1.9.1", + "morgan": "^1.10.0", "nopt": "^3.0.6", - "npm-package-arg": "^6.1.0", + "npm-package-arg": "^8.1.1", "p-defer": "^3.0.0", - "portfinder": "^1.0.21", - "promise-map-series": "^0.2.3", - "promise.prototype.finally": "^3.1.0", + "portfinder": "^1.0.28", + "promise-map-series": "^0.3.0", + "promise.hash.helper": "^1.0.7", "quick-temp": "^0.1.8", - "resolve": "^1.12.0", - "resolve-package-path": "^1.2.7", - "rsvp": "^4.8.5", + "resolve": "^1.20.0", + "resolve-package-path": "^3.1.0", "sane": "^4.1.0", - "semver": "^6.3.0", + "semver": "^7.3.4", "silent-error": "^1.1.1", - "sort-package-json": "^1.22.1", - "symlink-or-copy": "^1.2.0", - "temp": "0.9.0", - "testem": "^2.17.0", - "tiny-lr": "^1.1.1", - "tree-sync": "^2.0.0", - "uuid": "^3.3.2", - "walk-sync": "^2.0.2", + "sort-package-json": "^1.49.0", + "symlink-or-copy": "^1.3.1", + "temp": "0.9.4", + "testem": "^3.2.0", + "tiny-lr": "^2.0.0", + "tree-sync": "^2.1.0", + "uuid": "^8.3.2", + "walk-sync": "^2.2.0", "watch-detector": "^1.0.0", + "workerpool": "^6.1.4", "yam": "^1.0.0" }, "devDependencies": { - "@octokit/rest": "^16.28.6", - "broccoli-plugin": "^3.0.0", + "@ember/edition-utils": "^1.2.0", + "@octokit/rest": "^18.0.15", + "broccoli-plugin": "^4.0.3", "broccoli-test-helper": "^2.0.0", - "chai": "^4.2.0", + "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "chai-files": "^1.4.0", - "co": "^4.6.0", "ember-cli-blueprint-test-helpers": "^0.19.2", "ember-cli-internal-test-helpers": "^0.9.1", - "eslint": "^5.16.0", - "eslint-config-prettier": "^6.0.0", - "eslint-plugin-chai-expect": "^2.0.1", - "eslint-plugin-mocha": "^6.0.0", - "eslint-plugin-node": "^9.0.1", - "eslint-plugin-prettier": "^3.0.1", - "fixturify": "^1.2.0", - "fixturify-project": "^1.9.0", - "mocha": "^6.2.0", - "nock": "^10.0.6", - "nyc": "^14.1.0", - "prettier": "1.17.0", - "rimraf": "^2.6.3", - "strip-ansi": "^5.2.0", - "supertest": "^4.0.2", - "testdouble": "^3.12.4", - "tmp": "^0.1.0", - "websocket": "^1.0.30", - "which": "1.3.1", + "eslint": "^7.11.0", + "eslint-config-prettier": "^7.2.0", + "eslint-plugin-chai-expect": "^2.2.0", + "eslint-plugin-mocha": "^8.1.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.3.1", + "fixturify": "^2.1.0", + "is-language-code": "^2.0.0", + "jsdom": "^16.4.0", + "latest-version": "^5.1.0", + "mocha": "^8.3.2", + "nock": "^13.0.5", + "nyc": "^15.1.0", + "prettier": "2.2.1", + "release-it": "^14.4.1", + "rimraf": "^3.0.2", + "strip-ansi": "^6.0.0", + "supertest": "^6.1.3", + "testdouble": "^3.16.1", + "tmp": "^0.2.1", + "websocket": "^1.0.32", + "which": "2.0.2", "yuidoc-ember-cli-theme": "^1.0.4", "yuidocjs": "0.10.2" }, "engines": { - "node": "8.* || >= 10.*" + "node": ">= 12" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org" }, - "trackingCode": "UA-49225444-1", "greenkeeper": { "ignore": [] - } + }, + "release-it": { + "hooks": { + "after:release": "node ./dev/update-output-repos.js" + }, + "git": { + "tagName": "v${version}" + }, + "github": { + "draft": true, + "release": true, + "tokenRef": "GITHUB_AUTH" + } + }, + "trackingCode": "UA-49225444-1" } diff --git a/tests/acceptance/addon-dummy-generate-test.js b/tests/acceptance/addon-dummy-generate-test.js index f2a048fa2e..a3d32297b7 100644 --- a/tests/acceptance/addon-dummy-generate-test.js +++ b/tests/acceptance/addon-dummy-generate-test.js @@ -1,11 +1,8 @@ 'use strict'; -const co = require('co'); -const RSVP = require('rsvp'); const ember = require('../helpers/ember'); const fs = require('fs-extra'); const path = require('path'); -let remove = RSVP.denodeify(fs.remove); let root = process.cwd(); let tmproot = path.join(root, 'tmp'); const Blueprint = require('../../lib/models/blueprint'); @@ -16,27 +13,25 @@ const chai = require('../chai'); let expect = chai.expect; let file = chai.file; -describe('Acceptance: ember generate in-addon-dummy', function() { +describe('Acceptance: ember generate in-addon-dummy', function () { this.timeout(20000); - before(function() { + before(function () { BlueprintNpmTask.disableNPM(Blueprint); }); - after(function() { + after(function () { BlueprintNpmTask.restoreNPM(Blueprint); }); - beforeEach( - co.wrap(function*() { - let tmpdir = yield mkTmpDirIn(tmproot); - process.chdir(tmpdir); - }) - ); + beforeEach(async function () { + let tmpdir = await mkTmpDirIn(tmproot); + process.chdir(tmpdir); + }); - afterEach(function() { + afterEach(function () { process.chdir(root); - return remove(tmproot); + return fs.remove(tmproot); }); function initAddon() { @@ -52,215 +47,235 @@ describe('Acceptance: ember generate in-addon-dummy', function() { function generateInAddon(args) { let generateArgs = ['generate'].concat(args); - return initAddon().then(function() { + return initAddon().then(function () { return ember(generateArgs); }); } - it( - 'dummy blueprint foo', - co.wrap(function*() { - yield generateInAddon(['blueprint', 'foo', '--dummy']); - - expect(file('blueprints/foo/index.js')).to.contain( - 'module.exports = {\n' + - " description: ''\n" + - '\n' + - ' // locals(options) {\n' + - ' // // Return custom template variables here.\n' + - ' // return {\n' + - ' // foo: options.entity.options.foo\n' + - ' // };\n' + - ' // }\n' + - '\n' + - ' // afterInstall(options) {\n' + - ' // // Perform extra work here.\n' + - ' // }\n' + - '};' - ); - }) - ); - - it( - 'dummy blueprint foo/bar', - co.wrap(function*() { - yield generateInAddon(['blueprint', 'foo/bar', '--dummy']); - - expect(file('blueprints/foo/bar/index.js')).to.contain( - 'module.exports = {\n' + - " description: ''\n" + - '\n' + - ' // locals(options) {\n' + - ' // // Return custom template variables here.\n' + - ' // return {\n' + - ' // foo: options.entity.options.foo\n' + - ' // };\n' + - ' // }\n' + - '\n' + - ' // afterInstall(options) {\n' + - ' // // Perform extra work here.\n' + - ' // }\n' + - '};' - ); - }) - ); - - it( - 'dummy http-mock foo', - co.wrap(function*() { - yield generateInAddon(['http-mock', 'foo', '--dummy']); - - expect(file('server/index.js')).to.contain('mocks.forEach(route => route(app));'); - - expect(file('server/mocks/foo.js')).to.contain( - 'module.exports = function(app) {\n' + - " const express = require('express');\n" + - ' let fooRouter = express.Router();\n' + - '\n' + - " fooRouter.get('/', function(req, res) {\n" + - ' res.send({\n' + - " 'foo': []\n" + - ' });\n' + - ' });\n' + - '\n' + - " fooRouter.post('/', function(req, res) {\n" + - ' res.status(201).end();\n' + - ' });\n' + - '\n' + - " fooRouter.get('/:id', function(req, res) {\n" + - ' res.send({\n' + - " 'foo': {\n" + - ' id: req.params.id\n' + - ' }\n' + - ' });\n' + - ' });\n' + - '\n' + - " fooRouter.put('/:id', function(req, res) {\n" + - ' res.send({\n' + - " 'foo': {\n" + - ' id: req.params.id\n' + - ' }\n' + - ' });\n' + - ' });\n' + - '\n' + - " fooRouter.delete('/:id', function(req, res) {\n" + - ' res.status(204).end();\n' + - ' });\n' + - '\n' + - ' // The POST and PUT call will not contain a request body\n' + - ' // because the body-parser is not included by default.\n' + - ' // To use req.body, run:\n' + - '\n' + - ' // npm install --save-dev body-parser\n' + - '\n' + - ' // After installing, you need to `use` the body-parser for\n' + - ' // this mock uncommenting the following line:\n' + - ' //\n' + - " //app.use('/api/foo', require('body-parser').json());\n" + - " app.use('/api/foo', fooRouter);\n" + - '};\n' - ); - - expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); - }) - ); - - it( - 'dummy http-mock foo-bar', - co.wrap(function*() { - yield generateInAddon(['http-mock', 'foo-bar', '--dummy']); - - expect(file('server/index.js')).to.contain('mocks.forEach(route => route(app));'); - - expect(file('server/mocks/foo-bar.js')).to.contain( + it('dummy blueprint foo', async function () { + await generateInAddon(['blueprint', 'foo', '--dummy']); + + expect(file('blueprints/foo/index.js')).to.contain( + 'module.exports = {\n' + + " description: ''\n" + + '\n' + + ' // locals(options) {\n' + + ' // // Return custom template variables here.\n' + + ' // return {\n' + + ' // foo: options.entity.options.foo\n' + + ' // };\n' + + ' // }\n' + + '\n' + + ' // afterInstall(options) {\n' + + ' // // Perform extra work here.\n' + + ' // }\n' + + '};' + ); + }); + + it('dummy blueprint foo/bar', async function () { + await generateInAddon(['blueprint', 'foo/bar', '--dummy']); + + expect(file('blueprints/foo/bar/index.js')).to.contain( + 'module.exports = {\n' + + " description: ''\n" + + '\n' + + ' // locals(options) {\n' + + ' // // Return custom template variables here.\n' + + ' // return {\n' + + ' // foo: options.entity.options.foo\n' + + ' // };\n' + + ' // }\n' + + '\n' + + ' // afterInstall(options) {\n' + + ' // // Perform extra work here.\n' + + ' // }\n' + + '};' + ); + }); + + it('dummy http-mock foo', async function () { + await generateInAddon(['http-mock', 'foo', '--dummy']); + + expect(file('server/index.js')).to.contain('mocks.forEach(route => route(app));'); + + expect(file('server/mocks/foo.js')).to.contain( + 'module.exports = function(app) {\n' + + " const express = require('express');\n" + + ' let fooRouter = express.Router();\n' + + '\n' + + " fooRouter.get('/', function(req, res) {\n" + + ' res.send({\n' + + " 'foo': []\n" + + ' });\n' + + ' });\n' + + '\n' + + " fooRouter.post('/', function(req, res) {\n" + + ' res.status(201).end();\n' + + ' });\n' + + '\n' + + " fooRouter.get('/:id', function(req, res) {\n" + + ' res.send({\n' + + " 'foo': {\n" + + ' id: req.params.id\n' + + ' }\n' + + ' });\n' + + ' });\n' + + '\n' + + " fooRouter.put('/:id', function(req, res) {\n" + + ' res.send({\n' + + " 'foo': {\n" + + ' id: req.params.id\n' + + ' }\n' + + ' });\n' + + ' });\n' + + '\n' + + " fooRouter.delete('/:id', function(req, res) {\n" + + ' res.status(204).end();\n' + + ' });\n' + + '\n' + + ' // The POST and PUT call will not contain a request body\n' + + ' // because the body-parser is not included by default.\n' + + ' // To use req.body, run:\n' + + '\n' + + ' // npm install --save-dev body-parser\n' + + '\n' + + ' // After installing, you need to `use` the body-parser for\n' + + ' // this mock uncommenting the following line:\n' + + ' //\n' + + " //app.use('/api/foo', require('body-parser').json());\n" + + " app.use('/api/foo', fooRouter);\n" + + '};\n' + ); + + expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); + }); + + it('dummy http-mock foo-bar', async function () { + await generateInAddon(['http-mock', 'foo-bar', '--dummy']); + + expect(file('server/index.js')).to.contain('mocks.forEach(route => route(app));'); + + expect(file('server/mocks/foo-bar.js')).to.contain( + 'module.exports = function(app) {\n' + + " const express = require('express');\n" + + ' let fooBarRouter = express.Router();\n' + + '\n' + + " fooBarRouter.get('/', function(req, res) {\n" + + ' res.send({\n' + + " 'foo-bar': []\n" + + ' });\n' + + ' });\n' + + '\n' + + " fooBarRouter.post('/', function(req, res) {\n" + + ' res.status(201).end();\n' + + ' });\n' + + '\n' + + " fooBarRouter.get('/:id', function(req, res) {\n" + + ' res.send({\n' + + " 'foo-bar': {\n" + + ' id: req.params.id\n' + + ' }\n' + + ' });\n' + + ' });\n' + + '\n' + + " fooBarRouter.put('/:id', function(req, res) {\n" + + ' res.send({\n' + + " 'foo-bar': {\n" + + ' id: req.params.id\n' + + ' }\n' + + ' });\n' + + ' });\n' + + '\n' + + " fooBarRouter.delete('/:id', function(req, res) {\n" + + ' res.status(204).end();\n' + + ' });\n' + + '\n' + + ' // The POST and PUT call will not contain a request body\n' + + ' // because the body-parser is not included by default.\n' + + ' // To use req.body, run:\n' + + '\n' + + ' // npm install --save-dev body-parser\n' + + '\n' + + ' // After installing, you need to `use` the body-parser for\n' + + ' // this mock uncommenting the following line:\n' + + ' //\n' + + " //app.use('/api/foo-bar', require('body-parser').json());\n" + + " app.use('/api/foo-bar', fooBarRouter);\n" + + '};\n' + ); + + expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); + }); + + it('dummy http-proxy foo', async function () { + await generateInAddon(['http-proxy', 'foo', 'http://localhost:5000', '--dummy']); + + expect(file('server/index.js')).to.contain('proxies.forEach(route => route(app));'); + + expect(file('server/proxies/foo.js')).to.contain( + "const proxyPath = '/foo';\n" + + '\n' + 'module.exports = function(app) {\n' + - " const express = require('express');\n" + - ' let fooBarRouter = express.Router();\n' + - '\n' + - " fooBarRouter.get('/', function(req, res) {\n" + - ' res.send({\n' + - " 'foo-bar': []\n" + - ' });\n' + - ' });\n' + - '\n' + - " fooBarRouter.post('/', function(req, res) {\n" + - ' res.status(201).end();\n' + - ' });\n' + - '\n' + - " fooBarRouter.get('/:id', function(req, res) {\n" + - ' res.send({\n' + - " 'foo-bar': {\n" + - ' id: req.params.id\n' + - ' }\n' + - ' });\n' + - ' });\n' + - '\n' + - " fooBarRouter.put('/:id', function(req, res) {\n" + - ' res.send({\n' + - " 'foo-bar': {\n" + - ' id: req.params.id\n' + - ' }\n' + - ' });\n' + - ' });\n' + - '\n' + - " fooBarRouter.delete('/:id', function(req, res) {\n" + - ' res.status(204).end();\n' + - ' });\n' + - '\n' + - ' // The POST and PUT call will not contain a request body\n' + - ' // because the body-parser is not included by default.\n' + - ' // To use req.body, run:\n' + - '\n' + - ' // npm install --save-dev body-parser\n' + - '\n' + - ' // After installing, you need to `use` the body-parser for\n' + - ' // this mock uncommenting the following line:\n' + - ' //\n' + - " //app.use('/api/foo-bar', require('body-parser').json());\n" + - " app.use('/api/foo-bar', fooBarRouter);\n" + - '};\n' - ); - - expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); - }) - ); - - it( - 'dummy http-proxy foo', - co.wrap(function*() { - yield generateInAddon(['http-proxy', 'foo', 'http://localhost:5000', '--dummy']); - - expect(file('server/index.js')).to.contain('proxies.forEach(route => route(app));'); - - expect(file('server/proxies/foo.js')).to.contain( - "const proxyPath = '/foo';\n" + - '\n' + - 'module.exports = function(app) {\n' + - ' // For options, see:\n' + - ' // https://github.com/nodejitsu/node-http-proxy\n' + - " let proxy = require('http-proxy').createProxyServer({});\n" + - '\n' + - " proxy.on('error', function(err, req) {\n" + - ' console.error(err, req.url);\n' + - ' });\n' + - '\n' + - ' app.use(proxyPath, function(req, res, next){\n' + - ' // include root path in proxied request\n' + - " req.url = proxyPath + '/' + req.url;\n" + - " proxy.web(req, res, { target: 'http://localhost:5000' });\n" + - ' });\n' + - '};' - ); - - expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); - }) - ); - - it( - 'dummy server', - co.wrap(function*() { - yield generateInAddon(['server', '--dummy']); - expect(file('server/index.js')).to.exist; - }) - ); + ' // For options, see:\n' + + ' // https://github.com/nodejitsu/node-http-proxy\n' + + " let proxy = require('http-proxy').createProxyServer({});\n" + + '\n' + + " proxy.on('error', function(err, req) {\n" + + ' console.error(err, req.url);\n' + + ' });\n' + + '\n' + + ' app.use(proxyPath, function(req, res, next){\n' + + ' // include root path in proxied request\n' + + " req.url = proxyPath + '/' + req.url;\n" + + " proxy.web(req, res, { target: 'http://localhost:5000' });\n" + + ' });\n' + + '};' + ); + + expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); + }); + + it('dummy server', async function () { + await generateInAddon(['server', '--dummy']); + expect(file('server/index.js')).to.exist; + }); + + // ember addon foo --lang + // ------------------------------- + // Good: Correct Usage + it('ember addon foo --lang=(valid code): no message + set `lang` in index.html', async function () { + await ember(['addon', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--lang=en-US']); + expect(file('tests/dummy/app/index.html')).to.contain(''); + }); + + // Edge Case: both valid code AND programming language abbreviation, possible misuse + it('ember addon foo --lang=(valid code + programming language abbreviation): emit warning + set `lang` in index.html', async function () { + await ember(['addon', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--lang=css']); + expect(file('tests/dummy/app/index.html')).to.contain(''); + }); + + // Misuse: possibly an attempt to set app programming language + it('ember addon foo --lang=(programming language): emit warning + do not set `lang` in index.html', async function () { + await ember(['addon', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--lang=JavaScript']); + expect(file('tests/dummy/app/index.html')).to.contain(''); + }); + + // Misuse: possibly an attempt to set app programming language + it('ember addon foo --lang=(programming language abbreviation): emit warning + do not set `lang` in index.html', async function () { + await ember(['addon', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--lang=JS']); + expect(file('tests/dummy/app/index.html')).to.contain(''); + }); + + // Misuse: possibly an attempt to set app programming language + it('ember addon foo --lang=(programming language file extension): emit warning + do not set `lang` in index.html', async function () { + await ember(['addon', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--lang=.js']); + expect(file('tests/dummy/app/index.html')).to.contain(''); + }); + + // Misuse: Invalid Country Code + it('ember addon foo --lang=(invalid code): emit warning + do not set `lang` in index.html', async function () { + await ember(['addon', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--lang=en-UK']); + expect(file('tests/dummy/app/index.html')).to.contain(''); + }); }); diff --git a/tests/acceptance/addon-generate-test.js b/tests/acceptance/addon-generate-test.js index 9b8466dfac..367744ddf0 100644 --- a/tests/acceptance/addon-generate-test.js +++ b/tests/acceptance/addon-generate-test.js @@ -1,45 +1,37 @@ 'use strict'; -const co = require('co'); -const RSVP = require('rsvp'); const ember = require('../helpers/ember'); const path = require('path'); const fs = require('fs-extra'); -let outputFile = RSVP.denodeify(fs.outputFile); -let ensureDir = RSVP.denodeify(fs.ensureDir); -let remove = RSVP.denodeify(fs.remove); let root = process.cwd(); let tmproot = path.join(root, 'tmp'); const Blueprint = require('../../lib/models/blueprint'); const BlueprintNpmTask = require('ember-cli-internal-test-helpers/lib/helpers/disable-npm-on-blueprint'); const mkTmpDirIn = require('../../lib/utilities/mk-tmp-dir-in'); -const { isExperimentEnabled } = require('../../lib/experiments'); const chai = require('../chai'); let expect = chai.expect; let file = chai.file; -describe('Acceptance: ember generate in-addon', function() { +describe('Acceptance: ember generate in-addon', function () { this.timeout(20000); - before(function() { + before(function () { BlueprintNpmTask.disableNPM(Blueprint); }); - after(function() { + after(function () { BlueprintNpmTask.restoreNPM(Blueprint); }); - beforeEach( - co.wrap(function*() { - let tmpdir = yield mkTmpDirIn(tmproot); - process.chdir(tmpdir); - }) - ); + beforeEach(async function () { + let tmpdir = await mkTmpDirIn(tmproot); + process.chdir(tmpdir); + }); - afterEach(function() { + afterEach(function () { process.chdir(root); - return remove(tmproot); + return fs.remove(tmproot); }); function initAddon(name) { @@ -60,307 +52,237 @@ describe('Acceptance: ember generate in-addon', function() { name = arguments[1]; } - return initAddon(name).then(function() { + return initAddon(name).then(function () { return ember(generateArgs); }); } - it( - 'in-addon addon-import cannot be called directly', - co.wrap(function*() { - try { - yield generateInAddon(['addon-import', 'foo']); - } catch (error) { - expect(error.name).to.equal('SilentError'); - expect(error.message).to.equal('You cannot call the addon-import blueprint directly.'); - } - }) - ); - - if (isExperimentEnabled('MODULE_UNIFICATION')) { - it( - 'does not run the `addon-import` blueprint from a module unification addon', - co.wrap(function*() { - yield initAddon('my-addon'); - yield ensureDir('src'); - - yield outputFile( - 'blueprints/service/files/__root__/__path__/__name__.js', - "import Service from '@ember/service';\n" + 'export default Service.extend({ });\n' - ); - - yield ember(['generate', 'service', 'session']); - - expect(file('app/services/session.js')).to.not.exist; - }) + it('in-addon addon-import cannot be called directly', async function () { + try { + await generateInAddon(['addon-import', 'foo']); + } catch (error) { + expect(error.name).to.equal('SilentError'); + expect(error.message).to.equal('You cannot call the addon-import blueprint directly.'); + } + }); + + it('runs the `addon-import` blueprint from a classic addon', async function () { + await initAddon('my-addon'); + + await fs.outputFile( + 'blueprints/service/files/__root__/__path__/__name__.js', + "import Service from '@ember/service';\n" + 'export default Service.extend({ });\n' ); - } else { - it( - 'runs the `addon-import` blueprint from a classic addon', - co.wrap(function*() { - yield initAddon('my-addon'); - yield outputFile( - 'blueprints/service/files/__root__/__path__/__name__.js', - "import Service from '@ember/service';\n" + 'export default Service.extend({ });\n' - ); + await ember(['generate', 'service', 'session']); + + expect(file('app/services/session.js')).to.exist; + }); - yield ember(['generate', 'service', 'session']); + it('runs a custom "*-addon" blueprint from a classic addon', async function () { + await initAddon('my-addon'); - expect(file('app/services/session.js')).to.exist; - }) + await fs.outputFile( + 'blueprints/service/files/__root__/__path__/__name__.js', + "import Service from '@ember/service';\n" + 'export default Service.extend({ });\n' ); - } - if (isExperimentEnabled('MODULE_UNIFICATION')) { - it( - 'skips a custom "*-addon" blueprint from a module unification addon', - co.wrap(function*() { - yield initAddon('my-addon'); - yield ensureDir('src'); + await fs.outputFile( + 'blueprints/service-addon/files/app/services/session.js', + "export { default } from 'somewhere';\n" + ); - yield outputFile( - 'blueprints/service/files/__root__/__path__/__name__.js', - "import Service from '@ember/service';\n" + 'export default Service.extend({ });\n' - ); + await ember(['generate', 'service', 'session']); - yield outputFile( - 'blueprints/service-addon/files/app/services/session.js', - "export { default } from 'somewhere';\n" - ); + expect(file('app/services/session.js')).to.exist; + }); - yield ember(['generate', 'service', 'session']); + it('in-addon blueprint foo', async function () { + await generateInAddon(['blueprint', 'foo']); + + expect(file('blueprints/foo/index.js')).to.contain( + 'module.exports = {\n' + + " description: ''\n" + + '\n' + + ' // locals(options) {\n' + + ' // // Return custom template variables here.\n' + + ' // return {\n' + + ' // foo: options.entity.options.foo\n' + + ' // };\n' + + ' // }\n' + + '\n' + + ' // afterInstall(options) {\n' + + ' // // Perform extra work here.\n' + + ' // }\n' + + '};' + ); + }); - expect(file('app/services/session.js')).to.not.exist; - }) + it('in-addon blueprint foo/bar', async function () { + await generateInAddon(['blueprint', 'foo/bar']); + + expect(file('blueprints/foo/bar/index.js')).to.contain( + 'module.exports = {\n' + + " description: ''\n" + + '\n' + + ' // locals(options) {\n' + + ' // // Return custom template variables here.\n' + + ' // return {\n' + + ' // foo: options.entity.options.foo\n' + + ' // };\n' + + ' // }\n' + + '\n' + + ' // afterInstall(options) {\n' + + ' // // Perform extra work here.\n' + + ' // }\n' + + '};' ); - } else { - it( - 'runs a custom "*-addon" blueprint from a classic addon', - co.wrap(function*() { - yield initAddon('my-addon'); - - yield outputFile( - 'blueprints/service/files/__root__/__path__/__name__.js', - "import Service from '@ember/service';\n" + 'export default Service.extend({ });\n' - ); - - yield outputFile( - 'blueprints/service-addon/files/app/services/session.js', - "export { default } from 'somewhere';\n" - ); - - yield ember(['generate', 'service', 'session']); - - expect(file('app/services/session.js')).to.exist; - }) + }); + + it('in-addon http-mock foo', async function () { + await generateInAddon(['http-mock', 'foo']); + + expect(file('server/index.js')).to.contain('mocks.forEach(route => route(app));'); + + expect(file('server/mocks/foo.js')).to.contain( + 'module.exports = function(app) {\n' + + " const express = require('express');\n" + + ' let fooRouter = express.Router();\n' + + '\n' + + " fooRouter.get('/', function(req, res) {\n" + + ' res.send({\n' + + " 'foo': []\n" + + ' });\n' + + ' });\n' + + '\n' + + " fooRouter.post('/', function(req, res) {\n" + + ' res.status(201).end();\n' + + ' });\n' + + '\n' + + " fooRouter.get('/:id', function(req, res) {\n" + + ' res.send({\n' + + " 'foo': {\n" + + ' id: req.params.id\n' + + ' }\n' + + ' });\n' + + ' });\n' + + '\n' + + " fooRouter.put('/:id', function(req, res) {\n" + + ' res.send({\n' + + " 'foo': {\n" + + ' id: req.params.id\n' + + ' }\n' + + ' });\n' + + ' });\n' + + '\n' + + " fooRouter.delete('/:id', function(req, res) {\n" + + ' res.status(204).end();\n' + + ' });\n' + + '\n' + + ' // The POST and PUT call will not contain a request body\n' + + ' // because the body-parser is not included by default.\n' + + ' // To use req.body, run:\n' + + '\n' + + ' // npm install --save-dev body-parser\n' + + '\n' + + ' // After installing, you need to `use` the body-parser for\n' + + ' // this mock uncommenting the following line:\n' + + ' //\n' + + " //app.use('/api/foo', require('body-parser').json());\n" + + " app.use('/api/foo', fooRouter);\n" + + '};' ); - } - it( - 'in-addon blueprint foo', - co.wrap(function*() { - yield generateInAddon(['blueprint', 'foo']); - - expect(file('blueprints/foo/index.js')).to.contain( - 'module.exports = {\n' + - " description: ''\n" + - '\n' + - ' // locals(options) {\n' + - ' // // Return custom template variables here.\n' + - ' // return {\n' + - ' // foo: options.entity.options.foo\n' + - ' // };\n' + - ' // }\n' + - '\n' + - ' // afterInstall(options) {\n' + - ' // // Perform extra work here.\n' + - ' // }\n' + - '};' - ); - }) - ); - - it( - 'in-addon blueprint foo/bar', - co.wrap(function*() { - yield generateInAddon(['blueprint', 'foo/bar']); - - expect(file('blueprints/foo/bar/index.js')).to.contain( - 'module.exports = {\n' + - " description: ''\n" + - '\n' + - ' // locals(options) {\n' + - ' // // Return custom template variables here.\n' + - ' // return {\n' + - ' // foo: options.entity.options.foo\n' + - ' // };\n' + - ' // }\n' + - '\n' + - ' // afterInstall(options) {\n' + - ' // // Perform extra work here.\n' + - ' // }\n' + - '};' - ); - }) - ); - - it( - 'in-addon http-mock foo', - co.wrap(function*() { - yield generateInAddon(['http-mock', 'foo']); - - expect(file('server/index.js')).to.contain('mocks.forEach(route => route(app));'); - - expect(file('server/mocks/foo.js')).to.contain( - 'module.exports = function(app) {\n' + - " const express = require('express');\n" + - ' let fooRouter = express.Router();\n' + - '\n' + - " fooRouter.get('/', function(req, res) {\n" + - ' res.send({\n' + - " 'foo': []\n" + - ' });\n' + - ' });\n' + - '\n' + - " fooRouter.post('/', function(req, res) {\n" + - ' res.status(201).end();\n' + - ' });\n' + - '\n' + - " fooRouter.get('/:id', function(req, res) {\n" + - ' res.send({\n' + - " 'foo': {\n" + - ' id: req.params.id\n' + - ' }\n' + - ' });\n' + - ' });\n' + - '\n' + - " fooRouter.put('/:id', function(req, res) {\n" + - ' res.send({\n' + - " 'foo': {\n" + - ' id: req.params.id\n' + - ' }\n' + - ' });\n' + - ' });\n' + - '\n' + - " fooRouter.delete('/:id', function(req, res) {\n" + - ' res.status(204).end();\n' + - ' });\n' + - '\n' + - ' // The POST and PUT call will not contain a request body\n' + - ' // because the body-parser is not included by default.\n' + - ' // To use req.body, run:\n' + - '\n' + - ' // npm install --save-dev body-parser\n' + - '\n' + - ' // After installing, you need to `use` the body-parser for\n' + - ' // this mock uncommenting the following line:\n' + - ' //\n' + - " //app.use('/api/foo', require('body-parser').json());\n" + - " app.use('/api/foo', fooRouter);\n" + - '};' - ); - - expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); - }) - ); - - it( - 'in-addon http-mock foo-bar', - co.wrap(function*() { - yield generateInAddon(['http-mock', 'foo-bar']); - - expect(file('server/index.js')).to.contain('mocks.forEach(route => route(app));'); - - expect(file('server/mocks/foo-bar.js')).to.contain( + expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); + }); + + it('in-addon http-mock foo-bar', async function () { + await generateInAddon(['http-mock', 'foo-bar']); + + expect(file('server/index.js')).to.contain('mocks.forEach(route => route(app));'); + + expect(file('server/mocks/foo-bar.js')).to.contain( + 'module.exports = function(app) {\n' + + " const express = require('express');\n" + + ' let fooBarRouter = express.Router();\n' + + '\n' + + " fooBarRouter.get('/', function(req, res) {\n" + + ' res.send({\n' + + " 'foo-bar': []\n" + + ' });\n' + + ' });\n' + + '\n' + + " fooBarRouter.post('/', function(req, res) {\n" + + ' res.status(201).end();\n' + + ' });\n' + + '\n' + + " fooBarRouter.get('/:id', function(req, res) {\n" + + ' res.send({\n' + + " 'foo-bar': {\n" + + ' id: req.params.id\n' + + ' }\n' + + ' });\n' + + ' });\n' + + '\n' + + " fooBarRouter.put('/:id', function(req, res) {\n" + + ' res.send({\n' + + " 'foo-bar': {\n" + + ' id: req.params.id\n' + + ' }\n' + + ' });\n' + + ' });\n' + + '\n' + + " fooBarRouter.delete('/:id', function(req, res) {\n" + + ' res.status(204).end();\n' + + ' });\n' + + '\n' + + ' // The POST and PUT call will not contain a request body\n' + + ' // because the body-parser is not included by default.\n' + + ' // To use req.body, run:\n' + + '\n' + + ' // npm install --save-dev body-parser\n' + + '\n' + + ' // After installing, you need to `use` the body-parser for\n' + + ' // this mock uncommenting the following line:\n' + + ' //\n' + + " //app.use('/api/foo-bar', require('body-parser').json());\n" + + " app.use('/api/foo-bar', fooBarRouter);\n" + + '};' + ); + + expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); + }); + + it('in-addon http-proxy foo', async function () { + await generateInAddon(['http-proxy', 'foo', 'http://localhost:5000']); + + expect(file('server/index.js')).to.contain('proxies.forEach(route => route(app));'); + + expect(file('server/proxies/foo.js')).to.contain( + "const proxyPath = '/foo';\n" + + '\n' + 'module.exports = function(app) {\n' + - " const express = require('express');\n" + - ' let fooBarRouter = express.Router();\n' + - '\n' + - " fooBarRouter.get('/', function(req, res) {\n" + - ' res.send({\n' + - " 'foo-bar': []\n" + - ' });\n' + - ' });\n' + - '\n' + - " fooBarRouter.post('/', function(req, res) {\n" + - ' res.status(201).end();\n' + - ' });\n' + - '\n' + - " fooBarRouter.get('/:id', function(req, res) {\n" + - ' res.send({\n' + - " 'foo-bar': {\n" + - ' id: req.params.id\n' + - ' }\n' + - ' });\n' + - ' });\n' + - '\n' + - " fooBarRouter.put('/:id', function(req, res) {\n" + - ' res.send({\n' + - " 'foo-bar': {\n" + - ' id: req.params.id\n' + - ' }\n' + - ' });\n' + - ' });\n' + - '\n' + - " fooBarRouter.delete('/:id', function(req, res) {\n" + - ' res.status(204).end();\n' + - ' });\n' + - '\n' + - ' // The POST and PUT call will not contain a request body\n' + - ' // because the body-parser is not included by default.\n' + - ' // To use req.body, run:\n' + - '\n' + - ' // npm install --save-dev body-parser\n' + - '\n' + - ' // After installing, you need to `use` the body-parser for\n' + - ' // this mock uncommenting the following line:\n' + - ' //\n' + - " //app.use('/api/foo-bar', require('body-parser').json());\n" + - " app.use('/api/foo-bar', fooBarRouter);\n" + - '};' - ); - - expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); - }) - ); - - it( - 'in-addon http-proxy foo', - co.wrap(function*() { - yield generateInAddon(['http-proxy', 'foo', 'http://localhost:5000']); - - expect(file('server/index.js')).to.contain('proxies.forEach(route => route(app));'); - - expect(file('server/proxies/foo.js')).to.contain( - "const proxyPath = '/foo';\n" + - '\n' + - 'module.exports = function(app) {\n' + - ' // For options, see:\n' + - ' // https://github.com/nodejitsu/node-http-proxy\n' + - " let proxy = require('http-proxy').createProxyServer({});\n" + - '\n' + - " proxy.on('error', function(err, req) {\n" + - ' console.error(err, req.url);\n' + - ' });\n' + - '\n' + - ' app.use(proxyPath, function(req, res, next){\n' + - ' // include root path in proxied request\n' + - " req.url = proxyPath + '/' + req.url;\n" + - " proxy.web(req, res, { target: 'http://localhost:5000' });\n" + - ' });\n' + - '};' - ); - - expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); - }) - ); - - it( - 'in-addon server', - co.wrap(function*() { - yield generateInAddon(['server']); - expect(file('server/index.js')).to.exist; - }) - ); + ' // For options, see:\n' + + ' // https://github.com/nodejitsu/node-http-proxy\n' + + " let proxy = require('http-proxy').createProxyServer({});\n" + + '\n' + + " proxy.on('error', function(err, req) {\n" + + ' console.error(err, req.url);\n' + + ' });\n' + + '\n' + + ' app.use(proxyPath, function(req, res, next){\n' + + ' // include root path in proxied request\n' + + " req.url = proxyPath + '/' + req.url;\n" + + " proxy.web(req, res, { target: 'http://localhost:5000' });\n" + + ' });\n' + + '};' + ); + + expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); + }); + + it('in-addon server', async function () { + await generateInAddon(['server']); + expect(file('server/index.js')).to.exist; + }); }); diff --git a/tests/acceptance/addon-smoke-test-slow.js b/tests/acceptance/addon-smoke-test-slow.js index f98b5cec9e..af51a70250 100644 --- a/tests/acceptance/addon-smoke-test-slow.js +++ b/tests/acceptance/addon-smoke-test-slow.js @@ -1,13 +1,11 @@ 'use strict'; -const co = require('co'); -const Promise = require('rsvp').Promise; const path = require('path'); const fs = require('fs-extra'); const spawn = require('child_process').spawn; +const execa = require('execa'); const chalk = require('chalk'); -const { isExperimentEnabled } = require('../../lib/experiments'); const runCommand = require('../helpers/run-command'); const ember = require('../helpers/ember'); const copyFixtureFiles = require('../helpers/copy-fixture-files'); @@ -24,10 +22,10 @@ let dir = chai.dir; let addonName = 'some-cool-addon'; let addonRoot; -describe('Acceptance: addon-smoke-test', function() { +describe('Acceptance: addon-smoke-test', function () { this.timeout(450000); - before(function() { + before(function () { return createTestTargets(addonName, { command: 'addon', }); @@ -35,13 +33,14 @@ describe('Acceptance: addon-smoke-test', function() { after(teardownTestTargets); - beforeEach(function() { + beforeEach(function () { addonRoot = linkDependencies(addonName); process.env.JOBS = '1'; }); - afterEach(function() { + afterEach(function () { + runCommand.killAll(); // Cleans up a folder set up on the other side of a symlink. fs.removeSync(path.join(addonRoot, 'node_modules', 'developing-addon')); @@ -51,7 +50,7 @@ describe('Acceptance: addon-smoke-test', function() { delete process.env.JOBS; }); - it('generates package.json with proper metadata', function() { + it('generates package.json with proper metadata', function () { let packageContents = fs.readJsonSync('package.json'); expect(packageContents.name).to.equal(addonName); @@ -60,121 +59,119 @@ describe('Acceptance: addon-smoke-test', function() { expect(packageContents['ember-addon']).to.deep.equal({ configPath: 'tests/dummy/config' }); }); - (isExperimentEnabled('MODULE_UNIFICATION') ? it.skip : it)('ember addon foo, clean from scratch', function() { + it('ember addon foo, clean from scratch', function () { return ember(['test']); }); - it( - 'works in most common scenarios for an example addon', - co.wrap(function*() { - let fixtureFile = isExperimentEnabled('MODULE_UNIFICATION') ? 'kitchen-sink-mu' : 'kitchen-sink'; - yield copyFixtureFiles(`addon/${fixtureFile}`); + it('works in most common scenarios for an example addon', async function () { + await copyFixtureFiles('addon/kitchen-sink'); - let packageJsonPath = path.join(addonRoot, 'package.json'); - let packageJson = fs.readJsonSync(packageJsonPath); + let packageJsonPath = path.join(addonRoot, 'package.json'); + let packageJson = fs.readJsonSync(packageJsonPath); - expect(packageJson.devDependencies['ember-source']).to.not.be.empty; + expect(packageJson.devDependencies['ember-source']).to.not.be.empty; - packageJson.dependencies = packageJson.dependencies || {}; - // add HTMLBars for templates (generators do this automatically when components/templates are added) - packageJson.dependencies['ember-cli-htmlbars'] = 'latest'; + packageJson.dependencies = packageJson.dependencies || {}; + // add HTMLBars for templates (generators do this automatically when components/templates are added) + packageJson.dependencies['ember-cli-htmlbars'] = 'latest'; - fs.writeJsonSync(packageJsonPath, packageJson); + fs.writeJsonSync(packageJsonPath, packageJson); - let result = yield runCommand('node_modules/ember-cli/bin/ember', 'build'); + let result = await runCommand('node_modules/ember-cli/bin/ember', 'build'); - expect(result.code).to.eql(0); - let contents; + expect(result.code).to.eql(0); + let contents; - let indexPath = path.join(addonRoot, 'dist', 'index.html'); - contents = fs.readFileSync(indexPath, { encoding: 'utf8' }); - expect(contents).to.contain('"SOME AWESOME STUFF"'); + let indexPath = path.join(addonRoot, 'dist', 'index.html'); + contents = fs.readFileSync(indexPath, { encoding: 'utf8' }); + expect(contents).to.contain('"SOME AWESOME STUFF"'); - let cssPath = path.join(addonRoot, 'dist', 'assets', 'vendor.css'); - contents = fs.readFileSync(cssPath, { encoding: 'utf8' }); - expect(contents).to.contain('addon/styles/app.css is present'); + let cssPath = path.join(addonRoot, 'dist', 'assets', 'vendor.css'); + contents = fs.readFileSync(cssPath, { encoding: 'utf8' }); + expect(contents).to.equal('/* addon/styles/app.css is present */\n'); - let robotsPath = path.join(addonRoot, 'dist', 'robots.txt'); - contents = fs.readFileSync(robotsPath, { encoding: 'utf8' }); - expect(contents).to.contain('tests/dummy/public/robots.txt is present'); + let robotsPath = path.join(addonRoot, 'dist', 'robots.txt'); + contents = fs.readFileSync(robotsPath, { encoding: 'utf8' }); + expect(contents).to.contain('tests/dummy/public/robots.txt is present'); - result = yield runCommand('node_modules/ember-cli/bin/ember', 'test'); + result = await runCommand('node_modules/ember-cli/bin/ember', 'test'); - expect(result.code).to.eql(0); - }) - ); + expect(result.code).to.eql(0); + }); - it( - 'npm pack does not include unnecessary files', - co.wrap(function*() { - let handleError = function(error, commandName) { - if (error.code === 'ENOENT') { - console.warn(chalk.yellow(` Your system does not provide ${commandName} -> Skipped this test.`)); - } else { - throw new Error(error); - } - }; + it("works for addon's that specify a nested addon entry point", async function () { + await copyFixtureFiles('addon/nested-addon-main'); - try { - yield npmPack(); - } catch (error) { - return handleError(error, 'npm'); - } + let packageJsonPath = path.join(addonRoot, 'package.json'); + let packageJson = fs.readJsonSync(packageJsonPath); - let output; - try { - output = yield tar(); - } catch (error) { - return handleError(error, 'tar'); - } + expect(packageJson.devDependencies['ember-source']).to.not.be.empty; + expect(packageJson.devDependencies['ember-cli']).to.not.be.empty; - let unnecessaryFiles = [ - '.gitkeep', - '.travis.yml', - '.editorconfig', - 'testem.js', - '.ember-cli', - 'bower.json', - '.bowerrc', - ]; + fs.writeJsonSync(packageJsonPath, packageJson); - let unnecessaryFolders = ['tests/', 'bower_components/']; + let result = await runCommand('node_modules/ember-cli/bin/ember', 'build'); - let outputFiles = output.split('\n'); - expect(outputFiles).to.not.contain(unnecessaryFiles); - expect(outputFiles).to.not.contain(unnecessaryFolders); - }) - ); + expect(result.code).to.eql(0); + let contents; - if (isExperimentEnabled('MODULE_UNIFICATION')) { - it( - 'can run a MU unit test with a relative import', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/mu-unit-test-with-relative-import'); + let indexPath = path.join(addonRoot, 'dist', 'assets', 'vendor.js'); + contents = fs.readFileSync(indexPath, { encoding: 'utf8' }); + expect(contents).to.contain('"nested-addon-main/components/simple-component"'); + }); - let packageJsonPath = path.join(addonRoot, 'package.json'); - let packageJson = fs.readJsonSync(packageJsonPath); + it('npm pack does not include unnecessary files', async function () { + let handleError = function (error, commandName) { + if (error.code === 'ENOENT') { + console.warn(chalk.yellow(` Your system does not provide ${commandName} -> Skipped this test.`)); + } else { + throw new Error(error); + } + }; - packageJson.dependencies = packageJson.dependencies || {}; - // add HTMLBars for templates (generators do this automatically when components/templates are added) - packageJson.dependencies['ember-cli-htmlbars'] = 'latest'; + try { + await npmPack(); + } catch (error) { + return handleError(error, 'npm'); + } - fs.writeJsonSync(packageJsonPath, packageJson); + let output; + try { + let result = await tar(); + output = result.stdout; + } catch (error) { + return handleError(error, 'tar'); + } - let result = yield runCommand('node_modules/ember-cli/bin/ember', 'build'); - expect(result.code).to.eql(0); + let necessaryFiles = ['package.json', 'index.js', 'LICENSE.md', 'README.md', 'config/environment.js']; - let appFileContents = fs.readFileSync(path.join(addonRoot, 'dist', 'assets', 'tests.js'), { - encoding: 'utf8', - }); + let unnecessaryFiles = [ + '.gitkeep', + '.travis.yml', + '.editorconfig', + 'testem.js', + '.ember-cli', + 'bower.json', + '.bowerrc', + ]; - expect(appFileContents).to.include('Unit | Utility | string'); + let unnecessaryFolders = [/^tests\//, /^bower_components\//]; - result = yield runCommand('node_modules/ember-cli/bin/ember', 'test'); - expect(result.code).to.eql(0); - }) - ); - } + let outputFiles = output + .split('\n') + .filter(Boolean) + .map((f) => f.replace(/^package\//, '')); + + expect(outputFiles, 'verify our assumptions about the output structure').to.include.members(necessaryFiles); + + expect(outputFiles).to.not.have.members(unnecessaryFiles); + + for (let unnecessaryFolder of unnecessaryFolders) { + for (let outputFile of outputFiles) { + expect(outputFile).to.not.match(unnecessaryFolder); + } + } + }); }); function npmPack() { @@ -185,16 +182,12 @@ function npmPack() { }); } -function tar() { - return new Promise((resolve, reject) => { - let output; - let fileName = `${addonName}-0.0.0.tgz`; - if (fs.existsSync(fileName) === false) { - throw new Error(`unknown file: '${path.resolve(fileName)}'`); - } - let tar = spawn('tar', ['-tf', fileName]); - tar.on('error', reject); - tar.stdout.on('data', data => (output = data.toString())); - tar.on('close', () => resolve(output)); - }); +async function tar() { + let fileName = `${addonName}-0.0.0.tgz`; + + if (fs.existsSync(fileName) === false) { + throw new Error(`unknown file: '${path.resolve(fileName)}'`); + } + + return execa('tar', ['-tf', fileName]); } diff --git a/tests/acceptance/brocfile-smoke-test-slow.js b/tests/acceptance/brocfile-smoke-test-slow.js index d4cb1f0cfb..60296bf6ed 100644 --- a/tests/acceptance/brocfile-smoke-test-slow.js +++ b/tests/acceptance/brocfile-smoke-test-slow.js @@ -1,15 +1,13 @@ 'use strict'; -const co = require('co'); -const RSVP = require('rsvp'); const path = require('path'); const fs = require('fs-extra'); -let remove = RSVP.denodeify(fs.remove); const { isExperimentEnabled } = require('../../lib/experiments'); const runCommand = require('../helpers/run-command'); const acceptance = require('../helpers/acceptance'); const copyFixtureFiles = require('../helpers/copy-fixture-files'); +const DistChecker = require('../helpers/dist-checker'); let createTestTargets = acceptance.createTestTargets; let teardownTestTargets = acceptance.teardownTestTargets; let linkDependencies = acceptance.linkDependencies; @@ -23,252 +21,195 @@ let dir = chai.dir; let appName = 'some-cool-app'; let appRoot; -describe('Acceptance: brocfile-smoke-test', function() { +describe('Acceptance: brocfile-smoke-test', function () { this.timeout(500000); - before(function() { + before(function () { return createTestTargets(appName); }); after(teardownTestTargets); - beforeEach(function() { + beforeEach(function () { appRoot = linkDependencies(appName); }); - afterEach(function() { + afterEach(function () { + runCommand.killAll(); cleanupRun(appName); expect(dir(appRoot)).to.not.exist; }); - it( - 'a custom EmberENV in config/environment.js is used for window.EmberENV', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/custom-ember-env'); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + it('a custom EmberENV in config/environment.js is used for window.EmberENV', async function () { + await copyFixtureFiles('brocfile-tests/custom-ember-env'); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - let vendorContents = fs.readFileSync(path.join('dist', 'assets', 'vendor.js'), { - encoding: 'utf8', - }); + let checker = new DistChecker(path.join(appRoot, 'dist')); + await checker.evalScripts(); + let { window } = checker; - // Changes in ember-optional-features 0.7.0 cause all defined values in optional-features.json - // to end up in EmberENV. jquery-integration is explicitly defined for non MU apps - let expected = 'window.EmberENV = {"asdflkmawejf":";jlnu3yr23","_JQUERY_INTEGRATION":false};'; - expect(vendorContents).to.contain(expected, 'EmberENV should be in assets/vendor.js'); - }) - ); - - it( - 'a custom environment config can be used in Brocfile.js', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/custom-environment-config'); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'test'); - }) - ); - - if (!isExperimentEnabled('MODULE_UNIFICATION')) { - it( - 'without app/templates', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/pods-templates'); - yield remove(path.join(process.cwd(), 'app/templates')); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'test'); - }) - ); - } + // Changes in config/optional-features.json end up being set in EmberENV + expect(window.EmberENV.asdflkmawejf).to.eql(';jlnu3yr23'); + expect(window.EmberENV._APPLICATION_TEMPLATE_WRAPPER).to.be.false; + expect(window.EmberENV._DEFAULT_ASYNC_OBSERVERS).to.be.true; + expect(window.EmberENV._JQUERY_INTEGRATION).to.be.false; + expect(window.EmberENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS).to.be.true; + }); - it( - 'strips app/styles or app/templates from JS', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/styles-and-templates-stripped'); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + it('a custom environment config can be used in Brocfile.js', async function () { + await copyFixtureFiles('brocfile-tests/custom-environment-config'); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'test'); + }); - let appFileContents = fs.readFileSync(path.join(appRoot, 'dist', 'assets', `${appName}.js`), { - encoding: 'utf8', - }); + it('without app/templates', async function () { + await copyFixtureFiles('brocfile-tests/pods-templates'); + await fs.remove(path.join(process.cwd(), 'app/templates')); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'test'); + }); - expect(appFileContents).to.include('//app/templates-stuff.js'); - expect(appFileContents).to.include('//app/styles-manager.js'); - }) - ); - - it( - 'should throw if no build file is found', - co.wrap(function*() { - fs.removeSync('./ember-cli-build.js'); - try { - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - } catch (err) { - expect(err.code).to.eql(1); - } - }) - ); + it('strips app/styles or app/templates from JS', async function () { + await copyFixtureFiles('brocfile-tests/styles-and-templates-stripped'); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - it( - 'using autoRun: true', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/auto-run-true'); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + let checker = new DistChecker(path.join(appRoot, 'dist')); - let appFileContents = fs.readFileSync(path.join(appRoot, 'dist', 'assets', `${appName}.js`), { - encoding: 'utf8', - }); - const regex = isExperimentEnabled('MODULE_UNIFICATION') - ? /\/main"\)\["default"\]\.create\(/ - : /\/app"\)\["default"\]\.create\(/; - expect(appFileContents).to.match(regex); - }) - ); - - it( - 'using autoRun: false', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/auto-run-false'); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - - let appFileContents = fs.readFileSync(path.join(appRoot, 'dist', 'assets', `${appName}.js`), { - encoding: 'utf8', - }); + expect(checker.contains('js', '//app/templates-stuff.js')).to.be; + expect(checker.contains('js', '//app/styles-manager.js')).to.be; + }); + + it('should throw if no build file is found', async function () { + fs.removeSync('./ember-cli-build.js'); + try { + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + } catch (err) { + expect(err.code).to.eql(1); + } + }); - const regex = isExperimentEnabled('MODULE_UNIFICATION') - ? /\/main"\)\["default"\]\.create\(/ - : /\/app"\)\["default"\]\.create\(/; - expect(appFileContents).to.not.match(regex); - }) - ); + it('using autoRun: true', async function () { + await copyFixtureFiles('brocfile-tests/auto-run-true'); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - it( - 'app.import works properly with test tree files', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/app-test-import'); + let checker = new DistChecker(path.join(appRoot, 'dist')); + await checker.evalScripts(); + + let { window } = checker; + + expect(window.APP_HAS_LOADED).to.be.true; + }); + + it('using autoRun: false', async function () { + await copyFixtureFiles('brocfile-tests/auto-run-false'); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + + let checker = new DistChecker(path.join(appRoot, 'dist')); + await checker.evalScripts(); + + let { window } = checker; + + expect(window.APP_HAS_LOADED).to.be.undefined; + }); + + // we dont run postprocessTree in embroider + if (!isExperimentEnabled('EMBROIDER')) { + it('app.import works properly with test tree files', async function () { + await copyFixtureFiles('brocfile-tests/app-test-import'); let packageJsonPath = path.join(appRoot, 'package.json'); let packageJson = fs.readJsonSync(packageJsonPath); packageJson.devDependencies['ember-test-addon'] = 'latest'; fs.writeJsonSync(packageJsonPath, packageJson); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - let subjectFileContents = fs.readFileSync(path.join(appRoot, 'dist', 'assets', 'test-support.js'), { - encoding: 'utf8', - }); + let checker = new DistChecker(path.join(appRoot, 'dist')); + expect(checker.contains('js', '// File for test tree imported and added via postprocessTree()')).to.be; + }); + } - expect(subjectFileContents).to.contain('// File for test tree imported and added via postprocessTree()'); - }) - ); + it('app.import works properly with non-js/css files', async function () { + await copyFixtureFiles('brocfile-tests/app-import'); - it( - 'app.import works properly with non-js/css files', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/app-import'); + let packageJsonPath = path.join(appRoot, 'package.json'); + let packageJson = fs.readJsonSync(packageJsonPath); + packageJson.devDependencies['ember-random-addon'] = 'latest'; + fs.writeJsonSync(packageJsonPath, packageJson); - let packageJsonPath = path.join(appRoot, 'package.json'); - let packageJson = fs.readJsonSync(packageJsonPath); - packageJson.devDependencies['ember-random-addon'] = 'latest'; - fs.writeJsonSync(packageJsonPath, packageJson); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + let subjectFileContents = fs.readFileSync(path.join(appRoot, 'dist', 'assets', 'file-to-import.txt'), { + encoding: 'utf8', + }); - let subjectFileContents = fs.readFileSync(path.join(appRoot, 'dist', 'assets', 'file-to-import.txt'), { - encoding: 'utf8', - }); + expect(subjectFileContents).to.equal('EXAMPLE TEXT FILE CONTENT\n'); + }); - expect(subjectFileContents).to.equal('EXAMPLE TEXT FILE CONTENT\n'); - }) - ); + it('addons can have a public tree that is merged and returned namespaced by default', async function () { + await copyFixtureFiles('brocfile-tests/public-tree'); - it( - 'addons can have a public tree that is merged and returned namespaced by default', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/public-tree'); + let packageJsonPath = path.join(appRoot, 'package.json'); + let packageJson = fs.readJsonSync(packageJsonPath); + packageJson.devDependencies['ember-random-addon'] = 'latest'; + fs.writeJsonSync(packageJsonPath, packageJson); - let packageJsonPath = path.join(appRoot, 'package.json'); - let packageJson = fs.readJsonSync(packageJsonPath); - packageJson.devDependencies['ember-random-addon'] = 'latest'; - fs.writeJsonSync(packageJsonPath, packageJson); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + let subjectFileContents = fs.readFileSync(path.join(appRoot, 'dist', 'ember-random-addon', 'some-root-file.txt'), { + encoding: 'utf8', + }); - let subjectFileContents = fs.readFileSync( - path.join(appRoot, 'dist', 'ember-random-addon', 'some-root-file.txt'), - { - encoding: 'utf8', - } - ); + expect(subjectFileContents).to.equal('ROOT FILE\n'); + }); - expect(subjectFileContents).to.equal('ROOT FILE\n'); - }) - ); - - if (!isExperimentEnabled('MODULE_UNIFICATION')) { - it( - 'using pods based templates', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/pods-templates'); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'test'); - }) - ); - - it( - 'using pods based templates with a podModulePrefix', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/pods-with-prefix-templates'); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'test'); - }) - ); - - it( - 'addon trees are not jshinted', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/jshint-addon'); - - let packageJsonPath = path.join(appRoot, 'package.json'); - let packageJson = fs.readJsonSync(packageJsonPath); - packageJson['ember-addon'] = { - paths: ['./lib/ember-random-thing'], - }; - fs.writeJsonSync(packageJsonPath, packageJson); - - let ember = path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'); - - let error = yield expect(runCommand(ember, 'test', '--filter=jshint')).to.eventually.be.rejected; - - expect(error.output.join('')).to.include('Error: No tests matched the filter "jshint"'); - }) - ); - } + it('using pods based templates', async function () { + await copyFixtureFiles('brocfile-tests/pods-templates'); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'test'); + }); - it( - 'multiple css files in styles/ are output when a preprocessor is not used', - co.wrap(function*() { - let fixtureFolder = isExperimentEnabled('MODULE_UNIFICATION') ? 'multiple-css-files-mu' : 'multiple-css-files'; - yield copyFixtureFiles(`brocfile-tests/${fixtureFolder}`); + it('using pods based templates with a podModulePrefix', async function () { + await copyFixtureFiles('brocfile-tests/pods-with-prefix-templates'); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'test'); + }); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + it('addon trees are not jshinted', async function () { + await copyFixtureFiles('brocfile-tests/jshint-addon'); - let files = ['/assets/some-cool-app.css', '/assets/other.css']; + let packageJsonPath = path.join(appRoot, 'package.json'); + let packageJson = fs.readJsonSync(packageJsonPath); + packageJson['ember-addon'] = { + paths: ['./lib/ember-random-thing'], + }; + fs.writeJsonSync(packageJsonPath, packageJson); - let basePath = path.join(appRoot, 'dist'); - files.forEach(function(f) { - expect(file(path.join(basePath, f))).to.exist; - }); - }) - ); - - it( - 'specifying custom output paths works properly', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/custom-output-paths'); - - let themeCSSPath; - if (isExperimentEnabled('MODULE_UNIFICATION')) { - themeCSSPath = path.join(appRoot, 'src', 'ui', 'styles', 'theme.css'); - } else { - themeCSSPath = path.join(appRoot, 'app', 'styles', 'theme.css'); - } + let ember = path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'); + + let error = await expect(runCommand(ember, 'test', '--filter=jshint')).to.eventually.be.rejected; + + expect(error.output.join('')).to.include('Error: No tests matched the filter "jshint"'); + }); + + it('multiple css files in styles/ are output when a preprocessor is not used', async function () { + await copyFixtureFiles('brocfile-tests/multiple-css-files'); + + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + + let files = ['/assets/some-cool-app.css', '/assets/other.css']; + + let basePath = path.join(appRoot, 'dist'); + files.forEach(function (f) { + expect(file(path.join(basePath, f))).to.exist; + }); + }); + + // custom outputPaths are deprecated under embroider + if (!isExperimentEnabled('EMBROIDER')) { + it('specifying custom output paths works properly', async function () { + await copyFixtureFiles('brocfile-tests/custom-output-paths'); + + let themeCSSPath = path.join(appRoot, 'app', 'styles', 'theme.css'); fs.writeFileSync(themeCSSPath, 'html, body { margin: 20%; }'); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); let files = [ '/css/app.css', @@ -282,114 +223,127 @@ describe('Acceptance: brocfile-smoke-test', function() { ]; let basePath = path.join(appRoot, 'dist'); - files.forEach(function(f) { + files.forEach(function (f) { expect(file(path.join(basePath, f))).to.exist; }); - }) - ); + }); + } - it( - 'specifying outputFile results in an explicitly generated assets', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/app-import-output-file'); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + it('specifying outputFile results in an explicitly generated assets', async function () { + await copyFixtureFiles('brocfile-tests/app-import-output-file'); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - let files = ['/assets/output-file.js', '/assets/output-file.css', '/assets/vendor.css', '/assets/vendor.js']; + let files = ['/assets/output-file.js', '/assets/output-file.css', '/assets/vendor.css', '/assets/vendor.js']; - let basePath = path.join(appRoot, 'dist'); - files.forEach(function(f) { - expect(file(path.join(basePath, f))).to.exist; - }); - }) - ); + let basePath = path.join(appRoot, 'dist'); + files.forEach(function (f) { + expect(file(path.join(basePath, f))).to.exist; + }); + }); - it( - 'can use transformation to turn anonymous AMD into named AMD', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/app-import-anonymous-amd'); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + it('can use transformation to turn anonymous AMD into named AMD', async function () { + await copyFixtureFiles('brocfile-tests/app-import-anonymous-amd'); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + + let outputJS = fs.readFileSync(path.join(appRoot, 'dist', 'assets', 'output.js'), { + encoding: 'utf8', + }); + + (function () { + let defineCount = 0; + // eslint-disable-next-line no-unused-vars + function define(name, deps, factory) { + expect(name).to.equal('hello-world'); + expect(deps).to.deep.equal([]); + expect(factory()()).to.equal('Hello World'); + defineCount++; + } + /* eslint-disable no-eval */ + eval(outputJS); + /* eslint-enable no-eval */ + expect(defineCount).to.eql(1); + })(); + }); - let outputJS = fs.readFileSync(path.join(appRoot, 'dist', 'assets', 'output.js'), { - encoding: 'utf8', - }); + it('can use transformation to turn named UMD into named AMD', async function () { + await copyFixtureFiles('brocfile-tests/app-import-named-umd'); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + + let outputJS = fs.readFileSync(path.join(appRoot, 'dist', 'assets', 'output.js'), { + encoding: 'utf8', + }); + + (function () { + let defineCount = 0; + // eslint-disable-next-line no-unused-vars + function define(name, deps, factory) { + expect(name).to.equal('hello-world'); + expect(deps).to.deep.equal([]); + expect(factory()()).to.equal('Hello World'); + defineCount++; + } + /* eslint-disable no-eval */ + eval(outputJS); + /* eslint-enable no-eval */ + expect(defineCount).to.eql(1); + })(); + }); - (function() { - let defineCount = 0; - // eslint-disable-next-line no-unused-vars - function define(name, deps, factory) { - expect(name).to.equal('hello-world'); - expect(deps).to.deep.equal([]); - expect(factory()()).to.equal('Hello World'); - defineCount++; - } - /* eslint-disable no-eval */ - eval(outputJS); - /* eslint-enable no-eval */ - expect(defineCount).to.eql(1); - })(); - }) - ); - - it( - 'can do amd transform from addon', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/app-import-custom-transform'); + it('can do amd transform from addon', async function () { + await copyFixtureFiles('brocfile-tests/app-import-custom-transform'); - let packageJsonPath = path.join(appRoot, 'package.json'); - let packageJson = fs.readJsonSync(packageJsonPath); - packageJson.devDependencies['ember-transform-addon'] = 'latest'; - fs.writeJsonSync(packageJsonPath, packageJson); + let packageJsonPath = path.join(appRoot, 'package.json'); + let packageJson = fs.readJsonSync(packageJsonPath); + packageJson.devDependencies['ember-transform-addon'] = 'latest'; + fs.writeJsonSync(packageJsonPath, packageJson); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - let addonOutputJs = fs.readFileSync(path.join(appRoot, 'dist', 'assets', 'addon-output.js'), { - encoding: 'utf8', - }); + let addonOutputJs = fs.readFileSync(path.join(appRoot, 'dist', 'assets', 'addon-output.js'), { + encoding: 'utf8', + }); - (function() { - let defineCount = 0; - // eslint-disable-next-line no-unused-vars - function define(name, deps, factory) { - expect(name).to.equal('addon-vendor'); - expect(deps).to.deep.equal([]); - expect(factory()()).to.equal('Hello World'); - defineCount++; - } - /* eslint-disable no-eval */ - eval(addonOutputJs); - /* eslint-enable no-eval */ - expect(defineCount).to.eql(1); - })(); - }) - ); - - it( - 'can use transformation to turn library into custom transformation', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/app-import-custom-transform'); + (function () { + let defineCount = 0; + // eslint-disable-next-line no-unused-vars + function define(name, deps, factory) { + expect(name).to.equal('addon-vendor'); + expect(deps).to.deep.equal([]); + expect(factory()()).to.equal('Hello World'); + defineCount++; + } + /* eslint-disable no-eval */ + eval(addonOutputJs); + /* eslint-enable no-eval */ + expect(defineCount).to.eql(1); + })(); + }); - let packageJsonPath = path.join(appRoot, 'package.json'); - let packageJson = fs.readJsonSync(packageJsonPath); - packageJson.devDependencies['ember-transform-addon'] = 'latest'; - fs.writeJsonSync(packageJsonPath, packageJson); + it('can use transformation to turn library into custom transformation', async function () { + await copyFixtureFiles('brocfile-tests/app-import-custom-transform'); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + let packageJsonPath = path.join(appRoot, 'package.json'); + let packageJson = fs.readJsonSync(packageJsonPath); + packageJson.devDependencies['ember-transform-addon'] = 'latest'; + fs.writeJsonSync(packageJsonPath, packageJson); - let outputJS = fs.readFileSync(path.join(appRoot, 'dist', 'assets', 'output.js'), { - encoding: 'utf8', - }); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - expect(outputJS).to.be.equal( + let checker = new DistChecker(path.join(appRoot, 'dist')); + + expect( + checker.contains( + 'js', 'if (typeof FastBoot === \'undefined\') { window.hello = "hello world"; }//# sourceMappingURL=output.map\n' - ); - }) - ); + ) + ).to.be; + }); - // skipped because of potentially broken assertion that should be fixed correctly at a later point - it.skip( - 'specifying partial `outputPaths` hash deep merges options correctly', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/custom-output-paths'); + // custom outputPaths are deprecated under embroider + if (!isExperimentEnabled('EMBROIDER')) { + // skipped because of potentially broken assertion that should be fixed correctly at a later point + it.skip('specifying partial `outputPaths` hash deep merges options correctly', async function () { + await copyFixtureFiles('brocfile-tests/custom-output-paths'); let themeCSSPath = path.join(appRoot, 'app', 'styles', 'theme.css'); fs.writeFileSync(themeCSSPath, 'html, body { margin: 20%; }'); @@ -404,7 +358,7 @@ describe('Acceptance: brocfile-smoke-test', function() { fs.writeFileSync(brocfilePath, brocfile, 'utf8'); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); let files = [ '/css/theme/a.css', @@ -416,26 +370,25 @@ describe('Acceptance: brocfile-smoke-test', function() { ]; let basePath = path.join(appRoot, 'dist'); - files.forEach(function(f) { + files.forEach(function (f) { expect(file(path.join(basePath, f))).to.exist; }); expect(file(path.join(basePath, '/assets/some-cool-app.css'))).to.not.exist; - }) - ); + }); + } - it( - 'multiple paths can be CSS preprocessed', - co.wrap(function*() { - let fixtureFolder = isExperimentEnabled('MODULE_UNIFICATION') ? 'multiple-sass-files-mu' : 'multiple-sass-files'; - yield copyFixtureFiles(`brocfile-tests/${fixtureFolder}`); + // custom outputPaths are deprecated under embroider + if (!isExperimentEnabled('EMBROIDER')) { + it('multiple paths can be CSS preprocessed', async function () { + await copyFixtureFiles('brocfile-tests/multiple-sass-files'); let packageJsonPath = path.join(appRoot, 'package.json'); let packageJson = fs.readJsonSync(packageJsonPath); packageJson.devDependencies['ember-cli-sass'] = 'latest'; fs.writeJsonSync(packageJsonPath, packageJson); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); expect(file('dist/assets/main.css')).to.equal( 'body { background: black; }\n', @@ -446,79 +399,50 @@ describe('Acceptance: brocfile-smoke-test', function() { '.theme { color: red; }\n', 'theme/a.css contains correct content' ); - }) - ); + }); + } - it( - 'app.css is output to .css by default', - co.wrap(function*() { - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - expect(file(`dist/assets/${appName}.css`)).to.exist; - }) - ); + it('app.css is output to .css by default', async function () { + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + expect(file(`dist/assets/${appName}.css`)).to.exist; + }); // for backwards compat. - it( - 'app.scss is output to .css by default', - co.wrap(function*() { - let fixtureFolder = isExperimentEnabled('MODULE_UNIFICATION') ? 'multiple-sass-files-mu' : 'multiple-sass-files'; - yield copyFixtureFiles(`brocfile-tests/${fixtureFolder}`); + it('app.scss is output to .css by default', async function () { + await copyFixtureFiles('brocfile-tests/multiple-sass-files'); - let brocfilePath = path.join(appRoot, 'ember-cli-build.js'); - let brocfile = fs.readFileSync(brocfilePath, 'utf8'); + let brocfilePath = path.join(appRoot, 'ember-cli-build.js'); + let brocfile = fs.readFileSync(brocfilePath, 'utf8'); - // remove custom preprocessCss paths, use app.scss instead - brocfile = brocfile.replace(/outputPaths.*/, ''); + // remove custom preprocessCss paths, use app.scss instead + brocfile = brocfile.replace(/outputPaths.*/, ''); - fs.writeFileSync(brocfilePath, brocfile, 'utf8'); + fs.writeFileSync(brocfilePath, brocfile, 'utf8'); - let packageJsonPath = path.join(appRoot, 'package.json'); - let packageJson = fs.readJsonSync(packageJsonPath); - packageJson.devDependencies['ember-cli-sass'] = 'latest'; - fs.writeJsonSync(packageJsonPath, packageJson); - - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - - expect(file(`dist/assets/${appName}.css`)).to.equal('body { background: green; }\n'); - }) - ); + let packageJsonPath = path.join(appRoot, 'package.json'); + let packageJson = fs.readJsonSync(packageJsonPath); + packageJson.devDependencies['ember-cli-sass'] = 'latest'; + fs.writeJsonSync(packageJsonPath, packageJson); - it( - 'additional trees can be passed to the app', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/additional-trees'); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build', { verbose: true }); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - let files = [ - '/assets/custom-output-file.js', - '/assets/custom-output-file.css', - '/assets/vendor.css', - '/assets/vendor.js', - ]; + expect(file(`dist/assets/${appName}.css`)).to.equal('body { background: green; }\n'); + }); - let basePath = path.join(appRoot, 'dist'); - files.forEach(function(f) { - expect(file(path.join(basePath, f))).to.exist; - }); - }) - ); - - if (isExperimentEnabled('MODULE_UNIFICATION')) { - it( - 'can run a MU unit test with a relative import', - co.wrap(function*() { - yield copyFixtureFiles('brocfile-tests/mu-unit-test-with-relative-import'); - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - - let appFileContents = fs.readFileSync(path.join(appRoot, 'dist', 'assets', 'tests.js'), { - encoding: 'utf8', - }); - - expect(appFileContents).to.include('Unit | Utility | string'); - - let result = yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'test'); - expect(result.code).to.eql(0); - }) - ); - } + it('additional trees can be passed to the app', async function () { + await copyFixtureFiles('brocfile-tests/additional-trees'); + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build', { verbose: true }); + + let files = [ + '/assets/custom-output-file.js', + '/assets/custom-output-file.css', + '/assets/vendor.css', + '/assets/vendor.js', + ]; + + let basePath = path.join(appRoot, 'dist'); + files.forEach(function (f) { + expect(file(path.join(basePath, f))).to.exist; + }); + }); }); diff --git a/tests/acceptance/destroy-test.js b/tests/acceptance/destroy-test.js index d60cd5e726..0857a61cee 100644 --- a/tests/acceptance/destroy-test.js +++ b/tests/acceptance/destroy-test.js @@ -1,12 +1,8 @@ 'use strict'; -const co = require('co'); -const RSVP = require('rsvp'); const ember = require('../helpers/ember'); const fs = require('fs-extra'); -let outputFile = RSVP.denodeify(fs.outputFile); const path = require('path'); -let remove = RSVP.denodeify(fs.remove); let root = process.cwd(); let tmproot = path.join(root, 'tmp'); const mkTmpDirIn = require('../../lib/utilities/mk-tmp-dir-in'); @@ -18,28 +14,26 @@ const chai = require('../chai'); let expect = chai.expect; let file = chai.file; -describe('Acceptance: ember destroy', function() { +describe('Acceptance: ember destroy', function () { this.timeout(60000); let tmpdir; - before(function() { + before(function () { BlueprintNpmTask.disableNPM(Blueprint); }); - after(function() { + after(function () { BlueprintNpmTask.restoreNPM(Blueprint); }); - beforeEach( - co.wrap(function*() { - tmpdir = yield mkTmpDirIn(tmproot); - process.chdir(tmpdir); - }) - ); + beforeEach(async function () { + tmpdir = await mkTmpDirIn(tmproot); + process.chdir(tmpdir); + }); - afterEach(function() { + afterEach(function () { process.chdir(root); - return remove(tmproot); + return fs.remove(tmproot); }); function initApp() { @@ -57,108 +51,99 @@ describe('Acceptance: ember destroy', function() { } function assertFilesExist(files) { - files.forEach(function(f) { + files.forEach(function (f) { expect(file(f)).to.exist; }); } function assertFilesNotExist(files) { - files.forEach(function(f) { + files.forEach(function (f) { expect(file(f)).to.not.exist; }); } - const assertDestroyAfterGenerate = co.wrap(function*(args, files) { - yield initApp(); + const assertDestroyAfterGenerate = async function (args, files) { + await initApp(); - yield generate(args); + await generate(args); assertFilesExist(files); - let result = yield destroy(args); + let result = await destroy(args); expect(result, 'destroy command did not exit with errorCode').to.be.an('object'); assertFilesNotExist(files); - }); + }; - it('blueprint foo', function() { + it('blueprint foo', function () { let commandArgs = ['blueprint', 'foo']; let files = ['blueprints/foo/index.js']; return assertDestroyAfterGenerate(commandArgs, files); }); - it('blueprint foo/bar', function() { + it('blueprint foo/bar', function () { let commandArgs = ['blueprint', 'foo/bar']; let files = ['blueprints/foo/bar/index.js']; return assertDestroyAfterGenerate(commandArgs, files); }); - it('http-mock foo', function() { + it('http-mock foo', function () { let commandArgs = ['http-mock', 'foo']; let files = ['server/mocks/foo.js']; return assertDestroyAfterGenerate(commandArgs, files); }); - it('http-proxy foo', function() { + it('http-proxy foo', function () { let commandArgs = ['http-proxy', 'foo', 'bar']; let files = ['server/proxies/foo.js']; return assertDestroyAfterGenerate(commandArgs, files); }); - it( - 'deletes files generated using blueprints from the project directory', - co.wrap(function*() { - let commandArgs = ['foo', 'bar']; - let files = ['app/foos/bar.js']; - yield initApp(); - - yield outputFile( - 'blueprints/foo/files/app/foos/__name__.js', - "import Ember from 'ember';\n\n" + 'export default Ember.Object.extend({ foo: true });\n' - ); - - yield generate(commandArgs); - assertFilesExist(files); - - yield destroy(commandArgs); - assertFilesNotExist(files); - }) - ); - - it( - 'correctly identifies the root of the project', - co.wrap(function*() { - let commandArgs = ['controller', 'foo']; - let files = ['app/controllers/foo.js']; - yield initApp(); - - yield outputFile( - 'blueprints/controller/files/app/controllers/__name__.js', - "import Ember from 'ember';\n\n" + 'export default Ember.Controller.extend({ custom: true });\n' - ); - - yield generate(commandArgs); - assertFilesExist(files); - - process.chdir(path.join(tmpdir, 'app')); - yield destroy(commandArgs); - - process.chdir(tmpdir); - assertFilesNotExist(files); - }) - ); - - it( - 'http-mock does not remove server/', - co.wrap(function*() { - yield initApp(); - yield generate(['http-mock', 'foo']); - yield generate(['http-mock', 'bar']); - yield destroy(['http-mock', 'foo']); - - expect(file('server/index.js')).to.exist; - }) - ); + it('deletes files generated using blueprints from the project directory', async function () { + let commandArgs = ['foo', 'bar']; + let files = ['app/foos/bar.js']; + await initApp(); + + await fs.outputFile( + 'blueprints/foo/files/app/foos/__name__.js', + "import Ember from 'ember';\n\n" + 'export default Ember.Object.extend({ foo: true });\n' + ); + + await generate(commandArgs); + assertFilesExist(files); + + await destroy(commandArgs); + assertFilesNotExist(files); + }); + + it('correctly identifies the root of the project', async function () { + let commandArgs = ['controller', 'foo']; + let files = ['app/controllers/foo.js']; + await initApp(); + + await fs.outputFile( + 'blueprints/controller/files/app/controllers/__name__.js', + "import Ember from 'ember';\n\n" + 'export default Ember.Controller.extend({ custom: true });\n' + ); + + await generate(commandArgs); + assertFilesExist(files); + + process.chdir(path.join(tmpdir, 'app')); + await destroy(commandArgs); + + process.chdir(tmpdir); + assertFilesNotExist(files); + }); + + it('http-mock does not remove server/', async function () { + await initApp(); + await generate(['http-mock', 'foo']); + await generate(['http-mock', 'bar']); + await destroy(['http-mock', 'foo']); + + expect(file('server/index.js')).to.exist; + }); }); diff --git a/tests/acceptance/generate-test.js b/tests/acceptance/generate-test.js index 09c8d2dd89..49aef43485 100755 --- a/tests/acceptance/generate-test.js +++ b/tests/acceptance/generate-test.js @@ -1,45 +1,45 @@ 'use strict'; -const co = require('co'); -const RSVP = require('rsvp'); +const util = require('util'); const ember = require('../helpers/ember'); const fs = require('fs-extra'); -let outputFile = RSVP.denodeify(fs.outputFile); +let outputFile = util.promisify(fs.outputFile); const path = require('path'); -let remove = RSVP.denodeify(fs.remove); +let remove = util.promisify(fs.remove); const replaceFile = require('ember-cli-internal-test-helpers/lib/helpers/file-utils').replaceFile; let root = process.cwd(); let tmproot = path.join(root, 'tmp'); const Blueprint = require('../../lib/models/blueprint'); const BlueprintNpmTask = require('ember-cli-internal-test-helpers/lib/helpers/disable-npm-on-blueprint'); const mkTmpDirIn = require('../../lib/utilities/mk-tmp-dir-in'); +const td = require('testdouble'); +const lintFix = require('../../lib/utilities/lint-fix'); const chai = require('../chai'); let expect = chai.expect; let file = chai.file; let dir = chai.dir; -describe('Acceptance: ember generate', function() { +describe('Acceptance: ember generate', function () { this.timeout(20000); let tmpdir; - before(function() { + before(function () { BlueprintNpmTask.disableNPM(Blueprint); }); - after(function() { + after(function () { BlueprintNpmTask.restoreNPM(Blueprint); }); - beforeEach( - co.wrap(function*() { - tmpdir = yield mkTmpDirIn(tmproot); - process.chdir(tmpdir); - }) - ); + beforeEach(async function () { + tmpdir = await mkTmpDirIn(tmproot); + process.chdir(tmpdir); + }); - afterEach(function() { + afterEach(function () { + td.reset(); process.chdir(root); return remove(tmproot); }); @@ -57,329 +57,301 @@ describe('Acceptance: ember generate', function() { function generate(args) { let generateArgs = ['generate'].concat(args); - return initApp().then(function() { + return initApp().then(function () { return ember(generateArgs); }); } - it( - 'blueprint foo', - co.wrap(function*() { - yield generate(['blueprint', 'foo']); - - expect(file('blueprints/foo/index.js')).to.contain( - 'module.exports = {\n' + - " description: ''\n" + - '\n' + - ' // locals(options) {\n' + - ' // // Return custom template variables here.\n' + - ' // return {\n' + - ' // foo: options.entity.options.foo\n' + - ' // };\n' + - ' // }\n' + - '\n' + - ' // afterInstall(options) {\n' + - ' // // Perform extra work here.\n' + - ' // }\n' + - '};' - ); - }) - ); - - it( - 'blueprint foo/bar', - co.wrap(function*() { - yield generate(['blueprint', 'foo/bar']); - - expect(file('blueprints/foo/bar/index.js')).to.contain( - 'module.exports = {\n' + - " description: ''\n" + - '\n' + - ' // locals(options) {\n' + - ' // // Return custom template variables here.\n' + - ' // return {\n' + - ' // foo: options.entity.options.foo\n' + - ' // };\n' + - ' // }\n' + - '\n' + - ' // afterInstall(options) {\n' + - ' // // Perform extra work here.\n' + - ' // }\n' + - '};' - ); - }) - ); - - it( - 'http-mock foo', - co.wrap(function*() { - yield generate(['http-mock', 'foo']); - - expect(file('server/index.js')).to.contain('mocks.forEach(route => route(app));'); - - expect(file('server/mocks/foo.js')).to.contain( - 'module.exports = function(app) {\n' + - " const express = require('express');\n" + - ' let fooRouter = express.Router();\n' + - '\n' + - " fooRouter.get('/', function(req, res) {\n" + - ' res.send({\n' + - " 'foo': []\n" + - ' });\n' + - ' });\n' + - '\n' + - " fooRouter.post('/', function(req, res) {\n" + - ' res.status(201).end();\n' + - ' });\n' + - '\n' + - " fooRouter.get('/:id', function(req, res) {\n" + - ' res.send({\n' + - " 'foo': {\n" + - ' id: req.params.id\n' + - ' }\n' + - ' });\n' + - ' });\n' + - '\n' + - " fooRouter.put('/:id', function(req, res) {\n" + - ' res.send({\n' + - " 'foo': {\n" + - ' id: req.params.id\n' + - ' }\n' + - ' });\n' + - ' });\n' + - '\n' + - " fooRouter.delete('/:id', function(req, res) {\n" + - ' res.status(204).end();\n' + - ' });\n' + - '\n' + - ' // The POST and PUT call will not contain a request body\n' + - ' // because the body-parser is not included by default.\n' + - ' // To use req.body, run:\n' + - '\n' + - ' // npm install --save-dev body-parser\n' + - '\n' + - ' // After installing, you need to `use` the body-parser for\n' + - ' // this mock uncommenting the following line:\n' + - ' //\n' + - " //app.use('/api/foo', require('body-parser').json());\n" + - " app.use('/api/foo', fooRouter);\n" + - '};' - ); - - expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); - }) - ); - - it( - 'http-mock foo-bar', - co.wrap(function*() { - yield generate(['http-mock', 'foo-bar']); - - expect(file('server/index.js')).to.contain('mocks.forEach(route => route(app));'); - - expect(file('server/mocks/foo-bar.js')).to.contain( + it('blueprint foo', async function () { + await generate(['blueprint', 'foo']); + + expect(file('blueprints/foo/index.js')).to.contain( + 'module.exports = {\n' + + " description: ''\n" + + '\n' + + ' // locals(options) {\n' + + ' // // Return custom template variables here.\n' + + ' // return {\n' + + ' // foo: options.entity.options.foo\n' + + ' // };\n' + + ' // }\n' + + '\n' + + ' // afterInstall(options) {\n' + + ' // // Perform extra work here.\n' + + ' // }\n' + + '};' + ); + }); + + it('blueprint foo/bar', async function () { + await generate(['blueprint', 'foo/bar']); + + expect(file('blueprints/foo/bar/index.js')).to.contain( + 'module.exports = {\n' + + " description: ''\n" + + '\n' + + ' // locals(options) {\n' + + ' // // Return custom template variables here.\n' + + ' // return {\n' + + ' // foo: options.entity.options.foo\n' + + ' // };\n' + + ' // }\n' + + '\n' + + ' // afterInstall(options) {\n' + + ' // // Perform extra work here.\n' + + ' // }\n' + + '};' + ); + }); + + it('http-mock foo', async function () { + await generate(['http-mock', 'foo']); + + expect(file('server/index.js')).to.contain('mocks.forEach(route => route(app));'); + + expect(file('server/mocks/foo.js')).to.contain( + 'module.exports = function(app) {\n' + + " const express = require('express');\n" + + ' let fooRouter = express.Router();\n' + + '\n' + + " fooRouter.get('/', function(req, res) {\n" + + ' res.send({\n' + + " 'foo': []\n" + + ' });\n' + + ' });\n' + + '\n' + + " fooRouter.post('/', function(req, res) {\n" + + ' res.status(201).end();\n' + + ' });\n' + + '\n' + + " fooRouter.get('/:id', function(req, res) {\n" + + ' res.send({\n' + + " 'foo': {\n" + + ' id: req.params.id\n' + + ' }\n' + + ' });\n' + + ' });\n' + + '\n' + + " fooRouter.put('/:id', function(req, res) {\n" + + ' res.send({\n' + + " 'foo': {\n" + + ' id: req.params.id\n' + + ' }\n' + + ' });\n' + + ' });\n' + + '\n' + + " fooRouter.delete('/:id', function(req, res) {\n" + + ' res.status(204).end();\n' + + ' });\n' + + '\n' + + ' // The POST and PUT call will not contain a request body\n' + + ' // because the body-parser is not included by default.\n' + + ' // To use req.body, run:\n' + + '\n' + + ' // npm install --save-dev body-parser\n' + + '\n' + + ' // After installing, you need to `use` the body-parser for\n' + + ' // this mock uncommenting the following line:\n' + + ' //\n' + + " //app.use('/api/foo', require('body-parser').json());\n" + + " app.use('/api/foo', fooRouter);\n" + + '};' + ); + + expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); + }); + + it('http-mock foo-bar', async function () { + await generate(['http-mock', 'foo-bar']); + + expect(file('server/index.js')).to.contain('mocks.forEach(route => route(app));'); + + expect(file('server/mocks/foo-bar.js')).to.contain( + 'module.exports = function(app) {\n' + + " const express = require('express');\n" + + ' let fooBarRouter = express.Router();\n' + + '\n' + + " fooBarRouter.get('/', function(req, res) {\n" + + ' res.send({\n' + + " 'foo-bar': []\n" + + ' });\n' + + ' });\n' + + '\n' + + " fooBarRouter.post('/', function(req, res) {\n" + + ' res.status(201).end();\n' + + ' });\n' + + '\n' + + " fooBarRouter.get('/:id', function(req, res) {\n" + + ' res.send({\n' + + " 'foo-bar': {\n" + + ' id: req.params.id\n' + + ' }\n' + + ' });\n' + + ' });\n' + + '\n' + + " fooBarRouter.put('/:id', function(req, res) {\n" + + ' res.send({\n' + + " 'foo-bar': {\n" + + ' id: req.params.id\n' + + ' }\n' + + ' });\n' + + ' });\n' + + '\n' + + " fooBarRouter.delete('/:id', function(req, res) {\n" + + ' res.status(204).end();\n' + + ' });\n' + + '\n' + + ' // The POST and PUT call will not contain a request body\n' + + ' // because the body-parser is not included by default.\n' + + ' // To use req.body, run:\n' + + '\n' + + ' // npm install --save-dev body-parser\n' + + '\n' + + ' // After installing, you need to `use` the body-parser for\n' + + ' // this mock uncommenting the following line:\n' + + ' //\n' + + " //app.use('/api/foo-bar', require('body-parser').json());\n" + + " app.use('/api/foo-bar', fooBarRouter);\n" + + '};' + ); + + expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); + }); + + it('http-proxy foo', async function () { + await generate(['http-proxy', 'foo', 'http://localhost:5000']); + + expect(file('server/index.js')).to.contain('proxies.forEach(route => route(app));'); + + expect(file('server/proxies/foo.js')).to.contain( + "const proxyPath = '/foo';\n" + + '\n' + 'module.exports = function(app) {\n' + - " const express = require('express');\n" + - ' let fooBarRouter = express.Router();\n' + - '\n' + - " fooBarRouter.get('/', function(req, res) {\n" + - ' res.send({\n' + - " 'foo-bar': []\n" + - ' });\n' + - ' });\n' + - '\n' + - " fooBarRouter.post('/', function(req, res) {\n" + - ' res.status(201).end();\n' + - ' });\n' + - '\n' + - " fooBarRouter.get('/:id', function(req, res) {\n" + - ' res.send({\n' + - " 'foo-bar': {\n" + - ' id: req.params.id\n' + - ' }\n' + - ' });\n' + - ' });\n' + - '\n' + - " fooBarRouter.put('/:id', function(req, res) {\n" + - ' res.send({\n' + - " 'foo-bar': {\n" + - ' id: req.params.id\n' + - ' }\n' + - ' });\n' + - ' });\n' + - '\n' + - " fooBarRouter.delete('/:id', function(req, res) {\n" + - ' res.status(204).end();\n' + - ' });\n' + - '\n' + - ' // The POST and PUT call will not contain a request body\n' + - ' // because the body-parser is not included by default.\n' + - ' // To use req.body, run:\n' + - '\n' + - ' // npm install --save-dev body-parser\n' + - '\n' + - ' // After installing, you need to `use` the body-parser for\n' + - ' // this mock uncommenting the following line:\n' + - ' //\n' + - " //app.use('/api/foo-bar', require('body-parser').json());\n" + - " app.use('/api/foo-bar', fooBarRouter);\n" + - '};' - ); - - expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); - }) - ); - - it( - 'http-proxy foo', - co.wrap(function*() { - yield generate(['http-proxy', 'foo', 'http://localhost:5000']); - - expect(file('server/index.js')).to.contain('proxies.forEach(route => route(app));'); - - expect(file('server/proxies/foo.js')).to.contain( - "const proxyPath = '/foo';\n" + - '\n' + - 'module.exports = function(app) {\n' + - ' // For options, see:\n' + - ' // https://github.com/nodejitsu/node-http-proxy\n' + - " let proxy = require('http-proxy').createProxyServer({});\n" + - '\n' + - " proxy.on('error', function(err, req) {\n" + - ' console.error(err, req.url);\n' + - ' });\n' + - '\n' + - ' app.use(proxyPath, function(req, res, next){\n' + - ' // include root path in proxied request\n' + - " req.url = proxyPath + '/' + req.url;\n" + - " proxy.web(req, res, { target: 'http://localhost:5000' });\n" + - ' });\n' + - '};' - ); - - expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); - }) - ); - - it( - 'uses blueprints from the project directory', - co.wrap(function*() { - yield initApp(); - - yield outputFile( - 'blueprints/foo/files/app/foos/__name__.js', - "import Ember from 'ember';\n" + 'export default Ember.Object.extend({ foo: true });\n' - ); - - yield ember(['generate', 'foo', 'bar']); - - expect(file('app/foos/bar.js')).to.contain('foo: true'); - }) - ); - - it( - 'allows custom blueprints to override built-ins', - co.wrap(function*() { - yield initApp(); - yield outputFile( - 'blueprints/controller/files/app/controllers/__name__.js', - "import Ember from 'ember';\n\n" + 'export default Ember.Controller.extend({ custom: true });\n' - ); - - yield ember(['generate', 'controller', 'foo']); - - expect(file('app/controllers/foo.js')).to.contain('custom: true'); - }) - ); - - it( - 'passes custom cli arguments to blueprint options', - co.wrap(function*() { - yield initApp(); - - yield outputFile( - 'blueprints/customblue/files/app/__name__.js', - 'Q: Can I has custom command? A: <%= hasCustomCommand %>' - ); - - yield outputFile( - 'blueprints/customblue/index.js', - 'module.exports = {\n' + - ' locals(options) {\n' + - ' var loc = {};\n' + - " loc.hasCustomCommand = (options.customCommand) ? 'Yes!' : 'No. :C';\n" + - ' return loc;\n' + - ' },\n' + - '};\n' - ); - - yield ember(['generate', 'customblue', 'foo', '--custom-command']); - - expect(file('app/foo.js')).to.contain('A: Yes!'); - }) - ); - - it( - 'correctly identifies the root of the project', - co.wrap(function*() { - yield initApp(); - - yield outputFile( - 'blueprints/controller/files/app/controllers/__name__.js', - "import Ember from 'ember';\n\n" + 'export default Ember.Controller.extend({ custom: true });\n' - ); - - process.chdir(path.join(tmpdir, 'app')); - yield ember(['generate', 'controller', 'foo']); - - process.chdir(tmpdir); - expect(file('app/controllers/foo.js')).to.contain('custom: true'); - }) - ); - - it( - 'server', - co.wrap(function*() { - yield generate(['server']); - expect(file('server/index.js')).to.exist; - }) - ); - - it( - 'lib', - co.wrap(function*() { - yield generate(['lib']); - expect(dir('lib')).to.exist; - }) - ); - - it( - 'custom blueprint availableOptions', - co.wrap(function*() { - yield initApp(); - yield ember(['generate', 'blueprint', 'foo']); - - replaceFile( - 'blueprints/foo/index.js', - 'module.exports = {', - 'module.exports = {\navailableOptions: [ \n' + - "{ name: 'foo',\ntype: String, \n" + - "values: ['one', 'two'],\n" + - "default: 'one',\n" + - "aliases: [ {'one': 'one'}, {'two': 'two'} ] } ],\n" + - 'locals(options) {\n' + - 'return { foo: options.foo };\n' + - '},' - ); - - yield outputFile( - 'blueprints/foo/files/app/foos/__name__.js', - "import Ember from 'ember';\n" + 'export default Ember.Object.extend({ foo: <%= foo %> });\n' - ); - - yield ember(['generate', 'foo', 'bar', '-two']); - - expect(file('app/foos/bar.js')).to.contain('export default Ember.Object.extend({ foo: two });'); - }) - ); + ' // For options, see:\n' + + ' // https://github.com/nodejitsu/node-http-proxy\n' + + " let proxy = require('http-proxy').createProxyServer({});\n" + + '\n' + + " proxy.on('error', function(err, req) {\n" + + ' console.error(err, req.url);\n' + + ' });\n' + + '\n' + + ' app.use(proxyPath, function(req, res, next){\n' + + ' // include root path in proxied request\n' + + " req.url = proxyPath + '/' + req.url;\n" + + " proxy.web(req, res, { target: 'http://localhost:5000' });\n" + + ' });\n' + + '};' + ); + + expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); + }); + + it('uses blueprints from the project directory', async function () { + await initApp(); + + await outputFile( + 'blueprints/foo/files/app/foos/__name__.js', + "import Ember from 'ember';\n" + 'export default Ember.Object.extend({ foo: true });\n' + ); + + await ember(['generate', 'foo', 'bar']); + + expect(file('app/foos/bar.js')).to.contain('foo: true'); + }); + + it('allows custom blueprints to override built-ins', async function () { + await initApp(); + await outputFile( + 'blueprints/controller/files/app/controllers/__name__.js', + "import Ember from 'ember';\n\n" + 'export default Ember.Controller.extend({ custom: true });\n' + ); + + await ember(['generate', 'controller', 'foo']); + + expect(file('app/controllers/foo.js')).to.contain('custom: true'); + }); + + it('passes custom cli arguments to blueprint options', async function () { + await initApp(); + + await outputFile( + 'blueprints/customblue/files/app/__name__.js', + 'Q: Can I has custom command? A: <%= hasCustomCommand %>' + ); + + await outputFile( + 'blueprints/customblue/index.js', + 'module.exports = {\n' + + ' locals(options) {\n' + + ' var loc = {};\n' + + " loc.hasCustomCommand = (options.customCommand) ? 'Yes!' : 'No. :C';\n" + + ' return loc;\n' + + ' },\n' + + '};\n' + ); + + await ember(['generate', 'customblue', 'foo', '--custom-command']); + + expect(file('app/foo.js')).to.contain('A: Yes!'); + }); + + it('correctly identifies the root of the project', async function () { + await initApp(); + + await outputFile( + 'blueprints/controller/files/app/controllers/__name__.js', + "import Ember from 'ember';\n\n" + 'export default Ember.Controller.extend({ custom: true });\n' + ); + + process.chdir(path.join(tmpdir, 'app')); + await ember(['generate', 'controller', 'foo']); + + process.chdir(tmpdir); + expect(file('app/controllers/foo.js')).to.contain('custom: true'); + }); + + it('server', async function () { + await generate(['server']); + expect(file('server/index.js')).to.exist; + }); + + it('lib', async function () { + await generate(['lib']); + expect(dir('lib')).to.exist; + }); + + it('custom blueprint availableOptions', async function () { + await initApp(); + await ember(['generate', 'blueprint', 'foo']); + + replaceFile( + 'blueprints/foo/index.js', + 'module.exports = {', + 'module.exports = {\navailableOptions: [ \n' + + "{ name: 'foo',\ntype: String, \n" + + "values: ['one', 'two'],\n" + + "default: 'one',\n" + + "aliases: [ {'one': 'one'}, {'two': 'two'} ] } ],\n" + + 'locals(options) {\n' + + 'return { foo: options.foo };\n' + + '},' + ); + + await outputFile( + 'blueprints/foo/files/app/foos/__name__.js', + "import Ember from 'ember';\n" + 'export default Ember.Object.extend({ foo: <%= foo %> });\n' + ); + + await ember(['generate', 'foo', 'bar', '-two']); + + expect(file('app/foos/bar.js')).to.contain('export default Ember.Object.extend({ foo: two });'); + }); + + it('calls lint fix function', async function () { + let lintFixStub = td.replace(lintFix, 'run'); + + await generate(['blueprint', 'foo', '--lint-fix']); + + td.verify(lintFixStub(), { ignoreExtraArgs: true, times: 1 }); + }); }); diff --git a/tests/acceptance/help-test.js b/tests/acceptance/help-test.js index 4999dd7f54..4627c62ede 100644 --- a/tests/acceptance/help-test.js +++ b/tests/acceptance/help-test.js @@ -22,10 +22,10 @@ let FooCommand = Command.extend({ anonymousOptions: [''], }); -describe('Acceptance: ember help', function() { +describe('Acceptance: ember help', function () { let options, command; - beforeEach(function() { + beforeEach(function () { let commands = requireAsHash('../../lib/commands/*.js', Command); options = commandOptions({ @@ -43,7 +43,7 @@ describe('Acceptance: ember help', function() { command = new HelpCommand(options); }); - it('works', function() { + it('works', function () { command.run(options, []); let output = options.ui.output; @@ -53,8 +53,8 @@ describe('Acceptance: ember help', function() { expect(output).to.equal(expected); }); - it('prints addon commands', function() { - options.project.eachAddonCommand = function(cb) { + it('prints addon commands', function () { + options.project.eachAddonCommand = function (cb) { cb('dummy-addon', { Foo: FooCommand }); }; @@ -68,8 +68,8 @@ describe('Acceptance: ember help', function() { expect(output).to.equal(expected); }); - it('prints single addon commands', function() { - options.project.eachAddonCommand = function(cb) { + it('prints single addon commands', function () { + options.project.eachAddonCommand = function (cb) { cb('dummy-addon', { Foo: FooCommand }); }; @@ -83,7 +83,7 @@ describe('Acceptance: ember help', function() { expect(output).to.equal(expected); }); - it('prints all blueprints', function() { + it('prints all blueprints', function () { command.run(options, ['generate']); let output = options.ui.output; @@ -94,7 +94,7 @@ describe('Acceptance: ember help', function() { expect(output).to.contain(expected); }); - it('prints helpful message for unknown command', function() { + it('prints helpful message for unknown command', function () { command.run(options, ['asdf']); let output = options.ui.output; @@ -103,7 +103,7 @@ describe('Acceptance: ember help', function() { expect(output).to.not.contain('undefined'); }); - it('prints a single blueprints', function() { + it('prints a single blueprints', function () { command.run(options, ['generate', 'blueprint']); let output = options.ui.output; @@ -114,8 +114,8 @@ describe('Acceptance: ember help', function() { expect(output).to.equal(expected); }); - it('prints blueprints from addons', function() { - options.project.blueprintLookupPaths = function() { + it('prints blueprints from addons', function () { + options.project.blueprintLookupPaths = function () { return [path.join(__dirname, '..', 'fixtures', 'blueprints')]; }; @@ -129,12 +129,12 @@ describe('Acceptance: ember help', function() { expect(output).to.equal(expected); }); - describe('--json', function() { - beforeEach(function() { + describe('--json', function () { + beforeEach(function () { options.json = true; }); - it('works', function() { + it('works', function () { command.run(options, []); let json = convertToJson(options.ui.output); @@ -143,8 +143,8 @@ describe('Acceptance: ember help', function() { expect(json).to.deep.equal(expected); }); - it('prints commands from addons', function() { - options.project.eachAddonCommand = function(cb) { + it('prints commands from addons', function () { + options.project.eachAddonCommand = function (cb) { cb('dummy-addon', { Foo: FooCommand }); }; @@ -156,8 +156,8 @@ describe('Acceptance: ember help', function() { expect(json).to.deep.equal(expected); }); - it('prints blueprints from addons', function() { - options.project.blueprintLookupPaths = function() { + it('prints blueprints from addons', function () { + options.project.blueprintLookupPaths = function () { return [path.join(__dirname, '..', 'fixtures', 'blueprints')]; }; @@ -179,7 +179,7 @@ function loadTextFixture(path) { } function decodeUnicode(str) { - return str.replace(/\\u([\d\w]{4})/gi, function(match, grp) { + return str.replace(/\\u([\d\w]{4})/gi, function (match, grp) { return String.fromCharCode(parseInt(grp, 16)); }); } diff --git a/tests/acceptance/in-option-destroy-test.js b/tests/acceptance/in-option-destroy-test.js index baaff9cbfb..1500023c6c 100644 --- a/tests/acceptance/in-option-destroy-test.js +++ b/tests/acceptance/in-option-destroy-test.js @@ -1,11 +1,8 @@ 'use strict'; -const co = require('co'); -const RSVP = require('rsvp'); const ember = require('../helpers/ember'); const fs = require('fs-extra'); const path = require('path'); -let remove = RSVP.denodeify(fs.remove); let root = process.cwd(); let tmproot = path.join(root, 'tmp'); const mkTmpDirIn = require('../../lib/utilities/mk-tmp-dir-in'); @@ -19,31 +16,29 @@ const chai = require('../chai'); let expect = chai.expect; let file = chai.file; -describe('Acceptance: ember destroy with --in option', function() { +describe('Acceptance: ember destroy with --in option', function () { let tmpdir; this.timeout(20000); - before(function() { + before(function () { BlueprintNpmTask.disableNPM(Blueprint); }); - after(function() { + after(function () { BlueprintNpmTask.restoreNPM(Blueprint); }); - beforeEach( - co.wrap(function*() { - tmpdir = yield mkTmpDirIn(tmproot); - process.chdir(tmpdir); - }) - ); + beforeEach(async function () { + tmpdir = await mkTmpDirIn(tmproot); + process.chdir(tmpdir); + }); - afterEach(function() { + afterEach(function () { this.timeout(10000); process.chdir(root); - return remove(tmproot); + return fs.remove(tmproot); }); function generate(args) { @@ -57,31 +52,31 @@ describe('Acceptance: ember destroy with --in option', function() { } function assertFilesExist(files) { - files.forEach(function(f) { + files.forEach(function (f) { expect(file(f)).to.exist; }); } function assertFilesNotExist(files) { - files.forEach(function(f) { + files.forEach(function (f) { expect(file(f)).to.not.exist; }); } - const assertDestroyAfterGenerate = co.wrap(function*(args, addonPath, files) { - yield initApp(); - yield generateUtils.inRepoAddon(addonPath); - yield generateUtils.tempBlueprint(); - yield generate(args); + const assertDestroyAfterGenerate = async function (args, addonPath, files) { + await initApp(); + await generateUtils.inRepoAddon(addonPath); + await generateUtils.tempBlueprint(); + await generate(args); assertFilesExist(files); - let result = yield destroy(args); + let result = await destroy(args); expect(result, 'destroy command did not exit with errorCode').to.be.an('object'); assertFilesNotExist(files); - }); + }; - it('blueprint foo --in lib/other-thing', function() { + it('blueprint foo --in lib/other-thing', function () { let addonPath = './lib/other-thing'; let commandArgs = ['foo', 'bar', '--in', addonPath]; let files = ['lib/other-thing/addon/foos/bar.js']; @@ -89,7 +84,7 @@ describe('Acceptance: ember destroy with --in option', function() { return assertDestroyAfterGenerate(commandArgs, addonPath, files); }); - it('blueprint foo --in ./non-lib/other-thing', function() { + it('blueprint foo --in ./non-lib/other-thing', function () { let addonPath = './non-lib/other-thing'; let commandArgs = ['foo', 'bar', '--in', addonPath]; let files = ['non-lib/other-thing/addon/foos/bar.js']; @@ -97,7 +92,7 @@ describe('Acceptance: ember destroy with --in option', function() { return assertDestroyAfterGenerate(commandArgs, addonPath, files); }); - it('blueprint foo --in non-lib/other-thing', function() { + it('blueprint foo --in non-lib/other-thing', function () { let addonPath = 'non-lib/other-thing'; let commandArgs = ['foo', 'bar', '--in', addonPath]; let files = ['non-lib/other-thing/addon/foos/bar.js']; @@ -105,7 +100,7 @@ describe('Acceptance: ember destroy with --in option', function() { return assertDestroyAfterGenerate(commandArgs, addonPath, files); }); - it('blueprint foo --in non-lib/nested/other-thing', function() { + it('blueprint foo --in non-lib/nested/other-thing', function () { let addonPath = 'non-lib/nested/other-thing'; let commandArgs = ['foo', 'bar', '--in', addonPath]; let files = ['non-lib/nested/other-thing/addon/foos/bar.js']; diff --git a/tests/acceptance/in-option-generate-test.js b/tests/acceptance/in-option-generate-test.js index 5855d9344b..aa64e1954f 100644 --- a/tests/acceptance/in-option-generate-test.js +++ b/tests/acceptance/in-option-generate-test.js @@ -1,10 +1,8 @@ 'use strict'; -const RSVP = require('rsvp'); const ember = require('../helpers/ember'); const fs = require('fs-extra'); const path = require('path'); -let remove = RSVP.denodeify(fs.remove); let root = process.cwd(); let tmproot = path.join(root, 'tmp'); const Blueprint = require('../../lib/models/blueprint'); @@ -17,26 +15,26 @@ const chai = require('../chai'); let expect = chai.expect; let file = chai.file; -describe('Acceptance: ember generate with --in option', function() { +describe('Acceptance: ember generate with --in option', function () { this.timeout(20000); - before(function() { + before(function () { BlueprintNpmTask.disableNPM(Blueprint); }); - after(function() { + after(function () { BlueprintNpmTask.restoreNPM(Blueprint); }); - beforeEach(function() { - return mkTmpDirIn(tmproot).then(function(tmpdir) { + beforeEach(function () { + return mkTmpDirIn(tmproot).then(function (tmpdir) { process.chdir(tmpdir); }); }); - afterEach(function() { + afterEach(function () { process.chdir(root); - return remove(tmproot); + return fs.remove(tmproot); }); function removeAddonPath() { @@ -48,86 +46,57 @@ describe('Acceptance: ember generate with --in option', function() { return fs.writeJsonSync(packageJsonPath, packageJson); } - it('generate blueprint foo using lib', function() { + it('generate blueprint foo using lib', async function () { // build an app with an in-repo addon in a non-standard path - return ( - initApp() - .then(() => generateUtils.inRepoAddon('lib/other-thing')) - // generate in project blueprint to allow easier testing of in-repo generation - .then(() => generateUtils.tempBlueprint()) - // confirm that we can generate into the non-lib path - .then(() => - ember(['generate', 'foo', 'bar', '--in=lib/other-thing']).then(function() { - expect(file('lib/other-thing/addon/foos/bar.js')).to.exist; - }) - ) - ); + await initApp(); + await generateUtils.inRepoAddon('lib/other-thing'); + await generateUtils.tempBlueprint(); + await ember(['generate', 'foo', 'bar', '--in=lib/other-thing']); + + expect(file('lib/other-thing/addon/foos/bar.js')).to.exist; }); - it('generate blueprint foo using custom path using current directory', function() { + it('generate blueprint foo using custom path using current directory', async function () { // build an app with an in-repo addon in a non-standard path - return ( - initApp() - .then(() => generateUtils.inRepoAddon('./non-lib/other-thing')) - // generate in project blueprint to allow easier testing of in-repo generation - .then(() => generateUtils.tempBlueprint()) - // confirm that we can generate into the non-lib path - .then(() => - ember(['generate', 'foo', 'bar', '--in=non-lib/other-thing']).then(function() { - expect(file('non-lib/other-thing/addon/foos/bar.js')).to.exist; - }) - ) - ); + await initApp(); + await generateUtils.inRepoAddon('./non-lib/other-thing'); + await generateUtils.tempBlueprint(); + await ember(['generate', 'foo', 'bar', '--in=non-lib/other-thing']); + + expect(file('non-lib/other-thing/addon/foos/bar.js')).to.exist; }); - it('generate blueprint foo using custom path', function() { + it('generate blueprint foo using custom path', async function () { // build an app with an in-repo addon in a non-standard path - return ( - initApp() - .then(() => generateUtils.inRepoAddon('./non-lib/other-thing')) - // generate in project blueprint to allow easier testing of in-repo generation - .then(() => generateUtils.tempBlueprint()) - // confirm that we can generate into the non-lib path - .then(() => - ember(['generate', 'foo', 'bar', '--in=./non-lib/other-thing']).then(function() { - expect(file('non-lib/other-thing/addon/foos/bar.js')).to.exist; - }) - ) - ); + await initApp(); + await generateUtils.inRepoAddon('./non-lib/other-thing'); + // generate in project blueprint to allow easier testing of in-repo generation + await generateUtils.tempBlueprint(); + await ember(['generate', 'foo', 'bar', '--in=./non-lib/other-thing']); + + // confirm that we can generate into the non-lib path + expect(file('non-lib/other-thing/addon/foos/bar.js')).to.exist; }); - it('generate blueprint foo using custom nested path', function() { + it('generate blueprint foo using custom nested path', async function () { // build an app with an in-repo addon in a non-standard path - return ( - initApp() - .then(() => generateUtils.inRepoAddon('./non-lib/nested/other-thing')) - // generate in project blueprint to allow easier testing of in-repo generation - .then(() => generateUtils.tempBlueprint()) - // confirm that we can generate into the non-lib path - .then(() => - ember(['generate', 'foo', 'bar', '--in=./non-lib/nested/other-thing']).then(function() { - expect(file('non-lib/nested/other-thing/addon/foos/bar.js')).to.exist; - }) - ) - ); + await initApp(); + await generateUtils.inRepoAddon('./non-lib/nested/other-thing'); + await generateUtils.tempBlueprint(); + await ember(['generate', 'foo', 'bar', '--in=./non-lib/nested/other-thing']); + + expect(file('non-lib/nested/other-thing/addon/foos/bar.js')).to.exist; }); - it('generate blueprint foo using sibling path', function() { + it('generate blueprint foo using sibling path', async function () { // build an app with an in-repo addon in a non-standard path - return ( - initApp() - .then(() => fs.mkdirp('../sibling')) - .then(() => generateUtils.inRepoAddon('../sibling')) - // we want to ensure the project has no awareness of the in-repo addon via `ember-addon.paths`, so we remove it - .then(removeAddonPath) - // generate in project blueprint to allow easier testing of in-repo generation - .then(() => generateUtils.tempBlueprint()) - // confirm that we can generate into the non-lib path - .then(() => - ember(['generate', 'foo', 'bar', '--in=../sibling']).then(function() { - expect(file('../sibling/addon/foos/bar.js')).to.exist; - }) - ) - ); + await initApp(); + await fs.mkdirp('../sibling'); + await generateUtils.inRepoAddon('../sibling'); + await removeAddonPath(); + await generateUtils.tempBlueprint(); + await ember(['generate', 'foo', 'bar', '--in=../sibling']); + + expect(file('../sibling/addon/foos/bar.js')).to.exist; }); }); diff --git a/tests/acceptance/in-repo-addon-generate-test.js b/tests/acceptance/in-repo-addon-generate-test.js index c008fbdc23..e8e857af35 100644 --- a/tests/acceptance/in-repo-addon-generate-test.js +++ b/tests/acceptance/in-repo-addon-generate-test.js @@ -1,11 +1,11 @@ 'use strict'; -const RSVP = require('rsvp'); +const util = require('util'); const ember = require('../helpers/ember'); const fs = require('fs-extra'); const path = require('path'); -let outputFile = RSVP.denodeify(fs.outputFile); -let remove = RSVP.denodeify(fs.remove); +let outputFile = util.promisify(fs.outputFile); +let remove = util.promisify(fs.remove); let root = process.cwd(); let tmproot = path.join(root, 'tmp'); const Blueprint = require('../../lib/models/blueprint'); @@ -16,24 +16,23 @@ const chai = require('../chai'); let expect = chai.expect; let file = chai.file; -describe('Acceptance: ember generate in-repo-addon', function() { +describe('Acceptance: ember generate in-repo-addon', function () { this.timeout(20000); - before(function() { + before(function () { BlueprintNpmTask.disableNPM(Blueprint); }); - after(function() { + after(function () { BlueprintNpmTask.restoreNPM(Blueprint); }); - beforeEach(function() { - return mkTmpDirIn(tmproot).then(function(tmpdir) { - process.chdir(tmpdir); - }); + beforeEach(async function () { + const tmpdir = await mkTmpDirIn(tmproot); + return process.chdir(tmpdir); }); - afterEach(function() { + afterEach(function () { process.chdir(root); return remove(tmproot); }); @@ -42,31 +41,26 @@ describe('Acceptance: ember generate in-repo-addon', function() { return ember(['init', '--name=my-app', '--skip-npm', '--skip-bower']); } - function initInRepoAddon() { - return initApp().then(function() { - return ember(['generate', 'in-repo-addon', 'my-addon']); - }); + async function initInRepoAddon() { + await initApp(); + return ember(['generate', 'in-repo-addon', 'my-addon']); } - it('in-repo-addon blueprint foo inside alternate path', function() { + it('in-repo-addon blueprint foo inside alternate path', async function () { // build an app with an in-repo addon in a non-standard path - return ( - initApp() - .then(() => ember(['generate', 'in-repo-addon', './non-lib/other-thing'])) - // generate in project blueprint to allow easier testing of in-repo generation - .then(() => outputFile('blueprints/foo/files/__root__/foos/__name__.js', '/* whoah, empty foo! */')) - // confirm that we can generate into the non-lib path - .then(() => - ember(['generate', 'foo', 'bar', '--in-repo-addon=other-thing']).then(function() { - expect(file('non-lib/other-thing/addon/foos/bar.js')).to.exist; - }) - ) - ); + await initApp(); + await ember(['generate', 'in-repo-addon', './non-lib/other-thing']); + // generate in project blueprint to allow easier testing of in-repo generation + await outputFile('blueprints/foo/files/__root__/foos/__name__.js', '/* whoah, empty foo! */'); + // confirm that we can generate into the non-lib path + await ember(['generate', 'foo', 'bar', '--in-repo-addon=other-thing']); + + expect(file('non-lib/other-thing/addon/foos/bar.js')).to.exist; }); - it('in-repo-addon adds path to lib', function() { - return initInRepoAddon().then(function() { - expect(file('package.json')).to.contain('lib/my-addon'); - }); + it('in-repo-addon adds path to lib', async function () { + await initInRepoAddon(); + + expect(file('package.json')).to.contain('lib/my-addon'); }); }); diff --git a/tests/acceptance/init-test.js b/tests/acceptance/init-test.js index 3f439a7566..1407df2797 100644 --- a/tests/acceptance/init-test.js +++ b/tests/acceptance/init-test.js @@ -1,6 +1,5 @@ 'use strict'; -const co = require('co'); const ember = require('../helpers/ember'); const walkSync = require('walk-sync'); const glob = require('glob'); @@ -15,6 +14,8 @@ let intersect = lodash.intersection; let remove = lodash.remove; let forEach = lodash.forEach; const EOL = require('os').EOL; +const td = require('testdouble'); +const lintFix = require('../../lib/utilities/lint-fix'); const chai = require('../chai'); let expect = chai.expect; @@ -24,19 +25,18 @@ let defaultIgnoredFiles = Blueprint.ignoredFiles; let tmpPath = './tmp/init-test'; -describe('Acceptance: ember init', function() { +describe('Acceptance: ember init', function () { this.timeout(20000); - beforeEach( - co.wrap(function*() { - Blueprint.ignoredFiles = defaultIgnoredFiles; + beforeEach(async function () { + Blueprint.ignoredFiles = defaultIgnoredFiles; - yield tmp.setup(tmpPath); - process.chdir(tmpPath); - }) - ); + await tmp.setup(tmpPath); + process.chdir(tmpPath); + }); - afterEach(function() { + afterEach(function () { + td.reset(); return tmp.teardown(tmpPath); }); @@ -45,7 +45,7 @@ describe('Acceptance: ember init', function() { let expected = walkSync(blueprintPath).sort(); let actual = walkSync('.').sort(); - forEach(Blueprint.renamedFiles, function(destFile, srcFile) { + forEach(Blueprint.renamedFiles, function (destFile, srcFile) { expected[expected.indexOf(srcFile)] = destFile; }); @@ -94,13 +94,13 @@ describe('Acceptance: ember init', function() { } function removeTmp(array) { - remove(array, function(entry) { + remove(array, function (entry) { return /^tmp[\\/]$/.test(entry); }); } function removeIgnored(array) { - remove(array, function(fn) { - return Blueprint.ignoredFiles.some(function(ignoredFile) { + remove(array, function (fn) { + return Blueprint.ignoredFiles.some(function (ignoredFile) { return minimatch(fn, ignoredFile, { matchBase: true, }); @@ -108,92 +108,75 @@ describe('Acceptance: ember init', function() { }); } - it( - 'ember init', - co.wrap(function*() { - yield ember(['init', '--skip-npm', '--skip-bower']); + it('ember init', async function () { + await ember(['init', '--skip-npm', '--skip-bower']); - confirmBlueprinted(); - }) - ); + confirmBlueprinted(); + }); - it( - "init an already init'd folder", - co.wrap(function*() { - yield ember(['init', '--skip-npm', '--skip-bower']); + it("init an already init'd folder", async function () { + await ember(['init', '--skip-npm', '--skip-bower']); - yield ember(['init', '--skip-npm', '--skip-bower']); + await ember(['init', '--skip-npm', '--skip-bower']); - confirmBlueprinted(); - }) - ); + confirmBlueprinted(); + }); - it( - 'init a single file', - co.wrap(function*() { - yield ember(['init', 'app.js', '--skip-npm', '--skip-bower']); + it('init a single file', async function () { + await ember(['init', 'app.js', '--skip-npm', '--skip-bower']); - confirmGlobBlueprinted('app.js'); - }) - ); + confirmGlobBlueprinted('app.js'); + }); - it( - "init a single file on already init'd folder", - co.wrap(function*() { - yield ember(['init', '--skip-npm', '--skip-bower']); + it("init a single file on already init'd folder", async function () { + await ember(['init', '--skip-npm', '--skip-bower']); - yield ember(['init', 'app.js', '--skip-npm', '--skip-bower']); + await ember(['init', 'app.js', '--skip-npm', '--skip-bower']); - confirmBlueprinted(); - }) - ); + confirmBlueprinted(); + }); - it( - 'init multiple files by glob pattern', - co.wrap(function*() { - yield ember(['init', 'app/**', '--skip-npm', '--skip-bower']); + it('init multiple files by glob pattern', async function () { + await ember(['init', 'app/**', '--skip-npm', '--skip-bower']); - confirmGlobBlueprinted('app/**'); - }) - ); + confirmGlobBlueprinted('app/**'); + }); - it( - "init multiple files by glob pattern on already init'd folder", - co.wrap(function*() { - yield ember(['init', '--skip-npm', '--skip-bower']); + it("init multiple files by glob pattern on already init'd folder", async function () { + await ember(['init', '--skip-npm', '--skip-bower']); - yield ember(['init', 'app/**', '--skip-npm', '--skip-bower']); + await ember(['init', 'app/**', '--skip-npm', '--skip-bower']); - confirmBlueprinted(); - }) - ); + confirmBlueprinted(); + }); - it( - 'init multiple files by glob patterns', - co.wrap(function*() { - yield ember(['init', 'app/**', '{package,bower}.json', 'resolver.js', '--skip-npm', '--skip-bower']); + it('init multiple files by glob patterns', async function () { + await ember(['init', 'app/**', '{package,bower}.json', 'resolver.js', '--skip-npm', '--skip-bower']); - confirmGlobBlueprinted('{app/**,{package,bower}.json,resolver.js}'); - }) - ); + confirmGlobBlueprinted('{app/**,{package,bower}.json,resolver.js}'); + }); - it( - "init multiple files by glob patterns on already init'd folder", - co.wrap(function*() { - yield ember(['init', '--skip-npm', '--skip-bower']); + it("init multiple files by glob patterns on already init'd folder", async function () { + await ember(['init', '--skip-npm', '--skip-bower']); - yield ember(['init', 'app/**', '{package,bower}.json', 'resolver.js', '--skip-npm', '--skip-bower']); + await ember(['init', 'app/**', '{package,bower}.json', 'resolver.js', '--skip-npm', '--skip-bower']); - confirmBlueprinted(); - }) - ); + confirmBlueprinted(); + }); + + it('should not create .git folder', async function () { + await ember(['init', '--skip-npm', '--skip-bower']); + + expect(dir('.git')).to.not.exist; + }); - it( - 'should not create .git folder', - co.wrap(function*() { - yield ember(['init', '--skip-npm', '--skip-bower']); + it('calls lint fix function', async function () { + let lintFixStub = td.replace(lintFix, 'run'); - expect(dir('.git')).to.not.exist; - }) - ); + await ember(['init', '--skip-npm', '--skip-bower', '--lint-fix']); + + td.verify(lintFixStub(), { ignoreExtraArgs: true, times: 1 }); + + confirmBlueprinted(); + }); }); diff --git a/tests/acceptance/missing-before-addon-test.js b/tests/acceptance/missing-before-addon-test.js index 5cc9eec5b1..077c12ad43 100644 --- a/tests/acceptance/missing-before-addon-test.js +++ b/tests/acceptance/missing-before-addon-test.js @@ -4,16 +4,16 @@ const path = require('path'); const ember = require('../helpers/ember'); let root = process.cwd(); -describe('Acceptance: missing a before/after addon', function() { - before(function() { +describe('Acceptance: missing a before/after addon', function () { + beforeEach(function () { process.chdir(path.join(root, 'tests', 'fixtures', 'missing-before-addon')); }); - after(function() { + afterEach(function () { process.chdir(root); }); - it('does not break ember-cli', function() { + it('does not break ember-cli', function () { return ember(['help']); }); }); diff --git a/tests/acceptance/nested-addons-smoke-test-slow.js b/tests/acceptance/nested-addons-smoke-test-slow.js index a84ffe7436..6b5654ae7e 100644 --- a/tests/acceptance/nested-addons-smoke-test-slow.js +++ b/tests/acceptance/nested-addons-smoke-test-slow.js @@ -1,12 +1,12 @@ 'use strict'; -const co = require('co'); const path = require('path'); const fs = require('fs-extra'); const runCommand = require('../helpers/run-command'); const acceptance = require('../helpers/acceptance'); const copyFixtureFiles = require('../helpers/copy-fixture-files'); +const DistChecker = require('../helpers/dist-checker'); let createTestTargets = acceptance.createTestTargets; let teardownTestTargets = acceptance.teardownTestTargets; let linkDependencies = acceptance.linkDependencies; @@ -14,64 +14,58 @@ let cleanupRun = acceptance.cleanupRun; const chai = require('../chai'); let expect = chai.expect; -let file = chai.file; let dir = chai.dir; let appName = 'some-cool-app'; let appRoot; -describe('Acceptance: nested-addons-smoke-test', function() { +describe('Acceptance: nested-addons-smoke-test', function () { this.timeout(360000); - before(function() { + before(function () { return createTestTargets(appName); }); after(teardownTestTargets); - beforeEach(function() { + beforeEach(function () { appRoot = linkDependencies(appName); }); - afterEach(function() { + afterEach(function () { + runCommand.killAll(); cleanupRun(appName); expect(dir(appRoot)).to.not.exist; }); - it( - 'addons with nested addons compile correctly', - co.wrap(function*() { - yield copyFixtureFiles('addon/with-nested-addons'); - - let packageJsonPath = path.join(appRoot, 'package.json'); - let packageJson = fs.readJsonSync(packageJsonPath); - packageJson.devDependencies['ember-top-addon'] = 'latest'; - fs.writeJsonSync(packageJsonPath, packageJson); - - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - - expect(file('dist/assets/vendor.js')).to.contain('INNER_ADDON_IMPORT_WITH_APP_IMPORT'); - expect(file('dist/assets/vendor.js')).to.contain('INNER_ADDON_IMPORT_WITH_THIS_IMPORT'); - - // RAW comments should have been converted to PREPROCESSED by - // tests/fixtures/addon/with-nested-addons/node_modules/ember-top-addon/node_modules/preprocesstree-addon - // then from PREPROCESSED to POSTPROCESSED by - // tests/fixtures/addon/with-nested-addons/node_modules/ember-top-addon/node_modules/postprocesstree-addon - expect(file('dist/assets/vendor.js')).to.contain( - 'POSTPROCESSED node_modules/ember-top-addon/addon/templates/application.hbs' - ); - expect(file('dist/assets/vendor.js')).to.contain('POSTPROCESSED node_modules/ember-top-addon/addon/index.js'); - expect(file('dist/assets/vendor.css')).to.contain( - 'POSTPROCESSED node_modules/ember-top-addon/addon/styles/app.css' - ); - - // the pre/post process tree hooks above should *not* have changed RAW's in the current app - expect(file('dist/assets/some-cool-app.js')).to.contain('RAW app/foo.js'); - - // should *not* have changed RAW's in sibling addons - expect(file('dist/assets/vendor.js')).to.contain( - 'RAW node_modules/ember-top-addon/node_modules/ember-inner-addon/addon/index.js' - ); - }) - ); + it('addons with nested addons compile correctly', async function () { + await copyFixtureFiles('addon/with-nested-addons'); + + let packageJsonPath = path.join(appRoot, 'package.json'); + let packageJson = fs.readJsonSync(packageJsonPath); + packageJson.devDependencies['ember-top-addon'] = 'latest'; + fs.writeJsonSync(packageJsonPath, packageJson); + + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + + let checker = new DistChecker(path.join(appRoot, 'dist')); + + expect(checker.contains('js', 'INNER_ADDON_IMPORT_WITH_APP_IMPORT')).to.be; + expect(checker.contains('js', 'INNER_ADDON_IMPORT_WITH_THIS_IMPORT')).to.be; + + // RAW comments should have been converted to PREPROCESSED by + // tests/fixtures/addon/with-nested-addons/node_modules/ember-top-addon/node_modules/preprocesstree-addon + // then from PREPROCESSED to POSTPROCESSED by + // tests/fixtures/addon/with-nested-addons/node_modules/ember-top-addon/node_modules/postprocesstree-addon + expect(checker.contains('js', 'POSTPROCESSED node_modules/ember-top-addon/addon/templates/application.hbs')).to.be; + expect(checker.contains('js', 'POSTPROCESSED node_modules/ember-top-addon/addon/index.js')).to.be; + expect(checker.contains('css', 'POSTPROCESSED node_modules/ember-top-addon/addon/styles/app.css')).to.be; + + // the pre/post process tree hooks above should *not* have changed RAW's in the current app + expect(checker.contains('js', 'RAW app/foo.js')).to.be; + + // should *not* have changed RAW's in sibling addons + expect(checker.contains('js', 'RAW node_modules/ember-top-addon/node_modules/ember-inner-addon/addon/index.js')).to + .be; + }); }); diff --git a/tests/acceptance/new-test.js b/tests/acceptance/new-test.js index faf7e9e2ba..9b2c908617 100644 --- a/tests/acceptance/new-test.js +++ b/tests/acceptance/new-test.js @@ -1,6 +1,7 @@ 'use strict'; -const co = require('co'); +const execa = require('execa'); +const semver = require('semver'); const fs = require('fs-extra'); const ember = require('../helpers/ember'); const walkSync = require('walk-sync'); @@ -12,6 +13,7 @@ const util = require('util'); const EOL = require('os').EOL; const chalk = require('chalk'); const hasGlobalYarn = require('../helpers/has-global-yarn'); +const { isExperimentEnabled } = require('../../lib/experiments'); const chai = require('../chai'); let expect = chai.expect; @@ -19,24 +21,20 @@ let file = chai.file; let dir = chai.dir; const forEach = require('ember-cli-lodash-subset').forEach; const assertVersionLock = require('../helpers/assert-version-lock'); -const { isExperimentEnabled } = require('../../lib/experiments'); -const getURLFor = require('ember-source-channel-url'); let tmpDir = './tmp/new-test'; -describe('Acceptance: ember new', function() { +describe('Acceptance: ember new', function () { this.timeout(10000); let ORIGINAL_PROCESS_ENV_CI; - beforeEach( - co.wrap(function*() { - yield tmp.setup(tmpDir); - process.chdir(tmpDir); - ORIGINAL_PROCESS_ENV_CI = process.env.CI; - }) - ); + beforeEach(async function () { + await tmp.setup(tmpDir); + process.chdir(tmpDir); + ORIGINAL_PROCESS_ENV_CI = process.env.CI; + }); - afterEach(function() { + afterEach(function () { if (ORIGINAL_PROCESS_ENV_CI === undefined) { delete process.env.CI; } else { @@ -45,490 +43,553 @@ describe('Acceptance: ember new', function() { return tmp.teardown(tmpDir); }); - function confirmBlueprintedForDir(dir) { - let blueprintPath = path.join(root, dir, 'files'); + function confirmBlueprintedForDir(blueprintDir, expectedAppDir = 'foo') { + let blueprintPath = path.join(root, blueprintDir, 'files'); let expected = walkSync(blueprintPath); let actual = walkSync('.').sort(); let directory = path.basename(process.cwd()); - forEach(Blueprint.renamedFiles, function(destFile, srcFile) { + forEach(Blueprint.renamedFiles, function (destFile, srcFile) { expected[expected.indexOf(srcFile)] = destFile; }); expected.sort(); - expect(directory).to.equal('foo'); + expect(directory).to.equal(expectedAppDir); expect(expected).to.deep.equal( actual, `${EOL} expected: ${util.inspect(expected)}${EOL} but got: ${util.inspect(actual)}` ); } - function confirmBlueprintedApp() { - if (isExperimentEnabled('MODULE_UNIFICATION')) { - return confirmBlueprintedForDir('blueprints/module-unification-app'); + it('ember new adds ember-welcome-page by default', async function () { + await ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git']); + + expect(file('package.json')).to.match(/"ember-welcome-page"/); + + expect(file('app/templates/application.hbs')).to.contain(''); + }); + + it('ember new @foo/bar, when parent directory does not contain `foo`', async function () { + await ember(['new', '@foo/bar', '--skip-npm', '--skip-bower']); + + confirmBlueprintedForDir('blueprints/app', 'foo-bar'); + }); + + it('ember new @foo/bar, when direct parent directory contains `foo`', async function () { + let scopedDirectoryPath = path.join(process.cwd(), 'foo'); + fs.mkdirsSync(scopedDirectoryPath); + process.chdir(scopedDirectoryPath); + + await ember(['new', '@foo/bar', '--skip-npm', '--skip-bower']); + + confirmBlueprintedForDir('blueprints/app', 'bar'); + }); + + it('ember new @foo/bar, when parent directory heirarchy contains `foo`', async function () { + let scopedDirectoryPath = path.join(process.cwd(), 'foo', 'packages'); + fs.mkdirsSync(scopedDirectoryPath); + process.chdir(scopedDirectoryPath); + + await ember(['new', '@foo/bar', '--skip-npm', '--skip-bower']); + + confirmBlueprintedForDir('blueprints/app', 'bar'); + }); + + it('ember new --no-welcome skips installation of ember-welcome-page', async function () { + await ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--no-welcome']); + + expect(file('package.json')).not.to.match(/"ember-welcome-page"/); + + expect(file('app/templates/application.hbs')).to.contain('Welcome to Ember'); + }); + + // ember new foo --lang + // ------------------------------- + // Good: Correct Usage + it('ember new foo --lang=(valid code): no message + set `lang` in index.html', async function () { + await ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--lang=en-US']); + expect(file('app/index.html')).to.contain(''); + }); + + // Edge Case: both valid code AND programming language abbreviation, possible misuse + it('ember new foo --lang=(valid code + programming language abbreviation): emit warning + set `lang` in index.html', async function () { + await ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--lang=css']); + expect(file('app/index.html')).to.contain(''); + }); + + // Misuse: possibly an attempt to set app programming language + it('ember new foo --lang=(programming language): emit warning + do not set `lang` in index.html', async function () { + await ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--lang=JavaScript']); + expect(file('app/index.html')).to.contain(''); + }); + + // Misuse: possibly an attempt to set app programming language + it('ember new foo --lang=(programming language abbreviation): emit warning + do not set `lang` in index.html', async function () { + await ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--lang=JS']); + expect(file('app/index.html')).to.contain(''); + }); + + // Misuse: possibly an attempt to set app programming language + it('ember new foo --lang=(programming language file extension): emit warning + do not set `lang` in index.html', async function () { + await ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--lang=.js']); + expect(file('app/index.html')).to.contain(''); + }); + + // Misuse: Invalid Country Code + it('ember new foo --lang=(invalid code): emit warning + do not set `lang` in index.html', async function () { + await ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--lang=en-UK']); + expect(file('app/index.html')).to.contain(''); + }); + + it('ember new npm blueprint with old version', async function () { + await ember(['new', 'foo', '--blueprint', '@glimmer/blueprint@0.6.4', '--skip-npm', '--skip-bower']); + + expect(dir('src')).to.exist; + }); + + it('ember new foo, where foo does not yet exist, works', async function () { + await ember(['new', 'foo', '--skip-npm', '--skip-bower']); + + confirmBlueprintedForDir('blueprints/app'); + }); + + it('ember new foo, blueprint targets match the default ember-cli targets', async function () { + await ember(['new', 'foo', '--skip-npm', '--skip-bower']); + + process.env.CI = true; + const defaultTargets = ['last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions']; + const blueprintTargets = require(path.resolve('config/targets.js')).browsers; + expect(blueprintTargets).to.have.same.deep.members(defaultTargets); + }); + + it('ember new with empty app name fails with a warning', async function () { + let err = await expect(ember(['new', ''])).to.be.rejected; + + expect(err.name).to.equal('SilentError'); + expect(err.message).to.contain('The `ember new` command requires a name to be specified.'); + }); + + it('ember new without app name fails with a warning', async function () { + let err = await expect(ember(['new'])).to.be.rejected; + + expect(err.name).to.equal('SilentError'); + expect(err.message).to.contain('The `ember new` command requires a name to be specified.'); + }); + + it('ember new with app name creates new directory and has a dasherized package name', async function () { + await ember(['new', 'FooApp', '--skip-npm', '--skip-bower', '--skip-git']); + + expect(dir('FooApp')).to.not.exist; + expect(file('package.json')).to.exist; + + let pkgJson = fs.readJsonSync('package.json'); + expect(pkgJson.name).to.equal('foo-app'); + }); + + it('Can create new ember project in an existing empty directory', async function () { + fs.mkdirsSync('bar'); + + await ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--directory=bar']); + }); + + it('Cannot create new ember project in a populated directory', async function () { + fs.mkdirsSync('bar'); + fs.writeFileSync(path.join('bar', 'package.json'), '{}'); + + let error = await expect(ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--directory=bar'])).to.be + .rejected; + + expect(error.name).to.equal('SilentError'); + expect(error.message).to.equal("Directory 'bar' already exists."); + }); + + it('Cannot run ember new, inside of ember-cli project', async function () { + await ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git']); + + let error = await expect(ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git'])).to.be.rejected; + + expect(dir('foo')).to.not.exist; + expect(error.name).to.equal('SilentError'); + expect(error.message).to.equal(`You cannot use the ${chalk.green('new')} command inside an ember-cli project.`); + + confirmBlueprintedForDir('blueprints/app'); + }); + + it('ember new with blueprint uses the specified blueprint directory with a relative path', async function () { + fs.mkdirsSync('my_blueprint/files'); + fs.writeFileSync('my_blueprint/files/gitignore', ''); + + await ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--blueprint=./my_blueprint']); + + confirmBlueprintedForDir(path.join(tmpDir, 'my_blueprint')); + }); + + it('ember new with blueprint uses the specified blueprint directory with an absolute path', async function () { + fs.mkdirsSync('my_blueprint/files'); + fs.writeFileSync('my_blueprint/files/gitignore', ''); + + await ember([ + 'new', + 'foo', + '--skip-npm', + '--skip-bower', + '--skip-git', + `--blueprint=${path.resolve(process.cwd(), 'my_blueprint')}`, + ]); + + confirmBlueprintedForDir(path.join(tmpDir, 'my_blueprint')); + }); + + it('ember new with git blueprint checks out the blueprint and uses it', async function () { + this.timeout(20000); // relies on GH network stuff + + await ember([ + 'new', + 'foo', + '--skip-npm', + '--skip-bower', + '--skip-git', + '--blueprint=https://github.com/ember-cli/app-blueprint-test.git', + ]); + + expect(file('.ember-cli')).to.exist; + }); + + it('ember new with git blueprint and ref checks out the blueprint with the correct ref and uses it', async function () { + this.timeout(20000); // relies on GH network stuff + + await ember([ + 'new', + 'foo', + '--skip-npm', + '--skip-bower', + '--skip-git', + '--blueprint=https://github.com/ember-cli/app-blueprint-test.git#named-ref', + ]); + + expect(file('.named-ref')).to.exist; + }); + + it('ember new with shorthand git blueprint and ref checks out the blueprint with the correct ref and uses it', async function () { + // Temporarily skipped for npm versions <= v6.0.0. + // See https://github.com/npm/cli/issues/4896 for more info. + let { stdout: npmVersion } = await execa('npm', ['-v']); + if (semver.major(npmVersion) <= 6) { + this.skip(); } - return confirmBlueprintedForDir('blueprints/app'); - } - it( - 'ember new adds ember-welcome-page by default', - co.wrap(function*() { - yield ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git']); - - expect(file('package.json')).to.match(/"ember-welcome-page"/); - - const filePath = isExperimentEnabled('MODULE_UNIFICATION') - ? 'src/ui/routes/application/template.hbs' - : 'app/templates/application.hbs'; - - expect(file(filePath)).to.contain(''); - }) - ); - - it( - 'ember new --no-welcome skips installation of ember-welcome-page', - co.wrap(function*() { - yield ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--no-welcome']); - - expect(file('package.json')).not.to.match(/"ember-welcome-page"/); - - const filePath = isExperimentEnabled('MODULE_UNIFICATION') - ? 'src/ui/routes/application/template.hbs' - : 'app/templates/application.hbs'; - - expect(file(filePath)).to.contain('Welcome to Ember'); - }) - ); - - it( - 'ember new module-unification-app', - co.wrap(function*() { - yield ember(['new', 'foo', '--blueprint', 'module-unification-app', '--skip-npm', '--skip-bower']); - confirmBlueprintedForDir('blueprints/module-unification-app'); - - expect(dir('tests/unit')).to.not.exist; - expect(dir('tests/integration')).to.not.exist; - expect(dir('tests/acceptance')).to.exist; - }) - ); - - it( - 'ember new npm blueprint with old version', - co.wrap(function*() { - yield ember(['new', 'foo', '--blueprint', '@glimmer/blueprint@0.6.4', '--skip-npm', '--skip-bower']); - - expect(dir('src')).to.exist; - }) - ); - - it( - 'ember new foo, where foo does not yet exist, works', - co.wrap(function*() { - yield ember(['new', 'foo', '--skip-npm', '--skip-bower']); - - confirmBlueprintedApp(); - }) - ); - - it( - 'ember new foo, blueprint targets match the default ember-cli targets', - co.wrap(function*() { - yield ember(['new', 'foo', '--skip-npm', '--skip-bower']); - - process.env.CI = true; - const defaultTargets = require('../../lib/utilities/default-targets').browsers; - const blueprintTargets = require(path.resolve('config/targets.js')).browsers; - expect(blueprintTargets).to.have.same.deep.members(defaultTargets); - }) - ); - - it( - 'ember new with empty app name fails with a warning', - co.wrap(function*() { - let err = yield expect(ember(['new', ''])).to.be.rejected; - - expect(err.name).to.equal('SilentError'); - expect(err.message).to.contain('The `ember new` command requires a name to be specified.'); - }) - ); - - it( - 'ember new without app name fails with a warning', - co.wrap(function*() { - let err = yield expect(ember(['new'])).to.be.rejected; - - expect(err.name).to.equal('SilentError'); - expect(err.message).to.contain('The `ember new` command requires a name to be specified.'); - }) - ); - - it( - 'ember new with app name creates new directory and has a dasherized package name', - co.wrap(function*() { - yield ember(['new', 'FooApp', '--skip-npm', '--skip-bower', '--skip-git']); - - expect(dir('FooApp')).to.not.exist; - expect(file('package.json')).to.exist; + this.timeout(20000); // relies on GH network stuff - let pkgJson = fs.readJsonSync('package.json'); - expect(pkgJson.name).to.equal('foo-app'); - }) - ); - - it( - 'Can create new ember project in an existing empty directory', - co.wrap(function*() { - fs.mkdirsSync('bar'); - - yield ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--directory=bar']); - }) - ); - - it( - 'Cannot create new ember project in a populated directory', - co.wrap(function*() { - fs.mkdirsSync('bar'); - fs.writeFileSync(path.join('bar', 'package.json'), '{}'); - - let error = yield expect(ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--directory=bar'])).to - .be.rejected; - - expect(error.name).to.equal('SilentError'); - expect(error.message).to.equal("Directory 'bar' already exists."); - }) - ); - - it( - 'Cannot run ember new, inside of ember-cli project', - co.wrap(function*() { - yield ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git']); - - let error = yield expect(ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git'])).to.be.rejected; - - expect(dir('foo')).to.not.exist; - expect(error.name).to.equal('SilentError'); - expect(error.message).to.equal(`You cannot use the ${chalk.green('new')} command inside an ember-cli project.`); - - confirmBlueprintedApp(); - }) - ); - - it( - 'ember new with blueprint uses the specified blueprint directory with a relative path', - co.wrap(function*() { - fs.mkdirsSync('my_blueprint/files'); - fs.writeFileSync('my_blueprint/files/gitignore'); - - yield ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--blueprint=./my_blueprint']); - - confirmBlueprintedForDir(path.join(tmpDir, 'my_blueprint')); - }) - ); - - it( - 'ember new with blueprint uses the specified blueprint directory with an absolute path', - co.wrap(function*() { - fs.mkdirsSync('my_blueprint/files'); - fs.writeFileSync('my_blueprint/files/gitignore'); - - yield ember([ - 'new', - 'foo', - '--skip-npm', - '--skip-bower', - '--skip-git', - `--blueprint=${path.resolve(process.cwd(), 'my_blueprint')}`, - ]); - - confirmBlueprintedForDir(path.join(tmpDir, 'my_blueprint')); - }) - ); - - it( - 'ember new with git blueprint checks out the blueprint and uses it', - co.wrap(function*() { - this.timeout(20000); // relies on GH network stuff - - yield ember([ - 'new', - 'foo', - '--skip-npm', - '--skip-bower', - '--skip-git', - '--blueprint=https://github.com/ember-cli/app-blueprint-test.git', - ]); - - expect(file('.ember-cli')).to.exist; - }) - ); - - it( - 'ember new passes blueprint options through to blueprint', - co.wrap(function*() { - fs.mkdirsSync('my_blueprint/files'); - fs.writeFileSync( - 'my_blueprint/index.js', - [ - 'module.exports = {', - " availableOptions: [ { name: 'custom-option' } ],", - ' locals(options) {', - ' return {', - ' customOption: options.customOption', - ' };', - ' }', - '};', - ].join('\n') - ); - fs.writeFileSync('my_blueprint/files/gitignore', '<%= customOption %>'); - - yield ember([ - 'new', - 'foo', - '--skip-npm', - '--skip-bower', - '--skip-git', - '--blueprint=./my_blueprint', - '--custom-option=customValue', - ]); - - expect(file('.gitignore')).to.contain('customValue'); - }) - ); - - it( - 'ember new uses yarn when blueprint has yarn.lock', - co.wrap(function*() { - if (!hasGlobalYarn) { - this.skip(); - } + await ember([ + 'new', + 'foo', + '--skip-npm', + '--skip-bower', + '--skip-git', + '--blueprint=ember-cli/app-blueprint-test#named-ref', + ]); + + expect(file('.named-ref')).to.exist; + }); - fs.mkdirsSync('my_blueprint/files'); - fs.writeFileSync('my_blueprint/index.js', 'module.exports = {};'); - fs.writeFileSync('my_blueprint/files/package.json', '{ "name": "foo", "dependencies": { "fs-extra": "*" }}'); - fs.writeFileSync('my_blueprint/files/yarn.lock', ''); + it('ember new passes blueprint options through to blueprint', async function () { + fs.mkdirsSync('my_blueprint/files'); + fs.writeFileSync( + 'my_blueprint/index.js', + [ + 'module.exports = {', + " availableOptions: [ { name: 'custom-option' } ],", + ' locals(options) {', + ' return {', + ' customOption: options.customOption', + ' };', + ' }', + '};', + ].join('\n') + ); + fs.writeFileSync('my_blueprint/files/gitignore', '<%= customOption %>'); + + await ember([ + 'new', + 'foo', + '--skip-npm', + '--skip-bower', + '--skip-git', + '--blueprint=./my_blueprint', + '--custom-option=customValue', + ]); + + expect(file('.gitignore')).to.contain('customValue'); + }); - yield ember(['new', 'foo', '--skip-git', '--blueprint=./my_blueprint']); + it('ember new uses yarn when blueprint has yarn.lock', async function () { + if (!hasGlobalYarn) { + this.skip(); + } - expect(file('yarn.lock')).to.not.be.empty; - expect(dir('node_modules/fs-extra')).to.not.be.empty; - }) - ); + fs.mkdirsSync('my_blueprint/files'); + fs.writeFileSync('my_blueprint/index.js', 'module.exports = {};'); + fs.writeFileSync( + 'my_blueprint/files/package.json', + '{ "name": "foo", "dependencies": { "ember-try-test-suite-helper": "*" }}' + ); + fs.writeFileSync('my_blueprint/files/yarn.lock', ''); - it( - 'ember new without skip-git flag creates .git dir', - co.wrap(function*() { - yield ember(['new', 'foo', '--skip-npm', '--skip-bower'], { - skipGit: false, - }); + await ember(['new', 'foo', '--skip-git', '--blueprint=./my_blueprint']); - expect(dir('.git')).to.exist; - }) - ); + expect(file('yarn.lock')).to.not.be.empty; + expect(dir('node_modules/ember-try-test-suite-helper')).to.not.be.empty; + }); - it( - 'ember new cleans up after itself on error', - co.wrap(function*() { - fs.mkdirsSync('my_blueprint'); - fs.writeFileSync('my_blueprint/index.js', 'throw("this will break");'); + it('ember new without skip-git flag creates .git dir', async function () { + await ember(['new', 'foo', '--skip-npm', '--skip-bower'], { + skipGit: false, + }); - yield ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--blueprint=./my_blueprint']); + expect(dir('.git')).to.exist; + }); - expect(dir('foo')).to.not.exist; - }) - ); + it('ember new cleans up after itself on error', async function () { + fs.mkdirsSync('my_blueprint'); + fs.writeFileSync('my_blueprint/index.js', 'throw("this will break");'); - it( - 'ember new with --dry-run does not create new directory', - co.wrap(function*() { - yield ember(['new', 'foo', '--dry-run']); + await ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--blueprint=./my_blueprint']); - expect(process.cwd()).to.not.match(/foo/, 'does not change cwd to foo in a dry run'); - expect(dir('foo')).to.not.exist; - expect(dir('.git')).to.not.exist; - }) - ); + expect(dir('foo')).to.not.exist; + }); - it( - 'ember new with --directory uses given directory name and has correct package name', - co.wrap(function*() { - let workdir = process.cwd(); + it('ember new with --dry-run does not create new directory', async function () { + await ember(['new', 'foo', '--dry-run']); - yield ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--directory=bar']); + expect(process.cwd()).to.not.match(/foo/, 'does not change cwd to foo in a dry run'); + expect(dir('foo')).to.not.exist; + expect(dir('.git')).to.not.exist; + }); - expect(dir(path.join(workdir, 'foo'))).to.not.exist; - expect(dir(path.join(workdir, 'bar'))).to.exist; + it('ember new with --directory uses given directory name and has correct package name', async function () { + let workdir = process.cwd(); - let cwd = process.cwd(); - expect(cwd).to.not.match(/foo/, 'does not use app name for directory name'); - expect(cwd).to.match(/bar/, 'uses given directory name'); + await ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--directory=bar']); - let pkgJson = fs.readJsonSync('package.json'); - expect(pkgJson.name).to.equal('foo', 'uses app name for package name'); - }) - ); + expect(dir(path.join(workdir, 'foo'))).to.not.exist; + expect(dir(path.join(workdir, 'bar'))).to.exist; - it( - 'ember addon with --directory uses given directory name and has correct package name', - co.wrap(function*() { - let workdir = process.cwd(); + let cwd = process.cwd(); + expect(cwd).to.not.match(/foo/, 'does not use app name for directory name'); + expect(cwd).to.match(/bar/, 'uses given directory name'); - yield ember(['addon', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--directory=bar']); + let pkgJson = fs.readJsonSync('package.json'); + expect(pkgJson.name).to.equal('foo', 'uses app name for package name'); + }); - expect(dir(path.join(workdir, 'foo'))).to.not.exist; - expect(dir(path.join(workdir, 'bar'))).to.exist; + it('ember addon with --directory uses given directory name and has correct package name', async function () { + let workdir = process.cwd(); - let cwd = process.cwd(); - expect(cwd).to.not.match(/foo/, 'does not use addon name for directory name'); - expect(cwd).to.match(/bar/, 'uses given directory name'); + await ember(['addon', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--directory=bar']); + + expect(dir(path.join(workdir, 'foo'))).to.not.exist; + expect(dir(path.join(workdir, 'bar'))).to.exist; + + let cwd = process.cwd(); + expect(cwd).to.not.match(/foo/, 'does not use addon name for directory name'); + expect(cwd).to.match(/bar/, 'uses given directory name'); + + let pkgJson = fs.readJsonSync('package.json'); + expect(pkgJson.name).to.equal('foo', 'uses addon name for package name'); + }); + + it('ember addon @foo/bar when parent directory does not contain `foo`', async function () { + await ember(['addon', '@foo/bar', '--skip-npm', '--skip-bower', '--skip-git']); + + let directoryName = path.basename(process.cwd()); + + expect(directoryName).to.equal('foo-bar'); + + let pkgJson = fs.readJsonSync('package.json'); + expect(pkgJson.name).to.equal('@foo/bar', 'uses addon name for package name'); + }); + + it('ember addon @foo/bar when parent directory contains `foo`', async function () { + let scopedDirectoryPath = path.join(process.cwd(), 'foo'); + fs.mkdirsSync(scopedDirectoryPath); + process.chdir(scopedDirectoryPath); + + await ember(['addon', '@foo/bar', '--skip-npm', '--skip-bower', '--skip-git']); + + let directoryName = path.basename(process.cwd()); + + expect(directoryName).to.equal('bar'); + + let pkgJson = fs.readJsonSync('package.json'); + expect(pkgJson.name).to.equal('@foo/bar', 'uses addon name for package name'); + }); + + if (!isExperimentEnabled('CLASSIC')) { + it('embroider experiment creates the correct files', async function () { + let ORIGINAL_PROCESS_ENV = process.env.EMBER_CLI_EMBROIDER; + process.env['EMBER_CLI_EMBROIDER'] = 'true'; + await ember(['new', 'foo', '--skip-npm', '--skip-git', '--skip-bower']); + + if (ORIGINAL_PROCESS_ENV === undefined) { + delete process.env['EMBER_CLI_EMBROIDER']; + } else { + process.env['EMBER_CLI_EMBROIDER'] = ORIGINAL_PROCESS_ENV; + } let pkgJson = fs.readJsonSync('package.json'); - expect(pkgJson.name).to.equal('foo', 'uses addon name for package name'); - }) - ); - - describe('verify fixtures', function() { - let emberCanaryVersion; - if (isExperimentEnabled('MODULE_UNIFICATION')) { - before(function() { - return getURLFor('canary').then(function(url) { - emberCanaryVersion = url; - }); - }); - } + expect(pkgJson.devDependencies['@embroider/compat']).to.exist; + expect(pkgJson.devDependencies['@embroider/core']).to.exist; + expect(pkgJson.devDependencies['@embroider/webpack']).to.exist; + }); + } + + it('embroider enabled with --embroider', async function () { + await ember(['new', 'foo', '--skip-npm', '--skip-git', '--skip-bower', '--embroider']); + + let pkgJson = fs.readJsonSync('package.json'); + expect(pkgJson.devDependencies['@embroider/compat']).to.exist; + expect(pkgJson.devDependencies['@embroider/core']).to.exist; + expect(pkgJson.devDependencies['@embroider/webpack']).to.exist; + }); + describe('verify fixtures', function () { function checkEslintConfig(fixturePath) { expect(file('.eslintrc.js')).to.equal(file(path.join(__dirname, '../fixtures', fixturePath, '.eslintrc.js'))); } - function checkPackageJson(fixtureName) { - let currentVersion = isExperimentEnabled('MODULE_UNIFICATION') - ? 'github:ember-cli/ember-cli' - : require('../../package').version; - let fixturePath = path.join(__dirname, '../fixtures', fixtureName, 'package.json'); + function checkFileWithEmberCLIVersionReplacement(fixtureName, fileName) { + let currentVersion = require('../../package').version; + let fixturePath = path.join(__dirname, '../fixtures', fixtureName, fileName); let fixtureContents = fs .readFileSync(fixturePath, { encoding: 'utf-8' }) - .replace('<%= emberCLIVersion %>', currentVersion) - .replace('<%= emberCanaryVersion %>', emberCanaryVersion); + .replace('<%= emberCLIVersion %>', currentVersion); - expect(file('package.json')).to.equal(fixtureContents); + expect(file(fileName)).to.equal(fixtureContents); } - it( - 'app + npm + !welcome', - co.wrap(function*() { - yield ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--no-welcome']); + function checkEmberCLIBuild(fixtureName, fileName) { + let fixturePath = path.join(__dirname, '../fixtures', fixtureName, fileName); + let fixtureContents = fs.readFileSync(fixturePath, { encoding: 'utf-8' }); + expect(file(fileName)).to.equal(fixtureContents); + } - let namespace = isExperimentEnabled('MODULE_UNIFICATION') ? 'module-unification-app' : 'app'; - let applicationTemplate = isExperimentEnabled('MODULE_UNIFICATION') - ? 'src/ui/routes/application/template.hbs' - : 'app/templates/application.hbs'; - let fixturePath = `${namespace}/npm`; + it('app defaults', async function () { + await ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git']); - [applicationTemplate, '.travis.yml', 'README.md'].forEach(filePath => { - expect(file(filePath)).to.equal(file(path.join(__dirname, '../fixtures', fixturePath, filePath))); - }); + let namespace = 'app'; + let fixturePath = `${namespace}/defaults`; - checkPackageJson(fixturePath); + ['app/templates/application.hbs', '.travis.yml', 'README.md'].forEach((filePath) => { + expect(file(filePath)).to.equal(file(path.join(__dirname, '../fixtures', fixturePath, filePath))); + }); - // option independent, but piggy-backing on an existing generate for speed - checkEslintConfig(namespace); - }) - ); + if (isExperimentEnabled('EMBROIDER')) { + fixturePath = `${namespace}/embroider`; + } - it( - 'app + yarn + welcome', - co.wrap(function*() { - yield ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--yarn']); + checkFileWithEmberCLIVersionReplacement(fixturePath, 'config/ember-cli-update.json'); + checkFileWithEmberCLIVersionReplacement(fixturePath, 'package.json'); + checkEmberCLIBuild(fixturePath, 'ember-cli-build.js'); - let namespace = isExperimentEnabled('MODULE_UNIFICATION') ? 'module-unification-app' : 'app'; - let applicationTemplate = isExperimentEnabled('MODULE_UNIFICATION') - ? 'src/ui/routes/application/template.hbs' - : 'app/templates/application.hbs'; - let fixturePath = `${namespace}/yarn`; + // option independent, but piggy-backing on an existing generate for speed + checkEslintConfig(namespace); - [applicationTemplate, '.travis.yml', 'README.md'].forEach(filePath => { - expect(file(filePath)).to.equal(file(path.join(__dirname, '../fixtures', fixturePath, filePath))); - }); + // ember new without --lang flag (default) has no lang attribute in index.html + expect(file('app/index.html')).to.contain(''); + }); - checkPackageJson(fixturePath); - }) - ); + it('addon defaults', async function () { + await ember(['addon', 'foo', '--skip-npm', '--skip-bower', '--skip-git']); - it( - 'addon + yarn + welcome', - co.wrap(function*() { - yield ember(['addon', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--yarn', '--welcome']); - - let namespace = isExperimentEnabled('MODULE_UNIFICATION') ? 'module-unification-addon' : 'addon'; - let applicationTemplate = isExperimentEnabled('MODULE_UNIFICATION') - ? 'tests/dummy/src/ui/routes/application/template.hbs' - : 'tests/dummy/app/templates/application.hbs'; - let fixturePath = `${namespace}/yarn`; - - ['config/ember-try.js', applicationTemplate, '.travis.yml', 'README.md', 'CONTRIBUTING.md'].forEach( - filePath => { - expect(file(filePath)).to.equal(file(path.join(__dirname, '../fixtures', fixturePath, filePath))); - } - ); - - checkPackageJson(fixturePath); - }) - ); + let namespace = 'addon'; + let fixturePath = `${namespace}/defaults`; + + [ + 'config/ember-try.js', + 'tests/dummy/app/templates/application.hbs', + '.travis.yml', + 'README.md', + 'CONTRIBUTING.md', + ].forEach((filePath) => { + expect(file(filePath)).to.equal(file(path.join(__dirname, '../fixtures', fixturePath, filePath))); + }); - it( - 'addon + npm + !welcome', - co.wrap(function*() { - yield ember(['addon', 'foo', '--skip-npm', '--skip-bower', '--skip-git']); + checkFileWithEmberCLIVersionReplacement(fixturePath, 'package.json'); + checkFileWithEmberCLIVersionReplacement(fixturePath, 'tests/dummy/config/ember-cli-update.json'); - let namespace = isExperimentEnabled('MODULE_UNIFICATION') ? 'module-unification-addon' : 'addon'; - let applicationTemplate = isExperimentEnabled('MODULE_UNIFICATION') - ? 'tests/dummy/src/ui/routes/application/template.hbs' - : 'tests/dummy/app/templates/application.hbs'; - let fixturePath = `${namespace}/npm`; + // option independent, but piggy-backing on an existing generate for speed + checkEslintConfig(namespace); - ['config/ember-try.js', applicationTemplate, '.travis.yml', 'README.md', 'CONTRIBUTING.md'].forEach( - filePath => { - expect(file(filePath)).to.equal(file(path.join(__dirname, '../fixtures', fixturePath, filePath))); - } - ); + // ember addon without --lang flag (default) has no lang attribute in dummy index.html + expect(file('tests/dummy/app/index.html')).to.contain(''); + }); - checkPackageJson(fixturePath); + it('app + npm + !welcome', async function () { + await ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--no-welcome']); - // option independent, but piggy-backing on an existing generate for speed - checkEslintConfig(namespace); - }) - ); + let namespace = 'app'; + let fixturePath = `${namespace}/npm`; - if (isExperimentEnabled('MODULE_UNIFICATION')) { - it( - 'EMBER_CLI_MODULE_UNIFICATION: ember addon foo works', - co.wrap(function*() { - yield ember(['addon', 'foo', '--skip-npm', '--skip-bower']); - - // TODO: This test could be now removed because addon content is tested above - // - // the fixture files are now out of sync because - // this only tests file count and names, not contents - let expectedFiles = walkSync(path.join(__dirname, '../fixtures', 'module-unification-addon/yarn')); - let actualFiles = walkSync('.'); - expect(actualFiles).to.deep.equal(expectedFiles); - }) - ); - } + ['app/templates/application.hbs', '.travis.yml', 'README.md'].forEach((filePath) => { + expect(file(filePath)).to.equal(file(path.join(__dirname, '../fixtures', fixturePath, filePath))); + }); + + if (isExperimentEnabled('EMBROIDER')) { + fixturePath = 'app/embroider-no-welcome'; + } + + checkFileWithEmberCLIVersionReplacement(fixturePath, 'config/ember-cli-update.json'); + checkFileWithEmberCLIVersionReplacement(fixturePath, 'package.json'); + // option independent, but piggy-backing on an existing generate for speed + checkEslintConfig(namespace); + }); + + it('app + yarn + welcome', async function () { + await ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--yarn']); + + let fixturePath = 'app/yarn'; + + ['app/templates/application.hbs', '.travis.yml', 'README.md'].forEach((filePath) => { + expect(file(filePath)).to.equal(file(path.join(__dirname, '../fixtures', fixturePath, filePath))); + }); + + if (isExperimentEnabled('EMBROIDER')) { + fixturePath = 'app/embroider-yarn'; + } + + checkFileWithEmberCLIVersionReplacement(fixturePath, 'config/ember-cli-update.json'); + checkFileWithEmberCLIVersionReplacement(fixturePath, 'package.json'); + }); + + it('addon + yarn + welcome', async function () { + await ember(['addon', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--yarn', '--welcome']); + + let fixturePath = 'addon/yarn'; + + [ + 'config/ember-try.js', + 'tests/dummy/app/templates/application.hbs', + '.travis.yml', + 'README.md', + 'CONTRIBUTING.md', + ].forEach((filePath) => { + expect(file(filePath)).to.equal(file(path.join(__dirname, '../fixtures', fixturePath, filePath))); + }); + + checkFileWithEmberCLIVersionReplacement(fixturePath, 'package.json'); + checkFileWithEmberCLIVersionReplacement(fixturePath, 'tests/dummy/config/ember-cli-update.json'); + }); }); - describe('verify dependencies', function() { - it( - 'are locked down for pre-1.0 versions', - co.wrap(function*() { - yield ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--yarn', '--welcome']); + describe('verify dependencies', function () { + it('are locked down for pre-1.0 versions', async function () { + await ember(['new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', '--yarn', '--welcome']); - let pkg = fs.readJsonSync('package.json'); + let pkg = fs.readJsonSync('package.json'); - assertVersionLock(pkg.dependencies); - assertVersionLock(pkg.devDependencies); - }) - ); + assertVersionLock(pkg.dependencies); + assertVersionLock(pkg.devDependencies); + }); }); }); diff --git a/tests/acceptance/pods-destroy-test.js b/tests/acceptance/pods-destroy-test.js index 488ac982f9..79c8fc4d43 100644 --- a/tests/acceptance/pods-destroy-test.js +++ b/tests/acceptance/pods-destroy-test.js @@ -1,13 +1,12 @@ 'use strict'; -const co = require('co'); -const RSVP = require('rsvp'); const ember = require('../helpers/ember'); const fs = require('fs-extra'); +const util = require('util'); const replaceFile = require('ember-cli-internal-test-helpers/lib/helpers/file-utils').replaceFile; -let outputFile = RSVP.denodeify(fs.outputFile); +let outputFile = util.promisify(fs.outputFile); const path = require('path'); -let remove = RSVP.denodeify(fs.remove); +let remove = util.promisify(fs.remove); let root = process.cwd(); let tmproot = path.join(root, 'tmp'); const mkTmpDirIn = require('../../lib/utilities/mk-tmp-dir-in'); @@ -19,27 +18,25 @@ const chai = require('../chai'); let expect = chai.expect; let file = chai.file; -describe('Acceptance: ember destroy pod', function() { +describe('Acceptance: ember destroy pod', function () { let tmpdir; this.timeout(20000); - before(function() { + before(function () { BlueprintNpmTask.disableNPM(Blueprint); }); - after(function() { + after(function () { BlueprintNpmTask.restoreNPM(Blueprint); }); - beforeEach( - co.wrap(function*() { - tmpdir = yield mkTmpDirIn(tmproot); - process.chdir(tmpdir); - }) - ); + beforeEach(async function () { + tmpdir = await mkTmpDirIn(tmproot); + process.chdir(tmpdir); + }); - afterEach(function() { + afterEach(function () { this.timeout(10000); process.chdir(root); @@ -61,99 +58,93 @@ describe('Acceptance: ember destroy pod', function() { } function assertFilesExist(files) { - files.forEach(function(f) { + files.forEach(function (f) { expect(file(f)).to.exist; }); } function assertFilesNotExist(files) { - files.forEach(function(f) { + files.forEach(function (f) { expect(file(f)).to.not.exist; }); } - const assertDestroyAfterGenerate = co.wrap(function*(args, files) { - yield initApp(); + const assertDestroyAfterGenerate = async function (args, files) { + await initApp(); replaceFile('config/environment.js', '(var|let|const) ENV = {', "$1 ENV = {\npodModulePrefix: 'app/pods', \n"); - yield generate(args); + await generate(args); assertFilesExist(files); - let result = yield destroy(args); + let result = await destroy(args); expect(result, 'destroy command did not exit with errorCode').to.be.an('object'); assertFilesNotExist(files); - }); + }; - const destroyAfterGenerate = co.wrap(function*(args) { - yield initApp(); + const destroyAfterGenerate = async function (args) { + await initApp(); replaceFile('config/environment.js', '(var|let|const) ENV = {', "$1 ENV = {\npodModulePrefix: 'app/pods', \n"); - yield generate(args); - return yield destroy(args); - }); + await generate(args); + return await destroy(args); + }; - it('blueprint foo --pod', function() { + it('blueprint foo --pod', function () { let commandArgs = ['blueprint', 'foo', '--pod']; let files = ['blueprints/foo/index.js']; return assertDestroyAfterGenerate(commandArgs, files); }); - it('blueprint foo/bar --pod', function() { + it('blueprint foo/bar --pod', function () { let commandArgs = ['blueprint', 'foo/bar', '--pod']; let files = ['blueprints/foo/bar/index.js']; return assertDestroyAfterGenerate(commandArgs, files); }); - it('http-mock foo --pod', function() { + it('http-mock foo --pod', function () { let commandArgs = ['http-mock', 'foo', '--pod']; let files = ['server/mocks/foo.js']; return assertDestroyAfterGenerate(commandArgs, files); }); - it('http-proxy foo --pod', function() { + it('http-proxy foo --pod', function () { let commandArgs = ['http-proxy', 'foo', 'bar', '--pod']; let files = ['server/proxies/foo.js']; return assertDestroyAfterGenerate(commandArgs, files); }); - it( - 'deletes files generated using blueprints from the project directory', - co.wrap(function*() { - let commandArgs = ['foo', 'bar', '--pod']; - let files = ['app/foos/bar.js']; + it('deletes files generated using blueprints from the project directory', async function () { + let commandArgs = ['foo', 'bar', '--pod']; + let files = ['app/foos/bar.js']; - yield initApp(); + await initApp(); - yield outputFile( - 'blueprints/foo/files/app/foos/__name__.js', - "import Ember from 'ember';\n\n" + 'export default Ember.Object.extend({ foo: true });\n' - ); + await outputFile( + 'blueprints/foo/files/app/foos/__name__.js', + "import Ember from 'ember';\n\n" + 'export default Ember.Object.extend({ foo: true });\n' + ); - yield generate(commandArgs); - assertFilesExist(files); + await generate(commandArgs); + assertFilesExist(files); - yield destroy(commandArgs); - assertFilesNotExist(files); - }) - ); + await destroy(commandArgs); + assertFilesNotExist(files); + }); // Skip until podModulePrefix is deprecated - it.skip( - 'podModulePrefix deprecation warning', - co.wrap(function*() { - let result = yield destroyAfterGenerate(['controller', 'foo', '--pod']); - - expect(result.outputStream.join()).to.include( - '`podModulePrefix` is deprecated and will be' + - ' removed from future versions of ember-cli. Please move existing pods from' + - " 'app/pods/' to 'app/'." - ); - }) - ); + it.skip('podModulePrefix deprecation warning', async function () { + let result = await destroyAfterGenerate(['controller', 'foo', '--pod']); + + expect(result.outputStream.join()).to.include( + '`podModulePrefix` is deprecated and will be' + + ' removed from future versions of ember-cli. Please move existing pods from' + + " 'app/pods/' to 'app/'." + ); + }); }); diff --git a/tests/acceptance/pods-generate-test.js b/tests/acceptance/pods-generate-test.js index 7d06f73cbc..9b98d4726f 100644 --- a/tests/acceptance/pods-generate-test.js +++ b/tests/acceptance/pods-generate-test.js @@ -1,13 +1,9 @@ 'use strict'; -const co = require('co'); -const RSVP = require('rsvp'); const ember = require('../helpers/ember'); const replaceFile = require('ember-cli-internal-test-helpers/lib/helpers/file-utils').replaceFile; const fs = require('fs-extra'); -let outputFile = RSVP.denodeify(fs.outputFile); const path = require('path'); -let remove = RSVP.denodeify(fs.remove); let root = process.cwd(); let tmproot = path.join(root, 'tmp'); const mkTmpDirIn = require('../../lib/utilities/mk-tmp-dir-in'); @@ -19,29 +15,27 @@ const chai = require('../chai'); let expect = chai.expect; let file = chai.file; -describe('Acceptance: ember generate pod', function() { +describe('Acceptance: ember generate pod', function () { this.timeout(60000); let tmpdir; - before(function() { + before(function () { BlueprintNpmTask.disableNPM(Blueprint); }); - after(function() { + after(function () { BlueprintNpmTask.restoreNPM(Blueprint); }); - beforeEach( - co.wrap(function*() { - tmpdir = yield mkTmpDirIn(tmproot); - process.chdir(tmpdir); - }) - ); + beforeEach(async function () { + tmpdir = await mkTmpDirIn(tmproot); + process.chdir(tmpdir); + }); - afterEach(function() { + afterEach(function () { process.chdir(root); - return remove(tmproot); + return fs.remove(tmproot); }); function initApp() { @@ -57,7 +51,7 @@ describe('Acceptance: ember generate pod', function() { function generate(args) { let generateArgs = ['generate'].concat(args); - return initApp().then(function() { + return initApp().then(function () { return ember(generateArgs); }); } @@ -65,302 +59,272 @@ describe('Acceptance: ember generate pod', function() { function generateWithPrefix(args) { let generateArgs = ['generate'].concat(args); - return initApp().then(function() { + return initApp().then(function () { replaceFile('config/environment.js', '(var|let|const) ENV = {', "$1 ENV = {\npodModulePrefix: 'app/pods', \n"); return ember(generateArgs); }); } - it( - 'blueprint foo --pod', - co.wrap(function*() { - yield generate(['blueprint', 'foo', '--pod']); - - expect(file('blueprints/foo/index.js')).to.contain( - 'module.exports = {\n' + - " description: ''\n" + - '\n' + - ' // locals(options) {\n' + - ' // // Return custom template variables here.\n' + - ' // return {\n' + - ' // foo: options.entity.options.foo\n' + - ' // };\n' + - ' // }\n' + - '\n' + - ' // afterInstall(options) {\n' + - ' // // Perform extra work here.\n' + - ' // }\n' + - '};' - ); - }) - ); - - it( - 'blueprint foo/bar --pod', - co.wrap(function*() { - yield generate(['blueprint', 'foo/bar', '--pod']); - - expect(file('blueprints/foo/bar/index.js')).to.contain( - 'module.exports = {\n' + - " description: ''\n" + - '\n' + - ' // locals(options) {\n' + - ' // // Return custom template variables here.\n' + - ' // return {\n' + - ' // foo: options.entity.options.foo\n' + - ' // };\n' + - ' // }\n' + - '\n' + - ' // afterInstall(options) {\n' + - ' // // Perform extra work here.\n' + - ' // }\n' + - '};' - ); - }) - ); - - it( - 'http-mock foo --pod', - co.wrap(function*() { - yield generate(['http-mock', 'foo', '--pod']); - - expect(file('server/index.js')).to.contain('mocks.forEach(route => route(app));'); - - expect(file('server/mocks/foo.js')).to.contain( - 'module.exports = function(app) {\n' + - " const express = require('express');\n" + - ' let fooRouter = express.Router();\n' + - '\n' + - " fooRouter.get('/', function(req, res) {\n" + - ' res.send({\n' + - " 'foo': []\n" + - ' });\n' + - ' });\n' + - '\n' + - " fooRouter.post('/', function(req, res) {\n" + - ' res.status(201).end();\n' + - ' });\n' + - '\n' + - " fooRouter.get('/:id', function(req, res) {\n" + - ' res.send({\n' + - " 'foo': {\n" + - ' id: req.params.id\n' + - ' }\n' + - ' });\n' + - ' });\n' + - '\n' + - " fooRouter.put('/:id', function(req, res) {\n" + - ' res.send({\n' + - " 'foo': {\n" + - ' id: req.params.id\n' + - ' }\n' + - ' });\n' + - ' });\n' + - '\n' + - " fooRouter.delete('/:id', function(req, res) {\n" + - ' res.status(204).end();\n' + - ' });\n' + - '\n' + - ' // The POST and PUT call will not contain a request body\n' + - ' // because the body-parser is not included by default.\n' + - ' // To use req.body, run:\n' + - '\n' + - ' // npm install --save-dev body-parser\n' + - '\n' + - ' // After installing, you need to `use` the body-parser for\n' + - ' // this mock uncommenting the following line:\n' + - ' //\n' + - " //app.use('/api/foo', require('body-parser').json());\n" + - " app.use('/api/foo', fooRouter);\n" + - '};' - ); - - expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); - }) - ); - - it( - 'http-mock foo-bar --pod', - co.wrap(function*() { - yield generate(['http-mock', 'foo-bar', '--pod']); - - expect(file('server/index.js')).to.contain('mocks.forEach(route => route(app));'); - - expect(file('server/mocks/foo-bar.js')).to.contain( + it('blueprint foo --pod', async function () { + await generate(['blueprint', 'foo', '--pod']); + + expect(file('blueprints/foo/index.js')).to.contain( + 'module.exports = {\n' + + " description: ''\n" + + '\n' + + ' // locals(options) {\n' + + ' // // Return custom template variables here.\n' + + ' // return {\n' + + ' // foo: options.entity.options.foo\n' + + ' // };\n' + + ' // }\n' + + '\n' + + ' // afterInstall(options) {\n' + + ' // // Perform extra work here.\n' + + ' // }\n' + + '};' + ); + }); + + it('blueprint foo/bar --pod', async function () { + await generate(['blueprint', 'foo/bar', '--pod']); + + expect(file('blueprints/foo/bar/index.js')).to.contain( + 'module.exports = {\n' + + " description: ''\n" + + '\n' + + ' // locals(options) {\n' + + ' // // Return custom template variables here.\n' + + ' // return {\n' + + ' // foo: options.entity.options.foo\n' + + ' // };\n' + + ' // }\n' + + '\n' + + ' // afterInstall(options) {\n' + + ' // // Perform extra work here.\n' + + ' // }\n' + + '};' + ); + }); + + it('http-mock foo --pod', async function () { + await generate(['http-mock', 'foo', '--pod']); + + expect(file('server/index.js')).to.contain('mocks.forEach(route => route(app));'); + + expect(file('server/mocks/foo.js')).to.contain( + 'module.exports = function(app) {\n' + + " const express = require('express');\n" + + ' let fooRouter = express.Router();\n' + + '\n' + + " fooRouter.get('/', function(req, res) {\n" + + ' res.send({\n' + + " 'foo': []\n" + + ' });\n' + + ' });\n' + + '\n' + + " fooRouter.post('/', function(req, res) {\n" + + ' res.status(201).end();\n' + + ' });\n' + + '\n' + + " fooRouter.get('/:id', function(req, res) {\n" + + ' res.send({\n' + + " 'foo': {\n" + + ' id: req.params.id\n' + + ' }\n' + + ' });\n' + + ' });\n' + + '\n' + + " fooRouter.put('/:id', function(req, res) {\n" + + ' res.send({\n' + + " 'foo': {\n" + + ' id: req.params.id\n' + + ' }\n' + + ' });\n' + + ' });\n' + + '\n' + + " fooRouter.delete('/:id', function(req, res) {\n" + + ' res.status(204).end();\n' + + ' });\n' + + '\n' + + ' // The POST and PUT call will not contain a request body\n' + + ' // because the body-parser is not included by default.\n' + + ' // To use req.body, run:\n' + + '\n' + + ' // npm install --save-dev body-parser\n' + + '\n' + + ' // After installing, you need to `use` the body-parser for\n' + + ' // this mock uncommenting the following line:\n' + + ' //\n' + + " //app.use('/api/foo', require('body-parser').json());\n" + + " app.use('/api/foo', fooRouter);\n" + + '};' + ); + + expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); + }); + + it('http-mock foo-bar --pod', async function () { + await generate(['http-mock', 'foo-bar', '--pod']); + + expect(file('server/index.js')).to.contain('mocks.forEach(route => route(app));'); + + expect(file('server/mocks/foo-bar.js')).to.contain( + 'module.exports = function(app) {\n' + + " const express = require('express');\n" + + ' let fooBarRouter = express.Router();\n' + + '\n' + + " fooBarRouter.get('/', function(req, res) {\n" + + ' res.send({\n' + + " 'foo-bar': []\n" + + ' });\n' + + ' });\n' + + '\n' + + " fooBarRouter.post('/', function(req, res) {\n" + + ' res.status(201).end();\n' + + ' });\n' + + '\n' + + " fooBarRouter.get('/:id', function(req, res) {\n" + + ' res.send({\n' + + " 'foo-bar': {\n" + + ' id: req.params.id\n' + + ' }\n' + + ' });\n' + + ' });\n' + + '\n' + + " fooBarRouter.put('/:id', function(req, res) {\n" + + ' res.send({\n' + + " 'foo-bar': {\n" + + ' id: req.params.id\n' + + ' }\n' + + ' });\n' + + ' });\n' + + '\n' + + " fooBarRouter.delete('/:id', function(req, res) {\n" + + ' res.status(204).end();\n' + + ' });\n' + + '\n' + + ' // The POST and PUT call will not contain a request body\n' + + ' // because the body-parser is not included by default.\n' + + ' // To use req.body, run:\n' + + '\n' + + ' // npm install --save-dev body-parser\n' + + '\n' + + ' // After installing, you need to `use` the body-parser for\n' + + ' // this mock uncommenting the following line:\n' + + ' //\n' + + " //app.use('/api/foo-bar', require('body-parser').json());\n" + + " app.use('/api/foo-bar', fooBarRouter);\n" + + '};' + ); + + expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); + }); + + it('http-proxy foo --pod', async function () { + await generate(['http-proxy', 'foo', 'http://localhost:5000', '--pod']); + + expect(file('server/index.js')).to.contain('proxies.forEach(route => route(app));'); + + expect(file('server/proxies/foo.js')).to.contain( + "const proxyPath = '/foo';\n" + + '\n' + 'module.exports = function(app) {\n' + - " const express = require('express');\n" + - ' let fooBarRouter = express.Router();\n' + - '\n' + - " fooBarRouter.get('/', function(req, res) {\n" + - ' res.send({\n' + - " 'foo-bar': []\n" + - ' });\n' + - ' });\n' + - '\n' + - " fooBarRouter.post('/', function(req, res) {\n" + - ' res.status(201).end();\n' + - ' });\n' + - '\n' + - " fooBarRouter.get('/:id', function(req, res) {\n" + - ' res.send({\n' + - " 'foo-bar': {\n" + - ' id: req.params.id\n' + - ' }\n' + - ' });\n' + - ' });\n' + - '\n' + - " fooBarRouter.put('/:id', function(req, res) {\n" + - ' res.send({\n' + - " 'foo-bar': {\n" + - ' id: req.params.id\n' + - ' }\n' + - ' });\n' + - ' });\n' + - '\n' + - " fooBarRouter.delete('/:id', function(req, res) {\n" + - ' res.status(204).end();\n' + - ' });\n' + - '\n' + - ' // The POST and PUT call will not contain a request body\n' + - ' // because the body-parser is not included by default.\n' + - ' // To use req.body, run:\n' + - '\n' + - ' // npm install --save-dev body-parser\n' + - '\n' + - ' // After installing, you need to `use` the body-parser for\n' + - ' // this mock uncommenting the following line:\n' + - ' //\n' + - " //app.use('/api/foo-bar', require('body-parser').json());\n" + - " app.use('/api/foo-bar', fooBarRouter);\n" + - '};' - ); - - expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); - }) - ); - - it( - 'http-proxy foo --pod', - co.wrap(function*() { - yield generate(['http-proxy', 'foo', 'http://localhost:5000', '--pod']); - - expect(file('server/index.js')).to.contain('proxies.forEach(route => route(app));'); - - expect(file('server/proxies/foo.js')).to.contain( - "const proxyPath = '/foo';\n" + - '\n' + - 'module.exports = function(app) {\n' + - ' // For options, see:\n' + - ' // https://github.com/nodejitsu/node-http-proxy\n' + - " let proxy = require('http-proxy').createProxyServer({});\n" + - '\n' + - " proxy.on('error', function(err, req) {\n" + - ' console.error(err, req.url);\n' + - ' });\n' + - '\n' + - ' app.use(proxyPath, function(req, res, next){\n' + - ' // include root path in proxied request\n' + - " req.url = proxyPath + '/' + req.url;\n" + - " proxy.web(req, res, { target: 'http://localhost:5000' });\n" + - ' });\n' + - '};' - ); - - expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); - }) - ); - - it( - 'uses blueprints from the project directory', - co.wrap(function*() { - yield initApp(); - yield outputFile( - 'blueprints/foo/files/app/foos/__name__.js', - "import Ember from 'ember';\n" + 'export default Ember.Object.extend({ foo: true });\n' - ); - - yield ember(['generate', 'foo', 'bar', '--pod']); - - expect(file('app/foos/bar.js')).to.contain('foo: true'); - }) - ); - - it( - 'allows custom blueprints to override built-ins', - co.wrap(function*() { - yield initApp(); - yield outputFile( - 'blueprints/controller/files/app/__path__/__name__.js', - "import Ember from 'ember';\n\n" + 'export default Ember.Controller.extend({ custom: true });\n' - ); - - yield ember(['generate', 'controller', 'foo', '--pod']); - - expect(file('app/foo/controller.js')).to.contain('custom: true'); - }) - ); - - it( - 'passes custom cli arguments to blueprint options', - co.wrap(function*() { - yield initApp(); - yield outputFile( - 'blueprints/customblue/files/app/__name__.js', - 'Q: Can I has custom command? A: <%= hasCustomCommand %>' - ); - - yield outputFile( - 'blueprints/customblue/index.js', - 'module.exports = {\n' + - ' fileMapTokens(options) {\n' + - ' return {\n' + - ' __name__(options) {\n' + - ' return options.dasherizedModuleName;\n' + - ' }\n' + - ' };\n' + - ' },\n' + - ' locals(options) {\n' + - ' var loc = {};\n' + - " loc.hasCustomCommand = (options.customCommand) ? 'Yes!' : 'No. :C';\n" + - ' return loc;\n' + - ' },\n' + - '};\n' - ); - - yield ember(['generate', 'customblue', 'foo', '--custom-command', '--pod']); - - expect(file('app/foo.js')).to.contain('A: Yes!'); - }) - ); - - it( - 'correctly identifies the root of the project', - co.wrap(function*() { - yield initApp(); - yield outputFile( - 'blueprints/controller/files/app/__path__/__name__.js', - "import Ember from 'ember';\n\n" + 'export default Ember.Controller.extend({ custom: true });\n' - ); - - process.chdir(path.join(tmpdir, 'app')); - yield ember(['generate', 'controller', 'foo', '--pod']); - - process.chdir(tmpdir); - expect(file('app/foo/controller.js')).to.contain('custom: true'); - }) - ); + ' // For options, see:\n' + + ' // https://github.com/nodejitsu/node-http-proxy\n' + + " let proxy = require('http-proxy').createProxyServer({});\n" + + '\n' + + " proxy.on('error', function(err, req) {\n" + + ' console.error(err, req.url);\n' + + ' });\n' + + '\n' + + ' app.use(proxyPath, function(req, res, next){\n' + + ' // include root path in proxied request\n' + + " req.url = proxyPath + '/' + req.url;\n" + + " proxy.web(req, res, { target: 'http://localhost:5000' });\n" + + ' });\n' + + '};' + ); + + expect(file('server/.jshintrc')).to.contain('{\n "node": true\n}'); + }); + + it('uses blueprints from the project directory', async function () { + await initApp(); + await fs.outputFile( + 'blueprints/foo/files/app/foos/__name__.js', + "import Ember from 'ember';\n" + 'export default Ember.Object.extend({ foo: true });\n' + ); + + await ember(['generate', 'foo', 'bar', '--pod']); + + expect(file('app/foos/bar.js')).to.contain('foo: true'); + }); + + it('allows custom blueprints to override built-ins', async function () { + await initApp(); + await fs.outputFile( + 'blueprints/controller/files/app/__path__/__name__.js', + "import Ember from 'ember';\n\n" + 'export default Ember.Controller.extend({ custom: true });\n' + ); + + await ember(['generate', 'controller', 'foo', '--pod']); + + expect(file('app/foo/controller.js')).to.contain('custom: true'); + }); + + it('passes custom cli arguments to blueprint options', async function () { + await initApp(); + await fs.outputFile( + 'blueprints/customblue/files/app/__name__.js', + 'Q: Can I has custom command? A: <%= hasCustomCommand %>' + ); + + await fs.outputFile( + 'blueprints/customblue/index.js', + 'module.exports = {\n' + + ' fileMapTokens(options) {\n' + + ' return {\n' + + ' __name__(options) {\n' + + ' return options.dasherizedModuleName;\n' + + ' }\n' + + ' };\n' + + ' },\n' + + ' locals(options) {\n' + + ' var loc = {};\n' + + " loc.hasCustomCommand = (options.customCommand) ? 'Yes!' : 'No. :C';\n" + + ' return loc;\n' + + ' },\n' + + '};\n' + ); + + await ember(['generate', 'customblue', 'foo', '--custom-command', '--pod']); + + expect(file('app/foo.js')).to.contain('A: Yes!'); + }); + + it('correctly identifies the root of the project', async function () { + await initApp(); + await fs.outputFile( + 'blueprints/controller/files/app/__path__/__name__.js', + "import Ember from 'ember';\n\n" + 'export default Ember.Controller.extend({ custom: true });\n' + ); + + process.chdir(path.join(tmpdir, 'app')); + await ember(['generate', 'controller', 'foo', '--pod']); + + process.chdir(tmpdir); + expect(file('app/foo/controller.js')).to.contain('custom: true'); + }); // Skip until podModulePrefix is deprecated - it.skip( - 'podModulePrefix deprecation warning', - co.wrap(function*() { - let result = yield generateWithPrefix(['controller', 'foo', '--pod']); - - expect(result.outputStream.join()).to.include( - '`podModulePrefix` is deprecated and will be' + - ' removed from future versions of ember-cli. Please move existing pods from' + - " 'app/pods/' to 'app/'." - ); - }) - ); + it.skip('podModulePrefix deprecation warning', async function () { + let result = await generateWithPrefix(['controller', 'foo', '--pod']); + + expect(result.outputStream.join()).to.include( + '`podModulePrefix` is deprecated and will be' + + ' removed from future versions of ember-cli. Please move existing pods from' + + " 'app/pods/' to 'app/'." + ); + }); }); diff --git a/tests/acceptance/preprocessor-smoke-test-slow.js b/tests/acceptance/preprocessor-smoke-test-slow.js index afcec08ad8..651f61a3e6 100644 --- a/tests/acceptance/preprocessor-smoke-test-slow.js +++ b/tests/acceptance/preprocessor-smoke-test-slow.js @@ -1,12 +1,11 @@ 'use strict'; -const co = require('co'); const path = require('path'); const fs = require('fs-extra'); -const { isExperimentEnabled } = require('../../lib/experiments'); const runCommand = require('../helpers/run-command'); const acceptance = require('../helpers/acceptance'); +const DistChecker = require('../helpers/dist-checker'); const copyFixtureFiles = require('../helpers/copy-fixture-files'); let createTestTargets = acceptance.createTestTargets; let teardownTestTargets = acceptance.teardownTestTargets; @@ -15,87 +14,83 @@ let cleanupRun = acceptance.cleanupRun; const chai = require('../chai'); let expect = chai.expect; -let file = chai.file; let dir = chai.dir; let appName = 'some-cool-app'; let appRoot; -let fixturePrefix = isExperimentEnabled('MODULE_UNIFICATION') ? 'mu-app' : 'app'; - -describe('Acceptance: preprocessor-smoke-test', function() { +describe('Acceptance: preprocessor-smoke-test', function () { this.timeout(360000); - before(function() { + before(function () { return createTestTargets(appName); }); after(teardownTestTargets); - beforeEach(function() { + beforeEach(function () { appRoot = linkDependencies(appName); }); - afterEach(function() { + afterEach(function () { + runCommand.killAll(); cleanupRun(appName); expect(dir(appRoot)).to.not.exist; }); - it( - 'addons with standard preprocessors compile correctly', - co.wrap(function*() { - yield copyFixtureFiles(`preprocessor-tests/${fixturePrefix}-with-addon-with-preprocessors`); - - let packageJsonPath = path.join(appRoot, 'package.json'); - let packageJson = fs.readJsonSync(packageJsonPath); - packageJson.devDependencies['ember-cli-sass'] = 'latest'; - packageJson.devDependencies['ember-cool-addon'] = 'latest'; - fs.writeJsonSync(packageJsonPath, packageJson); - - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - - expect(file('dist/assets/some-cool-app.css')).to.contain('app styles included'); - expect(file('dist/assets/vendor.css')).to.contain('addon styles included'); - }) - ); - - it( - 'addon registry entries are added in the proper order', - co.wrap(function*() { - yield copyFixtureFiles(`preprocessor-tests/${fixturePrefix}-registry-ordering`); - - let packageJsonPath = path.join(appRoot, 'package.json'); - let packageJson = fs.readJsonSync(packageJsonPath); - packageJson.devDependencies['first-dummy-preprocessor'] = 'latest'; - packageJson.devDependencies['second-dummy-preprocessor'] = 'latest'; - fs.writeJsonSync(packageJsonPath, packageJson); - - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - - expect(file('dist/assets/some-cool-app.js')) - .to.contain('replacedByPreprocessor', 'token should have been replaced in app bundle') - .to.not.contain('__SECOND_PREPROCESSOR_REPLACEMENT_TOKEN__', 'token should not be contained') - .to.not.contain('__FIRST_PREPROCESSOR_REPLACEMENT_TOKEN__', 'token should not be contained'); - }) - ); - - it( - 'addons without preprocessors compile correctly', - co.wrap(function*() { - yield copyFixtureFiles(`preprocessor-tests/${fixturePrefix}-with-addon-without-preprocessors`); - - let packageJsonPath = path.join(appRoot, 'package.json'); - let packageJson = fs.readJsonSync(packageJsonPath); - packageJson.devDependencies['ember-cli-sass'] = 'latest'; - packageJson.devDependencies['ember-cool-addon'] = 'latest'; - fs.writeJsonSync(packageJsonPath, packageJson); - - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - - expect(file('dist/assets/some-cool-app.css')).to.contain('app styles included'); - expect(file('dist/assets/vendor.css')).to.contain('addon styles included'); - }) - ); + it('addons with standard preprocessors compile correctly', async function () { + await copyFixtureFiles(`preprocessor-tests/app-with-addon-with-preprocessors`); + + let packageJsonPath = path.join(appRoot, 'package.json'); + let packageJson = fs.readJsonSync(packageJsonPath); + packageJson.devDependencies['ember-cli-sass'] = 'latest'; + packageJson.devDependencies['ember-cool-addon'] = 'latest'; + fs.writeJsonSync(packageJsonPath, packageJson); + + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + + let checker = new DistChecker(path.join(appRoot, 'dist')); + + expect(checker.contains('css', 'app styles included')).to.be; + expect(checker.contains('css', 'addon styles included')).to.be; + }); + + it('addon registry entries are added in the proper order', async function () { + await copyFixtureFiles(`preprocessor-tests/app-registry-ordering`); + + let packageJsonPath = path.join(appRoot, 'package.json'); + let packageJson = fs.readJsonSync(packageJsonPath); + packageJson.devDependencies['first-dummy-preprocessor'] = 'latest'; + packageJson.devDependencies['second-dummy-preprocessor'] = 'latest'; + fs.writeJsonSync(packageJsonPath, packageJson); + + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + + let checker = new DistChecker(path.join(appRoot, 'dist')); + + expect(checker.contains('js', 'replacedByPreprocessor'), 'token should have been replaced in app bundle').to.be; + expect(checker.contains('js', '__SECOND_PREPROCESSOR_REPLACEMENT_TOKEN__'), 'token should not be contained').to.not + .be; + expect(checker.contains('js', '__FIRST_PREPROCESSOR_REPLACEMENT_TOKEN__'), 'token should not be contained').to.not + .be; + }); + + it('addons without preprocessors compile correctly', async function () { + await copyFixtureFiles(`preprocessor-tests/app-with-addon-without-preprocessors`); + + let packageJsonPath = path.join(appRoot, 'package.json'); + let packageJson = fs.readJsonSync(packageJsonPath); + packageJson.devDependencies['ember-cli-sass'] = 'latest'; + packageJson.devDependencies['ember-cool-addon'] = 'latest'; + fs.writeJsonSync(packageJsonPath, packageJson); + + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + + let checker = new DistChecker(path.join(appRoot, 'dist')); + + expect(checker.contains('css', 'app styles included')).to.be; + expect(checker.contains('css', 'addon styles included')).to.be; + }); /* [ app ] -> [ addon ] -> [ preprocessor addon ] @@ -104,27 +99,36 @@ describe('Acceptance: preprocessor-smoke-test', function() { | |-- preprocessor should not apply to this */ - it( - 'addons depending on preprocessor addon preprocesses addon but not app', - co.wrap(function*() { - yield copyFixtureFiles(`preprocessor-tests/${fixturePrefix}-with-addon-with-preprocessors-2`); - - let packageJsonPath = path.join(appRoot, 'package.json'); - let packageJson = fs.readJsonSync(packageJsonPath); - packageJson.devDependencies['ember-cool-addon'] = 'latest'; - fs.writeJsonSync(packageJsonPath, packageJson); - - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - - expect(file('dist/assets/some-cool-app.js')) - .to.contain('__PREPROCESSOR_REPLACEMENT_TOKEN__', 'token should not have been replaced in app bundle') - .to.not.contain('replacedByPreprocessor', 'token should not have been replaced in app bundle'); - - expect(file('dist/assets/vendor.js')) - .to.contain('replacedByPreprocessor', 'token should have been replaced in vendor bundle') - .to.not.contain('__PREPROCESSOR_REPLACEMENT_TOKEN__', 'token should have been replaced in vendor bundle'); - }) - ); + it('addons depending on preprocessor addon preprocesses addon but not app', async function () { + await copyFixtureFiles(`preprocessor-tests/app-with-addon-with-preprocessors-2`); + + let packageJsonPath = path.join(appRoot, 'package.json'); + let packageJson = fs.readJsonSync(packageJsonPath); + packageJson.devDependencies['ember-cool-addon'] = 'latest'; + fs.writeJsonSync(packageJsonPath, packageJson); + + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + + let checker = new DistChecker(path.join(appRoot, 'dist')); + + expect( + checker.contains('js', 'foo_in_app: __PREPROCESSOR_REPLACEMENT_TOKEN__'), + 'token should not have been replaced in app bundle' + ).to.be; + expect( + checker.contains('js', 'foo_in_app: "replacedByPreprocessor"'), + 'token should not have been replaced in app bundle' + ).to.not.be; + + expect( + checker.contains('js', 'foo_in_addon: "replacedByPreprocessor"'), + 'token should have been replaced in vendor bundle' + ).to.be; + expect( + checker.contains('js', 'foo_in_addon: __PREPROCESSOR_REPLACEMENT_TOKEN__'), + 'token should have been replaced in vendor bundle' + ).to.not.be; + }); /* [ app ] -> [ addon ] -> [ addon ] -> [ preprocessor addon ] @@ -135,34 +139,43 @@ describe('Acceptance: preprocessor-smoke-test', function() { | |-- preprocessor should not apply to this */ - it( - 'addon N levels deep depending on preprocessor preprocesses that parent addon only', - co.wrap(function*() { - yield copyFixtureFiles(`preprocessor-tests/${fixturePrefix}-with-addon-with-preprocessors-3`); - - let packageJsonPath = path.join(appRoot, 'package.json'); - let packageJson = fs.readJsonSync(packageJsonPath); - packageJson.devDependencies['ember-shallow-addon'] = 'latest'; - - fs.writeJsonSync(packageJsonPath, packageJson); - - yield runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); - - expect(file('dist/assets/some-cool-app.js')) - .to.contain('__PREPROCESSOR_REPLACEMENT_TOKEN__', 'token should not have been replaced in app bundle') - .to.not.contain('replacedByPreprocessor', 'token should not have been replaced in app bundle'); - - expect(file('dist/assets/vendor.js')) - .to.contain('deep: "replacedByPreprocessor"', 'token should have been replaced in deep component') - .to.contain( - 'shallow: __PREPROCESSOR_REPLACEMENT_TOKEN__', - 'token should not have been replaced in shallow component' - ) - .to.not.contain('deep: __PREPROCESSOR_REPLACEMENT_TOKEN__', 'token should have been replaced in deep component') - .to.not.contain( - 'shallow: "replacedByPreprocessor"', - 'token should not have been replaced in shallow component' - ); - }) - ); + it('addon N levels deep depending on preprocessor preprocesses that parent addon only', async function () { + await copyFixtureFiles(`preprocessor-tests/app-with-addon-with-preprocessors-3`); + + let packageJsonPath = path.join(appRoot, 'package.json'); + let packageJson = fs.readJsonSync(packageJsonPath); + packageJson.devDependencies['ember-shallow-addon'] = 'latest'; + + fs.writeJsonSync(packageJsonPath, packageJson); + + await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); + + let checker = new DistChecker(path.join(appRoot, 'dist')); + + expect( + checker.contains('js', 'foo_in_app: __PREPROCESSOR_REPLACEMENT_TOKEN__'), + 'token should not have been replaced in app bundle' + ).to.be; + expect( + checker.contains('js', 'foo_in_app: "replacedByPreprocessor"'), + 'token should not have been replaced in app bundle' + ).to.not.be; + + expect( + checker.contains('js', 'deep: "replacedByPreprocessor"'), + 'token should have been replaced in deep component' + ).to.be; + expect( + checker.contains('js', 'shallow: __PREPROCESSOR_REPLACEMENT_TOKEN__'), + 'token should not have been replaced in shallow component' + ).to.be; + expect( + checker.contains('js', 'deep: __PREPROCESSOR_REPLACEMENT_TOKEN__'), + 'token should have been replaced in deep component' + ).to.not.be; + expect( + checker.contains('js', 'shallow: "replacedByPreprocessor"'), + 'token should not have been replaced in shallow component' + ).to.not.be; + }); }); diff --git a/tests/acceptance/smoke-test-slow.js b/tests/acceptance/smoke-test-slow.js index 57010f7f0b..fd40358bd2 100644 --- a/tests/acceptance/smoke-test-slow.js +++ b/tests/acceptance/smoke-test-slow.js @@ -5,13 +5,15 @@ const fs = require('fs-extra'); const crypto = require('crypto'); const walkSync = require('walk-sync'); const EOL = require('os').EOL; +const execa = require('execa'); -const { isExperimentEnabled } = require('../../lib/experiments'); -const runCommand = require('../helpers/run-command'); const acceptance = require('../helpers/acceptance'); const copyFixtureFiles = require('../helpers/copy-fixture-files'); const killCliProcess = require('../helpers/kill-cli-process'); const ember = require('../helpers/ember'); +const runCommand = require('../helpers/run-command'); +const { isExperimentEnabled } = require('../../lib/experiments'); +const DistChecker = require('../helpers/dist-checker'); let createTestTargets = acceptance.createTestTargets; let teardownTestTargets = acceptance.teardownTestTargets; let linkDependencies = acceptance.linkDependencies; @@ -25,29 +27,30 @@ let dir = chai.dir; let appName = 'some-cool-app'; let appRoot; -describe('Acceptance: smoke-test', function() { +describe('Acceptance: smoke-test', function () { this.timeout(500000); - before(function() { + before(function () { return createTestTargets(appName); }); after(teardownTestTargets); - beforeEach(function() { + beforeEach(function () { appRoot = linkDependencies(appName); }); - afterEach(function() { + afterEach(function () { delete process.env._TESTEM_CONFIG_JS_RAN; + runCommand.killAll(); cleanupRun(appName); expect(dir(appRoot)).to.not.exist; }); - it('ember new foo, clean from scratch', function() { + it('ember new foo, clean from scratch', function () { return runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'test'); }); - it('ember new foo, make sure addon template overwrites', async function() { + it('ember new foo, make sure addon template overwrites', async function () { await ember(['generate', 'template', 'foo']); await ember(['generate', 'in-repo-addon', 'my-addon']); @@ -57,14 +60,8 @@ describe('Acceptance: smoke-test', function() { //return ember(['generate', 'template', 'foo', '--in-repo-addon=my-addon']); // temporary work around - let templatePath, packageJsonPath; - if (isExperimentEnabled('MODULE_UNIFICATION')) { - templatePath = path.join('packages', 'my-addon', 'src', 'ui', 'routes', 'foo.hbs'); - packageJsonPath = path.join('packages', 'my-addon', 'package.json'); - } else { - templatePath = path.join('lib', 'my-addon', 'app', 'templates', 'foo.hbs'); - packageJsonPath = path.join('lib', 'my-addon', 'package.json'); - } + let templatePath = path.join('lib', 'my-addon', 'app', 'templates', 'foo.hbs'); + let packageJsonPath = path.join('lib', 'my-addon', 'package.json'); fs.mkdirsSync(path.dirname(templatePath)); fs.writeFileSync(templatePath, 'Hi, Mom!', { encoding: 'utf8' }); @@ -79,7 +76,7 @@ describe('Acceptance: smoke-test', function() { expect(result.code).to.equal(0); }); - it('ember test still runs when a JavaScript testem config exists', async function() { + it('ember test still runs when a JavaScript testem config exists', async function () { await copyFixtureFiles('smoke-tests/js-testem-config'); let result = await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'test'); @@ -91,7 +88,7 @@ describe('Acceptance: smoke-test', function() { expect(output).to.include('***CUSTOM_TESTEM_JS**'); }); - it('eslint passes after running ember new', async function() { + it('eslint passes after running ember new', async function () { let result = await runCommand(path.join('.', 'node_modules', 'eslint', 'bin', 'eslint.js'), appRoot); let exitCode = result.code; @@ -104,36 +101,42 @@ describe('Acceptance: smoke-test', function() { // when run in isolation, it passes // here is the error: // test-support-80f2fe63fae0c44478fe0f8af73200a7.js contains the fingerprint (2871106928f813936fdd64f4d16005ac): expected 'test-support-80f2fe63fae0c44478fe0f8af73200a7.js' to include '2871106928f813936fdd64f4d16005ac' - it.skip('ember new foo, build production and verify fingerprint', async function() { - await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build', '--environment=production'); + if (!isExperimentEnabled('EMBROIDER')) { + it.skip('ember new foo, build production and verify fingerprint', async function () { + await runCommand( + path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), + 'build', + '--environment=production' + ); - let dirPath = path.join(appRoot, 'dist', 'assets'); - let dir = fs.readdirSync(dirPath); - let files = []; + let dirPath = path.join(appRoot, 'dist', 'assets'); + let dir = fs.readdirSync(dirPath); + let files = []; - dir.forEach(function(filepath) { - if (filepath === '.gitkeep') { - return; - } + dir.forEach(function (filepath) { + if (filepath === '.gitkeep') { + return; + } - files.push(filepath); + files.push(filepath); - let file = fs.readFileSync(path.join(dirPath, filepath), { encoding: null }); + let file = fs.readFileSync(path.join(dirPath, filepath), { encoding: null }); - let md5 = crypto.createHash('md5'); - md5.update(file); - let hex = md5.digest('hex'); + let md5 = crypto.createHash('md5'); + md5.update(file); + let hex = md5.digest('hex'); - expect(filepath).to.contain(hex, `${filepath} contains the fingerprint (${hex})`); - }); + expect(filepath).to.contain(hex, `${filepath} contains the fingerprint (${hex})`); + }); - let indexHtml = file('dist/index.html'); - files.forEach(function(filename) { - expect(indexHtml).to.contain(filename); + let indexHtml = file('dist/index.html'); + files.forEach(function (filename) { + expect(indexHtml).to.contain(filename); + }); }); - }); + } - it('ember test --environment=production', async function() { + it('ember test --environment=production', async function () { await copyFixtureFiles('smoke-tests/passing-test'); let result = await runCommand( @@ -146,12 +149,11 @@ describe('Acceptance: smoke-test', function() { let output = result.output.join(EOL); expect(exitCode).to.equal(0, 'exit code should be 0 for passing tests'); - expect(output).to.match(/ESLint/, 'ESLint should be run on production assets'); expect(output).to.match(/fail\s+0/, 'no failures'); expect(output).to.match(/pass\s+\d+/, 'many passing'); }); - it('ember test --path with previous build', async function() { + it('ember test --path with previous build', async function () { let originalWrite = process.stdout.write; let output = []; @@ -161,8 +163,8 @@ describe('Acceptance: smoke-test', function() { await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); // TODO: Figure out how to get this to write into the MockUI - process.stdout.write = (function() { - return function() { + process.stdout.write = (function () { + return function () { output.push(arguments[0]); }; })(originalWrite); @@ -182,7 +184,7 @@ describe('Acceptance: smoke-test', function() { expect(output).to.match(/pass\s+\d+/, 'many passing'); }); - it('ember test wasm', async function() { + it('ember test wasm', async function () { let originalWrite = process.stdout.write; let output = []; @@ -192,8 +194,8 @@ describe('Acceptance: smoke-test', function() { await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); // TODO: Figure out how to get this to write into the MockUI - process.stdout.write = (function() { - return function() { + process.stdout.write = (function () { + return function () { output.push(arguments[0]); }; })(originalWrite); @@ -213,7 +215,7 @@ describe('Acceptance: smoke-test', function() { expect(output).to.match(/pass\s+\d+/, 'many passing'); }); - it('ember new foo, build development, and verify generated files', async function() { + it('ember new foo, build development, and verify generated files', async function () { await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); let dirPath = path.join(appRoot, 'dist'); @@ -222,9 +224,8 @@ describe('Acceptance: smoke-test', function() { expect(paths).to.have.length.below(24, `expected fewer than 24 files in dist, found ${paths.length}`); }); - it('ember build exits with non-zero code when build fails', async function() { - let rootPath = isExperimentEnabled('MODULE_UNIFICATION') ? 'src' : 'app'; - let appJsPath = path.join(appRoot, rootPath, 'app.js'); + it('ember build exits with non-zero code when build fails', async function () { + let appJsPath = path.join(appRoot, 'app', 'app.js'); let result = await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build'); expect(result.code).to.equal(0, `expected exit code to be zero, but got ${result.code}`); @@ -238,7 +239,7 @@ describe('Acceptance: smoke-test', function() { expect(result.code).to.not.equal(0, `expected exit code to be non-zero, but got ${result.code}`); }); - it('ember build generates instrumentation files when viz is enabled', async function() { + it('ember build generates instrumentation files when viz is enabled', async function () { process.env.BROCCOLI_VIZ = '1'; try { @@ -256,7 +257,7 @@ describe('Acceptance: smoke-test', function() { 'instrumentation.command.json', 'instrumentation.init.json', 'instrumentation.shutdown.json', - ].forEach(instrumentationFile => { + ].forEach((instrumentationFile) => { expect(fs.existsSync(instrumentationFile)).to.equal(true); let json = fs.readJsonSync(instrumentationFile); @@ -266,21 +267,20 @@ describe('Acceptance: smoke-test', function() { }); }); - it.skip('ember new foo, build --watch development, and verify rebuilt after change', async function() { + it('ember new foo, build --watch development, and verify rebuilt after change', async function () { let touched = false; let appJsPath = path.join(appRoot, 'app', 'app.js'); - let builtJsPath = path.join(appRoot, 'dist', 'assets', 'some-cool-app.js'); let text = 'anotuhaonteuhanothunaothanoteh'; let line = `console.log("${text}");`; + let checker = new DistChecker(path.join(appRoot, 'dist')); + try { await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build', '--watch', { onOutput(string, child) { if (touched) { if (string.match(/Build successful/)) { - // build after change to app.js - let contents = fs.readFileSync(builtJsPath).toString(); - expect(contents).to.contain(text, 'must contain changed line after rebuild'); + expect(checker.contains('js', text)).to.be; killCliProcess(child); } } else if (string.match(/Build successful/)) { @@ -295,16 +295,17 @@ describe('Acceptance: smoke-test', function() { } }); - it.skip('ember new foo, build --watch development, and verify rebuilt after multiple changes', async function() { + it('ember new foo, build --watch development, and verify rebuilt after multiple changes', async function () { let buildCount = 0; let touched = false; let appJsPath = path.join(appRoot, 'app', 'app.js'); - let builtJsPath = path.join(appRoot, 'dist', 'assets', 'some-cool-app.js'); let firstText = 'anotuhaonteuhanothunaothanoteh'; let firstLine = `console.log("${firstText}");`; let secondText = 'aahsldfjlwioruoiiononociwewqwr'; let secondLine = `console.log("${secondText}");`; + let checker = new DistChecker(path.join(appRoot, 'dist')); + try { await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build', '--watch', { onOutput(string, child) { @@ -324,9 +325,7 @@ describe('Acceptance: smoke-test', function() { } } else if (touched && buildCount === 2) { if (string.match(/Build successful/)) { - // build after change to app.js - let contents = fs.readFileSync(builtJsPath).toString(); - expect(contents).to.contain(secondText, 'must contain second changed line after rebuild'); + expect(checker.contains('js', secondText)).to.be; killCliProcess(child); } } @@ -337,7 +336,46 @@ describe('Acceptance: smoke-test', function() { } }); - it.skip('ember new foo, server, SIGINT clears tmp/', async function() { + it('build failures should be logged correctly', async function () { + fs.writeFileSync( + `${process.cwd()}/ember-cli-build.js`, + ` +const Plugin = require('broccoli-plugin'); + +module.exports = function() { + return new class extends Plugin { + constructor() { + super([]); + } + build() { + throw new Error('I AM A BUILD FAILURE'); + } + } +} + ` + ); + + await runCommand( + path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), + 'server', + '--port=0', + '--live-reload=false', + { + onOutput(string, child) { + if (string.includes('I AM A BUILD FAILURE')) { + killCliProcess(child); + } + }, + onError(string, child) { + if (string.includes('I AM A BUILD FAILURE')) { + killCliProcess(child); + } + }, + } + ); + }); + + it('ember new foo, server, SIGINT clears tmp/', async function () { let result = await runCommand( path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'server', @@ -360,12 +398,12 @@ describe('Acceptance: smoke-test', function() { // With broccoli2 that should not exist, they should be using os.tmpdir(). // So we'll just check for "if tmp/ is there, are the contents correct?" if (fs.existsSync(dirPath)) { - let dir = fs.readdirSync(dirPath).filter(file => file !== '.metadata_never_index'); + let dir = fs.readdirSync(dirPath).filter((file) => file !== '.metadata_never_index'); expect(dir.length, `${dirPath} should be empty`).to.equal(0); } }); - it('ember new foo, test, SIGINT exits with error and clears tmp/', async function() { + it('ember new foo, test, SIGINT exits with error and clears tmp/', async function () { let result = await expect( runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'test', '--test-port=25522', { onOutput(string, child) { @@ -385,20 +423,20 @@ describe('Acceptance: smoke-test', function() { // With broccoli2 that should not exist, they should be using os.tmpdir(). // So we'll just check for "if tmp/ is there, are the contents correct?" if (fs.existsSync(dirPath)) { - let dir = fs.readdirSync(dirPath).filter(file => file !== '.metadata_never_index'); + let dir = fs.readdirSync(dirPath).filter((file) => file !== '.metadata_never_index'); expect(dir.length, `${dirPath} should be empty`).to.equal(0); } }); - it('ember new foo, build production and verify css files are concatenated', async function() { - await copyFixtureFiles(isExperimentEnabled('MODULE_UNIFICATION') ? 'with-styles-mu' : 'with-styles'); + it('ember new foo, build production and verify css files are concatenated', async function () { + await copyFixtureFiles('with-styles'); await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build', '--environment=production'); let dirPath = path.join(appRoot, 'dist', 'assets'); let dir = fs.readdirSync(dirPath); let cssNameRE = new RegExp(`${appName}-([a-f0-9]+)\\.css`, 'i'); - dir.forEach(function(filepath) { + dir.forEach(function (filepath) { if (cssNameRE.test(filepath)) { expect(file(`dist/assets/${filepath}`)) .to.contain('.some-weird-selector') @@ -407,17 +445,15 @@ describe('Acceptance: smoke-test', function() { }); }); - it('ember new foo, build production and verify css files are minified', async function() { - await copyFixtureFiles( - isExperimentEnabled('MODULE_UNIFICATION') ? 'with-unminified-styles-mu' : 'with-unminified-styles' - ); + it('ember new foo, build production and verify css files are minified', async function () { + await copyFixtureFiles('with-unminified-styles'); await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build', '--environment=production'); let dirPath = path.join(appRoot, 'dist', 'assets'); let dir = fs.readdirSync(dirPath); let cssNameRE = new RegExp(`${appName}-([a-f0-9]+)\\.css`, 'i'); - dir.forEach(function(filepath) { + dir.forEach(function (filepath) { if (cssNameRE.test(filepath)) { let contents = fs.readFileSync(path.join(appRoot, 'dist', 'assets', filepath), { encoding: 'utf8' }); expect(contents).to.match(/^\S+$/, 'css file is minified'); @@ -425,35 +461,18 @@ describe('Acceptance: smoke-test', function() { }); }); - it('ember new foo, build production and verify single "use strict";', async function() { - await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build', '--environment=production'); - - let dirPath = path.join(appRoot, 'dist', 'assets'); - let dir = fs.readdirSync(dirPath); - let appNameRE = new RegExp(`${appName}-([a-f0-9]+)\\.js`, 'i'); - dir.forEach(function(filepath) { - if (appNameRE.test(filepath)) { - let contents = fs.readFileSync(path.join(appRoot, 'dist', 'assets', filepath), { encoding: 'utf8' }); - let count = (contents.match(/(["'])use strict\1;/g) || []).length; - expect(count).to.equal(1); - } - }); - }); - - it('ember can override and reuse the built-in blueprints', async function() { + it('ember can override and reuse the built-in blueprints', async function () { await copyFixtureFiles('addon/with-blueprint-override'); await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'generate', 'component', 'foo-bar'); - let filePath = isExperimentEnabled('MODULE_UNIFICATION') - ? 'src/ui/components/new-path/foo-bar/component.js' - : 'app/components/new-path/foo-bar.js'; + let filePath = 'app/components/new-path/foo-bar.js'; // because we're overriding, the fileMapTokens is default, sans 'component' expect(file(filePath)).to.contain('generated component successfully'); }); - it('template linting works properly for pods and classic structured templates', async function() { + it('template linting works properly for pods and classic structured templates', async function () { await copyFixtureFiles('smoke-tests/with-template-failing-linting'); let packageJsonPath = 'package.json'; @@ -470,4 +489,30 @@ describe('Acceptance: smoke-test', function() { expect(output).to.match(/fail\s+2/, 'two templates failed linting'); expect(result.code).to.equal(1); }); + + describe('lint fixing after file generation', function () { + beforeEach(async function () { + await copyFixtureFiles('app/with-blueprint-override-lint-fail'); + }); + + let componentName = 'foo-bar'; + + it('does not fix lint errors with --no-lint-fix', async function () { + await ember(['generate', 'component', componentName, '--component-class=@ember/component', '--no-lint-fix']); + + await expect(execa('eslint', ['.'], { cwd: appRoot, preferLocal: true })).to.eventually.be.rejectedWith( + `${componentName}.js` + ); + await expect( + execa('ember-template-lint', ['.'], { cwd: appRoot, preferLocal: true }) + ).to.eventually.be.rejectedWith(`${componentName}.hbs`); + }); + + it('does fix lint errors with --lint-fix', async function () { + await ember(['generate', 'component', componentName, '--component-class=@ember/component', '--lint-fix']); + + await expect(execa('eslint', ['.'], { cwd: appRoot, preferLocal: true })).to.eventually.be.ok; + await expect(execa('ember-template-lint', ['.'], { cwd: appRoot, preferLocal: true })).to.eventually.be.ok; + }); + }); }); diff --git a/tests/factories/command-options.js b/tests/factories/command-options.js index 2dfc439b85..5f1df04fbe 100644 --- a/tests/factories/command-options.js +++ b/tests/factories/command-options.js @@ -7,10 +7,10 @@ const MockProject = require('../helpers/mock-project'); function createProject() { let project = new MockProject(); - project.isEmberCLIProject = function() { + project.isEmberCLIProject = function () { return true; }; - project.config = function() { + project.config = function () { return {}; }; return project; diff --git a/tests/fixtures/addon/.eslintrc.js b/tests/fixtures/addon/.eslintrc.js index d43fd96b4e..ab627f4f0c 100644 --- a/tests/fixtures/addon/.eslintrc.js +++ b/tests/fixtures/addon/.eslintrc.js @@ -1,53 +1,53 @@ +'use strict'; + module.exports = { root: true, parser: 'babel-eslint', parserOptions: { ecmaVersion: 2018, - sourceType: 'module' + sourceType: 'module', + ecmaFeatures: { + legacyDecorators: true, + }, }, - plugins: [ - 'ember' - ], + plugins: ['ember'], extends: [ 'eslint:recommended', - 'plugin:ember/recommended' + 'plugin:ember/recommended', + 'plugin:prettier/recommended', ], env: { - browser: true - }, - rules: { - 'ember/no-jquery': 'error' + browser: true, }, + rules: {}, overrides: [ // node files { files: [ - '.eslintrc.js', - '.template-lintrc.js', - 'ember-cli-build.js', - 'index.js', - 'testem.js', - 'blueprints/*/index.js', - 'config/**/*.js', - 'tests/dummy/config/**/*.js' - ], - excludedFiles: [ - 'addon/**', - 'addon-test-support/**', - 'app/**', - 'tests/dummy/app/**' + './.eslintrc.js', + './.prettierrc.js', + './.template-lintrc.js', + './ember-cli-build.js', + './index.js', + './testem.js', + './blueprints/*/index.js', + './config/**/*.js', + './tests/dummy/config/**/*.js', ], parserOptions: { - sourceType: 'script' + sourceType: 'script', }, env: { browser: false, - node: true + node: true, }, plugins: ['node'], - rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { - // add your custom rules and overrides for node files here - }) - } - ] + extends: ['plugin:node/recommended'], + }, + { + // Test files: + files: ['tests/**/*-test.{js,ts}'], + extends: ['plugin:qunit/recommended'], + }, + ], }; diff --git a/tests/fixtures/addon/component-with-template/tests/acceptance/main-test.js b/tests/fixtures/addon/component-with-template/tests/acceptance/main-test.js index 88f6347bbe..4e9a95a804 100644 --- a/tests/fixtures/addon/component-with-template/tests/acceptance/main-test.js +++ b/tests/fixtures/addon/component-with-template/tests/acceptance/main-test.js @@ -6,14 +6,14 @@ import { module, test } from 'qunit'; module('Acceptance', function(hooks) { setupApplicationTest(hooks); - test('renders properly', async function(assert) { + test('renders properly', async function (assert) { await visit('/'); var element = this.element.querySelector('.basic-thing'); assert.equal(element.textContent.trim(), 'WOOT!!'); }); - test('renders imported component', async function(assert) { + test('renders imported component', async function (assert) { await visit('/'); var element = this.element.querySelector('.second-thing'); diff --git a/tests/fixtures/addon/npm/.travis.yml b/tests/fixtures/addon/defaults/.travis.yml similarity index 68% rename from tests/fixtures/addon/npm/.travis.yml rename to tests/fixtures/addon/defaults/.travis.yml index a45eb4a560..9b3dfabbdb 100644 --- a/tests/fixtures/addon/npm/.travis.yml +++ b/tests/fixtures/addon/defaults/.travis.yml @@ -3,10 +3,9 @@ language: node_js node_js: # we recommend testing addons with the same minimum supported node version as Ember CLI # so that your addon works for all apps - - "8" + - "12" -sudo: false -dist: trusty +dist: xenial addons: chrome: stable @@ -27,29 +26,36 @@ branches: - /^v\d+\.\d+\.\d+/ jobs: - fail_fast: true + fast_finish: true allow_failures: - env: EMBER_TRY_SCENARIO=ember-canary include: # runs linting and tests with current locked deps - - stage: "Tests" name: "Tests" script: - - npm run lint:hbs - - npm run lint:js - - npm test + - npm run lint + - npm run test:ember + + - stage: "Additional Tests" + name: "Floating Dependencies" + install: + - npm install --no-package-lock + script: + - npm run test:ember # we recommend new addons test the current and previous LTS # as well as latest stable release (bonus points to beta/canary) - - stage: "Additional Tests" - env: EMBER_TRY_SCENARIO=ember-lts-3.4 - - env: EMBER_TRY_SCENARIO=ember-lts-3.8 + - env: EMBER_TRY_SCENARIO=ember-lts-3.24 + - env: EMBER_TRY_SCENARIO=ember-lts-3.28 - env: EMBER_TRY_SCENARIO=ember-release - env: EMBER_TRY_SCENARIO=ember-beta - env: EMBER_TRY_SCENARIO=ember-canary - env: EMBER_TRY_SCENARIO=ember-default-with-jquery + - env: EMBER_TRY_SCENARIO=ember-classic + - env: EMBER_TRY_SCENARIO=embroider-safe + - env: EMBER_TRY_SCENARIO=embroider-optimized script: - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO diff --git a/tests/fixtures/module-unification-addon/npm/CONTRIBUTING.md b/tests/fixtures/addon/defaults/CONTRIBUTING.md similarity index 83% rename from tests/fixtures/module-unification-addon/npm/CONTRIBUTING.md rename to tests/fixtures/addon/defaults/CONTRIBUTING.md index 8fc5b869d6..fe508af0ed 100644 --- a/tests/fixtures/module-unification-addon/npm/CONTRIBUTING.md +++ b/tests/fixtures/addon/defaults/CONTRIBUTING.md @@ -8,9 +8,8 @@ ## Linting -* `npm run lint:hbs` -* `npm run lint:js` -* `npm run lint:js -- --fix` +* `npm run lint` +* `npm run lint:fix` ## Running tests @@ -23,4 +22,4 @@ * `ember serve` * Visit the dummy application at [http://localhost:4200](http://localhost:4200). -For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). \ No newline at end of file +For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). diff --git a/tests/fixtures/module-unification-addon/npm/README.md b/tests/fixtures/addon/defaults/README.md similarity index 91% rename from tests/fixtures/module-unification-addon/npm/README.md rename to tests/fixtures/addon/defaults/README.md index 36bd8fd416..378fb8a256 100644 --- a/tests/fixtures/module-unification-addon/npm/README.md +++ b/tests/fixtures/addon/defaults/README.md @@ -7,9 +7,9 @@ foo Compatibility ------------------------------------------------------------------------------ -* Ember.js v3.4 or above -* Ember CLI v2.13 or above -* Node.js v8 or above +* Ember.js v3.24 or above +* Ember CLI v3.24 or above +* Node.js v12 or above Installation diff --git a/tests/fixtures/addon/defaults/config/ember-try.js b/tests/fixtures/addon/defaults/config/ember-try.js new file mode 100644 index 0000000000..2612023fa0 --- /dev/null +++ b/tests/fixtures/addon/defaults/config/ember-try.js @@ -0,0 +1,84 @@ +'use strict'; + +const getChannelURL = require('ember-source-channel-url'); +const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup'); + +module.exports = async function () { + return { + scenarios: [ + { + name: 'ember-lts-3.24', + npm: { + devDependencies: { + 'ember-source': '~3.24.3', + }, + }, + }, + { + name: 'ember-lts-3.28', + npm: { + devDependencies: { + 'ember-source': '~3.28.0', + }, + }, + }, + { + name: 'ember-release', + npm: { + devDependencies: { + 'ember-source': await getChannelURL('release'), + }, + }, + }, + { + name: 'ember-beta', + npm: { + devDependencies: { + 'ember-source': await getChannelURL('beta'), + }, + }, + }, + { + name: 'ember-canary', + npm: { + devDependencies: { + 'ember-source': await getChannelURL('canary'), + }, + }, + }, + { + name: 'ember-default-with-jquery', + env: { + EMBER_OPTIONAL_FEATURES: JSON.stringify({ + 'jquery-integration': true, + }), + }, + npm: { + devDependencies: { + '@ember/jquery': '^1.1.0', + }, + }, + }, + { + name: 'ember-classic', + env: { + EMBER_OPTIONAL_FEATURES: JSON.stringify({ + 'application-template-wrapper': true, + 'default-async-observers': false, + 'template-only-glimmer-components': false, + }), + }, + npm: { + devDependencies: { + 'ember-source': '~3.28.0', + }, + ember: { + edition: 'classic', + }, + }, + }, + embroiderSafe(), + embroiderOptimized(), + ], + }; +}; diff --git a/tests/fixtures/addon/defaults/package.json b/tests/fixtures/addon/defaults/package.json new file mode 100644 index 0000000000..9888cde77c --- /dev/null +++ b/tests/fixtures/addon/defaults/package.json @@ -0,0 +1,78 @@ +{ + "name": "foo", + "version": "0.0.0", + "description": "The default blueprint for ember-cli addons.", + "keywords": [ + "ember-addon" + ], + "repository": "", + "license": "MIT", + "author": "", + "directories": { + "doc": "doc", + "test": "tests" + }, + "scripts": { + "build": "ember build --environment=production", + "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", + "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", + "lint:hbs": "ember-template-lint .", + "lint:hbs:fix": "ember-template-lint . --fix", + "lint:js": "eslint . --cache", + "lint:js:fix": "eslint . --fix", + "start": "ember serve", + "test": "npm-run-all lint test:*", + "test:ember": "ember test", + "test:ember-compatibility": "ember try:each" + }, + "dependencies": { + "ember-cli-babel": "^7.26.10", + "ember-cli-htmlbars": "^5.7.2" + }, + "devDependencies": { + "@ember/optional-features": "^2.0.0", + "@ember/test-helpers": "^2.6.0", + "@embroider/test-setup": "^0.48.1", + "@glimmer/component": "^1.0.4", + "@glimmer/tracking": "^1.0.4", + "babel-eslint": "^10.1.0", + "broccoli-asset-rev": "^3.0.0", + "ember-auto-import": "^1.12.0", + "ember-cli": "~<%= emberCLIVersion %>", + "ember-cli-dependency-checker": "^3.2.0", + "ember-cli-inject-live-reload": "^2.1.0", + "ember-cli-sri": "^2.1.1", + "ember-cli-terser": "^4.0.2", + "ember-disable-prototype-extensions": "^1.1.3", + "ember-export-application-global": "^2.0.1", + "ember-load-initializers": "^2.1.2", + "ember-maybe-import-regenerator": "^0.1.6", + "ember-page-title": "^6.2.2", + "ember-qunit": "^5.1.5", + "ember-resolver": "^8.0.3", + "ember-source": "~3.28.8", + "ember-source-channel-url": "^3.0.0", + "ember-template-lint": "^3.15.0", + "ember-try": "^1.4.0", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-ember": "^10.5.8", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.4.1", + "eslint-plugin-qunit": "^6.2.0", + "loader.js": "^4.7.0", + "npm-run-all": "^4.1.5", + "prettier": "^2.5.1", + "qunit": "^2.17.2", + "qunit-dom": "^1.6.0" + }, + "engines": { + "node": "12.* || 14.* || >= 16" + }, + "ember": { + "edition": "octane" + }, + "ember-addon": { + "configPath": "tests/dummy/config" + } +} diff --git a/tests/fixtures/addon/npm/tests/dummy/app/templates/application.hbs b/tests/fixtures/addon/defaults/tests/dummy/app/templates/application.hbs similarity index 66% rename from tests/fixtures/addon/npm/tests/dummy/app/templates/application.hbs rename to tests/fixtures/addon/defaults/tests/dummy/app/templates/application.hbs index 5230580f82..1001d149e1 100644 --- a/tests/fixtures/addon/npm/tests/dummy/app/templates/application.hbs +++ b/tests/fixtures/addon/defaults/tests/dummy/app/templates/application.hbs @@ -1,3 +1,5 @@ +{{page-title "Dummy"}} +

Welcome to Ember

{{outlet}} \ No newline at end of file diff --git a/tests/fixtures/addon/defaults/tests/dummy/config/ember-cli-update.json b/tests/fixtures/addon/defaults/tests/dummy/config/ember-cli-update.json new file mode 100644 index 0000000000..b2d33140fb --- /dev/null +++ b/tests/fixtures/addon/defaults/tests/dummy/config/ember-cli-update.json @@ -0,0 +1,18 @@ +{ + "schemaVersion": "1.0.0", + "packages": [ + { + "name": "ember-cli", + "version": "<%= emberCLIVersion %>", + "blueprints": [ + { + "name": "addon", + "outputRepo": "https://github.com/ember-cli/ember-addon-output", + "codemodsSource": "ember-addon-codemods-manifest@1", + "isBaseBlueprint": true, + "options": [] + } + ] + } + ] +} diff --git a/tests/fixtures/addon/kitchen-sink-mu/addon-test-support/helper.js b/tests/fixtures/addon/kitchen-sink-mu/addon-test-support/helper.js deleted file mode 100644 index 259c58832a..0000000000 --- a/tests/fixtures/addon/kitchen-sink-mu/addon-test-support/helper.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function truthyHelper() { - return true; -} diff --git a/tests/fixtures/addon/kitchen-sink-mu/index.js b/tests/fixtures/addon/kitchen-sink-mu/index.js deleted file mode 100644 index f5a8aad507..0000000000 --- a/tests/fixtures/addon/kitchen-sink-mu/index.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - name: require('./package').name, - - contentFor(type, config) { - if (type === 'head') { - return '"SOME AWESOME STUFF"'; - } - } -}; diff --git a/tests/fixtures/addon/kitchen-sink-mu/src/ui/components/basic-thing/template.hbs b/tests/fixtures/addon/kitchen-sink-mu/src/ui/components/basic-thing/template.hbs deleted file mode 100644 index 8c0ab5bfed..0000000000 --- a/tests/fixtures/addon/kitchen-sink-mu/src/ui/components/basic-thing/template.hbs +++ /dev/null @@ -1,3 +0,0 @@ -
- {{yield}} -
diff --git a/tests/fixtures/addon/kitchen-sink-mu/src/ui/styles/app.css b/tests/fixtures/addon/kitchen-sink-mu/src/ui/styles/app.css deleted file mode 100644 index 441f327ae2..0000000000 --- a/tests/fixtures/addon/kitchen-sink-mu/src/ui/styles/app.css +++ /dev/null @@ -1 +0,0 @@ -/* addon/styles/app.css is present */ diff --git a/tests/fixtures/addon/kitchen-sink-mu/tests/acceptance/main-test.js b/tests/fixtures/addon/kitchen-sink-mu/tests/acceptance/main-test.js deleted file mode 100644 index 1d9ab59bf4..0000000000 --- a/tests/fixtures/addon/kitchen-sink-mu/tests/acceptance/main-test.js +++ /dev/null @@ -1,23 +0,0 @@ -import { setupApplicationTest } from 'ember-qunit'; -import { visit } from '@ember/test-helpers'; -import truthyHelper from 'some-cool-addon/test-support/helper'; -import { module, test } from 'qunit'; - -module('Acceptance', function(hooks) { - setupApplicationTest(hooks); - - test('renders properly', async function(assert) { - await visit('/'); - - var element = this.element.querySelector('.basic-thing'); - assert.equal(element.textContent.trim(), 'WOOT!!'); - assert.ok(truthyHelper(), 'addon-test-support helper'); - }); - - test('renders imported component', async function(assert) { - await visit('/'); - - var element = this.element.querySelector('.second-thing'); - assert.equal(element.textContent.trim(), 'SECOND!!'); - }); -}); diff --git a/tests/fixtures/addon/kitchen-sink-mu/tests/dummy/public/robots.txt b/tests/fixtures/addon/kitchen-sink-mu/tests/dummy/public/robots.txt deleted file mode 100644 index 5062e4180f..0000000000 --- a/tests/fixtures/addon/kitchen-sink-mu/tests/dummy/public/robots.txt +++ /dev/null @@ -1 +0,0 @@ -# tests/dummy/public/robots.txt is present \ No newline at end of file diff --git a/tests/fixtures/addon/kitchen-sink-mu/tests/dummy/src/ui/components/second-thing/component.js b/tests/fixtures/addon/kitchen-sink-mu/tests/dummy/src/ui/components/second-thing/component.js deleted file mode 100644 index ae81d6883f..0000000000 --- a/tests/fixtures/addon/kitchen-sink-mu/tests/dummy/src/ui/components/second-thing/component.js +++ /dev/null @@ -1,5 +0,0 @@ -import BasicThing from 'some-cool-addon/src/ui/components/basic-thing/component'; - -export default BasicThing.extend({ - classNames: ['second-thing'] -}); diff --git a/tests/fixtures/addon/kitchen-sink-mu/tests/dummy/src/ui/routes/application/template.hbs b/tests/fixtures/addon/kitchen-sink-mu/tests/dummy/src/ui/routes/application/template.hbs deleted file mode 100644 index 885261e680..0000000000 --- a/tests/fixtures/addon/kitchen-sink-mu/tests/dummy/src/ui/routes/application/template.hbs +++ /dev/null @@ -1,2 +0,0 @@ -{{#basic-thing}}WOOT!!{{/basic-thing}} -{{#second-thing}}SECOND!!{{/second-thing}} diff --git a/tests/fixtures/addon/kitchen-sink/tests/acceptance/main-test.js b/tests/fixtures/addon/kitchen-sink/tests/acceptance/main-test.js index 1d9ab59bf4..58e3387552 100644 --- a/tests/fixtures/addon/kitchen-sink/tests/acceptance/main-test.js +++ b/tests/fixtures/addon/kitchen-sink/tests/acceptance/main-test.js @@ -6,7 +6,7 @@ import { module, test } from 'qunit'; module('Acceptance', function(hooks) { setupApplicationTest(hooks); - test('renders properly', async function(assert) { + test('renders properly', async function (assert) { await visit('/'); var element = this.element.querySelector('.basic-thing'); @@ -14,7 +14,7 @@ module('Acceptance', function(hooks) { assert.ok(truthyHelper(), 'addon-test-support helper'); }); - test('renders imported component', async function(assert) { + test('renders imported component', async function (assert) { await visit('/'); var element = this.element.querySelector('.second-thing'); diff --git a/tests/fixtures/addon/kitchen-sink/tests/dummy/app/templates/application.hbs b/tests/fixtures/addon/kitchen-sink/tests/dummy/app/templates/application.hbs index 885261e680..50b5fd9f4e 100644 --- a/tests/fixtures/addon/kitchen-sink/tests/dummy/app/templates/application.hbs +++ b/tests/fixtures/addon/kitchen-sink/tests/dummy/app/templates/application.hbs @@ -1,2 +1,2 @@ -{{#basic-thing}}WOOT!!{{/basic-thing}} -{{#second-thing}}SECOND!!{{/second-thing}} +WOOT!! +SECOND!! diff --git a/tests/fixtures/addon/nested-addon-main/package.json b/tests/fixtures/addon/nested-addon-main/package.json new file mode 100644 index 0000000000..78a5d6a7c9 --- /dev/null +++ b/tests/fixtures/addon/nested-addon-main/package.json @@ -0,0 +1,18 @@ +{ + "name": "nested-addon-main", + "version": "0.0.1", + "keywords": [ + "ember-addon" + ], + "dependencies": { + "ember-cli-htmlbars": "latest", + "ember-cli-babel": "latest" + }, + "devDependencies": { + "ember-source": "latest", + "ember-cli": "latest" + }, + "ember-addon": { + "main": "src/main.js" + } +} diff --git a/tests/fixtures/addon/kitchen-sink-mu/src/ui/components/basic-thing/component.js b/tests/fixtures/addon/nested-addon-main/src/addon/components/simple-component.js similarity index 52% rename from tests/fixtures/addon/kitchen-sink-mu/src/ui/components/basic-thing/component.js rename to tests/fixtures/addon/nested-addon-main/src/addon/components/simple-component.js index 9d49a792ea..dde1898656 100644 --- a/tests/fixtures/addon/kitchen-sink-mu/src/ui/components/basic-thing/component.js +++ b/tests/fixtures/addon/nested-addon-main/src/addon/components/simple-component.js @@ -1,6 +1,6 @@ -import template from './template'; import Component from '@ember/component'; +import layout from '../templates/components/simple-component'; export default Component.extend({ - layout: template + layout }); \ No newline at end of file diff --git a/tests/fixtures/addon/nested-addon-main/src/addon/templates/components/simple-component.hbs b/tests/fixtures/addon/nested-addon-main/src/addon/templates/components/simple-component.hbs new file mode 100644 index 0000000000..bb3feb913a --- /dev/null +++ b/tests/fixtures/addon/nested-addon-main/src/addon/templates/components/simple-component.hbs @@ -0,0 +1 @@ +

This is a simple component

diff --git a/tests/fixtures/module-unification-addon/yarn/index.js b/tests/fixtures/addon/nested-addon-main/src/main.js similarity index 50% rename from tests/fixtures/module-unification-addon/yarn/index.js rename to tests/fixtures/addon/nested-addon-main/src/main.js index 2e1d1d8d5f..3ad817af82 100644 --- a/tests/fixtures/module-unification-addon/yarn/index.js +++ b/tests/fixtures/addon/nested-addon-main/src/main.js @@ -1,5 +1,5 @@ 'use strict'; module.exports = { - name: require('./package').name + name: require('../package').name, }; diff --git a/tests/fixtures/addon/npm/CONTRIBUTING.md b/tests/fixtures/addon/npm/CONTRIBUTING.md deleted file mode 100644 index 8fc5b869d6..0000000000 --- a/tests/fixtures/addon/npm/CONTRIBUTING.md +++ /dev/null @@ -1,26 +0,0 @@ -# How To Contribute - -## Installation - -* `git clone ` -* `cd foo` -* `npm install` - -## Linting - -* `npm run lint:hbs` -* `npm run lint:js` -* `npm run lint:js -- --fix` - -## Running tests - -* `ember test` – Runs the test suite on the current Ember version -* `ember test --server` – Runs the test suite in "watch mode" -* `ember try:each` – Runs the test suite against multiple Ember versions - -## Running the dummy application - -* `ember serve` -* Visit the dummy application at [http://localhost:4200](http://localhost:4200). - -For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). \ No newline at end of file diff --git a/tests/fixtures/addon/npm/README.md b/tests/fixtures/addon/npm/README.md deleted file mode 100644 index 36bd8fd416..0000000000 --- a/tests/fixtures/addon/npm/README.md +++ /dev/null @@ -1,38 +0,0 @@ -foo -============================================================================== - -[Short description of the addon.] - - -Compatibility ------------------------------------------------------------------------------- - -* Ember.js v3.4 or above -* Ember CLI v2.13 or above -* Node.js v8 or above - - -Installation ------------------------------------------------------------------------------- - -``` -ember install foo -``` - - -Usage ------------------------------------------------------------------------------- - -[Longer description of how to use the addon in apps.] - - -Contributing ------------------------------------------------------------------------------- - -See the [Contributing](CONTRIBUTING.md) guide for details. - - -License ------------------------------------------------------------------------------- - -This project is licensed under the [MIT License](LICENSE.md). diff --git a/tests/fixtures/addon/npm/config/ember-try.js b/tests/fixtures/addon/npm/config/ember-try.js deleted file mode 100644 index b2a665182f..0000000000 --- a/tests/fixtures/addon/npm/config/ember-try.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict'; - -const getChannelURL = require('ember-source-channel-url'); - -module.exports = async function() { - return { - scenarios: [ - { - name: 'ember-lts-3.4', - npm: { - devDependencies: { - 'ember-source': '~3.4.0' - } - } - }, - { - name: 'ember-lts-3.8', - npm: { - devDependencies: { - 'ember-source': '~3.8.0' - } - } - }, - { - name: 'ember-release', - npm: { - devDependencies: { - 'ember-source': await getChannelURL('release') - } - } - }, - { - name: 'ember-beta', - npm: { - devDependencies: { - 'ember-source': await getChannelURL('beta') - } - } - }, - { - name: 'ember-canary', - npm: { - devDependencies: { - 'ember-source': await getChannelURL('canary') - } - } - }, - // The default `.travis.yml` runs this scenario via `npm test`, - // not via `ember try`. It's still included here so that running - // `ember try:each` manually or from a customized CI config will run it - // along with all the other scenarios. - { - name: 'ember-default', - npm: { - devDependencies: {} - } - }, - { - name: 'ember-default-with-jquery', - env: { - EMBER_OPTIONAL_FEATURES: JSON.stringify({ - 'jquery-integration': true - }) - }, - npm: { - devDependencies: { - '@ember/jquery': '^0.5.1' - } - } - } - ] - }; -}; diff --git a/tests/fixtures/addon/npm/package.json b/tests/fixtures/addon/npm/package.json deleted file mode 100644 index 58e43045f3..0000000000 --- a/tests/fixtures/addon/npm/package.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "name": "foo", - "version": "0.0.0", - "description": "The default blueprint for ember-cli addons.", - "keywords": [ - "ember-addon" - ], - "repository": "", - "license": "MIT", - "author": "", - "directories": { - "doc": "doc", - "test": "tests" - }, - "scripts": { - "build": "ember build", - "lint:hbs": "ember-template-lint .", - "lint:js": "eslint .", - "start": "ember serve", - "test": "ember test", - "test:all": "ember try:each" - }, - "dependencies": { - "ember-cli-babel": "^7.11.1", - "ember-cli-htmlbars": "^4.0.0" - }, - "devDependencies": { - "@ember/optional-features": "^1.0.0", - "babel-eslint": "^10.0.3", - "broccoli-asset-rev": "^3.0.0", - "ember-cli": "~<%= emberCLIVersion %>", - "ember-cli-dependency-checker": "^3.1.0", - "ember-cli-eslint": "^5.1.0", - "ember-cli-htmlbars-inline-precompile": "^3.0.0", - "ember-cli-inject-live-reload": "^2.0.1", - "ember-cli-sri": "^2.1.1", - "ember-cli-template-lint": "^1.0.0-beta.3", - "ember-cli-uglify": "^3.0.0", - "ember-disable-prototype-extensions": "^1.1.3", - "ember-export-application-global": "^2.0.0", - "ember-load-initializers": "^2.1.0", - "ember-maybe-import-regenerator": "^0.1.6", - "ember-qunit": "^4.5.1", - "ember-resolver": "^5.3.0", - "ember-source": "~3.13.0", - "ember-source-channel-url": "^2.0.1", - "ember-try": "^1.2.1", - "eslint-plugin-ember": "^7.1.0", - "eslint-plugin-node": "^10.0.0", - "loader.js": "^4.7.0", - "qunit-dom": "^0.9.0" - }, - "engines": { - "node": "8.* || >= 10.*" - }, - "ember-addon": { - "configPath": "tests/dummy/config" - } -} diff --git a/tests/fixtures/module-unification-addon/npm/index.js b/tests/fixtures/addon/simple/lib/ember-super-button/lib/ember-with-addon-main/lib/main.js similarity index 50% rename from tests/fixtures/module-unification-addon/npm/index.js rename to tests/fixtures/addon/simple/lib/ember-super-button/lib/ember-with-addon-main/lib/main.js index 2e1d1d8d5f..3ad817af82 100644 --- a/tests/fixtures/module-unification-addon/npm/index.js +++ b/tests/fixtures/addon/simple/lib/ember-super-button/lib/ember-with-addon-main/lib/main.js @@ -1,5 +1,5 @@ 'use strict'; module.exports = { - name: require('./package').name + name: require('../package').name, }; diff --git a/tests/fixtures/addon/simple/lib/ember-super-button/lib/ember-with-addon-main/package.json b/tests/fixtures/addon/simple/lib/ember-super-button/lib/ember-with-addon-main/package.json new file mode 100644 index 0000000000..6cf2ccf370 --- /dev/null +++ b/tests/fixtures/addon/simple/lib/ember-super-button/lib/ember-with-addon-main/package.json @@ -0,0 +1,9 @@ +{ + "name": "ember-with-addon-main", + "keywords": [ + "ember-addon" + ], + "ember-addon": { + "main": "lib/main.js" + } +} diff --git a/tests/fixtures/addon/simple/lib/ember-super-button/package.json b/tests/fixtures/addon/simple/lib/ember-super-button/package.json index d493c4084b..a2c50d4bbf 100644 --- a/tests/fixtures/addon/simple/lib/ember-super-button/package.json +++ b/tests/fixtures/addon/simple/lib/ember-super-button/package.json @@ -4,9 +4,12 @@ "ember-addon" ], "ember-addon": { - "paths": ["./lib/ember-ng"] + "paths": [ + "./lib/ember-ng", + "./lib/ember-with-addon-main" + ] }, "dependencies": { "ember-yagni": "0" } -} +} \ No newline at end of file diff --git a/tests/fixtures/addon/simple/package.json b/tests/fixtures/addon/simple/package.json index 077cd9c130..ef32076f98 100644 --- a/tests/fixtures/addon/simple/package.json +++ b/tests/fixtures/addon/simple/package.json @@ -5,10 +5,13 @@ "something-else": "latest" }, "ember-addon": { - "paths": ["./lib/ember-super-button"] + "paths": [ + "./lib/ember-super-button", + "./lib/ember-super-button/lib/ember-with-addon-main" + ] }, "devDependencies": { - "ember-resolver": "^5.0.1", + "ember-resolver": "^7.0.0", "ember-cli": "latest", "ember-random-addon": "latest", "ember-non-root-addon": "latest", @@ -20,4 +23,4 @@ "ember-addon-with-dependencies": "latest", "loader.js": "latest" } -} +} \ No newline at end of file diff --git a/tests/fixtures/addon/with-app-styles/package.json b/tests/fixtures/addon/with-app-styles/package.json index e22086c4b8..e069a08539 100644 --- a/tests/fixtures/addon/with-app-styles/package.json +++ b/tests/fixtures/addon/with-app-styles/package.json @@ -8,7 +8,7 @@ "paths": ["./lib/ember-super-button"] }, "devDependencies": { - "ember-resolver": "^5.0.1", + "ember-resolver": "^7.0.0", "ember-cli": "latest", "ember-random-addon": "latest", "ember-non-root-addon": "latest", diff --git a/tests/fixtures/addon/with-blueprint-override/blueprints/component/index.js b/tests/fixtures/addon/with-blueprint-override/blueprints/component/index.js index 4a2db1a359..2dee6f0719 100644 --- a/tests/fixtures/addon/with-blueprint-override/blueprints/component/index.js +++ b/tests/fixtures/addon/with-blueprint-override/blueprints/component/index.js @@ -5,32 +5,15 @@ const path = require('path'); module.exports = { filesPath: function() { - let filesDirectory = 'files'; - - if (isExperimentEnabled('MODULE_UNIFICATION')) { - filesDirectory = 'module-unification-files'; - } - - return path.join(this.path, filesDirectory); + return path.join(this.path, 'files'); }, fileMapTokens: function() { - if (isExperimentEnabled('MODULE_UNIFICATION')) { - return { - __root__(options) { - return 'src'; - }, - __path__(options) { - return path.join('ui', 'components', 'new-path', options.dasherizedModuleName); - }, - }; - } else { - return { - __path__: function(options) { - return path.join('components', 'new-path'); - }, - }; - } + return { + __path__: function(options) { + return path.join('components', 'new-path'); + }, + }; }, locals(options) { diff --git a/tests/fixtures/addon/yarn/.travis.yml b/tests/fixtures/addon/yarn/.travis.yml index ab3e592e53..1ab83d9aa4 100644 --- a/tests/fixtures/addon/yarn/.travis.yml +++ b/tests/fixtures/addon/yarn/.travis.yml @@ -3,10 +3,9 @@ language: node_js node_js: # we recommend testing addons with the same minimum supported node version as Ember CLI # so that your addon works for all apps - - "8" + - "12" -sudo: false -dist: trusty +dist: xenial addons: chrome: stable @@ -26,42 +25,40 @@ branches: - /^v\d+\.\d+\.\d+/ jobs: - fail_fast: true + fast_finish: true allow_failures: - env: EMBER_TRY_SCENARIO=ember-canary include: # runs linting and tests with current locked deps - - stage: "Tests" name: "Tests" - install: - - yarn install --non-interactive script: - - yarn lint:hbs - - yarn lint:js - - yarn test + - yarn lint + - yarn test:ember - - name: "Floating Dependencies" + - stage: "Additional Tests" + name: "Floating Dependencies" + install: + - yarn install --no-lockfile --non-interactive script: - - yarn test + - yarn test:ember # we recommend new addons test the current and previous LTS # as well as latest stable release (bonus points to beta/canary) - - stage: "Additional Tests" - env: EMBER_TRY_SCENARIO=ember-lts-3.4 - - env: EMBER_TRY_SCENARIO=ember-lts-3.8 + - env: EMBER_TRY_SCENARIO=ember-lts-3.24 + - env: EMBER_TRY_SCENARIO=ember-lts-3.28 - env: EMBER_TRY_SCENARIO=ember-release - env: EMBER_TRY_SCENARIO=ember-beta - env: EMBER_TRY_SCENARIO=ember-canary - env: EMBER_TRY_SCENARIO=ember-default-with-jquery + - env: EMBER_TRY_SCENARIO=ember-classic + - env: EMBER_TRY_SCENARIO=embroider-safe + - env: EMBER_TRY_SCENARIO=embroider-optimized before_install: - curl -o- -L https://yarnpkg.com/install.sh | bash - export PATH=$HOME/.yarn/bin:$PATH -install: - - yarn install --no-lockfile --non-interactive - script: - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO diff --git a/tests/fixtures/addon/yarn/CONTRIBUTING.md b/tests/fixtures/addon/yarn/CONTRIBUTING.md index 59e532958a..9ae5a1bfd5 100644 --- a/tests/fixtures/addon/yarn/CONTRIBUTING.md +++ b/tests/fixtures/addon/yarn/CONTRIBUTING.md @@ -8,9 +8,8 @@ ## Linting -* `yarn lint:hbs` -* `yarn lint:js` -* `yarn lint:js --fix` +* `yarn lint` +* `yarn lint:fix` ## Running tests @@ -23,4 +22,4 @@ * `ember serve` * Visit the dummy application at [http://localhost:4200](http://localhost:4200). -For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). \ No newline at end of file +For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). diff --git a/tests/fixtures/addon/yarn/README.md b/tests/fixtures/addon/yarn/README.md index 36bd8fd416..378fb8a256 100644 --- a/tests/fixtures/addon/yarn/README.md +++ b/tests/fixtures/addon/yarn/README.md @@ -7,9 +7,9 @@ foo Compatibility ------------------------------------------------------------------------------ -* Ember.js v3.4 or above -* Ember CLI v2.13 or above -* Node.js v8 or above +* Ember.js v3.24 or above +* Ember CLI v3.24 or above +* Node.js v12 or above Installation diff --git a/tests/fixtures/addon/yarn/config/ember-try.js b/tests/fixtures/addon/yarn/config/ember-try.js index c1d83d6f42..8e178a63ca 100644 --- a/tests/fixtures/addon/yarn/config/ember-try.js +++ b/tests/fixtures/addon/yarn/config/ember-try.js @@ -1,74 +1,85 @@ 'use strict'; const getChannelURL = require('ember-source-channel-url'); +const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup'); -module.exports = async function() { +module.exports = async function () { return { useYarn: true, scenarios: [ { - name: 'ember-lts-3.4', + name: 'ember-lts-3.24', npm: { devDependencies: { - 'ember-source': '~3.4.0' - } - } + 'ember-source': '~3.24.3', + }, + }, }, { - name: 'ember-lts-3.8', + name: 'ember-lts-3.28', npm: { devDependencies: { - 'ember-source': '~3.8.0' - } - } + 'ember-source': '~3.28.0', + }, + }, }, { name: 'ember-release', npm: { devDependencies: { - 'ember-source': await getChannelURL('release') - } - } + 'ember-source': await getChannelURL('release'), + }, + }, }, { name: 'ember-beta', npm: { devDependencies: { - 'ember-source': await getChannelURL('beta') - } - } + 'ember-source': await getChannelURL('beta'), + }, + }, }, { name: 'ember-canary', npm: { devDependencies: { - 'ember-source': await getChannelURL('canary') - } - } + 'ember-source': await getChannelURL('canary'), + }, + }, }, - // The default `.travis.yml` runs this scenario via `yarn test`, - // not via `ember try`. It's still included here so that running - // `ember try:each` manually or from a customized CI config will run it - // along with all the other scenarios. { - name: 'ember-default', + name: 'ember-default-with-jquery', + env: { + EMBER_OPTIONAL_FEATURES: JSON.stringify({ + 'jquery-integration': true, + }), + }, npm: { - devDependencies: {} - } + devDependencies: { + '@ember/jquery': '^1.1.0', + }, + }, }, { - name: 'ember-default-with-jquery', + name: 'ember-classic', env: { EMBER_OPTIONAL_FEATURES: JSON.stringify({ - 'jquery-integration': true - }) + 'application-template-wrapper': true, + 'default-async-observers': false, + 'template-only-glimmer-components': false, + }), }, npm: { devDependencies: { - '@ember/jquery': '^0.5.1' - } - } - } - ] + 'ember-source': '~3.28.0', + }, + ember: { + edition: 'classic', + }, + }, + }, + embroiderSafe(), + embroiderOptimized(), + ], }; }; diff --git a/tests/fixtures/addon/yarn/package.json b/tests/fixtures/addon/yarn/package.json index 1228d436b5..ebec47e82f 100644 --- a/tests/fixtures/addon/yarn/package.json +++ b/tests/fixtures/addon/yarn/package.json @@ -13,46 +13,65 @@ "test": "tests" }, "scripts": { - "build": "ember build", + "build": "ember build --environment=production", + "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", + "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", "lint:hbs": "ember-template-lint .", - "lint:js": "eslint .", + "lint:hbs:fix": "ember-template-lint . --fix", + "lint:js": "eslint . --cache", + "lint:js:fix": "eslint . --fix", "start": "ember serve", - "test": "ember test", - "test:all": "ember try:each" + "test": "npm-run-all lint test:*", + "test:ember": "ember test", + "test:ember-compatibility": "ember try:each" }, "dependencies": { - "ember-cli-babel": "^7.11.1", - "ember-cli-htmlbars": "^4.0.0" + "ember-cli-babel": "^7.26.10", + "ember-cli-htmlbars": "^5.7.2" }, "devDependencies": { - "@ember/optional-features": "^1.0.0", - "babel-eslint": "^10.0.3", + "@ember/optional-features": "^2.0.0", + "@ember/test-helpers": "^2.6.0", + "@embroider/test-setup": "^0.48.1", + "@glimmer/component": "^1.0.4", + "@glimmer/tracking": "^1.0.4", + "babel-eslint": "^10.1.0", "broccoli-asset-rev": "^3.0.0", + "ember-auto-import": "^1.12.0", "ember-cli": "~<%= emberCLIVersion %>", - "ember-cli-dependency-checker": "^3.1.0", - "ember-cli-eslint": "^5.1.0", - "ember-cli-htmlbars-inline-precompile": "^3.0.0", - "ember-cli-inject-live-reload": "^2.0.1", + "ember-cli-dependency-checker": "^3.2.0", + "ember-cli-inject-live-reload": "^2.1.0", "ember-cli-sri": "^2.1.1", - "ember-cli-template-lint": "^1.0.0-beta.3", - "ember-cli-uglify": "^3.0.0", + "ember-cli-terser": "^4.0.2", "ember-disable-prototype-extensions": "^1.1.3", - "ember-export-application-global": "^2.0.0", - "ember-load-initializers": "^2.1.0", + "ember-export-application-global": "^2.0.1", + "ember-load-initializers": "^2.1.2", "ember-maybe-import-regenerator": "^0.1.6", - "ember-qunit": "^4.5.1", - "ember-resolver": "^5.3.0", - "ember-source": "~3.13.0", - "ember-source-channel-url": "^2.0.1", - "ember-try": "^1.2.1", - "ember-welcome-page": "^4.0.0", - "eslint-plugin-ember": "^7.1.0", - "eslint-plugin-node": "^10.0.0", + "ember-page-title": "^6.2.2", + "ember-qunit": "^5.1.5", + "ember-resolver": "^8.0.3", + "ember-source": "~3.28.8", + "ember-source-channel-url": "^3.0.0", + "ember-template-lint": "^3.15.0", + "ember-try": "^1.4.0", + "ember-welcome-page": "^4.1.0", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-ember": "^10.5.8", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.4.1", + "eslint-plugin-qunit": "^6.2.0", "loader.js": "^4.7.0", - "qunit-dom": "^0.9.0" + "npm-run-all": "^4.1.5", + "prettier": "^2.5.1", + "qunit": "^2.17.2", + "qunit-dom": "^1.6.0" }, "engines": { - "node": "8.* || >= 10.*" + "node": "12.* || 14.* || >= 16" + }, + "ember": { + "edition": "octane" }, "ember-addon": { "configPath": "tests/dummy/config" diff --git a/tests/fixtures/addon/yarn/tests/dummy/app/templates/application.hbs b/tests/fixtures/addon/yarn/tests/dummy/app/templates/application.hbs index 7b1b1040fe..ef465c353f 100644 --- a/tests/fixtures/addon/yarn/tests/dummy/app/templates/application.hbs +++ b/tests/fixtures/addon/yarn/tests/dummy/app/templates/application.hbs @@ -1,3 +1,5 @@ +{{page-title "Dummy"}} + {{!-- The following component displays Ember's default welcome message. --}} {{!-- Feel free to remove this! --}} diff --git a/tests/fixtures/addon/yarn/tests/dummy/config/ember-cli-update.json b/tests/fixtures/addon/yarn/tests/dummy/config/ember-cli-update.json new file mode 100644 index 0000000000..0a6c3c1a50 --- /dev/null +++ b/tests/fixtures/addon/yarn/tests/dummy/config/ember-cli-update.json @@ -0,0 +1,21 @@ +{ + "schemaVersion": "1.0.0", + "packages": [ + { + "name": "ember-cli", + "version": "<%= emberCLIVersion %>", + "blueprints": [ + { + "name": "addon", + "outputRepo": "https://github.com/ember-cli/ember-addon-output", + "codemodsSource": "ember-addon-codemods-manifest@1", + "isBaseBlueprint": true, + "options": [ + "--welcome", + "--yarn" + ] + } + ] + } + ] +} diff --git a/tests/fixtures/app/.eslintrc.js b/tests/fixtures/app/.eslintrc.js index 3500e4a6d1..41a1a43d82 100644 --- a/tests/fixtures/app/.eslintrc.js +++ b/tests/fixtures/app/.eslintrc.js @@ -1,51 +1,58 @@ +'use strict'; + module.exports = { root: true, parser: 'babel-eslint', parserOptions: { ecmaVersion: 2018, - sourceType: 'module' + sourceType: 'module', + ecmaFeatures: { + legacyDecorators: true, + }, }, - plugins: [ - 'ember' - ], + plugins: ['ember'], extends: [ 'eslint:recommended', - 'plugin:ember/recommended' + 'plugin:ember/recommended', + 'plugin:prettier/recommended', ], env: { - browser: true - }, - rules: { - 'ember/no-jquery': 'error' + browser: true, }, + rules: {}, overrides: [ // node files { files: [ - '.eslintrc.js', - '.template-lintrc.js', - 'ember-cli-build.js', - 'testem.js', - 'blueprints/*/index.js', - 'config/**/*.js', - 'lib/*/index.js', - 'server/**/*.js' + './.eslintrc.js', + './.prettierrc.js', + './.template-lintrc.js', + './ember-cli-build.js', + './testem.js', + './blueprints/*/index.js', + './config/**/*.js', + './lib/*/index.js', + './server/**/*.js', ], parserOptions: { - sourceType: 'script' + sourceType: 'script', }, env: { browser: false, - node: true + node: true, }, plugins: ['node'], - rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { - // add your custom rules and overrides for node files here - + extends: ['plugin:node/recommended'], + rules: { // this can be removed once the following is fixed // https://github.com/mysticatea/eslint-plugin-node/issues/77 - 'node/no-unpublished-require': 'off' - }) - } - ] + 'node/no-unpublished-require': 'off', + }, + }, + { + // Test files: + files: ['tests/**/*-test.{js,ts}'], + extends: ['plugin:qunit/recommended'], + }, + ], }; diff --git a/tests/fixtures/module-unification-app/npm/.travis.yml b/tests/fixtures/app/defaults/.travis.yml similarity index 72% rename from tests/fixtures/module-unification-app/npm/.travis.yml rename to tests/fixtures/app/defaults/.travis.yml index 1fe0f1fca7..bf02ddcad6 100644 --- a/tests/fixtures/module-unification-app/npm/.travis.yml +++ b/tests/fixtures/app/defaults/.travis.yml @@ -1,10 +1,9 @@ --- language: node_js node_js: - - "8" + - "12" -sudo: false -dist: trusty +dist: xenial addons: chrome: stable @@ -18,7 +17,9 @@ env: # See https://git.io/vdao3 for details. - JOBS=1 +branches: + only: + - master + script: - - npm run lint:hbs - - npm run lint:js - npm test diff --git a/tests/fixtures/module-unification-app/npm/README.md b/tests/fixtures/app/defaults/README.md similarity index 95% rename from tests/fixtures/module-unification-app/npm/README.md rename to tests/fixtures/app/defaults/README.md index c81ac108ab..dba454682e 100644 --- a/tests/fixtures/module-unification-app/npm/README.md +++ b/tests/fixtures/app/defaults/README.md @@ -35,9 +35,8 @@ Make use of the many generators for code, try `ember help generate` for more det ### Linting -* `npm run lint:hbs` -* `npm run lint:js` -* `npm run lint:js -- --fix` +* `npm run lint` +* `npm run lint:fix` ### Building diff --git a/tests/fixtures/module-unification-addon/yarn/tests/dummy/src/ui/routes/application/template.hbs b/tests/fixtures/app/defaults/app/templates/application.hbs similarity index 86% rename from tests/fixtures/module-unification-addon/yarn/tests/dummy/src/ui/routes/application/template.hbs rename to tests/fixtures/app/defaults/app/templates/application.hbs index 7b1b1040fe..c50aa5fb7c 100644 --- a/tests/fixtures/module-unification-addon/yarn/tests/dummy/src/ui/routes/application/template.hbs +++ b/tests/fixtures/app/defaults/app/templates/application.hbs @@ -1,3 +1,5 @@ +{{page-title "Foo"}} + {{!-- The following component displays Ember's default welcome message. --}} {{!-- Feel free to remove this! --}} diff --git a/tests/fixtures/app/defaults/config/ember-cli-update.json b/tests/fixtures/app/defaults/config/ember-cli-update.json new file mode 100644 index 0000000000..a0462c3b6e --- /dev/null +++ b/tests/fixtures/app/defaults/config/ember-cli-update.json @@ -0,0 +1,18 @@ +{ + "schemaVersion": "1.0.0", + "packages": [ + { + "name": "ember-cli", + "version": "<%= emberCLIVersion %>", + "blueprints": [ + { + "name": "app", + "outputRepo": "https://github.com/ember-cli/ember-new-output", + "codemodsSource": "ember-app-codemods-manifest@1", + "isBaseBlueprint": true, + "options": [] + } + ] + } + ] +} diff --git a/blueprints/module-unification-app/files/ember-cli-build.js b/tests/fixtures/app/defaults/ember-cli-build.js similarity index 95% rename from blueprints/module-unification-app/files/ember-cli-build.js rename to tests/fixtures/app/defaults/ember-cli-build.js index d690a2531e..48e94e9e44 100644 --- a/blueprints/module-unification-app/files/ember-cli-build.js +++ b/tests/fixtures/app/defaults/ember-cli-build.js @@ -2,7 +2,7 @@ const EmberApp = require('ember-cli/lib/broccoli/ember-app'); -module.exports = function(defaults) { +module.exports = function (defaults) { let app = new EmberApp(defaults, { // Add options here }); diff --git a/tests/fixtures/app/defaults/package.json b/tests/fixtures/app/defaults/package.json new file mode 100644 index 0000000000..ba9a25733e --- /dev/null +++ b/tests/fixtures/app/defaults/package.json @@ -0,0 +1,70 @@ +{ + "name": "foo", + "version": "0.0.0", + "private": true, + "description": "Small description for foo goes here", + "repository": "", + "license": "MIT", + "author": "", + "directories": { + "doc": "doc", + "test": "tests" + }, + "scripts": { + "build": "ember build --environment=production", + "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", + "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", + "lint:hbs": "ember-template-lint .", + "lint:hbs:fix": "ember-template-lint . --fix", + "lint:js": "eslint . --cache", + "lint:js:fix": "eslint . --fix", + "start": "ember serve", + "test": "npm-run-all lint test:*", + "test:ember": "ember test" + }, + "devDependencies": { + "@ember/optional-features": "^2.0.0", + "@ember/test-helpers": "^2.6.0", + "@glimmer/component": "^1.0.4", + "@glimmer/tracking": "^1.0.4", + "babel-eslint": "^10.1.0", + "broccoli-asset-rev": "^3.0.0", + "ember-auto-import": "^1.12.0", + "ember-cli": "~<%= emberCLIVersion %>", + "ember-cli-app-version": "^5.0.0", + "ember-cli-babel": "^7.26.10", + "ember-cli-dependency-checker": "^3.2.0", + "ember-cli-htmlbars": "^5.7.2", + "ember-cli-inject-live-reload": "^2.1.0", + "ember-cli-sri": "^2.1.1", + "ember-cli-terser": "^4.0.2", + "ember-data": "~3.28.6", + "ember-export-application-global": "^2.0.1", + "ember-fetch": "^8.1.1", + "ember-load-initializers": "^2.1.2", + "ember-maybe-import-regenerator": "^0.1.6", + "ember-page-title": "^6.2.2", + "ember-qunit": "^5.1.5", + "ember-resolver": "^8.0.3", + "ember-source": "~3.28.8", + "ember-template-lint": "^3.15.0", + "ember-welcome-page": "^4.1.0", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-ember": "^10.5.8", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.4.1", + "eslint-plugin-qunit": "^6.2.0", + "loader.js": "^4.7.0", + "npm-run-all": "^4.1.5", + "prettier": "^2.5.1", + "qunit": "^2.17.2", + "qunit-dom": "^1.6.0" + }, + "engines": { + "node": "12.* || 14.* || >= 16" + }, + "ember": { + "edition": "octane" + } +} diff --git a/tests/fixtures/app/embroider-no-welcome/config/ember-cli-update.json b/tests/fixtures/app/embroider-no-welcome/config/ember-cli-update.json new file mode 100644 index 0000000000..4322ddea26 --- /dev/null +++ b/tests/fixtures/app/embroider-no-welcome/config/ember-cli-update.json @@ -0,0 +1,21 @@ +{ + "schemaVersion": "1.0.0", + "packages": [ + { + "name": "ember-cli", + "version": "<%= emberCLIVersion %>", + "blueprints": [ + { + "name": "app", + "outputRepo": "https://github.com/ember-cli/ember-new-output", + "codemodsSource": "ember-app-codemods-manifest@1", + "isBaseBlueprint": true, + "options": [ + "--no-welcome", + "--embroider" + ] + } + ] + } + ] +} diff --git a/tests/fixtures/app/embroider-no-welcome/package.json b/tests/fixtures/app/embroider-no-welcome/package.json new file mode 100644 index 0000000000..6fb63f6814 --- /dev/null +++ b/tests/fixtures/app/embroider-no-welcome/package.json @@ -0,0 +1,73 @@ +{ + "name": "foo", + "version": "0.0.0", + "private": true, + "description": "Small description for foo goes here", + "repository": "", + "license": "MIT", + "author": "", + "directories": { + "doc": "doc", + "test": "tests" + }, + "scripts": { + "build": "ember build --environment=production", + "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", + "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", + "lint:hbs": "ember-template-lint .", + "lint:hbs:fix": "ember-template-lint . --fix", + "lint:js": "eslint . --cache", + "lint:js:fix": "eslint . --fix", + "start": "ember serve", + "test": "npm-run-all lint test:*", + "test:ember": "ember test" + }, + "devDependencies": { + "@ember/optional-features": "^2.0.0", + "@ember/test-helpers": "^2.6.0", + "@embroider/compat": "^0.48.1", + "@embroider/core": "^0.48.1", + "@embroider/webpack": "^0.48.1", + "@glimmer/component": "^1.0.4", + "@glimmer/tracking": "^1.0.4", + "babel-eslint": "^10.1.0", + "broccoli-asset-rev": "^3.0.0", + "ember-auto-import": "^1.12.0", + "ember-cli": "~<%= emberCLIVersion %>", + "ember-cli-app-version": "^5.0.0", + "ember-cli-babel": "^7.26.10", + "ember-cli-dependency-checker": "^3.2.0", + "ember-cli-htmlbars": "^5.7.2", + "ember-cli-inject-live-reload": "^2.1.0", + "ember-cli-sri": "^2.1.1", + "ember-cli-terser": "^4.0.2", + "ember-data": "~3.28.6", + "ember-export-application-global": "^2.0.1", + "ember-fetch": "^8.1.1", + "ember-load-initializers": "^2.1.2", + "ember-maybe-import-regenerator": "^0.1.6", + "ember-page-title": "^6.2.2", + "ember-qunit": "^5.1.5", + "ember-resolver": "^8.0.3", + "ember-source": "~3.28.8", + "ember-template-lint": "^3.15.0", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-ember": "^10.5.8", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.4.1", + "eslint-plugin-qunit": "^6.2.0", + "loader.js": "^4.7.0", + "npm-run-all": "^4.1.5", + "prettier": "^2.5.1", + "qunit": "^2.17.2", + "qunit-dom": "^1.6.0", + "webpack": "^5.65.0" + }, + "engines": { + "node": "12.* || 14.* || >= 16" + }, + "ember": { + "edition": "octane" + } +} diff --git a/tests/fixtures/app/embroider-yarn/config/ember-cli-update.json b/tests/fixtures/app/embroider-yarn/config/ember-cli-update.json new file mode 100644 index 0000000000..140099c30a --- /dev/null +++ b/tests/fixtures/app/embroider-yarn/config/ember-cli-update.json @@ -0,0 +1,21 @@ +{ + "schemaVersion": "1.0.0", + "packages": [ + { + "name": "ember-cli", + "version": "<%= emberCLIVersion %>", + "blueprints": [ + { + "name": "app", + "outputRepo": "https://github.com/ember-cli/ember-new-output", + "codemodsSource": "ember-app-codemods-manifest@1", + "isBaseBlueprint": true, + "options": [ + "--yarn", + "--embroider" + ] + } + ] + } + ] +} diff --git a/tests/fixtures/app/embroider-yarn/package.json b/tests/fixtures/app/embroider-yarn/package.json new file mode 100644 index 0000000000..001418ba75 --- /dev/null +++ b/tests/fixtures/app/embroider-yarn/package.json @@ -0,0 +1,74 @@ +{ + "name": "foo", + "version": "0.0.0", + "private": true, + "description": "Small description for foo goes here", + "repository": "", + "license": "MIT", + "author": "", + "directories": { + "doc": "doc", + "test": "tests" + }, + "scripts": { + "build": "ember build --environment=production", + "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", + "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", + "lint:hbs": "ember-template-lint .", + "lint:hbs:fix": "ember-template-lint . --fix", + "lint:js": "eslint . --cache", + "lint:js:fix": "eslint . --fix", + "start": "ember serve", + "test": "npm-run-all lint test:*", + "test:ember": "ember test" + }, + "devDependencies": { + "@ember/optional-features": "^2.0.0", + "@ember/test-helpers": "^2.6.0", + "@embroider/compat": "^0.48.1", + "@embroider/core": "^0.48.1", + "@embroider/webpack": "^0.48.1", + "@glimmer/component": "^1.0.4", + "@glimmer/tracking": "^1.0.4", + "babel-eslint": "^10.1.0", + "broccoli-asset-rev": "^3.0.0", + "ember-auto-import": "^1.12.0", + "ember-cli": "~<%= emberCLIVersion %>", + "ember-cli-app-version": "^5.0.0", + "ember-cli-babel": "^7.26.10", + "ember-cli-dependency-checker": "^3.2.0", + "ember-cli-htmlbars": "^5.7.2", + "ember-cli-inject-live-reload": "^2.1.0", + "ember-cli-sri": "^2.1.1", + "ember-cli-terser": "^4.0.2", + "ember-data": "~3.28.6", + "ember-export-application-global": "^2.0.1", + "ember-fetch": "^8.1.1", + "ember-load-initializers": "^2.1.2", + "ember-maybe-import-regenerator": "^0.1.6", + "ember-page-title": "^6.2.2", + "ember-qunit": "^5.1.5", + "ember-resolver": "^8.0.3", + "ember-source": "~3.28.8", + "ember-template-lint": "^3.15.0", + "ember-welcome-page": "^4.1.0", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-ember": "^10.5.8", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.4.1", + "eslint-plugin-qunit": "^6.2.0", + "loader.js": "^4.7.0", + "npm-run-all": "^4.1.5", + "prettier": "^2.5.1", + "qunit": "^2.17.2", + "qunit-dom": "^1.6.0", + "webpack": "^5.65.0" + }, + "engines": { + "node": "12.* || 14.* || >= 16" + }, + "ember": { + "edition": "octane" + } +} diff --git a/tests/fixtures/app/embroider/config/ember-cli-update.json b/tests/fixtures/app/embroider/config/ember-cli-update.json new file mode 100644 index 0000000000..2cd80dfc9d --- /dev/null +++ b/tests/fixtures/app/embroider/config/ember-cli-update.json @@ -0,0 +1,20 @@ +{ + "schemaVersion": "1.0.0", + "packages": [ + { + "name": "ember-cli", + "version": "<%= emberCLIVersion %>", + "blueprints": [ + { + "name": "app", + "outputRepo": "https://github.com/ember-cli/ember-new-output", + "codemodsSource": "ember-app-codemods-manifest@1", + "isBaseBlueprint": true, + "options": [ + "--embroider" + ] + } + ] + } + ] +} diff --git a/tests/fixtures/app/embroider/ember-cli-build.js b/tests/fixtures/app/embroider/ember-cli-build.js new file mode 100644 index 0000000000..1d9b7e50ef --- /dev/null +++ b/tests/fixtures/app/embroider/ember-cli-build.js @@ -0,0 +1,31 @@ +'use strict'; + +const EmberApp = require('ember-cli/lib/broccoli/ember-app'); + +module.exports = function (defaults) { + let app = new EmberApp(defaults, { + // Add options here + }); + + // Use `app.import` to add additional libraries to the generated + // output files. + // + // If you need to use different assets in different + // environments, specify an object as the first parameter. That + // object's keys should be the environment name and the values + // should be the asset to use in that environment. + // + // If the library that you are including contains AMD or ES6 + // modules that you would like to import into your application + // please specify an object with the list of modules as keys + // along with the exports of each module as its value. + + const { Webpack } = require('@embroider/webpack'); + return require('@embroider/compat').compatBuild(app, Webpack, { + skipBabel: [ + { + package: 'qunit', + }, + ], + }); +}; diff --git a/tests/fixtures/app/embroider/package.json b/tests/fixtures/app/embroider/package.json new file mode 100644 index 0000000000..001418ba75 --- /dev/null +++ b/tests/fixtures/app/embroider/package.json @@ -0,0 +1,74 @@ +{ + "name": "foo", + "version": "0.0.0", + "private": true, + "description": "Small description for foo goes here", + "repository": "", + "license": "MIT", + "author": "", + "directories": { + "doc": "doc", + "test": "tests" + }, + "scripts": { + "build": "ember build --environment=production", + "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", + "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", + "lint:hbs": "ember-template-lint .", + "lint:hbs:fix": "ember-template-lint . --fix", + "lint:js": "eslint . --cache", + "lint:js:fix": "eslint . --fix", + "start": "ember serve", + "test": "npm-run-all lint test:*", + "test:ember": "ember test" + }, + "devDependencies": { + "@ember/optional-features": "^2.0.0", + "@ember/test-helpers": "^2.6.0", + "@embroider/compat": "^0.48.1", + "@embroider/core": "^0.48.1", + "@embroider/webpack": "^0.48.1", + "@glimmer/component": "^1.0.4", + "@glimmer/tracking": "^1.0.4", + "babel-eslint": "^10.1.0", + "broccoli-asset-rev": "^3.0.0", + "ember-auto-import": "^1.12.0", + "ember-cli": "~<%= emberCLIVersion %>", + "ember-cli-app-version": "^5.0.0", + "ember-cli-babel": "^7.26.10", + "ember-cli-dependency-checker": "^3.2.0", + "ember-cli-htmlbars": "^5.7.2", + "ember-cli-inject-live-reload": "^2.1.0", + "ember-cli-sri": "^2.1.1", + "ember-cli-terser": "^4.0.2", + "ember-data": "~3.28.6", + "ember-export-application-global": "^2.0.1", + "ember-fetch": "^8.1.1", + "ember-load-initializers": "^2.1.2", + "ember-maybe-import-regenerator": "^0.1.6", + "ember-page-title": "^6.2.2", + "ember-qunit": "^5.1.5", + "ember-resolver": "^8.0.3", + "ember-source": "~3.28.8", + "ember-template-lint": "^3.15.0", + "ember-welcome-page": "^4.1.0", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-ember": "^10.5.8", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.4.1", + "eslint-plugin-qunit": "^6.2.0", + "loader.js": "^4.7.0", + "npm-run-all": "^4.1.5", + "prettier": "^2.5.1", + "qunit": "^2.17.2", + "qunit-dom": "^1.6.0", + "webpack": "^5.65.0" + }, + "engines": { + "node": "12.* || 14.* || >= 16" + }, + "ember": { + "edition": "octane" + } +} diff --git a/tests/fixtures/app/nested-project/actual-project/app/templates/application.hbs b/tests/fixtures/app/nested-project/actual-project/app/templates/application.hbs new file mode 100644 index 0000000000..41f167ffc2 --- /dev/null +++ b/tests/fixtures/app/nested-project/actual-project/app/templates/application.hbs @@ -0,0 +1,7 @@ +{{page-title "Actual Project"}} + +{{!-- The following component displays Ember's default welcome message. --}} + +{{!-- Feel free to remove this! --}} + +{{outlet}} diff --git a/tests/fixtures/app/nested-project/actual-project/ember-cli-build.js b/tests/fixtures/app/nested-project/actual-project/ember-cli-build.js new file mode 100644 index 0000000000..48e94e9e44 --- /dev/null +++ b/tests/fixtures/app/nested-project/actual-project/ember-cli-build.js @@ -0,0 +1,24 @@ +'use strict'; + +const EmberApp = require('ember-cli/lib/broccoli/ember-app'); + +module.exports = function (defaults) { + let app = new EmberApp(defaults, { + // Add options here + }); + + // Use `app.import` to add additional libraries to the generated + // output files. + // + // If you need to use different assets in different + // environments, specify an object as the first parameter. That + // object's keys should be the environment name and the values + // should be the asset to use in that environment. + // + // If the library that you are including contains AMD or ES6 + // modules that you would like to import into your application + // please specify an object with the list of modules as keys + // along with the exports of each module as its value. + + return app.toTree(); +}; diff --git a/tests/fixtures/app/nested-project/actual-project/package.json b/tests/fixtures/app/nested-project/actual-project/package.json new file mode 100644 index 0000000000..736721e65f --- /dev/null +++ b/tests/fixtures/app/nested-project/actual-project/package.json @@ -0,0 +1,70 @@ +{ + "name": "actual-project", + "version": "0.0.0", + "private": true, + "description": "Small description for actual-project goes here", + "repository": "", + "license": "MIT", + "author": "", + "directories": { + "doc": "doc", + "test": "tests" + }, + "scripts": { + "build": "ember build --environment=production", + "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", + "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", + "lint:hbs": "ember-template-lint .", + "lint:hbs:fix": "ember-template-lint . --fix", + "lint:js": "eslint . --cache", + "lint:js:fix": "eslint . --fix", + "start": "ember serve", + "test": "npm-run-all lint test:*", + "test:ember": "ember test" + }, + "devDependencies": { + "@ember/optional-features": "^2.0.0", + "@ember/test-helpers": "^2.2.5", + "@glimmer/component": "^1.0.4", + "@glimmer/tracking": "^1.0.4", + "babel-eslint": "^10.1.0", + "broccoli-asset-rev": "^3.0.0", + "ember-auto-import": "^1.11.2", + "ember-cli": "~<%= emberCLIVersion %>", + "ember-cli-app-version": "^5.0.0", + "ember-cli-babel": "^7.26.3", + "ember-cli-dependency-checker": "^3.2.0", + "ember-cli-htmlbars": "^5.7.1", + "ember-cli-inject-live-reload": "^2.0.2", + "ember-cli-sri": "^2.1.1", + "ember-cli-terser": "^4.0.1", + "ember-data": "~3.27.0-beta.0", + "ember-export-application-global": "^2.0.1", + "ember-fetch": "^8.0.4", + "ember-load-initializers": "^2.1.2", + "ember-maybe-import-regenerator": "^0.1.6", + "ember-page-title": "^6.2.1", + "ember-qunit": "^5.1.4", + "ember-resolver": "^8.0.2", + "ember-source": "~3.27.0-beta.3", + "ember-template-lint": "^3.2.0", + "ember-welcome-page": "^4.0.0", + "eslint": "^7.24.0", + "eslint-config-prettier": "^8.1.0", + "eslint-plugin-ember": "^10.3.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.3.1", + "eslint-plugin-qunit": "^6.0.0", + "loader.js": "^4.7.0", + "npm-run-all": "^4.1.5", + "prettier": "^2.2.1", + "qunit": "^2.14.1", + "qunit-dom": "^1.6.0" + }, + "engines": { + "node": "12.* || 14.* || >= 16" + }, + "ember": { + "edition": "octane" + } +} diff --git a/tests/fixtures/app/nested-project/package.json b/tests/fixtures/app/nested-project/package.json new file mode 100644 index 0000000000..1c6ac30bdd --- /dev/null +++ b/tests/fixtures/app/nested-project/package.json @@ -0,0 +1,6 @@ +{ + "name": "nested-project", + "ember-addon": { + "projectRoot": "./actual-project" + } +} \ No newline at end of file diff --git a/tests/fixtures/app/npm/.travis.yml b/tests/fixtures/app/npm/.travis.yml index 1fe0f1fca7..bf02ddcad6 100644 --- a/tests/fixtures/app/npm/.travis.yml +++ b/tests/fixtures/app/npm/.travis.yml @@ -1,10 +1,9 @@ --- language: node_js node_js: - - "8" + - "12" -sudo: false -dist: trusty +dist: xenial addons: chrome: stable @@ -18,7 +17,9 @@ env: # See https://git.io/vdao3 for details. - JOBS=1 +branches: + only: + - master + script: - - npm run lint:hbs - - npm run lint:js - npm test diff --git a/tests/fixtures/app/npm/README.md b/tests/fixtures/app/npm/README.md index c81ac108ab..dba454682e 100644 --- a/tests/fixtures/app/npm/README.md +++ b/tests/fixtures/app/npm/README.md @@ -35,9 +35,8 @@ Make use of the many generators for code, try `ember help generate` for more det ### Linting -* `npm run lint:hbs` -* `npm run lint:js` -* `npm run lint:js -- --fix` +* `npm run lint` +* `npm run lint:fix` ### Building diff --git a/tests/fixtures/app/npm/app/templates/application.hbs b/tests/fixtures/app/npm/app/templates/application.hbs index 5230580f82..76f2c5e009 100644 --- a/tests/fixtures/app/npm/app/templates/application.hbs +++ b/tests/fixtures/app/npm/app/templates/application.hbs @@ -1,3 +1,5 @@ +{{page-title "Foo"}} +

Welcome to Ember

{{outlet}} \ No newline at end of file diff --git a/tests/fixtures/app/npm/config/ember-cli-update.json b/tests/fixtures/app/npm/config/ember-cli-update.json new file mode 100644 index 0000000000..e7a244c12a --- /dev/null +++ b/tests/fixtures/app/npm/config/ember-cli-update.json @@ -0,0 +1,20 @@ +{ + "schemaVersion": "1.0.0", + "packages": [ + { + "name": "ember-cli", + "version": "<%= emberCLIVersion %>", + "blueprints": [ + { + "name": "app", + "outputRepo": "https://github.com/ember-cli/ember-new-output", + "codemodsSource": "ember-app-codemods-manifest@1", + "isBaseBlueprint": true, + "options": [ + "--no-welcome" + ] + } + ] + } + ] +} diff --git a/tests/fixtures/app/npm/package.json b/tests/fixtures/app/npm/package.json index 162ca5641f..113cc926b8 100644 --- a/tests/fixtures/app/npm/package.json +++ b/tests/fixtures/app/npm/package.json @@ -11,41 +11,59 @@ "test": "tests" }, "scripts": { - "build": "ember build", + "build": "ember build --environment=production", + "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", + "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", "lint:hbs": "ember-template-lint .", - "lint:js": "eslint .", + "lint:hbs:fix": "ember-template-lint . --fix", + "lint:js": "eslint . --cache", + "lint:js:fix": "eslint . --fix", "start": "ember serve", - "test": "ember test" + "test": "npm-run-all lint test:*", + "test:ember": "ember test" }, "devDependencies": { - "@ember/optional-features": "^1.0.0", - "babel-eslint": "^10.0.3", + "@ember/optional-features": "^2.0.0", + "@ember/test-helpers": "^2.6.0", + "@glimmer/component": "^1.0.4", + "@glimmer/tracking": "^1.0.4", + "babel-eslint": "^10.1.0", "broccoli-asset-rev": "^3.0.0", + "ember-auto-import": "^1.12.0", "ember-cli": "~<%= emberCLIVersion %>", - "ember-cli-app-version": "^3.2.0", - "ember-cli-babel": "^7.11.1", - "ember-cli-dependency-checker": "^3.1.0", - "ember-cli-eslint": "^5.1.0", - "ember-cli-htmlbars": "^4.0.0", - "ember-cli-htmlbars-inline-precompile": "^3.0.0", - "ember-cli-inject-live-reload": "^2.0.1", + "ember-cli-app-version": "^5.0.0", + "ember-cli-babel": "^7.26.10", + "ember-cli-dependency-checker": "^3.2.0", + "ember-cli-htmlbars": "^5.7.2", + "ember-cli-inject-live-reload": "^2.1.0", "ember-cli-sri": "^2.1.1", - "ember-cli-template-lint": "^1.0.0-beta.3", - "ember-cli-uglify": "^3.0.0", - "ember-data": "~3.13.0", - "ember-export-application-global": "^2.0.0", - "ember-fetch": "^6.7.0", - "ember-load-initializers": "^2.1.0", + "ember-cli-terser": "^4.0.2", + "ember-data": "~3.28.6", + "ember-export-application-global": "^2.0.1", + "ember-fetch": "^8.1.1", + "ember-load-initializers": "^2.1.2", "ember-maybe-import-regenerator": "^0.1.6", - "ember-qunit": "^4.5.1", - "ember-resolver": "^5.3.0", - "ember-source": "~3.13.0", - "eslint-plugin-ember": "^7.1.0", - "eslint-plugin-node": "^10.0.0", + "ember-page-title": "^6.2.2", + "ember-qunit": "^5.1.5", + "ember-resolver": "^8.0.3", + "ember-source": "~3.28.8", + "ember-template-lint": "^3.15.0", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-ember": "^10.5.8", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.4.1", + "eslint-plugin-qunit": "^6.2.0", "loader.js": "^4.7.0", - "qunit-dom": "^0.9.0" + "npm-run-all": "^4.1.5", + "prettier": "^2.5.1", + "qunit": "^2.17.2", + "qunit-dom": "^1.6.0" }, "engines": { - "node": "8.* || >= 10.*" + "node": "12.* || 14.* || >= 16" + }, + "ember": { + "edition": "octane" } } diff --git a/blueprints/app/files/app/templates/components/.gitkeep b/tests/fixtures/app/project-root-with-ember-cli-build/ember-cli-build.js similarity index 100% rename from blueprints/app/files/app/templates/components/.gitkeep rename to tests/fixtures/app/project-root-with-ember-cli-build/ember-cli-build.js diff --git a/tests/fixtures/app/project-root-with-ember-cli-build/package.json b/tests/fixtures/app/project-root-with-ember-cli-build/package.json new file mode 100644 index 0000000000..da66aad832 --- /dev/null +++ b/tests/fixtures/app/project-root-with-ember-cli-build/package.json @@ -0,0 +1,6 @@ +{ + "name": "project-root-with-ember-cli-build", + "ember-addon": { + "projectRoot": "./actual-project" + } +} \ No newline at end of file diff --git a/tests/fixtures/app/with-blueprint-override-lint-fail/.eslintrc.js b/tests/fixtures/app/with-blueprint-override-lint-fail/.eslintrc.js new file mode 100644 index 0000000000..056424da73 --- /dev/null +++ b/tests/fixtures/app/with-blueprint-override-lint-fail/.eslintrc.js @@ -0,0 +1,53 @@ +'use strict'; + +module.exports = { + root: true, + parser: 'babel-eslint', + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + legacyDecorators: true, + }, + }, + plugins: ['ember'], + extends: [ + 'eslint:recommended', + 'plugin:ember/recommended', + 'plugin:prettier/recommended', + ], + env: { + browser: true, + }, + rules: {}, + overrides: [ + // node files + { + files: [ + './.eslintrc.js', + './.prettierrc.js', + './.template-lintrc.js', + './ember-cli-build.js', + './testem.js', + './blueprints/*/index.js', + './config/**/*.js', + './lib/*/index.js', + './server/**/*.js', + ], + parserOptions: { + sourceType: 'script', + }, + env: { + browser: false, + node: true, + }, + plugins: ['node'], + extends: ['plugin:node/recommended'], + rules: { + // this can be removed once the following is fixed + // https://github.com/mysticatea/eslint-plugin-node/issues/77 + 'node/no-unpublished-require': 'off', + }, + }, + ], +}; diff --git a/tests/fixtures/app/with-blueprint-override-lint-fail/.prettierrc.js b/tests/fixtures/app/with-blueprint-override-lint-fail/.prettierrc.js new file mode 100644 index 0000000000..6d05baa3d4 --- /dev/null +++ b/tests/fixtures/app/with-blueprint-override-lint-fail/.prettierrc.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + singleQuote: true, // blueprint overrides use double quotes +}; diff --git a/tests/fixtures/app/with-blueprint-override-lint-fail/.template-lintrc.js b/tests/fixtures/app/with-blueprint-override-lint-fail/.template-lintrc.js new file mode 100644 index 0000000000..8360c930a4 --- /dev/null +++ b/tests/fixtures/app/with-blueprint-override-lint-fail/.template-lintrc.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = { + extends: 'recommended', + rules: { + 'require-button-type': true, // blueprint override has missing button type + }, +}; diff --git a/tests/fixtures/app/with-blueprint-override-lint-fail/.travis.yml b/tests/fixtures/app/with-blueprint-override-lint-fail/.travis.yml new file mode 100644 index 0000000000..bf02ddcad6 --- /dev/null +++ b/tests/fixtures/app/with-blueprint-override-lint-fail/.travis.yml @@ -0,0 +1,25 @@ +--- +language: node_js +node_js: + - "12" + +dist: xenial + +addons: + chrome: stable + +cache: + directories: + - $HOME/.npm + +env: + global: + # See https://git.io/vdao3 for details. + - JOBS=1 + +branches: + only: + - master + +script: + - npm test diff --git a/tests/fixtures/module-unification-app/yarn/README.md b/tests/fixtures/app/with-blueprint-override-lint-fail/README.md similarity index 90% rename from tests/fixtures/module-unification-app/yarn/README.md rename to tests/fixtures/app/with-blueprint-override-lint-fail/README.md index cce6022935..dba454682e 100644 --- a/tests/fixtures/module-unification-app/yarn/README.md +++ b/tests/fixtures/app/with-blueprint-override-lint-fail/README.md @@ -8,8 +8,7 @@ A short introduction of this app could easily go here. You will need the following things properly installed on your computer. * [Git](https://git-scm.com/) -* [Node.js](https://nodejs.org/) -* [Yarn](https://yarnpkg.com/) +* [Node.js](https://nodejs.org/) (with npm) * [Ember CLI](https://ember-cli.com/) * [Google Chrome](https://google.com/chrome/) @@ -17,7 +16,7 @@ You will need the following things properly installed on your computer. * `git clone ` this repository * `cd foo` -* `yarn install` +* `npm install` ## Running / Development @@ -36,9 +35,8 @@ Make use of the many generators for code, try `ember help generate` for more det ### Linting -* `yarn lint:hbs` -* `yarn lint:js` -* `yarn lint:js --fix` +* `npm run lint` +* `npm run lint:fix` ### Building diff --git a/tests/fixtures/module-unification-app/yarn/src/ui/routes/application/template.hbs b/tests/fixtures/app/with-blueprint-override-lint-fail/app/templates/application.hbs similarity index 86% rename from tests/fixtures/module-unification-app/yarn/src/ui/routes/application/template.hbs rename to tests/fixtures/app/with-blueprint-override-lint-fail/app/templates/application.hbs index 7b1b1040fe..c50aa5fb7c 100644 --- a/tests/fixtures/module-unification-app/yarn/src/ui/routes/application/template.hbs +++ b/tests/fixtures/app/with-blueprint-override-lint-fail/app/templates/application.hbs @@ -1,3 +1,5 @@ +{{page-title "Foo"}} + {{!-- The following component displays Ember's default welcome message. --}} {{!-- Feel free to remove this! --}} diff --git a/tests/fixtures/app/with-blueprint-override-lint-fail/blueprints/component/files/__root__/__path__/__name__.hbs b/tests/fixtures/app/with-blueprint-override-lint-fail/blueprints/component/files/__root__/__path__/__name__.hbs new file mode 100644 index 0000000000..f72a7ea3e6 --- /dev/null +++ b/tests/fixtures/app/with-blueprint-override-lint-fail/blueprints/component/files/__root__/__path__/__name__.hbs @@ -0,0 +1 @@ + diff --git a/tests/fixtures/app/with-blueprint-override-lint-fail/blueprints/component/files/__root__/__path__/__name__.js b/tests/fixtures/app/with-blueprint-override-lint-fail/blueprints/component/files/__root__/__path__/__name__.js new file mode 100644 index 0000000000..85ce559e8f --- /dev/null +++ b/tests/fixtures/app/with-blueprint-override-lint-fail/blueprints/component/files/__root__/__path__/__name__.js @@ -0,0 +1 @@ +console.log("foo"); diff --git a/tests/fixtures/app/with-blueprint-override-lint-fail/config/ember-cli-update.json b/tests/fixtures/app/with-blueprint-override-lint-fail/config/ember-cli-update.json new file mode 100644 index 0000000000..a0462c3b6e --- /dev/null +++ b/tests/fixtures/app/with-blueprint-override-lint-fail/config/ember-cli-update.json @@ -0,0 +1,18 @@ +{ + "schemaVersion": "1.0.0", + "packages": [ + { + "name": "ember-cli", + "version": "<%= emberCLIVersion %>", + "blueprints": [ + { + "name": "app", + "outputRepo": "https://github.com/ember-cli/ember-new-output", + "codemodsSource": "ember-app-codemods-manifest@1", + "isBaseBlueprint": true, + "options": [] + } + ] + } + ] +} diff --git a/tests/fixtures/app/with-blueprint-override-lint-fail/ember-cli-build.js b/tests/fixtures/app/with-blueprint-override-lint-fail/ember-cli-build.js new file mode 100644 index 0000000000..48e94e9e44 --- /dev/null +++ b/tests/fixtures/app/with-blueprint-override-lint-fail/ember-cli-build.js @@ -0,0 +1,24 @@ +'use strict'; + +const EmberApp = require('ember-cli/lib/broccoli/ember-app'); + +module.exports = function (defaults) { + let app = new EmberApp(defaults, { + // Add options here + }); + + // Use `app.import` to add additional libraries to the generated + // output files. + // + // If you need to use different assets in different + // environments, specify an object as the first parameter. That + // object's keys should be the environment name and the values + // should be the asset to use in that environment. + // + // If the library that you are including contains AMD or ES6 + // modules that you would like to import into your application + // please specify an object with the list of modules as keys + // along with the exports of each module as its value. + + return app.toTree(); +}; diff --git a/tests/fixtures/app/with-blueprint-override-lint-fail/package.json b/tests/fixtures/app/with-blueprint-override-lint-fail/package.json new file mode 100644 index 0000000000..965063373d --- /dev/null +++ b/tests/fixtures/app/with-blueprint-override-lint-fail/package.json @@ -0,0 +1,69 @@ +{ + "name": "foo", + "version": "0.0.0", + "private": true, + "description": "Small description for foo goes here", + "repository": "", + "license": "MIT", + "author": "", + "directories": { + "doc": "doc", + "test": "tests" + }, + "scripts": { + "build": "ember build --environment=production", + "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", + "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", + "lint:hbs": "ember-template-lint .", + "lint:hbs:fix": "ember-template-lint . --fix", + "lint:js": "eslint . --cache", + "lint:js:fix": "eslint . --fix", + "start": "ember serve", + "test": "npm-run-all lint test:*", + "test:ember": "ember test" + }, + "devDependencies": { + "@ember/optional-features": "^2.0.0", + "@ember/test-helpers": "^2.2.0", + "@glimmer/component": "^1.0.3", + "@glimmer/tracking": "^1.0.3", + "babel-eslint": "^10.1.0", + "broccoli-asset-rev": "^3.0.0", + "ember-auto-import": "^1.10.1", + "ember-cli": "~<%= emberCLIVersion %>", + "ember-cli-app-version": "^4.0.0", + "ember-cli-babel": "^7.23.1", + "ember-cli-dependency-checker": "^3.2.0", + "ember-cli-htmlbars": "^5.3.2", + "ember-cli-inject-live-reload": "^2.0.2", + "ember-cli-sri": "^2.1.1", + "ember-cli-terser": "^4.0.1", + "ember-data": "~3.26.0-beta.0", + "ember-export-application-global": "^2.0.1", + "ember-fetch": "^8.0.4", + "ember-load-initializers": "^2.1.2", + "ember-maybe-import-regenerator": "^0.1.6", + "ember-page-title": "^6.2.1", + "ember-qunit": "^5.1.2", + "ember-resolver": "^8.0.2", + "ember-source": "~3.26.0-beta.2", + "ember-template-lint": "^2.18.1", + "ember-welcome-page": "^4.0.0", + "eslint": "^7.20.0", + "eslint-config-prettier": "^7.2.0", + "eslint-plugin-ember": "^10.2.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.3.1", + "loader.js": "^4.7.0", + "npm-run-all": "^4.1.5", + "prettier": "^2.2.1", + "qunit": "^2.14.1", + "qunit-dom": "^1.6.0" + }, + "engines": { + "node": "12.* || 14.* || >= 16" + }, + "ember": { + "edition": "octane" + } +} diff --git a/tests/fixtures/app/yarn/.travis.yml b/tests/fixtures/app/yarn/.travis.yml index e8232192cb..d908403ee5 100644 --- a/tests/fixtures/app/yarn/.travis.yml +++ b/tests/fixtures/app/yarn/.travis.yml @@ -1,10 +1,9 @@ --- language: node_js node_js: - - "8" + - "12" -sudo: false -dist: trusty +dist: xenial addons: chrome: stable @@ -17,14 +16,13 @@ env: # See https://git.io/vdao3 for details. - JOBS=1 +branches: + only: + - master + before_install: - curl -o- -L https://yarnpkg.com/install.sh | bash - export PATH=$HOME/.yarn/bin:$PATH -install: - - yarn install --non-interactive - script: - - yarn lint:hbs - - yarn lint:js - yarn test diff --git a/tests/fixtures/app/yarn/README.md b/tests/fixtures/app/yarn/README.md index cce6022935..21a6679ce9 100644 --- a/tests/fixtures/app/yarn/README.md +++ b/tests/fixtures/app/yarn/README.md @@ -36,9 +36,8 @@ Make use of the many generators for code, try `ember help generate` for more det ### Linting -* `yarn lint:hbs` -* `yarn lint:js` -* `yarn lint:js --fix` +* `yarn lint` +* `yarn lint:fix` ### Building diff --git a/tests/fixtures/app/yarn/app/templates/application.hbs b/tests/fixtures/app/yarn/app/templates/application.hbs index 7b1b1040fe..c50aa5fb7c 100644 --- a/tests/fixtures/app/yarn/app/templates/application.hbs +++ b/tests/fixtures/app/yarn/app/templates/application.hbs @@ -1,3 +1,5 @@ +{{page-title "Foo"}} + {{!-- The following component displays Ember's default welcome message. --}} {{!-- Feel free to remove this! --}} diff --git a/tests/fixtures/app/yarn/config/ember-cli-update.json b/tests/fixtures/app/yarn/config/ember-cli-update.json new file mode 100644 index 0000000000..51d5e44556 --- /dev/null +++ b/tests/fixtures/app/yarn/config/ember-cli-update.json @@ -0,0 +1,20 @@ +{ + "schemaVersion": "1.0.0", + "packages": [ + { + "name": "ember-cli", + "version": "<%= emberCLIVersion %>", + "blueprints": [ + { + "name": "app", + "outputRepo": "https://github.com/ember-cli/ember-new-output", + "codemodsSource": "ember-app-codemods-manifest@1", + "isBaseBlueprint": true, + "options": [ + "--yarn" + ] + } + ] + } + ] +} diff --git a/tests/fixtures/app/yarn/package.json b/tests/fixtures/app/yarn/package.json index 63686d491a..ba9a25733e 100644 --- a/tests/fixtures/app/yarn/package.json +++ b/tests/fixtures/app/yarn/package.json @@ -11,42 +11,60 @@ "test": "tests" }, "scripts": { - "build": "ember build", + "build": "ember build --environment=production", + "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", + "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", "lint:hbs": "ember-template-lint .", - "lint:js": "eslint .", + "lint:hbs:fix": "ember-template-lint . --fix", + "lint:js": "eslint . --cache", + "lint:js:fix": "eslint . --fix", "start": "ember serve", - "test": "ember test" + "test": "npm-run-all lint test:*", + "test:ember": "ember test" }, "devDependencies": { - "@ember/optional-features": "^1.0.0", - "babel-eslint": "^10.0.3", + "@ember/optional-features": "^2.0.0", + "@ember/test-helpers": "^2.6.0", + "@glimmer/component": "^1.0.4", + "@glimmer/tracking": "^1.0.4", + "babel-eslint": "^10.1.0", "broccoli-asset-rev": "^3.0.0", + "ember-auto-import": "^1.12.0", "ember-cli": "~<%= emberCLIVersion %>", - "ember-cli-app-version": "^3.2.0", - "ember-cli-babel": "^7.11.1", - "ember-cli-dependency-checker": "^3.1.0", - "ember-cli-eslint": "^5.1.0", - "ember-cli-htmlbars": "^4.0.0", - "ember-cli-htmlbars-inline-precompile": "^3.0.0", - "ember-cli-inject-live-reload": "^2.0.1", + "ember-cli-app-version": "^5.0.0", + "ember-cli-babel": "^7.26.10", + "ember-cli-dependency-checker": "^3.2.0", + "ember-cli-htmlbars": "^5.7.2", + "ember-cli-inject-live-reload": "^2.1.0", "ember-cli-sri": "^2.1.1", - "ember-cli-template-lint": "^1.0.0-beta.3", - "ember-cli-uglify": "^3.0.0", - "ember-data": "~3.13.0", - "ember-export-application-global": "^2.0.0", - "ember-fetch": "^6.7.0", - "ember-load-initializers": "^2.1.0", + "ember-cli-terser": "^4.0.2", + "ember-data": "~3.28.6", + "ember-export-application-global": "^2.0.1", + "ember-fetch": "^8.1.1", + "ember-load-initializers": "^2.1.2", "ember-maybe-import-regenerator": "^0.1.6", - "ember-qunit": "^4.5.1", - "ember-resolver": "^5.3.0", - "ember-source": "~3.13.0", - "ember-welcome-page": "^4.0.0", - "eslint-plugin-ember": "^7.1.0", - "eslint-plugin-node": "^10.0.0", + "ember-page-title": "^6.2.2", + "ember-qunit": "^5.1.5", + "ember-resolver": "^8.0.3", + "ember-source": "~3.28.8", + "ember-template-lint": "^3.15.0", + "ember-welcome-page": "^4.1.0", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-ember": "^10.5.8", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.4.1", + "eslint-plugin-qunit": "^6.2.0", "loader.js": "^4.7.0", - "qunit-dom": "^0.9.0" + "npm-run-all": "^4.1.5", + "prettier": "^2.5.1", + "qunit": "^2.17.2", + "qunit-dom": "^1.6.0" }, "engines": { - "node": "8.* || >= 10.*" + "node": "12.* || 14.* || >= 16" + }, + "ember": { + "edition": "octane" } } diff --git a/tests/fixtures/blueprints/basic/index.js b/tests/fixtures/blueprints/basic/index.js index 495ee6c0fd..5c24b277e8 100644 --- a/tests/fixtures/blueprints/basic/index.js +++ b/tests/fixtures/blueprints/basic/index.js @@ -1,7 +1,6 @@ 'use strict'; const Blueprint = require('../../../../lib/models/blueprint'); -const Promise = require('rsvp').Promise; module.exports = Blueprint.extend({ description: 'A basic blueprint', diff --git a/tests/fixtures/brocfile-tests/additional-trees/ember-cli-build.js b/tests/fixtures/brocfile-tests/additional-trees/ember-cli-build.js index 7b496cd57f..19c8b96588 100644 --- a/tests/fixtures/brocfile-tests/additional-trees/ember-cli-build.js +++ b/tests/fixtures/brocfile-tests/additional-trees/ember-cli-build.js @@ -1,5 +1,6 @@ const EmberApp = require('ember-cli/lib/broccoli/ember-app'); const Funnel = require('broccoli-funnel'); +const { isExperimentEnabled } = require('ember-cli/lib/experiments'); module.exports = function (defaults) { let app = new EmberApp(defaults, {}); @@ -9,5 +10,12 @@ module.exports = function (defaults) { destDir: '/assets' }); + if (isExperimentEnabled('EMBROIDER')) { + const { Webpack } = require('@embroider/webpack'); + return require('@embroider/compat').compatBuild(app, Webpack, { + extraPublicTrees: [funnel] + }); + } + return app.toTree(funnel); }; diff --git a/tests/fixtures/brocfile-tests/app-import-anonymous-amd/ember-cli-build.js b/tests/fixtures/brocfile-tests/app-import-anonymous-amd/ember-cli-build.js index 2102fc85dc..def1e548cc 100644 --- a/tests/fixtures/brocfile-tests/app-import-anonymous-amd/ember-cli-build.js +++ b/tests/fixtures/brocfile-tests/app-import-anonymous-amd/ember-cli-build.js @@ -1,4 +1,5 @@ const EmberApp = require('ember-cli/lib/broccoli/ember-app'); +const { isExperimentEnabled } = require('ember-cli/lib/experiments'); module.exports = function (defaults) { var app = new EmberApp(defaults, { @@ -18,5 +19,10 @@ module.exports = function (defaults) { outputFile: '/assets/output.js' }); + if (isExperimentEnabled('EMBROIDER')) { + const { Webpack } = require('@embroider/webpack'); + return require('@embroider/compat').compatBuild(app, Webpack); + } + return app.toTree(); }; diff --git a/tests/fixtures/brocfile-tests/app-import-custom-transform/ember-cli-build.js b/tests/fixtures/brocfile-tests/app-import-custom-transform/ember-cli-build.js index 850f677281..a7986a6f81 100644 --- a/tests/fixtures/brocfile-tests/app-import-custom-transform/ember-cli-build.js +++ b/tests/fixtures/brocfile-tests/app-import-custom-transform/ember-cli-build.js @@ -1,5 +1,5 @@ const EmberApp = require('ember-cli/lib/broccoli/ember-app'); -const map = require('broccoli-stew').map; +const { isExperimentEnabled } = require('ember-cli/lib/experiments'); module.exports = function (defaults) { var app = new EmberApp(defaults, { @@ -14,5 +14,10 @@ module.exports = function (defaults) { outputFile: '/assets/output.js' }); + if (isExperimentEnabled('EMBROIDER')) { + const { Webpack } = require('@embroider/webpack'); + return require('@embroider/compat').compatBuild(app, Webpack); + } + return app.toTree(); }; \ No newline at end of file diff --git a/tests/fixtures/brocfile-tests/app-import-named-umd/ember-cli-build.js b/tests/fixtures/brocfile-tests/app-import-named-umd/ember-cli-build.js new file mode 100644 index 0000000000..7a95a3fc55 --- /dev/null +++ b/tests/fixtures/brocfile-tests/app-import-named-umd/ember-cli-build.js @@ -0,0 +1,28 @@ +const EmberApp = require('ember-cli/lib/broccoli/ember-app'); +const { isExperimentEnabled } = require('ember-cli/lib/experiments'); + +module.exports = function (defaults) { + var app = new EmberApp(defaults, { + }); + + app.import('vendor/named-umd-example.js', { + using: [ + { transformation: 'amd'} + ], + outputFile: '/assets/output.js' + }); + + app.import('vendor/named-umd-example.js', { + using: [ + { transformation: 'amd'} + ], + outputFile: '/assets/output.js' + }); + + if (isExperimentEnabled('EMBROIDER')) { + const { Webpack } = require('@embroider/webpack'); + return require('@embroider/compat').compatBuild(app, Webpack); + } + + return app.toTree(); +}; diff --git a/tests/fixtures/brocfile-tests/app-import-named-umd/vendor/named-umd-example.js b/tests/fixtures/brocfile-tests/app-import-named-umd/vendor/named-umd-example.js new file mode 100644 index 0000000000..4438314d39 --- /dev/null +++ b/tests/fixtures/brocfile-tests/app-import-named-umd/vendor/named-umd-example.js @@ -0,0 +1,11 @@ +!function(e, t) { + if ("function" == typeof define && define.amd) { + define("hello-world", [], t); + } else { + throw new Error("No amd loader found"); + } +}(this, function() { + return function helloWorld() { + return "Hello World"; + } +}); diff --git a/tests/fixtures/brocfile-tests/app-import-output-file/ember-cli-build.js b/tests/fixtures/brocfile-tests/app-import-output-file/ember-cli-build.js index 60c6e1ab90..5cfadb2569 100644 --- a/tests/fixtures/brocfile-tests/app-import-output-file/ember-cli-build.js +++ b/tests/fixtures/brocfile-tests/app-import-output-file/ember-cli-build.js @@ -1,4 +1,5 @@ const EmberApp = require('ember-cli/lib/broccoli/ember-app'); +const { isExperimentEnabled } = require('ember-cli/lib/experiments'); module.exports = function (defaults) { var app = new EmberApp(defaults, {}); @@ -6,5 +7,10 @@ module.exports = function (defaults) { app.import('vendor/custom-output-file.js', {outputFile: '/assets/output-file.js'}); app.import('vendor/custom-output-file.css', {outputFile: '/assets/output-file.css'}); + if (isExperimentEnabled('EMBROIDER')) { + const { Webpack } = require('@embroider/webpack'); + return require('@embroider/compat').compatBuild(app, Webpack); + } + return app.toTree(); }; diff --git a/tests/fixtures/brocfile-tests/auto-run-false/app/app.js b/tests/fixtures/brocfile-tests/auto-run-false/app/app.js new file mode 100644 index 0000000000..d3809c8ca7 --- /dev/null +++ b/tests/fixtures/brocfile-tests/auto-run-false/app/app.js @@ -0,0 +1,17 @@ +import Application from '@ember/application'; +import Resolver from 'ember-resolver'; +import loadInitializers from 'ember-load-initializers'; +import config from 'some-cool-app/config/environment'; + +export default class App extends Application { + modulePrefix = config.modulePrefix; + podModulePrefix = config.podModulePrefix; + Resolver = Resolver; + + init() { + super.init(); + window.APP_HAS_LOADED = true; + } +} + +loadInitializers(App, config.modulePrefix); \ No newline at end of file diff --git a/tests/fixtures/brocfile-tests/auto-run-false/config/targets.js b/tests/fixtures/brocfile-tests/auto-run-false/config/targets.js new file mode 100644 index 0000000000..b55f37e561 --- /dev/null +++ b/tests/fixtures/brocfile-tests/auto-run-false/config/targets.js @@ -0,0 +1,10 @@ +const browsers = [ + 'last 1 Chrome versions', + 'last 1 Firefox versions', + 'last 1 Safari versions', +]; + +module.exports = { + browsers, + node: 'current', +}; diff --git a/tests/fixtures/brocfile-tests/auto-run-false/ember-cli-build.js b/tests/fixtures/brocfile-tests/auto-run-false/ember-cli-build.js index 9826d0a551..f71a75a3c4 100644 --- a/tests/fixtures/brocfile-tests/auto-run-false/ember-cli-build.js +++ b/tests/fixtures/brocfile-tests/auto-run-false/ember-cli-build.js @@ -1,9 +1,15 @@ const EmberApp = require('ember-cli/lib/broccoli/ember-app'); +const { isExperimentEnabled } = require('ember-cli/lib/experiments'); -module.exports = function(defaults) { +module.exports = function (defaults) { var app = new EmberApp(defaults, { autoRun: false }); + if (isExperimentEnabled('EMBROIDER')) { + const { Webpack } = require('@embroider/webpack'); + return require('@embroider/compat').compatBuild(app, Webpack); + } + return app.toTree(); }; diff --git a/tests/fixtures/brocfile-tests/auto-run-true/app/app.js b/tests/fixtures/brocfile-tests/auto-run-true/app/app.js new file mode 100644 index 0000000000..d3809c8ca7 --- /dev/null +++ b/tests/fixtures/brocfile-tests/auto-run-true/app/app.js @@ -0,0 +1,17 @@ +import Application from '@ember/application'; +import Resolver from 'ember-resolver'; +import loadInitializers from 'ember-load-initializers'; +import config from 'some-cool-app/config/environment'; + +export default class App extends Application { + modulePrefix = config.modulePrefix; + podModulePrefix = config.podModulePrefix; + Resolver = Resolver; + + init() { + super.init(); + window.APP_HAS_LOADED = true; + } +} + +loadInitializers(App, config.modulePrefix); \ No newline at end of file diff --git a/tests/fixtures/brocfile-tests/auto-run-true/config/targets.js b/tests/fixtures/brocfile-tests/auto-run-true/config/targets.js new file mode 100644 index 0000000000..b55f37e561 --- /dev/null +++ b/tests/fixtures/brocfile-tests/auto-run-true/config/targets.js @@ -0,0 +1,10 @@ +const browsers = [ + 'last 1 Chrome versions', + 'last 1 Firefox versions', + 'last 1 Safari versions', +]; + +module.exports = { + browsers, + node: 'current', +}; diff --git a/tests/fixtures/brocfile-tests/auto-run-true/ember-cli-build.js b/tests/fixtures/brocfile-tests/auto-run-true/ember-cli-build.js index 6a34d1ab79..320ff492c4 100644 --- a/tests/fixtures/brocfile-tests/auto-run-true/ember-cli-build.js +++ b/tests/fixtures/brocfile-tests/auto-run-true/ember-cli-build.js @@ -1,9 +1,15 @@ const EmberApp = require('ember-cli/lib/broccoli/ember-app'); +const { isExperimentEnabled } = require('ember-cli/lib/experiments'); -module.exports = function(defaults) { +module.exports = function (defaults) { var app = new EmberApp(defaults, { autoRun: true }); + if (isExperimentEnabled('EMBROIDER')) { + const { Webpack } = require('@embroider/webpack'); + return require('@embroider/compat').compatBuild(app, Webpack); + } + return app.toTree(); }; diff --git a/tests/fixtures/brocfile-tests/custom-ember-env/config/environment.js b/tests/fixtures/brocfile-tests/custom-ember-env/config/environment.js index e93f8a1357..19c57d2ff2 100644 --- a/tests/fixtures/brocfile-tests/custom-ember-env/config/environment.js +++ b/tests/fixtures/brocfile-tests/custom-ember-env/config/environment.js @@ -1,6 +1,7 @@ module.exports = function() { return { modulePrefix: 'some-cool-app', + rootURL: '/', EmberENV: { asdflkmawejf: ';jlnu3yr23' }, diff --git a/tests/fixtures/brocfile-tests/custom-ember-env/config/targets.js b/tests/fixtures/brocfile-tests/custom-ember-env/config/targets.js new file mode 100644 index 0000000000..b55f37e561 --- /dev/null +++ b/tests/fixtures/brocfile-tests/custom-ember-env/config/targets.js @@ -0,0 +1,10 @@ +const browsers = [ + 'last 1 Chrome versions', + 'last 1 Firefox versions', + 'last 1 Safari versions', +]; + +module.exports = { + browsers, + node: 'current', +}; diff --git a/tests/fixtures/brocfile-tests/custom-environment-config/config/environment.js b/tests/fixtures/brocfile-tests/custom-environment-config/config/environment.js index 8b9b53f050..2eefbf4eeb 100644 --- a/tests/fixtures/brocfile-tests/custom-environment-config/config/environment.js +++ b/tests/fixtures/brocfile-tests/custom-environment-config/config/environment.js @@ -2,7 +2,7 @@ module.exports = function() { return { modulePrefix: 'some-cool-app', fileUsed: 'config/environment.js', - baseURL: '/', + rootURL: '/', locationType: 'auto', }; }; diff --git a/tests/fixtures/brocfile-tests/custom-environment-config/config/something-else.js b/tests/fixtures/brocfile-tests/custom-environment-config/config/something-else.js index 2494f79005..5b444a9174 100644 --- a/tests/fixtures/brocfile-tests/custom-environment-config/config/something-else.js +++ b/tests/fixtures/brocfile-tests/custom-environment-config/config/something-else.js @@ -2,7 +2,7 @@ module.exports = function() { return { modulePrefix: 'some-cool-app', fileUsed: 'config/something-else.js', - baseURL: '/', + rootURL: '/', locationType: 'auto', APP: { autoboot: false diff --git a/tests/fixtures/brocfile-tests/custom-environment-config/ember-cli-build.js b/tests/fixtures/brocfile-tests/custom-environment-config/ember-cli-build.js index 85f5e30644..2f439b757e 100644 --- a/tests/fixtures/brocfile-tests/custom-environment-config/ember-cli-build.js +++ b/tests/fixtures/brocfile-tests/custom-environment-config/ember-cli-build.js @@ -1,9 +1,19 @@ const EmberApp = require('ember-cli/lib/broccoli/ember-app'); +const { isExperimentEnabled } = require('ember-cli/lib/experiments'); -module.exports = function(defaults) { +module.exports = function (defaults) { var app = new EmberApp(defaults, { configPath: 'config/something-else' }); + if (isExperimentEnabled('EMBROIDER')) { + const { Webpack } = require('@embroider/webpack'); + return require('@embroider/compat').compatBuild(app, Webpack, { + skipBabel: [{ + package: 'qunit' + }] + }); + } + return app.toTree(); }; diff --git a/tests/fixtures/brocfile-tests/custom-output-paths/ember-cli-build.js b/tests/fixtures/brocfile-tests/custom-output-paths/ember-cli-build.js index 92dabd3c69..190b32c3d6 100644 --- a/tests/fixtures/brocfile-tests/custom-output-paths/ember-cli-build.js +++ b/tests/fixtures/brocfile-tests/custom-output-paths/ember-cli-build.js @@ -1,4 +1,5 @@ const EmberApp = require('ember-cli/lib/broccoli/ember-app'); +const { isExperimentEnabled } = require('ember-cli/lib/experiments'); module.exports = function (defaults) { var app = new EmberApp(defaults, { @@ -25,5 +26,10 @@ module.exports = function (defaults) { } }); + if (isExperimentEnabled('EMBROIDER')) { + const { Webpack } = require('@embroider/webpack'); + return require('@embroider/compat').compatBuild(app, Webpack); + } + return app.toTree(); }; diff --git a/tests/fixtures/brocfile-tests/default-development/tests/integration/app-boots-test.js b/tests/fixtures/brocfile-tests/default-development/tests/integration/app-boots-test.js index 4de7473168..9534272ff3 100644 --- a/tests/fixtures/brocfile-tests/default-development/tests/integration/app-boots-test.js +++ b/tests/fixtures/brocfile-tests/default-development/tests/integration/app-boots-test.js @@ -6,7 +6,7 @@ import { module, test } from 'qunit'; module('default-development - Integration', function(hook) { setupApplicationTest(hooks); - test('renders properly', async function(assert) { + test('renders properly', async function (assert) { await visit('/'); var elements = this.element.querySelectorAll('.ember-view'); diff --git a/tests/fixtures/brocfile-tests/jshint-addon/lib/ember-random-thing/index.js b/tests/fixtures/brocfile-tests/jshint-addon/lib/ember-random-thing/index.js index e54cf2e76d..bd29a4e196 100644 --- a/tests/fixtures/brocfile-tests/jshint-addon/lib/ember-random-thing/index.js +++ b/tests/fixtures/brocfile-tests/jshint-addon/lib/ember-random-thing/index.js @@ -1,5 +1,5 @@ 'use strict'; module.exports = { - name: require('./package').name + name: require('./package').name, } diff --git a/tests/fixtures/brocfile-tests/multiple-css-files-mu/src/ui/styles/app.css b/tests/fixtures/brocfile-tests/multiple-css-files-mu/src/ui/styles/app.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/brocfile-tests/multiple-css-files-mu/src/ui/styles/other.css b/tests/fixtures/brocfile-tests/multiple-css-files-mu/src/ui/styles/other.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/brocfile-tests/multiple-sass-files-mu/ember-cli-build.js b/tests/fixtures/brocfile-tests/multiple-sass-files-mu/ember-cli-build.js deleted file mode 100644 index b49e140169..0000000000 --- a/tests/fixtures/brocfile-tests/multiple-sass-files-mu/ember-cli-build.js +++ /dev/null @@ -1,10 +0,0 @@ -const EmberApp = require('ember-cli/lib/broccoli/ember-app'); - -module.exports = function(defaults) { - var app = new EmberApp(defaults, { - name: require('./package.json').name, - outputPaths: { app: { css: { 'main': '/assets/main.css', 'theme/a': '/assets/theme/a.css' } } } - }); - - return app.toTree(); -}; diff --git a/tests/fixtures/brocfile-tests/multiple-sass-files-mu/node_modules/broccoli-sass/index.js b/tests/fixtures/brocfile-tests/multiple-sass-files-mu/node_modules/broccoli-sass/index.js deleted file mode 100644 index 20d9dd22ec..0000000000 --- a/tests/fixtures/brocfile-tests/multiple-sass-files-mu/node_modules/broccoli-sass/index.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict'; - -const fs = require('fs'); -const path = require('path'); -const Plugin = require('broccoli-plugin'); - -function copyPreserveSync (src, dest) { - var srcStats = fs.statSync(src); - if (srcStats.isFile()) { - var destDir = path.dirname(dest); - var dirs = []; - while (destDir && !fs.existsSync(destDir)) { - dirs.unshift(destDir); - destDir = path.dirname(destDir); - } - dirs.forEach(function (dir) { - fs.mkdirSync(dir); - }); - var content = fs.readFileSync(src); - fs.writeFileSync(dest, content, { flag: 'wx' }); - fs.utimesSync(dest, srcStats.atime, srcStats.mtime); - } else { - throw new Error('Unexpected file type for ' + src); - } -} - -module.exports = SassCompiler; -SassCompiler.prototype = Object.create(Plugin.prototype); -SassCompiler.prototype.constructor = SassCompiler; -function SassCompiler (inputNodes, inputFile, outputFile, options) { - if (!(this instanceof SassCompiler)) return new SassCompiler(inputNodes, inputFile, outputFile, options); - Plugin.call(this, inputNodes); - this.inputFile = inputFile; - this.outputFile = outputFile; -} - -SassCompiler.prototype.build = function () { - copyPreserveSync( - path.join(this.inputPaths[0], this.inputFile), - path.join(this.outputPath, this.outputFile)); -}; diff --git a/tests/fixtures/brocfile-tests/multiple-sass-files-mu/node_modules/broccoli-sass/package.json b/tests/fixtures/brocfile-tests/multiple-sass-files-mu/node_modules/broccoli-sass/package.json deleted file mode 100644 index 0bf9181a28..0000000000 --- a/tests/fixtures/brocfile-tests/multiple-sass-files-mu/node_modules/broccoli-sass/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "broccoli-sass", - "private": true, - "version": "1.0.0" -} diff --git a/tests/fixtures/brocfile-tests/multiple-sass-files-mu/node_modules/ember-cli-sass/index.js b/tests/fixtures/brocfile-tests/multiple-sass-files-mu/node_modules/ember-cli-sass/index.js deleted file mode 100644 index 0af2c4cf95..0000000000 --- a/tests/fixtures/brocfile-tests/multiple-sass-files-mu/node_modules/ember-cli-sass/index.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -const path = require('path'); -const mergeTrees = require('broccoli-merge-trees'); -const SassCompiler = require('../broccoli-sass'); - -function SASSPlugin() { - this.name = 'ember-cli-sass'; - this.ext = ['scss', 'sass']; -} - -SASSPlugin.prototype.toTree = function(tree, inputPath, outputPath, inputOptions) { - var options = inputOptions; - - var inputTrees = [tree]; - if (options.includePaths) { - inputTrees = inputTrees.concat(options.includePaths); - } - - var ext = options.extension || 'scss'; - var paths = options.outputPaths; - var trees = Object.keys(paths).map(function(file) { - var input = path.join(inputPath, file + '.' + ext); - var output = paths[file]; - return new SassCompiler(inputTrees, input, output, options); - }); - - return mergeTrees(trees); -}; - -module.exports = { - name: require('./package').name, - - setupPreprocessorRegistry(type, registry) { - registry.add('css', new SASSPlugin()); - }, -}; diff --git a/tests/fixtures/brocfile-tests/multiple-sass-files-mu/node_modules/ember-cli-sass/package.json b/tests/fixtures/brocfile-tests/multiple-sass-files-mu/node_modules/ember-cli-sass/package.json deleted file mode 100644 index 4076438315..0000000000 --- a/tests/fixtures/brocfile-tests/multiple-sass-files-mu/node_modules/ember-cli-sass/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "ember-cli-sass", - "private": true, - "version": "1.0.0", - "main": "index.js", - "keywords": [ - "ember-addon" - ], - "dependencies": { - "broccoli-sass": "latest" - } -} diff --git a/tests/fixtures/brocfile-tests/multiple-sass-files-mu/src/ui/styles/app.scss b/tests/fixtures/brocfile-tests/multiple-sass-files-mu/src/ui/styles/app.scss deleted file mode 100644 index 56af6df5a4..0000000000 --- a/tests/fixtures/brocfile-tests/multiple-sass-files-mu/src/ui/styles/app.scss +++ /dev/null @@ -1 +0,0 @@ -body { background: green; } diff --git a/tests/fixtures/brocfile-tests/multiple-sass-files-mu/src/ui/styles/main.scss b/tests/fixtures/brocfile-tests/multiple-sass-files-mu/src/ui/styles/main.scss deleted file mode 100644 index c08d072353..0000000000 --- a/tests/fixtures/brocfile-tests/multiple-sass-files-mu/src/ui/styles/main.scss +++ /dev/null @@ -1 +0,0 @@ -body { background: black; } diff --git a/tests/fixtures/brocfile-tests/multiple-sass-files-mu/src/ui/styles/theme/a.scss b/tests/fixtures/brocfile-tests/multiple-sass-files-mu/src/ui/styles/theme/a.scss deleted file mode 100644 index dab2ff7985..0000000000 --- a/tests/fixtures/brocfile-tests/multiple-sass-files-mu/src/ui/styles/theme/a.scss +++ /dev/null @@ -1 +0,0 @@ -.theme { color: red; } diff --git a/tests/fixtures/brocfile-tests/multiple-sass-files/ember-cli-build.js b/tests/fixtures/brocfile-tests/multiple-sass-files/ember-cli-build.js index b49e140169..bc8e1d4db8 100644 --- a/tests/fixtures/brocfile-tests/multiple-sass-files/ember-cli-build.js +++ b/tests/fixtures/brocfile-tests/multiple-sass-files/ember-cli-build.js @@ -1,10 +1,16 @@ const EmberApp = require('ember-cli/lib/broccoli/ember-app'); +const { isExperimentEnabled } = require('ember-cli/lib/experiments'); -module.exports = function(defaults) { +module.exports = function (defaults) { var app = new EmberApp(defaults, { name: require('./package.json').name, outputPaths: { app: { css: { 'main': '/assets/main.css', 'theme/a': '/assets/theme/a.css' } } } }); + if (isExperimentEnabled('EMBROIDER')) { + const { Webpack } = require('@embroider/webpack'); + return require('@embroider/compat').compatBuild(app, Webpack); + } + return app.toTree(); }; diff --git a/tests/fixtures/brocfile-tests/pods-templates/tests/integration/pods-template-test.js b/tests/fixtures/brocfile-tests/pods-templates/tests/integration/pods-template-test.js index 5169799ac1..442704ee90 100644 --- a/tests/fixtures/brocfile-tests/pods-templates/tests/integration/pods-template-test.js +++ b/tests/fixtures/brocfile-tests/pods-templates/tests/integration/pods-template-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; module('pods based templates', function(hooks) { setupApplicationTest(hooks); - test('the application boots properly with pods based templates', async function(assert) { + test('the application boots properly with pods based templates', async function (assert) { assert.expect(1); await visit('/'); diff --git a/tests/fixtures/brocfile-tests/pods-with-prefix-templates/app/app.js b/tests/fixtures/brocfile-tests/pods-with-prefix-templates/app/app.js index 886c8976c2..07328a0e65 100644 --- a/tests/fixtures/brocfile-tests/pods-with-prefix-templates/app/app.js +++ b/tests/fixtures/brocfile-tests/pods-with-prefix-templates/app/app.js @@ -2,12 +2,10 @@ import Application from '@ember/application'; import Resolver from 'ember-resolver'; import loadInitializers from 'ember-load-initializers'; -const App = Application.extend({ - modulePrefix: 'some-cool-app', - podModulePrefix: 'some-cool-app/pods', - Resolver: Resolver -}); +export default class App extends Application { + modulePrefix = 'some-cool-app'; + podModulePrefix = 'some-cool-app/pods'; + Resolver = Resolver; +} loadInitializers(App, 'some-cool-app'); - -export default App; diff --git a/tests/fixtures/brocfile-tests/pods-with-prefix-templates/tests/integration/pods-template-test.js b/tests/fixtures/brocfile-tests/pods-with-prefix-templates/tests/integration/pods-template-test.js index 50a4e4adb6..8185900d00 100644 --- a/tests/fixtures/brocfile-tests/pods-with-prefix-templates/tests/integration/pods-template-test.js +++ b/tests/fixtures/brocfile-tests/pods-with-prefix-templates/tests/integration/pods-template-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; module('pods based templates', function(hooks) { setupApplicationTest(hooks); - test('the application boots properly with pods based templates with a podModulePrefix set', async function(assert) { + test('the application boots properly with pods based templates with a podModulePrefix set', async function (assert) { assert.expect(1); await visit('/'); diff --git a/tests/fixtures/brocfile-tests/query/app/app.js b/tests/fixtures/brocfile-tests/query/app/app.js index 0bf285d84d..9417e1509d 100644 --- a/tests/fixtures/brocfile-tests/query/app/app.js +++ b/tests/fixtures/brocfile-tests/query/app/app.js @@ -2,12 +2,10 @@ import Application from '@ember/application'; import Resolver from 'ember-resolver'; import loadInitializers from 'ember-load-initializers'; -const App = Application.extend({ - modulePrefix: 'query', - podModulePrefix: 'app/pods', - Resolver: Resolver -}); +export default class App extends Application { + modulePrefix = 'query'; + podModulePrefix = 'app/pods'; + Resolver = Resolver; +} loadInitializers(App, 'query'); - -export default App; diff --git a/tests/fixtures/brocfile-tests/query/package.json b/tests/fixtures/brocfile-tests/query/package.json index 650ce9123a..443fa3735f 100644 --- a/tests/fixtures/brocfile-tests/query/package.json +++ b/tests/fixtures/brocfile-tests/query/package.json @@ -3,7 +3,7 @@ "dependencies": { "ember-cli": "*", "ember-cli-htmlbars": "^3.0.0", - "ember-resolver": "^5.0.1", + "ember-resolver": "^7.0.0", "loader.js": "latest" } } diff --git a/tests/fixtures/help/foo.txt b/tests/fixtures/help/foo.txt index b80b7d46b2..cffb99fc74 100644 --- a/tests/fixtures/help/foo.txt +++ b/tests/fixtures/help/foo.txt @@ -1,7 +1,7 @@ Requested ember-cli commands: -ember foo \u001b[33m\u001b[39m \u001b[36m\u001b[39m +ember foo   Initializes the warp drive. - \u001b[36m--dry-run\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -d\u001b[39m + --dry-run (Boolean) (Default: false) + aliases: -d diff --git a/tests/fixtures/help/generate-blueprint.txt b/tests/fixtures/help/generate-blueprint.txt index cd34778deb..f0337babbc 100644 --- a/tests/fixtures/help/generate-blueprint.txt +++ b/tests/fixtures/help/generate-blueprint.txt @@ -15,6 +15,7 @@ ember generate \u001b[33m\u001b[39m \u001b[36m\u001b[39m \u001b[90maliases: -dum, -id\u001b[39m \u001b[36m--in-repo-addon\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m \u001b[90maliases: --in-repo , -ir \u001b[39m + \u001b[36m--lint-fix\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: true)\u001b[39m \u001b[36m--in\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project. blueprint \u001b[33m\u001b[39m diff --git a/tests/fixtures/help/generate-with-addon.txt b/tests/fixtures/help/generate-with-addon.txt index 550f599ae2..79380f5f64 100644 --- a/tests/fixtures/help/generate-with-addon.txt +++ b/tests/fixtures/help/generate-with-addon.txt @@ -1,58 +1,53 @@ Requested ember-cli commands: -ember generate \u001b[33m\u001b[39m \u001b[36m\u001b[39m +ember generate   Generates new code from blueprints. - \u001b[90maliases: g\u001b[39m - \u001b[36m--dry-run\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -d\u001b[39m - \u001b[36m--verbose\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -v\u001b[39m - \u001b[36m--pod\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -p, -pods\u001b[39m - \u001b[36m--classic\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -c\u001b[39m - \u001b[36m--dummy\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -dum, -id\u001b[39m - \u001b[36m--in-repo-addon\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m - \u001b[90maliases: --in-repo , -ir \u001b[39m - \u001b[36m--in\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project. + aliases: g + --dry-run (Boolean) (Default: false) + aliases: -d + --verbose (Boolean) (Default: false) + aliases: -v + --pod (Boolean) (Default: false) + aliases: -p, -pods + --classic (Boolean) (Default: false) + aliases: -c + --dummy (Boolean) (Default: false) + aliases: -dum, -id + --in-repo-addon (String) (Default: null) + aliases: --in-repo , -ir  + --lint-fix (Boolean) (Default: true) + --in (String) (Default: null) Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project. Available blueprints: fixtures: - basic \u001b[33m\u001b[39m - \u001b[90mA basic blueprint\u001b[39m - basic_2 \u001b[33m\u001b[39m - \u001b[90mAnother basic blueprint\u001b[39m - exporting-object \u001b[33m\u001b[39m - \u001b[90mA blueprint that exports an object\u001b[39m - with-templating \u001b[33m\u001b[39m - \u001b[90mA blueprint with templating\u001b[39m + basic  + A basic blueprint + basic_2  + Another basic blueprint + exporting-object  + A blueprint that exports an object + with-templating  + A blueprint with templating ember-cli: - addon \u001b[33m\u001b[39m - \u001b[90mThe default blueprint for ember-cli addons.\u001b[39m - addon-import \u001b[33m\u001b[39m - \u001b[90mGenerates an import wrapper.\u001b[39m - app \u001b[33m\u001b[39m - \u001b[90mThe default blueprint for ember-cli projects.\u001b[39m - blueprint \u001b[33m\u001b[39m - \u001b[90mGenerates a blueprint and definition.\u001b[39m - http-mock \u001b[33m\u001b[39m - \u001b[90mGenerates a mock api endpoint in /api prefix.\u001b[39m - http-proxy \u001b[33m \u001b[39m - \u001b[90mGenerates a relative proxy to another server.\u001b[39m - in-repo-addon \u001b[33m\u001b[39m - \u001b[90mThe blueprint for addon in repo ember-cli addons.\u001b[39m - lib \u001b[33m\u001b[39m - \u001b[90mGenerates a lib directory for in-repo addons.\u001b[39m - module-unification-addon \u001b[33m\u001b[39m - \u001b[90mGenerates an Ember addon with a module unification layout.\u001b[39m - module-unification-app \u001b[33m\u001b[39m - \u001b[90mGenerates an Ember application with a module unification layout.\u001b[39m - packages \u001b[33m\u001b[39m - \u001b[90mGenerates a packages directory for module unification in-repo addons.\u001b[39m - server \u001b[33m\u001b[39m - \u001b[90mGenerates a server directory for mocks and proxies.\u001b[39m - vendor-shim \u001b[33m\u001b[39m - \u001b[90mGenerates an ES6 module shim for global libraries.\u001b[39m + addon  + The default blueprint for ember-cli addons. + addon-import  + Generates an import wrapper. + app  + The default blueprint for ember-cli projects. + blueprint  + Generates a blueprint and definition. + http-mock  + Generates a mock api endpoint in /api prefix. + http-proxy   + Generates a relative proxy to another server. + in-repo-addon  + The blueprint for addon in repo ember-cli addons. + lib  + Generates a lib directory for in-repo addons. + server  + Generates a server directory for mocks and proxies. + vendor-shim  + Generates an ES6 module shim for global libraries. diff --git a/tests/fixtures/help/generate.txt b/tests/fixtures/help/generate.txt index 70600d79f1..43e22db6ff 100644 --- a/tests/fixtures/help/generate.txt +++ b/tests/fixtures/help/generate.txt @@ -17,12 +17,6 @@ \u001b[90mThe blueprint for addon in repo ember-cli addons.\u001b[39m lib \u001b[33m\u001b[39m \u001b[90mGenerates a lib directory for in-repo addons.\u001b[39m - module-unification-addon \u001b[33m\u001b[39m - \u001b[90mGenerates an Ember addon with a module unification layout.\u001b[39m - module-unification-app \u001b[33m\u001b[39m - \u001b[90mGenerates an Ember application with a module unification layout.\u001b[39m - packages \u001b[33m\u001b[39m - \u001b[90mGenerates a packages directory for module unification in-repo addons.\u001b[39m server \u001b[33m\u001b[39m \u001b[90mGenerates a server directory for mocks and proxies.\u001b[39m vendor-shim \u001b[33m\u001b[39m diff --git a/tests/fixtures/help/help-with-addon.txt b/tests/fixtures/help/help-with-addon.txt index 1f10bbd5a3..a2e6f46d2b 100644 --- a/tests/fixtures/help/help-with-addon.txt +++ b/tests/fixtures/help/help-with-addon.txt @@ -1,211 +1,218 @@ -Usage: ember \u001b[33m\u001b[39m +Usage: ember  Available commands in ember-cli: -ember addon \u001b[33m\u001b[39m \u001b[36m\u001b[39m +ember addon   Generates a new folder structure for building an addon, complete with test harness. - \u001b[36m--dry-run\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -d\u001b[39m - \u001b[36m--verbose\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -v\u001b[39m - \u001b[36m--blueprint\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: addon)\u001b[39m - \u001b[90maliases: -b \u001b[39m - \u001b[36m--skip-npm\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -sn\u001b[39m - \u001b[36m--skip-bower\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -sb\u001b[39m - \u001b[36m--skip-git\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -sg\u001b[39m - \u001b[36m--yarn\u001b[39m \u001b[36m(Boolean)\u001b[39m - \u001b[36m--directory\u001b[39m \u001b[36m(String)\u001b[39m - \u001b[90maliases: -dir \u001b[39m - -ember asset-sizes \u001b[36m\u001b[39m + --dry-run (Boolean) (Default: false) + aliases: -d + --verbose (Boolean) (Default: false) + aliases: -v + --blueprint (String) (Default: addon) + aliases: -b  + --skip-npm (Boolean) (Default: false) + aliases: -sn + --skip-bower (Boolean) (Default: false) + aliases: -sb + --skip-git (Boolean) (Default: false) + aliases: -sg + --yarn (Boolean) + --directory (String) + aliases: -dir  + --lang (String) Sets the base human language of the addon's own test application via index.html + +ember asset-sizes  Shows the sizes of your asset files. - \u001b[36m--output-path\u001b[39m \u001b[36m(Path)\u001b[39m \u001b[36m(Default: dist/)\u001b[39m - \u001b[90maliases: -o \u001b[39m - \u001b[36m--json\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m + --output-path (Path) (Default: dist/) + aliases: -o  + --json (Boolean) (Default: false) -ember build \u001b[36m\u001b[39m +ember build  Builds your app and places it into the output path (dist/ by default). - \u001b[90maliases: b\u001b[39m - \u001b[36m--environment\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: development)\u001b[39m Possible values are "development", "production", and "test". - \u001b[90maliases: -e , -dev (--environment=development), -prod (--environment=production)\u001b[39m - \u001b[36m--output-path\u001b[39m \u001b[36m(Path)\u001b[39m \u001b[36m(Default: dist/)\u001b[39m - \u001b[90maliases: -o \u001b[39m - \u001b[36m--watch\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -w\u001b[39m - \u001b[36m--watcher\u001b[39m \u001b[36m(String)\u001b[39m - \u001b[36m--suppress-sizes\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - -ember destroy \u001b[33m\u001b[39m \u001b[36m\u001b[39m + aliases: b + --environment (String) (Default: development) Possible values are "development", "production", and "test". + aliases: -e , -dev (--environment=development), -prod (--environment=production) + --output-path (Path) (Default: dist/) + aliases: -o  + --watch (Boolean) (Default: false) + aliases: -w + --watcher (String) + --suppress-sizes (Boolean) (Default: false) + +ember destroy   Destroys code generated by `generate` command. - \u001b[90maliases: d\u001b[39m - \u001b[36m--dry-run\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -d\u001b[39m - \u001b[36m--verbose\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -v\u001b[39m - \u001b[36m--pod\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -p, -pods\u001b[39m - \u001b[36m--classic\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -c\u001b[39m - \u001b[36m--dummy\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -dum, -id\u001b[39m - \u001b[36m--in-repo-addon\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m - \u001b[90maliases: --in-repo , -ir \u001b[39m - \u001b[36m--in\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project. - -ember generate \u001b[33m\u001b[39m \u001b[36m\u001b[39m + aliases: d + --dry-run (Boolean) (Default: false) + aliases: -d + --verbose (Boolean) (Default: false) + aliases: -v + --pod (Boolean) (Default: false) + aliases: -p, -pods + --classic (Boolean) (Default: false) + aliases: -c + --dummy (Boolean) (Default: false) + aliases: -dum, -id + --in-repo-addon (String) (Default: null) + aliases: --in-repo , -ir  + --in (String) (Default: null) Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project. + +ember generate   Generates new code from blueprints. - \u001b[90maliases: g\u001b[39m - \u001b[36m--dry-run\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -d\u001b[39m - \u001b[36m--verbose\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -v\u001b[39m - \u001b[36m--pod\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -p, -pods\u001b[39m - \u001b[36m--classic\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -c\u001b[39m - \u001b[36m--dummy\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -dum, -id\u001b[39m - \u001b[36m--in-repo-addon\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m - \u001b[90maliases: --in-repo , -ir \u001b[39m - \u001b[36m--in\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project. - -ember help \u001b[33m\u001b[39m \u001b[36m\u001b[39m + aliases: g + --dry-run (Boolean) (Default: false) + aliases: -d + --verbose (Boolean) (Default: false) + aliases: -v + --pod (Boolean) (Default: false) + aliases: -p, -pods + --classic (Boolean) (Default: false) + aliases: -c + --dummy (Boolean) (Default: false) + aliases: -dum, -id + --in-repo-addon (String) (Default: null) + aliases: --in-repo , -ir  + --lint-fix (Boolean) (Default: true) + --in (String) (Default: null) Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project. + +ember help   Outputs the usage instructions for all commands or the provided command - \u001b[90maliases: h, --help, -h\u001b[39m - \u001b[36m--verbose\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -v\u001b[39m - \u001b[36m--json\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - -ember init \u001b[33m\u001b[39m \u001b[36m\u001b[39m - Creates a new ember-cli project in the current folder. - \u001b[36m--dry-run\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -d\u001b[39m - \u001b[36m--verbose\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -v\u001b[39m - \u001b[36m--blueprint\u001b[39m \u001b[36m(String)\u001b[39m - \u001b[90maliases: -b \u001b[39m - \u001b[36m--skip-npm\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -sn\u001b[39m - \u001b[36m--skip-bower\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -sb\u001b[39m - \u001b[36m--welcome\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: true)\u001b[39m Installs and uses {{ember-welcome-page}}. Use --no-welcome to skip it. - \u001b[36m--yarn\u001b[39m \u001b[36m(Boolean)\u001b[39m - \u001b[36m--name\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: "")\u001b[39m - \u001b[90maliases: -n \u001b[39m - -ember install \u001b[33m\u001b[39m \u001b[36m\u001b[39m + aliases: h, --help, -h + --verbose (Boolean) (Default: false) + aliases: -v + --json (Boolean) (Default: false) + +ember init   + Reinitializes a new ember-cli project in the current folder. + --dry-run (Boolean) (Default: false) + aliases: -d + --verbose (Boolean) (Default: false) + aliases: -v + --blueprint (String) + aliases: -b  + --skip-npm (Boolean) (Default: false) + aliases: -sn + --skip-bower (Boolean) (Default: false) + aliases: -sb + --lint-fix (Boolean) (Default: true) + --welcome (Boolean) (Default: true) Installs and uses {{ember-welcome-page}}. Use --no-welcome to skip it. + --yarn (Boolean) + --name (String) (Default: "") + aliases: -n  + --lang (String) Sets the base human language of the application via index.html + --embroider (Boolean) (Default: false) Enables the build system to use Embroider + +ember install   Installs an ember-cli addon from npm. - \u001b[90maliases: i\u001b[39m - \u001b[36m--save\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -S\u001b[39m - \u001b[36m--save-dev\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: true)\u001b[39m - \u001b[90maliases: -D\u001b[39m - \u001b[36m--save-exact\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -E, --exact\u001b[39m - \u001b[36m--yarn\u001b[39m \u001b[36m(Boolean)\u001b[39m Use --yarn to enforce yarn usage, or --no-yarn to enforce npm - -ember new \u001b[33m\u001b[39m \u001b[36m\u001b[39m - Creates a new directory and runs \u001b[32member init\u001b[39m in it. - \u001b[36m--dry-run\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -d\u001b[39m - \u001b[36m--verbose\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -v\u001b[39m - \u001b[36m--blueprint\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: app)\u001b[39m - \u001b[90maliases: -b \u001b[39m - \u001b[36m--skip-npm\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -sn\u001b[39m - \u001b[36m--skip-bower\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -sb\u001b[39m - \u001b[36m--skip-git\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -sg\u001b[39m - \u001b[36m--welcome\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: true)\u001b[39m Installs and uses {{ember-welcome-page}}. Use --no-welcome to skip it. - \u001b[36m--yarn\u001b[39m \u001b[36m(Boolean)\u001b[39m - \u001b[36m--directory\u001b[39m \u001b[36m(String)\u001b[39m - \u001b[90maliases: -dir \u001b[39m - -ember serve \u001b[36m\u001b[39m + aliases: i + --save (Boolean) (Default: false) + aliases: -S + --save-dev (Boolean) (Default: true) + aliases: -D + --save-exact (Boolean) (Default: false) + aliases: -E, --exact + --yarn (Boolean) Use --yarn to enforce yarn usage, or --no-yarn to enforce npm + +ember new   + Creates a new directory and runs ember init in it. + --dry-run (Boolean) (Default: false) + aliases: -d + --verbose (Boolean) (Default: false) + aliases: -v + --blueprint (String) (Default: app) + aliases: -b  + --skip-npm (Boolean) (Default: false) + aliases: -sn + --skip-bower (Boolean) (Default: false) + aliases: -sb + --skip-git (Boolean) (Default: false) + aliases: -sg + --welcome (Boolean) (Default: true) Installs and uses {{ember-welcome-page}}. Use --no-welcome to skip it. + --yarn (Boolean) + --directory (String) + aliases: -dir  + --lang (String) Sets the base human language of the application via index.html + --embroider (Boolean) (Default: false) Enables the build system to use Embroider + +ember serve  Builds and serves your app, rebuilding on file changes. - \u001b[90maliases: server, s\u001b[39m - \u001b[36m--port\u001b[39m \u001b[36m(Number)\u001b[39m \u001b[36m(Default: 4200)\u001b[39m To use a port different than 4200. Pass 0 to automatically pick an available port. - \u001b[90maliases: -p \u001b[39m - \u001b[36m--host\u001b[39m \u001b[36m(String)\u001b[39m Listens on all interfaces by default - \u001b[90maliases: -H \u001b[39m - \u001b[36m--proxy\u001b[39m \u001b[36m(String)\u001b[39m - \u001b[90maliases: -pr , -pxy \u001b[39m - \u001b[36m--proxy-in-timeout\u001b[39m \u001b[36m(Number)\u001b[39m \u001b[36m(Default: 120000)\u001b[39m When using --proxy: timeout (in ms) for incoming requests - \u001b[90maliases: -pit \u001b[39m - \u001b[36m--proxy-out-timeout\u001b[39m \u001b[36m(Number)\u001b[39m \u001b[36m(Default: 0)\u001b[39m When using --proxy: timeout (in ms) for outgoing requests - \u001b[90maliases: -pot \u001b[39m - \u001b[36m--secure-proxy\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: true)\u001b[39m Set to false to proxy self-signed SSL certificates - \u001b[90maliases: -spr\u001b[39m - \u001b[36m--transparent-proxy\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: true)\u001b[39m Set to false to omit x-forwarded-* headers when proxying - \u001b[90maliases: --transp\u001b[39m - \u001b[36m--watcher\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: events)\u001b[39m - \u001b[90maliases: -w \u001b[39m - \u001b[36m--live-reload\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: true)\u001b[39m - \u001b[90maliases: -lr\u001b[39m - \u001b[36m--live-reload-host\u001b[39m \u001b[36m(String)\u001b[39m Defaults to host - \u001b[90maliases: -lrh \u001b[39m - \u001b[36m--live-reload-base-url\u001b[39m \u001b[36m(String)\u001b[39m Defaults to baseURL - \u001b[90maliases: -lrbu \u001b[39m - \u001b[36m--live-reload-port\u001b[39m \u001b[36m(Number)\u001b[39m Defaults to same port as ember app - \u001b[90maliases: -lrp \u001b[39m - \u001b[36m--live-reload-prefix\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: _lr)\u001b[39m Default to _lr - \u001b[90maliases: --lrprefix \u001b[39m - \u001b[36m--environment\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: development)\u001b[39m Possible values are "development", "production", and "test". - \u001b[90maliases: -e , -dev (--environment=development), -prod (--environment=production)\u001b[39m - \u001b[36m--output-path\u001b[39m \u001b[36m(Path)\u001b[39m \u001b[36m(Default: dist/)\u001b[39m - \u001b[90maliases: -op , -out \u001b[39m - \u001b[36m--ssl\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m Set to true to configure Ember CLI to serve using SSL. - \u001b[36m--ssl-key\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: ssl/server.key)\u001b[39m Specify the private key to use for SSL. - \u001b[36m--ssl-cert\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: ssl/server.crt)\u001b[39m Specify the certificate to use for SSL. - \u001b[36m--path\u001b[39m \u001b[36m(Path)\u001b[39m Reuse an existing build at given path. - -ember test \u001b[36m\u001b[39m + aliases: server, s + --port (Number) (Default: 4200) To use a port different than 4200. Pass 0 to automatically pick an available port. + aliases: -p  + --host (String) Listens on all interfaces by default + aliases: -H  + --proxy (String) + aliases: -pr , -pxy  + --proxy-in-timeout (Number) (Default: 120000) When using --proxy: timeout (in ms) for incoming requests + aliases: -pit  + --proxy-out-timeout (Number) (Default: 0) When using --proxy: timeout (in ms) for outgoing requests + aliases: -pot  + --secure-proxy (Boolean) (Default: true) Set to false to proxy self-signed SSL certificates + aliases: -spr + --transparent-proxy (Boolean) (Default: true) Set to false to omit x-forwarded-* headers when proxying + aliases: --transp + --watcher (String) (Default: events) + aliases: -w  + --live-reload (Boolean) (Default: true) + aliases: -lr + --live-reload-host (String) Defaults to host + aliases: -lrh  + --live-reload-base-url (String) Defaults to baseURL + aliases: -lrbu  + --live-reload-port (Number) Defaults to same port as ember app + aliases: -lrp  + --live-reload-prefix (String) (Default: _lr) Default to _lr + aliases: --lrprefix  + --environment (String) (Default: development) Possible values are "development", "production", and "test". + aliases: -e , -dev (--environment=development), -prod (--environment=production) + --output-path (Path) (Default: dist/) + aliases: -op , -out  + --ssl (Boolean) (Default: false) Set to true to configure Ember CLI to serve using SSL. + --ssl-key (String) (Default: ssl/server.key) Specify the private key to use for SSL. + --ssl-cert (String) (Default: ssl/server.crt) Specify the certificate to use for SSL. + --path (Path) Reuse an existing build at given path. + +ember test  Runs your app's test suite. - \u001b[90maliases: t\u001b[39m - \u001b[36m--environment\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: test)\u001b[39m Possible values are "development", "production", and "test". - \u001b[90maliases: -e \u001b[39m - \u001b[36m--config-file\u001b[39m \u001b[36m(String)\u001b[39m - \u001b[90maliases: -c , -cf \u001b[39m - \u001b[36m--server\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -s\u001b[39m - \u001b[36m--host\u001b[39m \u001b[36m(String)\u001b[39m - \u001b[90maliases: -H \u001b[39m - \u001b[36m--test-port\u001b[39m \u001b[36m(Number)\u001b[39m \u001b[36m(Default: 7357)\u001b[39m The test port to use when running tests. Pass 0 to automatically pick an available port - \u001b[90maliases: -tp \u001b[39m - \u001b[36m--filter\u001b[39m \u001b[36m(String)\u001b[39m A string to filter tests to run - \u001b[90maliases: -f \u001b[39m - \u001b[36m--module\u001b[39m \u001b[36m(String)\u001b[39m The name of a test module to run - \u001b[90maliases: -m \u001b[39m - \u001b[36m--watcher\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: events)\u001b[39m - \u001b[90maliases: -w \u001b[39m - \u001b[36m--launch\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: false)\u001b[39m A comma separated list of browsers to launch for tests. - \u001b[36m--reporter\u001b[39m \u001b[36m(String)\u001b[39m Test reporter to use [tap|dot|xunit] (default: tap) - \u001b[90maliases: -r \u001b[39m - \u001b[36m--silent\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m Suppress any output except for the test report - \u001b[36m--ssl\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m Set to true to configure testem to run the test suite using SSL. - \u001b[36m--ssl-key\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: ssl/server.key)\u001b[39m Specify the private key to use for SSL. - \u001b[36m--ssl-cert\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: ssl/server.crt)\u001b[39m Specify the certificate to use for SSL. - \u001b[36m--testem-debug\u001b[39m \u001b[36m(String)\u001b[39m File to write a debug log from testem - \u001b[36m--test-page\u001b[39m \u001b[36m(String)\u001b[39m Test page to invoke - \u001b[36m--path\u001b[39m \u001b[36m(Path)\u001b[39m Reuse an existing build at given path. - \u001b[36m--query\u001b[39m \u001b[36m(String)\u001b[39m A query string to append to the test page URL. - \u001b[36m--output-path\u001b[39m \u001b[36m(Path)\u001b[39m - \u001b[90maliases: -o \u001b[39m - -ember version \u001b[36m\u001b[39m + aliases: t + --environment (String) (Default: test) Possible values are "development", "production", and "test". + aliases: -e  + --config-file (String) + aliases: -c , -cf  + --server (Boolean) (Default: false) + aliases: -s + --host (String) + aliases: -H  + --test-port (Number) (Default: 7357) The test port to use when running tests. Pass 0 to automatically pick an available port + aliases: -tp  + --filter (String) A string to filter tests to run + aliases: -f  + --module (String) The name of a test module to run + aliases: -m  + --watcher (String) (Default: events) + aliases: -w  + --launch (String) (Default: false) A comma separated list of browsers to launch for tests. + --reporter (String) Test reporter to use [tap|dot|xunit] (default: tap) + aliases: -r  + --silent (Boolean) (Default: false) Suppress any output except for the test report + --ssl (Boolean) (Default: false) Set to true to configure testem to run the test suite using SSL. + --ssl-key (String) (Default: ssl/server.key) Specify the private key to use for SSL. + --ssl-cert (String) (Default: ssl/server.crt) Specify the certificate to use for SSL. + --testem-debug (String) File to write a debug log from testem + --test-page (String) Test page to invoke + --path (Path) Reuse an existing build at given path. + --query (String) A query string to append to the test page URL. + --output-path (Path) + aliases: -o  + +ember version  outputs ember-cli version - \u001b[90maliases: v, --version, -v\u001b[39m - \u001b[36m--verbose\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m + aliases: v, --version, -v + --verbose (Boolean) (Default: false) Available commands from dummy-addon: -ember foo \u001b[33m\u001b[39m \u001b[36m\u001b[39m +ember foo   Initializes the warp drive. - \u001b[36m--dry-run\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -d\u001b[39m + --dry-run (Boolean) (Default: false) + aliases: -d diff --git a/tests/fixtures/help/help.js b/tests/fixtures/help/help.js index 1fb60fd621..5028c78fbf 100644 --- a/tests/fixtures/help/help.js +++ b/tests/fixtures/help/help.js @@ -69,6 +69,12 @@ module.exports = { aliases: ['dir'], key: 'directory', required: false + }, + { + name: 'lang', + key: 'lang', + description: "Sets the base human language of the addon's own test application via index.html", + required: false } ], anonymousOptions: [''] @@ -249,6 +255,12 @@ module.exports = { key: 'inRepoAddon', required: false }, + { + name: 'lint-fix', + default: true, + key: 'lintFix', + required: false + }, { name: 'in', default: null, @@ -317,27 +329,6 @@ module.exports = { anonymousOptions: ['name'], overridden: false }, - { - name: 'module-unification-addon', - description: 'Generates an Ember addon with a module unification layout.', - availableOptions: [], - anonymousOptions: ['name'], - overridden: false - }, - { - name: 'module-unification-app', - description: 'Generates an Ember application with a module unification layout.', - availableOptions: [], - anonymousOptions: ['name'], - overridden: false - }, - { - name: 'packages', - description: 'Generates a packages directory for module unification in-repo addons.', - availableOptions: [], - anonymousOptions: ['name'], - overridden: false - }, { name: 'server', description: 'Generates a server directory for mocks and proxies.', @@ -380,7 +371,7 @@ module.exports = { }, { name: 'init', - description: 'Creates a new ember-cli project in the current folder.', + description: 'Reinitializes a new ember-cli project in the current folder.', aliases: [], works: 'everywhere', availableOptions: [ @@ -418,6 +409,12 @@ module.exports = { key: 'skipBower', required: false }, + { + name: 'lint-fix', + default: true, + key: 'lintFix', + required: false + }, { name: 'welcome', key: 'welcome', @@ -436,6 +433,19 @@ module.exports = { aliases: ['n'], key: 'name', required: false + }, + { + name: 'lang', + key: 'lang', + description: 'Sets the base human language of the application via index.html', + required: false + }, + { + default: false, + key: 'embroider', + name: 'embroider', + description: 'Enables the build system to use Embroider', + required: false, } ], anonymousOptions: [''] @@ -541,6 +551,19 @@ module.exports = { aliases: ['dir'], key: 'directory', required: false + }, + { + name: 'lang', + key: 'lang', + description: 'Sets the base human language of the application via index.html', + required: false + }, + { + default: false, + key: 'embroider', + name: 'embroider', + description: 'Enables the build system to use Embroider', + required: false, } ], anonymousOptions: [''] diff --git a/tests/fixtures/help/help.txt b/tests/fixtures/help/help.txt index 555f5d315e..68ebccf8a2 100644 --- a/tests/fixtures/help/help.txt +++ b/tests/fixtures/help/help.txt @@ -1,204 +1,211 @@ -Usage: ember \u001b[33m\u001b[39m +Usage: ember  Available commands in ember-cli: -ember addon \u001b[33m\u001b[39m \u001b[36m\u001b[39m +ember addon   Generates a new folder structure for building an addon, complete with test harness. - \u001b[36m--dry-run\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -d\u001b[39m - \u001b[36m--verbose\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -v\u001b[39m - \u001b[36m--blueprint\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: addon)\u001b[39m - \u001b[90maliases: -b \u001b[39m - \u001b[36m--skip-npm\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -sn\u001b[39m - \u001b[36m--skip-bower\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -sb\u001b[39m - \u001b[36m--skip-git\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -sg\u001b[39m - \u001b[36m--yarn\u001b[39m \u001b[36m(Boolean)\u001b[39m - \u001b[36m--directory\u001b[39m \u001b[36m(String)\u001b[39m - \u001b[90maliases: -dir \u001b[39m + --dry-run (Boolean) (Default: false) + aliases: -d + --verbose (Boolean) (Default: false) + aliases: -v + --blueprint (String) (Default: addon) + aliases: -b  + --skip-npm (Boolean) (Default: false) + aliases: -sn + --skip-bower (Boolean) (Default: false) + aliases: -sb + --skip-git (Boolean) (Default: false) + aliases: -sg + --yarn (Boolean) + --directory (String) + aliases: -dir  + --lang (String) Sets the base human language of the addon's own test application via index.html -ember asset-sizes \u001b[36m\u001b[39m +ember asset-sizes  Shows the sizes of your asset files. - \u001b[36m--output-path\u001b[39m \u001b[36m(Path)\u001b[39m \u001b[36m(Default: dist/)\u001b[39m - \u001b[90maliases: -o \u001b[39m - \u001b[36m--json\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m + --output-path (Path) (Default: dist/) + aliases: -o  + --json (Boolean) (Default: false) -ember build \u001b[36m\u001b[39m +ember build  Builds your app and places it into the output path (dist/ by default). - \u001b[90maliases: b\u001b[39m - \u001b[36m--environment\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: development)\u001b[39m Possible values are "development", "production", and "test". - \u001b[90maliases: -e , -dev (--environment=development), -prod (--environment=production)\u001b[39m - \u001b[36m--output-path\u001b[39m \u001b[36m(Path)\u001b[39m \u001b[36m(Default: dist/)\u001b[39m - \u001b[90maliases: -o \u001b[39m - \u001b[36m--watch\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -w\u001b[39m - \u001b[36m--watcher\u001b[39m \u001b[36m(String)\u001b[39m - \u001b[36m--suppress-sizes\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m + aliases: b + --environment (String) (Default: development) Possible values are "development", "production", and "test". + aliases: -e , -dev (--environment=development), -prod (--environment=production) + --output-path (Path) (Default: dist/) + aliases: -o  + --watch (Boolean) (Default: false) + aliases: -w + --watcher (String) + --suppress-sizes (Boolean) (Default: false) -ember destroy \u001b[33m\u001b[39m \u001b[36m\u001b[39m +ember destroy   Destroys code generated by `generate` command. - \u001b[90maliases: d\u001b[39m - \u001b[36m--dry-run\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -d\u001b[39m - \u001b[36m--verbose\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -v\u001b[39m - \u001b[36m--pod\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -p, -pods\u001b[39m - \u001b[36m--classic\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -c\u001b[39m - \u001b[36m--dummy\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -dum, -id\u001b[39m - \u001b[36m--in-repo-addon\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m - \u001b[90maliases: --in-repo , -ir \u001b[39m - \u001b[36m--in\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project. + aliases: d + --dry-run (Boolean) (Default: false) + aliases: -d + --verbose (Boolean) (Default: false) + aliases: -v + --pod (Boolean) (Default: false) + aliases: -p, -pods + --classic (Boolean) (Default: false) + aliases: -c + --dummy (Boolean) (Default: false) + aliases: -dum, -id + --in-repo-addon (String) (Default: null) + aliases: --in-repo , -ir  + --in (String) (Default: null) Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project. -ember generate \u001b[33m\u001b[39m \u001b[36m\u001b[39m +ember generate   Generates new code from blueprints. - \u001b[90maliases: g\u001b[39m - \u001b[36m--dry-run\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -d\u001b[39m - \u001b[36m--verbose\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -v\u001b[39m - \u001b[36m--pod\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -p, -pods\u001b[39m - \u001b[36m--classic\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -c\u001b[39m - \u001b[36m--dummy\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -dum, -id\u001b[39m - \u001b[36m--in-repo-addon\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m - \u001b[90maliases: --in-repo , -ir \u001b[39m - \u001b[36m--in\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: null)\u001b[39m Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project. + aliases: g + --dry-run (Boolean) (Default: false) + aliases: -d + --verbose (Boolean) (Default: false) + aliases: -v + --pod (Boolean) (Default: false) + aliases: -p, -pods + --classic (Boolean) (Default: false) + aliases: -c + --dummy (Boolean) (Default: false) + aliases: -dum, -id + --in-repo-addon (String) (Default: null) + aliases: --in-repo , -ir  + --lint-fix (Boolean) (Default: true) + --in (String) (Default: null) Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project. -ember help \u001b[33m\u001b[39m \u001b[36m\u001b[39m +ember help   Outputs the usage instructions for all commands or the provided command - \u001b[90maliases: h, --help, -h\u001b[39m - \u001b[36m--verbose\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -v\u001b[39m - \u001b[36m--json\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m + aliases: h, --help, -h + --verbose (Boolean) (Default: false) + aliases: -v + --json (Boolean) (Default: false) -ember init \u001b[33m\u001b[39m \u001b[36m\u001b[39m - Creates a new ember-cli project in the current folder. - \u001b[36m--dry-run\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -d\u001b[39m - \u001b[36m--verbose\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -v\u001b[39m - \u001b[36m--blueprint\u001b[39m \u001b[36m(String)\u001b[39m - \u001b[90maliases: -b \u001b[39m - \u001b[36m--skip-npm\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -sn\u001b[39m - \u001b[36m--skip-bower\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -sb\u001b[39m - \u001b[36m--welcome\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: true)\u001b[39m Installs and uses {{ember-welcome-page}}. Use --no-welcome to skip it. - \u001b[36m--yarn\u001b[39m \u001b[36m(Boolean)\u001b[39m - \u001b[36m--name\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: "")\u001b[39m - \u001b[90maliases: -n \u001b[39m +ember init   + Reinitializes a new ember-cli project in the current folder. + --dry-run (Boolean) (Default: false) + aliases: -d + --verbose (Boolean) (Default: false) + aliases: -v + --blueprint (String) + aliases: -b  + --skip-npm (Boolean) (Default: false) + aliases: -sn + --skip-bower (Boolean) (Default: false) + aliases: -sb + --lint-fix (Boolean) (Default: true) + --welcome (Boolean) (Default: true) Installs and uses {{ember-welcome-page}}. Use --no-welcome to skip it. + --yarn (Boolean) + --name (String) (Default: "") + aliases: -n  + --lang (String) Sets the base human language of the application via index.html + --embroider (Boolean) (Default: false) Enables the build system to use Embroider -ember install \u001b[33m\u001b[39m \u001b[36m\u001b[39m +ember install   Installs an ember-cli addon from npm. - \u001b[90maliases: i\u001b[39m - \u001b[36m--save\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -S\u001b[39m - \u001b[36m--save-dev\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: true)\u001b[39m - \u001b[90maliases: -D\u001b[39m - \u001b[36m--save-exact\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -E, --exact\u001b[39m - \u001b[36m--yarn\u001b[39m \u001b[36m(Boolean)\u001b[39m Use --yarn to enforce yarn usage, or --no-yarn to enforce npm + aliases: i + --save (Boolean) (Default: false) + aliases: -S + --save-dev (Boolean) (Default: true) + aliases: -D + --save-exact (Boolean) (Default: false) + aliases: -E, --exact + --yarn (Boolean) Use --yarn to enforce yarn usage, or --no-yarn to enforce npm -ember new \u001b[33m\u001b[39m \u001b[36m\u001b[39m - Creates a new directory and runs \u001b[32member init\u001b[39m in it. - \u001b[36m--dry-run\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -d\u001b[39m - \u001b[36m--verbose\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -v\u001b[39m - \u001b[36m--blueprint\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: app)\u001b[39m - \u001b[90maliases: -b \u001b[39m - \u001b[36m--skip-npm\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -sn\u001b[39m - \u001b[36m--skip-bower\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -sb\u001b[39m - \u001b[36m--skip-git\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -sg\u001b[39m - \u001b[36m--welcome\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: true)\u001b[39m Installs and uses {{ember-welcome-page}}. Use --no-welcome to skip it. - \u001b[36m--yarn\u001b[39m \u001b[36m(Boolean)\u001b[39m - \u001b[36m--directory\u001b[39m \u001b[36m(String)\u001b[39m - \u001b[90maliases: -dir \u001b[39m +ember new   + Creates a new directory and runs ember init in it. + --dry-run (Boolean) (Default: false) + aliases: -d + --verbose (Boolean) (Default: false) + aliases: -v + --blueprint (String) (Default: app) + aliases: -b  + --skip-npm (Boolean) (Default: false) + aliases: -sn + --skip-bower (Boolean) (Default: false) + aliases: -sb + --skip-git (Boolean) (Default: false) + aliases: -sg + --welcome (Boolean) (Default: true) Installs and uses {{ember-welcome-page}}. Use --no-welcome to skip it. + --yarn (Boolean) + --directory (String) + aliases: -dir  + --lang (String) Sets the base human language of the application via index.html + --embroider (Boolean) (Default: false) Enables the build system to use Embroider -ember serve \u001b[36m\u001b[39m +ember serve  Builds and serves your app, rebuilding on file changes. - \u001b[90maliases: server, s\u001b[39m - \u001b[36m--port\u001b[39m \u001b[36m(Number)\u001b[39m \u001b[36m(Default: 4200)\u001b[39m To use a port different than 4200. Pass 0 to automatically pick an available port. - \u001b[90maliases: -p \u001b[39m - \u001b[36m--host\u001b[39m \u001b[36m(String)\u001b[39m Listens on all interfaces by default - \u001b[90maliases: -H \u001b[39m - \u001b[36m--proxy\u001b[39m \u001b[36m(String)\u001b[39m - \u001b[90maliases: -pr , -pxy \u001b[39m - \u001b[36m--proxy-in-timeout\u001b[39m \u001b[36m(Number)\u001b[39m \u001b[36m(Default: 120000)\u001b[39m When using --proxy: timeout (in ms) for incoming requests - \u001b[90maliases: -pit \u001b[39m - \u001b[36m--proxy-out-timeout\u001b[39m \u001b[36m(Number)\u001b[39m \u001b[36m(Default: 0)\u001b[39m When using --proxy: timeout (in ms) for outgoing requests - \u001b[90maliases: -pot \u001b[39m - \u001b[36m--secure-proxy\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: true)\u001b[39m Set to false to proxy self-signed SSL certificates - \u001b[90maliases: -spr\u001b[39m - \u001b[36m--transparent-proxy\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: true)\u001b[39m Set to false to omit x-forwarded-* headers when proxying - \u001b[90maliases: --transp\u001b[39m - \u001b[36m--watcher\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: events)\u001b[39m - \u001b[90maliases: -w \u001b[39m - \u001b[36m--live-reload\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: true)\u001b[39m - \u001b[90maliases: -lr\u001b[39m - \u001b[36m--live-reload-host\u001b[39m \u001b[36m(String)\u001b[39m Defaults to host - \u001b[90maliases: -lrh \u001b[39m - \u001b[36m--live-reload-base-url\u001b[39m \u001b[36m(String)\u001b[39m Defaults to baseURL - \u001b[90maliases: -lrbu \u001b[39m - \u001b[36m--live-reload-port\u001b[39m \u001b[36m(Number)\u001b[39m Defaults to same port as ember app - \u001b[90maliases: -lrp \u001b[39m - \u001b[36m--live-reload-prefix\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: _lr)\u001b[39m Default to _lr - \u001b[90maliases: --lrprefix \u001b[39m - \u001b[36m--environment\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: development)\u001b[39m Possible values are "development", "production", and "test". - \u001b[90maliases: -e , -dev (--environment=development), -prod (--environment=production)\u001b[39m - \u001b[36m--output-path\u001b[39m \u001b[36m(Path)\u001b[39m \u001b[36m(Default: dist/)\u001b[39m - \u001b[90maliases: -op , -out \u001b[39m - \u001b[36m--ssl\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m Set to true to configure Ember CLI to serve using SSL. - \u001b[36m--ssl-key\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: ssl/server.key)\u001b[39m Specify the private key to use for SSL. - \u001b[36m--ssl-cert\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: ssl/server.crt)\u001b[39m Specify the certificate to use for SSL. - \u001b[36m--path\u001b[39m \u001b[36m(Path)\u001b[39m Reuse an existing build at given path. + aliases: server, s + --port (Number) (Default: 4200) To use a port different than 4200. Pass 0 to automatically pick an available port. + aliases: -p  + --host (String) Listens on all interfaces by default + aliases: -H  + --proxy (String) + aliases: -pr , -pxy  + --proxy-in-timeout (Number) (Default: 120000) When using --proxy: timeout (in ms) for incoming requests + aliases: -pit  + --proxy-out-timeout (Number) (Default: 0) When using --proxy: timeout (in ms) for outgoing requests + aliases: -pot  + --secure-proxy (Boolean) (Default: true) Set to false to proxy self-signed SSL certificates + aliases: -spr + --transparent-proxy (Boolean) (Default: true) Set to false to omit x-forwarded-* headers when proxying + aliases: --transp + --watcher (String) (Default: events) + aliases: -w  + --live-reload (Boolean) (Default: true) + aliases: -lr + --live-reload-host (String) Defaults to host + aliases: -lrh  + --live-reload-base-url (String) Defaults to baseURL + aliases: -lrbu  + --live-reload-port (Number) Defaults to same port as ember app + aliases: -lrp  + --live-reload-prefix (String) (Default: _lr) Default to _lr + aliases: --lrprefix  + --environment (String) (Default: development) Possible values are "development", "production", and "test". + aliases: -e , -dev (--environment=development), -prod (--environment=production) + --output-path (Path) (Default: dist/) + aliases: -op , -out  + --ssl (Boolean) (Default: false) Set to true to configure Ember CLI to serve using SSL. + --ssl-key (String) (Default: ssl/server.key) Specify the private key to use for SSL. + --ssl-cert (String) (Default: ssl/server.crt) Specify the certificate to use for SSL. + --path (Path) Reuse an existing build at given path. -ember test \u001b[36m\u001b[39m +ember test  Runs your app's test suite. - \u001b[90maliases: t\u001b[39m - \u001b[36m--environment\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: test)\u001b[39m Possible values are "development", "production", and "test". - \u001b[90maliases: -e \u001b[39m - \u001b[36m--config-file\u001b[39m \u001b[36m(String)\u001b[39m - \u001b[90maliases: -c , -cf \u001b[39m - \u001b[36m--server\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m - \u001b[90maliases: -s\u001b[39m - \u001b[36m--host\u001b[39m \u001b[36m(String)\u001b[39m - \u001b[90maliases: -H \u001b[39m - \u001b[36m--test-port\u001b[39m \u001b[36m(Number)\u001b[39m \u001b[36m(Default: 7357)\u001b[39m The test port to use when running tests. Pass 0 to automatically pick an available port - \u001b[90maliases: -tp \u001b[39m - \u001b[36m--filter\u001b[39m \u001b[36m(String)\u001b[39m A string to filter tests to run - \u001b[90maliases: -f \u001b[39m - \u001b[36m--module\u001b[39m \u001b[36m(String)\u001b[39m The name of a test module to run - \u001b[90maliases: -m \u001b[39m - \u001b[36m--watcher\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: events)\u001b[39m - \u001b[90maliases: -w \u001b[39m - \u001b[36m--launch\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: false)\u001b[39m A comma separated list of browsers to launch for tests. - \u001b[36m--reporter\u001b[39m \u001b[36m(String)\u001b[39m Test reporter to use [tap|dot|xunit] (default: tap) - \u001b[90maliases: -r \u001b[39m - \u001b[36m--silent\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m Suppress any output except for the test report - \u001b[36m--ssl\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m Set to true to configure testem to run the test suite using SSL. - \u001b[36m--ssl-key\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: ssl/server.key)\u001b[39m Specify the private key to use for SSL. - \u001b[36m--ssl-cert\u001b[39m \u001b[36m(String)\u001b[39m \u001b[36m(Default: ssl/server.crt)\u001b[39m Specify the certificate to use for SSL. - \u001b[36m--testem-debug\u001b[39m \u001b[36m(String)\u001b[39m File to write a debug log from testem - \u001b[36m--test-page\u001b[39m \u001b[36m(String)\u001b[39m Test page to invoke - \u001b[36m--path\u001b[39m \u001b[36m(Path)\u001b[39m Reuse an existing build at given path. - \u001b[36m--query\u001b[39m \u001b[36m(String)\u001b[39m A query string to append to the test page URL. - \u001b[36m--output-path\u001b[39m \u001b[36m(Path)\u001b[39m - \u001b[90maliases: -o \u001b[39m + aliases: t + --environment (String) (Default: test) Possible values are "development", "production", and "test". + aliases: -e  + --config-file (String) + aliases: -c , -cf  + --server (Boolean) (Default: false) + aliases: -s + --host (String) + aliases: -H  + --test-port (Number) (Default: 7357) The test port to use when running tests. Pass 0 to automatically pick an available port + aliases: -tp  + --filter (String) A string to filter tests to run + aliases: -f  + --module (String) The name of a test module to run + aliases: -m  + --watcher (String) (Default: events) + aliases: -w  + --launch (String) (Default: false) A comma separated list of browsers to launch for tests. + --reporter (String) Test reporter to use [tap|dot|xunit] (default: tap) + aliases: -r  + --silent (Boolean) (Default: false) Suppress any output except for the test report + --ssl (Boolean) (Default: false) Set to true to configure testem to run the test suite using SSL. + --ssl-key (String) (Default: ssl/server.key) Specify the private key to use for SSL. + --ssl-cert (String) (Default: ssl/server.crt) Specify the certificate to use for SSL. + --testem-debug (String) File to write a debug log from testem + --test-page (String) Test page to invoke + --path (Path) Reuse an existing build at given path. + --query (String) A query string to append to the test page URL. + --output-path (Path) + aliases: -o  -ember version \u001b[36m\u001b[39m +ember version  outputs ember-cli version - \u001b[90maliases: v, --version, -v\u001b[39m - \u001b[36m--verbose\u001b[39m \u001b[36m(Boolean)\u001b[39m \u001b[36m(Default: false)\u001b[39m + aliases: v, --version, -v + --verbose (Boolean) (Default: false) diff --git a/tests/fixtures/help/with-addon-blueprints.js b/tests/fixtures/help/with-addon-blueprints.js index f998775d6e..f8aaec5c50 100644 --- a/tests/fixtures/help/with-addon-blueprints.js +++ b/tests/fixtures/help/with-addon-blueprints.js @@ -69,6 +69,12 @@ module.exports = { aliases: ['dir'], key: 'directory', required: false + }, + { + name: 'lang', + key: 'lang', + description: "Sets the base human language of the addon's own test application via index.html", + required: false } ], anonymousOptions: [''] @@ -249,6 +255,12 @@ module.exports = { key: 'inRepoAddon', required: false }, + { + name: 'lint-fix', + default: true, + key: 'lintFix', + required: false + }, { name: 'in', default: null, @@ -349,27 +361,6 @@ module.exports = { anonymousOptions: ['name'], overridden: false }, - { - name: 'module-unification-addon', - description: 'Generates an Ember addon with a module unification layout.', - availableOptions: [], - anonymousOptions: ['name'], - overridden: false - }, - { - name: 'module-unification-app', - description: 'Generates an Ember application with a module unification layout.', - availableOptions: [], - anonymousOptions: ['name'], - overridden: false - }, - { - name: 'packages', - description: 'Generates a packages directory for module unification in-repo addons.', - availableOptions: [], - anonymousOptions: ['name'], - overridden: false - }, { name: 'server', description: 'Generates a server directory for mocks and proxies.', @@ -412,7 +403,7 @@ module.exports = { }, { name: 'init', - description: 'Creates a new ember-cli project in the current folder.', + description: 'Reinitializes a new ember-cli project in the current folder.', aliases: [], works: 'everywhere', availableOptions: [ @@ -450,6 +441,12 @@ module.exports = { key: 'skipBower', required: false }, + { + name: 'lint-fix', + default: true, + key: 'lintFix', + required: false + }, { name: 'welcome', key: 'welcome', @@ -468,6 +465,19 @@ module.exports = { aliases: ['n'], key: 'name', required: false + }, + { + name: 'lang', + key: 'lang', + description: 'Sets the base human language of the application via index.html', + required: false + }, + { + default: false, + key: 'embroider', + name: 'embroider', + description: 'Enables the build system to use Embroider', + required: false } ], anonymousOptions: [''] @@ -573,6 +583,19 @@ module.exports = { aliases: ['dir'], key: 'directory', required: false + }, + { + name: 'lang', + key: 'lang', + description: 'Sets the base human language of the application via index.html', + required: false + }, + { + default: false, + key: 'embroider', + name: 'embroider', + description: 'Enables the build system to use Embroider', + required: false } ], anonymousOptions: [''] diff --git a/tests/fixtures/help/with-addon-commands.js b/tests/fixtures/help/with-addon-commands.js index 4251ef1bda..d12d54cc6a 100644 --- a/tests/fixtures/help/with-addon-commands.js +++ b/tests/fixtures/help/with-addon-commands.js @@ -69,6 +69,12 @@ module.exports = { aliases: ['dir'], key: 'directory', required: false + }, + { + name: 'lang', + key: 'lang', + description: "Sets the base human language of the addon's own test application via index.html", + required: false } ], anonymousOptions: [''] @@ -249,6 +255,12 @@ module.exports = { key: 'inRepoAddon', required: false }, + { + name: 'lint-fix', + default: true, + key: 'lintFix', + required: false + }, { name: 'in', default: null, @@ -317,27 +329,6 @@ module.exports = { anonymousOptions: ['name'], overridden: false }, - { - name: 'module-unification-addon', - description: 'Generates an Ember addon with a module unification layout.', - availableOptions: [], - anonymousOptions: ['name'], - overridden: false - }, - { - name: 'module-unification-app', - description: 'Generates an Ember application with a module unification layout.', - availableOptions: [], - anonymousOptions: ['name'], - overridden: false - }, - { - name: 'packages', - description: 'Generates a packages directory for module unification in-repo addons.', - availableOptions: [], - anonymousOptions: ['name'], - overridden: false - }, { name: 'server', description: 'Generates a server directory for mocks and proxies.', @@ -380,7 +371,7 @@ module.exports = { }, { name: 'init', - description: 'Creates a new ember-cli project in the current folder.', + description: 'Reinitializes a new ember-cli project in the current folder.', aliases: [], works: 'everywhere', availableOptions: [ @@ -418,6 +409,12 @@ module.exports = { key: 'skipBower', required: false }, + { + name: 'lint-fix', + default: true, + key: 'lintFix', + required: false + }, { name: 'welcome', key: 'welcome', @@ -436,6 +433,19 @@ module.exports = { aliases: ['n'], key: 'name', required: false + }, + { + name: 'lang', + key: 'lang', + description: 'Sets the base human language of the application via index.html', + required: false + }, + { + default: false, + key: 'embroider', + name: 'embroider', + description: 'Enables the build system to use Embroider', + required: false } ], anonymousOptions: [''] @@ -541,6 +551,19 @@ module.exports = { aliases: ['dir'], key: 'directory', required: false + }, + { + name: 'lang', + key: 'lang', + description: 'Sets the base human language of the application via index.html', + required: false + }, + { + default: false, + key: 'embroider', + name: 'embroider', + description: 'Enables the build system to use Embroider', + required: false } ], anonymousOptions: [''] diff --git a/tests/fixtures/missing-before-addon/lib/sample-addon/index.js b/tests/fixtures/missing-before-addon/lib/sample-addon/index.js index 149ef39fa1..3a77c77d62 100644 --- a/tests/fixtures/missing-before-addon/lib/sample-addon/index.js +++ b/tests/fixtures/missing-before-addon/lib/sample-addon/index.js @@ -1,3 +1,3 @@ module.exports = { - name: require('./package').name + name: require('./package').name, }; diff --git a/tests/fixtures/module-unification-addon/.eslintrc.js b/tests/fixtures/module-unification-addon/.eslintrc.js deleted file mode 100644 index 657ee6fd51..0000000000 --- a/tests/fixtures/module-unification-addon/.eslintrc.js +++ /dev/null @@ -1,54 +0,0 @@ -module.exports = { - root: true, - parser: 'babel-eslint', - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module' - }, - plugins: [ - 'ember' - ], - extends: [ - 'eslint:recommended', - 'plugin:ember/recommended' - ], - env: { - browser: true - }, - rules: { - }, - overrides: [ - // node files - { - files: [ - '.eslintrc.js', - '.template-lintrc.js', - 'ember-cli-build.js', - 'index.js', - 'testem.js', - 'blueprints/*/index.js', - 'config/**/*.js', - 'tests/dummy/config/**/*.js' - ], - excludedFiles: [ - 'src/**', - 'tests/dummy/app/**' - ], - parserOptions: { - sourceType: 'script' - }, - env: { - browser: false, - node: true - }, - plugins: ['node'], - rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { - // add your custom rules and overrides for node files here - - // this can be removed once the following is fixed - // https://github.com/mysticatea/eslint-plugin-node/issues/77 - 'node/no-unpublished-require': 'off' - }) - } - ] -}; diff --git a/tests/fixtures/module-unification-addon/npm/.editorconfig b/tests/fixtures/module-unification-addon/npm/.editorconfig deleted file mode 100644 index 219985c228..0000000000 --- a/tests/fixtures/module-unification-addon/npm/.editorconfig +++ /dev/null @@ -1,20 +0,0 @@ -# EditorConfig helps developers define and maintain consistent -# coding styles between different editors and IDEs -# editorconfig.org - -root = true - - -[*] -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true -indent_style = space -indent_size = 2 - -[*.hbs] -insert_final_newline = false - -[*.{diff,md}] -trim_trailing_whitespace = false diff --git a/tests/fixtures/module-unification-addon/npm/.ember-cli b/tests/fixtures/module-unification-addon/npm/.ember-cli deleted file mode 100644 index ee64cfed2a..0000000000 --- a/tests/fixtures/module-unification-addon/npm/.ember-cli +++ /dev/null @@ -1,9 +0,0 @@ -{ - /** - Ember CLI sends analytics information by default. The data is completely - anonymous, but there are times when you might want to disable this behavior. - - Setting `disableAnalytics` to true will prevent any data from being sent. - */ - "disableAnalytics": false -} diff --git a/tests/fixtures/module-unification-addon/npm/.eslintignore b/tests/fixtures/module-unification-addon/npm/.eslintignore deleted file mode 100644 index 9f2dd3ba02..0000000000 --- a/tests/fixtures/module-unification-addon/npm/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -!.* diff --git a/tests/fixtures/module-unification-addon/npm/.eslintrc.js b/tests/fixtures/module-unification-addon/npm/.eslintrc.js deleted file mode 100644 index 43c0e36b3c..0000000000 --- a/tests/fixtures/module-unification-addon/npm/.eslintrc.js +++ /dev/null @@ -1,50 +0,0 @@ -module.exports = { - root: true, - parser: 'babel-eslint', - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module' - }, - plugins: [ - 'ember' - ], - extends: [ - 'eslint:recommended', - 'plugin:ember/recommended' - ], - env: { - browser: true - }, - rules: { - }, - overrides: [ - // node files - { - files: [ - '.eslintrc.js', - '.template-lintrc.js', - 'ember-cli-build.js', - 'index.js', - 'testem.js', - 'blueprints/*/index.js', - 'config/**/*.js', - 'tests/dummy/config/**/*.js' - ], - excludedFiles: [ - 'src/**', - 'tests/dummy/app/**' - ], - parserOptions: { - sourceType: 'script' - }, - env: { - browser: false, - node: true - }, - plugins: ['node'], - rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { - // add your custom rules and overrides for node files here - }) - } - ] -}; diff --git a/tests/fixtures/module-unification-addon/npm/.gitignore b/tests/fixtures/module-unification-addon/npm/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/module-unification-addon/npm/.npmignore b/tests/fixtures/module-unification-addon/npm/.npmignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/module-unification-addon/npm/.template-lintrc.js b/tests/fixtures/module-unification-addon/npm/.template-lintrc.js deleted file mode 100644 index 3f204f3fad..0000000000 --- a/tests/fixtures/module-unification-addon/npm/.template-lintrc.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -module.exports = { - extends: 'recommended' -} diff --git a/tests/fixtures/module-unification-addon/npm/.travis.yml b/tests/fixtures/module-unification-addon/npm/.travis.yml deleted file mode 100644 index 1bad18f943..0000000000 --- a/tests/fixtures/module-unification-addon/npm/.travis.yml +++ /dev/null @@ -1,42 +0,0 @@ ---- -language: node_js -node_js: - # we recommend testing addons with the same minimum supported node version as Ember CLI - # so that your addon works for all apps - - "8" - -sudo: false -dist: trusty - -addons: - chrome: stable - -cache: - directories: - - $HOME/.npm - -env: - global: - # See https://git.io/vdao3 for details. - - JOBS=1 - matrix: - # we recommend new addons test the current and previous LTS - # as well as latest stable release (bonus points to beta/canary) - - EMBER_TRY_SCENARIO=ember-lts-3.4 - - EMBER_TRY_SCENARIO=ember-lts-3.8 - - EMBER_TRY_SCENARIO=ember-release - - EMBER_TRY_SCENARIO=ember-beta - - EMBER_TRY_SCENARIO=ember-canary - - EMBER_TRY_SCENARIO=ember-default - -matrix: - fast_finish: true - allow_failures: - - env: EMBER_TRY_SCENARIO=ember-canary - -script: - - npm run lint:hbs - - npm run lint:js - # Usually, it's ok to finish the test scenario without reverting - # to the addon's original dependency state, skipping "cleanup". - - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO --skip-cleanup diff --git a/tests/fixtures/module-unification-addon/npm/.watchmanconfig b/tests/fixtures/module-unification-addon/npm/.watchmanconfig deleted file mode 100644 index e7834e3e4f..0000000000 --- a/tests/fixtures/module-unification-addon/npm/.watchmanconfig +++ /dev/null @@ -1,3 +0,0 @@ -{ - "ignore_dirs": ["tmp", "dist"] -} diff --git a/tests/fixtures/module-unification-addon/npm/LICENSE.md b/tests/fixtures/module-unification-addon/npm/LICENSE.md deleted file mode 100644 index 198f6dacf4..0000000000 --- a/tests/fixtures/module-unification-addon/npm/LICENSE.md +++ /dev/null @@ -1,9 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2018 - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/tests/fixtures/module-unification-addon/npm/config/ember-try.js b/tests/fixtures/module-unification-addon/npm/config/ember-try.js deleted file mode 100644 index 4e1ece175e..0000000000 --- a/tests/fixtures/module-unification-addon/npm/config/ember-try.js +++ /dev/null @@ -1,56 +0,0 @@ -'use strict'; - -const getChannelURL = require('ember-source-channel-url'); - -module.exports = async function() { - return { - scenarios: [ - { - name: 'ember-lts-3.4', - npm: { - devDependencies: { - 'ember-source': '~3.4.0' - } - } - }, - { - name: 'ember-lts-3.8', - npm: { - devDependencies: { - 'ember-source': '~3.8.0' - } - } - }, - { - name: 'ember-release', - npm: { - devDependencies: { - 'ember-source': await getChannelURL('release') - } - } - }, - { - name: 'ember-beta', - npm: { - devDependencies: { - 'ember-source': await getChannelURL('beta') - } - } - }, - { - name: 'ember-canary', - npm: { - devDependencies: { - 'ember-source': await getChannelURL('canary') - } - } - }, - { - name: 'ember-default', - npm: { - devDependencies: {} - } - } - ] - }; -}; diff --git a/tests/fixtures/module-unification-addon/npm/config/environment.js b/tests/fixtures/module-unification-addon/npm/config/environment.js deleted file mode 100644 index 0dfaed4728..0000000000 --- a/tests/fixtures/module-unification-addon/npm/config/environment.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -module.exports = function(/* environment, appConfig */) { - return { }; -}; diff --git a/tests/fixtures/module-unification-addon/npm/ember-cli-build.js b/tests/fixtures/module-unification-addon/npm/ember-cli-build.js deleted file mode 100644 index ba0649dc93..0000000000 --- a/tests/fixtures/module-unification-addon/npm/ember-cli-build.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); - -module.exports = function (defaults) { - let app = new EmberAddon(defaults, { - // Add options here - }); - - /* - This build file specifies the options for the dummy test app of this - addon, located in `/tests/dummy` - This build file does *not* influence how the addon or the app using it - behave. You most likely want to be modifying `./index.js` or app's build file - */ - - return app.toTree(); -}; diff --git a/tests/fixtures/module-unification-addon/npm/package.json b/tests/fixtures/module-unification-addon/npm/package.json deleted file mode 100644 index a2aa2afe72..0000000000 --- a/tests/fixtures/module-unification-addon/npm/package.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "name": "foo", - "version": "0.0.0", - "description": "Generates an Ember addon with a module unification layout.", - "keywords": [ - "ember-addon" - ], - "repository": "", - "license": "MIT", - "author": "", - "directories": { - "doc": "doc", - "test": "tests" - }, - "scripts": { - "build": "ember build", - "lint:hbs": "ember-template-lint .", - "lint:js": "eslint .", - "start": "ember serve", - "test": "ember test", - "test:all": "ember try:each" - }, - "dependencies": { - "ember-cli-babel": "^7.11.0", - "ember-cli-htmlbars": "^3.1.0" - }, - "devDependencies": { - "@ember/optional-features": "^0.7.0", - "babel-eslint": "^10.0.3", - "broccoli-asset-rev": "^3.0.0", - "ember-cli": "<%= emberCLIVersion %>", - "ember-cli-dependency-checker": "^3.1.0", - "ember-cli-eslint": "^5.1.0", - "ember-cli-htmlbars-inline-precompile": "^3.0.0", - "ember-cli-inject-live-reload": "^2.0.1", - "ember-cli-sri": "^2.1.1", - "ember-cli-template-lint": "^1.0.0-beta.3", - "ember-cli-uglify": "^3.0.0", - "ember-disable-prototype-extensions": "^1.1.3", - "ember-export-application-global": "^2.0.0", - "ember-load-initializers": "^2.1.0", - "ember-maybe-import-regenerator": "^0.1.6", - "ember-qunit": "^4.5.1", - "ember-resolver": "^5.2.1", - "ember-source": "<%= emberCanaryVersion %>", - "ember-source-channel-url": "^2.0.1", - "ember-try": "^1.2.1", - "eslint-plugin-ember": "^7.0.0", - "eslint-plugin-node": "^9.2.0", - "loader.js": "^4.7.0", - "qunit-dom": "^0.9.0" - }, - "engines": { - "node": "8.* || >= 10.*" - }, - "ember-addon": { - "configPath": "tests/dummy/config" - } -} diff --git a/tests/fixtures/module-unification-addon/npm/src/.gitkeep b/tests/fixtures/module-unification-addon/npm/src/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/module-unification-addon/npm/testem.js b/tests/fixtures/module-unification-addon/npm/testem.js deleted file mode 100644 index 96589a75d2..0000000000 --- a/tests/fixtures/module-unification-addon/npm/testem.js +++ /dev/null @@ -1,23 +0,0 @@ -module.exports = { - test_page: 'tests/index.html?hidepassed', - disable_watching: true, - launch_in_ci: [ - 'Chrome' - ], - launch_in_dev: [ - 'Chrome' - ], - browser_args: { - Chrome: { - mode: 'ci', - args: [ - // --no-sandbox is needed when running Chrome inside a container - process.env.TRAVIS ? '--no-sandbox' : null, - - '--headless', - '--remote-debugging-port=0', - '--window-size=1440,900' - ].filter(Boolean) - } - } -}; diff --git a/tests/fixtures/module-unification-addon/npm/tests/acceptance/.gitkeep b/tests/fixtures/module-unification-addon/npm/tests/acceptance/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/module-unification-addon/npm/tests/dummy/.gitkeep b/tests/fixtures/module-unification-addon/npm/tests/dummy/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/module-unification-addon/npm/tests/dummy/config/environment.js b/tests/fixtures/module-unification-addon/npm/tests/dummy/config/environment.js deleted file mode 100644 index 9d3feec603..0000000000 --- a/tests/fixtures/module-unification-addon/npm/tests/dummy/config/environment.js +++ /dev/null @@ -1,56 +0,0 @@ -'use strict'; - -module.exports = function(environment) { - let ENV = { - 'ember-resolver': { - features: { - EMBER_RESOLVER_MODULE_UNIFICATION: true - } - }, - modulePrefix: 'dummy', - environment, - rootURL: '/', - locationType: 'auto', - EmberENV: { - FEATURES: { - // Here you can enable experimental features on an ember canary build - // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true - EMBER_MODULE_UNIFICATION: true - }, - EXTEND_PROTOTYPES: { - // Prevent Ember Data from overriding Date.parse. - Date: false - } - }, - - APP: { - // Here you can pass flags/options to your application instance - // when it is created - } - }; - - if (environment === 'development') { - // ENV.APP.LOG_RESOLVER = true; - // ENV.APP.LOG_ACTIVE_GENERATION = true; - // ENV.APP.LOG_TRANSITIONS = true; - // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; - // ENV.APP.LOG_VIEW_LOOKUPS = true; - } - - if (environment === 'test') { - // Testem prefers this... - ENV.locationType = 'none'; - - // keep test console output quieter - ENV.APP.LOG_ACTIVE_GENERATION = false; - ENV.APP.LOG_VIEW_LOOKUPS = false; - - ENV.APP.rootElement = '#ember-testing'; - } - - if (environment === 'production') { - // here you can enable a production-specific feature - } - - return ENV; -}; diff --git a/tests/fixtures/module-unification-addon/npm/tests/dummy/config/targets.js b/tests/fixtures/module-unification-addon/npm/tests/dummy/config/targets.js deleted file mode 100644 index 1a7647d06b..0000000000 --- a/tests/fixtures/module-unification-addon/npm/tests/dummy/config/targets.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - browsers: [ - 'ie 11', - 'last 1 Chrome versions', - 'last 1 Firefox versions', - 'last 1 Safari versions' - ] -}; diff --git a/tests/fixtures/module-unification-addon/npm/tests/dummy/public/robots.txt b/tests/fixtures/module-unification-addon/npm/tests/dummy/public/robots.txt deleted file mode 100644 index f5916452e5..0000000000 --- a/tests/fixtures/module-unification-addon/npm/tests/dummy/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# http://www.robotstxt.org -User-agent: * -Disallow: diff --git a/tests/fixtures/module-unification-addon/npm/tests/dummy/src/main.js b/tests/fixtures/module-unification-addon/npm/tests/dummy/src/main.js deleted file mode 100644 index 84b092ff19..0000000000 --- a/tests/fixtures/module-unification-addon/npm/tests/dummy/src/main.js +++ /dev/null @@ -1,19 +0,0 @@ -import Application from "@ember/application"; -import Resolver from "./resolver"; -import loadInitializers from "ember-load-initializers"; -import config from "../config/environment"; - -const App = Application.extend({ - modulePrefix: config.modulePrefix, - podModulePrefix: config.podModulePrefix, - Resolver -}); - -loadInitializers(App, config.modulePrefix + "/src/init"); - -/* - * This line is added to support initializers in the `app/` directory - */ -loadInitializers(App, config.modulePrefix); - -export default App; diff --git a/tests/fixtures/module-unification-addon/npm/tests/dummy/src/resolver.js b/tests/fixtures/module-unification-addon/npm/tests/dummy/src/resolver.js deleted file mode 100644 index f0b8a1ccea..0000000000 --- a/tests/fixtures/module-unification-addon/npm/tests/dummy/src/resolver.js +++ /dev/null @@ -1,13 +0,0 @@ -import Resolver from 'ember-resolver/resolvers/fallback'; -import buildResolverConfig from 'ember-resolver/ember-config'; -import config from '../config/environment'; - -let moduleConfig = buildResolverConfig(config.modulePrefix); -/* - * If your application has custom types and collections, modify moduleConfig here - * to add support for them. - */ - -export default Resolver.extend({ - config: moduleConfig -}); diff --git a/tests/fixtures/module-unification-addon/npm/tests/dummy/src/router.js b/tests/fixtures/module-unification-addon/npm/tests/dummy/src/router.js deleted file mode 100644 index 7f2298b0dc..0000000000 --- a/tests/fixtures/module-unification-addon/npm/tests/dummy/src/router.js +++ /dev/null @@ -1,11 +0,0 @@ -import EmberRouter from "@ember/routing/router"; -import config from "../config/environment"; - -const Router = EmberRouter.extend({ - location: config.locationType, - rootURL: config.rootURL -}); - -Router.map(function() {}); - -export default Router; diff --git a/tests/fixtures/module-unification-addon/npm/tests/dummy/src/ui/components/.gitkeep b/tests/fixtures/module-unification-addon/npm/tests/dummy/src/ui/components/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/module-unification-addon/npm/tests/dummy/src/ui/index.html b/tests/fixtures/module-unification-addon/npm/tests/dummy/src/ui/index.html deleted file mode 100644 index e4e6c5d6e9..0000000000 --- a/tests/fixtures/module-unification-addon/npm/tests/dummy/src/ui/index.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - dummy - - - - {{content-for "head"}} - - - - - {{content-for "head-footer"}} - - - {{content-for "body"}} - - - - - {{content-for "body-footer"}} - - diff --git a/tests/fixtures/module-unification-addon/npm/tests/dummy/src/ui/routes/application/template.hbs b/tests/fixtures/module-unification-addon/npm/tests/dummy/src/ui/routes/application/template.hbs deleted file mode 100644 index 5230580f82..0000000000 --- a/tests/fixtures/module-unification-addon/npm/tests/dummy/src/ui/routes/application/template.hbs +++ /dev/null @@ -1,3 +0,0 @@ -

Welcome to Ember

- -{{outlet}} \ No newline at end of file diff --git a/tests/fixtures/module-unification-addon/npm/tests/dummy/src/ui/styles/app.css b/tests/fixtures/module-unification-addon/npm/tests/dummy/src/ui/styles/app.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/module-unification-addon/npm/tests/index.html b/tests/fixtures/module-unification-addon/npm/tests/index.html deleted file mode 100644 index 5209b85232..0000000000 --- a/tests/fixtures/module-unification-addon/npm/tests/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - Dummy Tests - - - - {{content-for "head"}} - {{content-for "test-head"}} - - - - - - {{content-for "head-footer"}} - {{content-for "test-head-footer"}} - - - {{content-for "body"}} - {{content-for "test-body"}} - - - - - - - - {{content-for "body-footer"}} - {{content-for "test-body-footer"}} - - diff --git a/tests/fixtures/module-unification-addon/npm/tests/test-helper.js b/tests/fixtures/module-unification-addon/npm/tests/test-helper.js deleted file mode 100644 index 007bd6e506..0000000000 --- a/tests/fixtures/module-unification-addon/npm/tests/test-helper.js +++ /dev/null @@ -1,8 +0,0 @@ -import Application from '../src/main'; -import config from '../config/environment'; -import { setApplication } from '@ember/test-helpers'; -import { start } from 'ember-qunit'; - -setApplication(Application.create(config.APP)); - -start(); diff --git a/tests/fixtures/module-unification-addon/npm/vendor/.gitkeep b/tests/fixtures/module-unification-addon/npm/vendor/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/module-unification-addon/yarn/.editorconfig b/tests/fixtures/module-unification-addon/yarn/.editorconfig deleted file mode 100644 index 219985c228..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/.editorconfig +++ /dev/null @@ -1,20 +0,0 @@ -# EditorConfig helps developers define and maintain consistent -# coding styles between different editors and IDEs -# editorconfig.org - -root = true - - -[*] -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true -indent_style = space -indent_size = 2 - -[*.hbs] -insert_final_newline = false - -[*.{diff,md}] -trim_trailing_whitespace = false diff --git a/tests/fixtures/module-unification-addon/yarn/.ember-cli b/tests/fixtures/module-unification-addon/yarn/.ember-cli deleted file mode 100644 index ee64cfed2a..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/.ember-cli +++ /dev/null @@ -1,9 +0,0 @@ -{ - /** - Ember CLI sends analytics information by default. The data is completely - anonymous, but there are times when you might want to disable this behavior. - - Setting `disableAnalytics` to true will prevent any data from being sent. - */ - "disableAnalytics": false -} diff --git a/tests/fixtures/module-unification-addon/yarn/.eslintignore b/tests/fixtures/module-unification-addon/yarn/.eslintignore deleted file mode 100644 index 9f2dd3ba02..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -!.* diff --git a/tests/fixtures/module-unification-addon/yarn/.eslintrc.js b/tests/fixtures/module-unification-addon/yarn/.eslintrc.js deleted file mode 100644 index 43c0e36b3c..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/.eslintrc.js +++ /dev/null @@ -1,50 +0,0 @@ -module.exports = { - root: true, - parser: 'babel-eslint', - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module' - }, - plugins: [ - 'ember' - ], - extends: [ - 'eslint:recommended', - 'plugin:ember/recommended' - ], - env: { - browser: true - }, - rules: { - }, - overrides: [ - // node files - { - files: [ - '.eslintrc.js', - '.template-lintrc.js', - 'ember-cli-build.js', - 'index.js', - 'testem.js', - 'blueprints/*/index.js', - 'config/**/*.js', - 'tests/dummy/config/**/*.js' - ], - excludedFiles: [ - 'src/**', - 'tests/dummy/app/**' - ], - parserOptions: { - sourceType: 'script' - }, - env: { - browser: false, - node: true - }, - plugins: ['node'], - rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { - // add your custom rules and overrides for node files here - }) - } - ] -}; diff --git a/tests/fixtures/module-unification-addon/yarn/.gitignore b/tests/fixtures/module-unification-addon/yarn/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/module-unification-addon/yarn/.npmignore b/tests/fixtures/module-unification-addon/yarn/.npmignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/module-unification-addon/yarn/.template-lintrc.js b/tests/fixtures/module-unification-addon/yarn/.template-lintrc.js deleted file mode 100644 index 3f204f3fad..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/.template-lintrc.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -module.exports = { - extends: 'recommended' -} diff --git a/tests/fixtures/module-unification-addon/yarn/.travis.yml b/tests/fixtures/module-unification-addon/yarn/.travis.yml deleted file mode 100644 index 1bad18f943..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/.travis.yml +++ /dev/null @@ -1,42 +0,0 @@ ---- -language: node_js -node_js: - # we recommend testing addons with the same minimum supported node version as Ember CLI - # so that your addon works for all apps - - "8" - -sudo: false -dist: trusty - -addons: - chrome: stable - -cache: - directories: - - $HOME/.npm - -env: - global: - # See https://git.io/vdao3 for details. - - JOBS=1 - matrix: - # we recommend new addons test the current and previous LTS - # as well as latest stable release (bonus points to beta/canary) - - EMBER_TRY_SCENARIO=ember-lts-3.4 - - EMBER_TRY_SCENARIO=ember-lts-3.8 - - EMBER_TRY_SCENARIO=ember-release - - EMBER_TRY_SCENARIO=ember-beta - - EMBER_TRY_SCENARIO=ember-canary - - EMBER_TRY_SCENARIO=ember-default - -matrix: - fast_finish: true - allow_failures: - - env: EMBER_TRY_SCENARIO=ember-canary - -script: - - npm run lint:hbs - - npm run lint:js - # Usually, it's ok to finish the test scenario without reverting - # to the addon's original dependency state, skipping "cleanup". - - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO --skip-cleanup diff --git a/tests/fixtures/module-unification-addon/yarn/.watchmanconfig b/tests/fixtures/module-unification-addon/yarn/.watchmanconfig deleted file mode 100644 index e7834e3e4f..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/.watchmanconfig +++ /dev/null @@ -1,3 +0,0 @@ -{ - "ignore_dirs": ["tmp", "dist"] -} diff --git a/tests/fixtures/module-unification-addon/yarn/CONTRIBUTING.md b/tests/fixtures/module-unification-addon/yarn/CONTRIBUTING.md deleted file mode 100644 index 59e532958a..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/CONTRIBUTING.md +++ /dev/null @@ -1,26 +0,0 @@ -# How To Contribute - -## Installation - -* `git clone ` -* `cd foo` -* `yarn install` - -## Linting - -* `yarn lint:hbs` -* `yarn lint:js` -* `yarn lint:js --fix` - -## Running tests - -* `ember test` – Runs the test suite on the current Ember version -* `ember test --server` – Runs the test suite in "watch mode" -* `ember try:each` – Runs the test suite against multiple Ember versions - -## Running the dummy application - -* `ember serve` -* Visit the dummy application at [http://localhost:4200](http://localhost:4200). - -For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). \ No newline at end of file diff --git a/tests/fixtures/module-unification-addon/yarn/LICENSE.md b/tests/fixtures/module-unification-addon/yarn/LICENSE.md deleted file mode 100644 index 198f6dacf4..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/LICENSE.md +++ /dev/null @@ -1,9 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2018 - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/tests/fixtures/module-unification-addon/yarn/README.md b/tests/fixtures/module-unification-addon/yarn/README.md deleted file mode 100644 index 36bd8fd416..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/README.md +++ /dev/null @@ -1,38 +0,0 @@ -foo -============================================================================== - -[Short description of the addon.] - - -Compatibility ------------------------------------------------------------------------------- - -* Ember.js v3.4 or above -* Ember CLI v2.13 or above -* Node.js v8 or above - - -Installation ------------------------------------------------------------------------------- - -``` -ember install foo -``` - - -Usage ------------------------------------------------------------------------------- - -[Longer description of how to use the addon in apps.] - - -Contributing ------------------------------------------------------------------------------- - -See the [Contributing](CONTRIBUTING.md) guide for details. - - -License ------------------------------------------------------------------------------- - -This project is licensed under the [MIT License](LICENSE.md). diff --git a/tests/fixtures/module-unification-addon/yarn/config/ember-try.js b/tests/fixtures/module-unification-addon/yarn/config/ember-try.js deleted file mode 100644 index 2d304fdcbb..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/config/ember-try.js +++ /dev/null @@ -1,57 +0,0 @@ -'use strict'; - -const getChannelURL = require('ember-source-channel-url'); - -module.exports = async function() { - return { - useYarn: true, - scenarios: [ - { - name: 'ember-lts-3.4', - npm: { - devDependencies: { - 'ember-source': '~3.4.0' - } - } - }, - { - name: 'ember-lts-3.8', - npm: { - devDependencies: { - 'ember-source': '~3.8.0' - } - } - }, - { - name: 'ember-release', - npm: { - devDependencies: { - 'ember-source': await getChannelURL('release') - } - } - }, - { - name: 'ember-beta', - npm: { - devDependencies: { - 'ember-source': await getChannelURL('beta') - } - } - }, - { - name: 'ember-canary', - npm: { - devDependencies: { - 'ember-source': await getChannelURL('canary') - } - } - }, - { - name: 'ember-default', - npm: { - devDependencies: {} - } - } - ] - }; -}; diff --git a/tests/fixtures/module-unification-addon/yarn/config/environment.js b/tests/fixtures/module-unification-addon/yarn/config/environment.js deleted file mode 100644 index 0dfaed4728..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/config/environment.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -module.exports = function(/* environment, appConfig */) { - return { }; -}; diff --git a/tests/fixtures/module-unification-addon/yarn/ember-cli-build.js b/tests/fixtures/module-unification-addon/yarn/ember-cli-build.js deleted file mode 100644 index ba0649dc93..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/ember-cli-build.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); - -module.exports = function (defaults) { - let app = new EmberAddon(defaults, { - // Add options here - }); - - /* - This build file specifies the options for the dummy test app of this - addon, located in `/tests/dummy` - This build file does *not* influence how the addon or the app using it - behave. You most likely want to be modifying `./index.js` or app's build file - */ - - return app.toTree(); -}; diff --git a/tests/fixtures/module-unification-addon/yarn/package.json b/tests/fixtures/module-unification-addon/yarn/package.json deleted file mode 100644 index ad5603aa1e..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/package.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "name": "foo", - "version": "0.0.0", - "description": "Generates an Ember addon with a module unification layout.", - "keywords": [ - "ember-addon" - ], - "repository": "", - "license": "MIT", - "author": "", - "directories": { - "doc": "doc", - "test": "tests" - }, - "scripts": { - "build": "ember build", - "lint:hbs": "ember-template-lint .", - "lint:js": "eslint .", - "start": "ember serve", - "test": "ember test", - "test:all": "ember try:each" - }, - "dependencies": { - "ember-cli-babel": "^7.11.0", - "ember-cli-htmlbars": "^3.1.0" - }, - "devDependencies": { - "@ember/optional-features": "^0.7.0", - "babel-eslint": "^10.0.3", - "broccoli-asset-rev": "^3.0.0", - "ember-cli": "<%= emberCLIVersion %>", - "ember-cli-dependency-checker": "^3.1.0", - "ember-cli-eslint": "^5.1.0", - "ember-cli-htmlbars-inline-precompile": "^3.0.0", - "ember-cli-inject-live-reload": "^2.0.1", - "ember-cli-sri": "^2.1.1", - "ember-cli-template-lint": "^1.0.0-beta.3", - "ember-cli-uglify": "^3.0.0", - "ember-disable-prototype-extensions": "^1.1.3", - "ember-export-application-global": "^2.0.0", - "ember-load-initializers": "^2.1.0", - "ember-maybe-import-regenerator": "^0.1.6", - "ember-qunit": "^4.5.1", - "ember-resolver": "^5.2.1", - "ember-source": "<%= emberCanaryVersion %>", - "ember-source-channel-url": "^2.0.1", - "ember-try": "^1.2.1", - "ember-welcome-page": "^4.0.0", - "eslint-plugin-ember": "^7.0.0", - "eslint-plugin-node": "^9.2.0", - "loader.js": "^4.7.0", - "qunit-dom": "^0.9.0" - }, - "engines": { - "node": "8.* || >= 10.*" - }, - "ember-addon": { - "configPath": "tests/dummy/config" - } -} diff --git a/tests/fixtures/module-unification-addon/yarn/src/.gitkeep b/tests/fixtures/module-unification-addon/yarn/src/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/module-unification-addon/yarn/testem.js b/tests/fixtures/module-unification-addon/yarn/testem.js deleted file mode 100644 index 96589a75d2..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/testem.js +++ /dev/null @@ -1,23 +0,0 @@ -module.exports = { - test_page: 'tests/index.html?hidepassed', - disable_watching: true, - launch_in_ci: [ - 'Chrome' - ], - launch_in_dev: [ - 'Chrome' - ], - browser_args: { - Chrome: { - mode: 'ci', - args: [ - // --no-sandbox is needed when running Chrome inside a container - process.env.TRAVIS ? '--no-sandbox' : null, - - '--headless', - '--remote-debugging-port=0', - '--window-size=1440,900' - ].filter(Boolean) - } - } -}; diff --git a/tests/fixtures/module-unification-addon/yarn/tests/acceptance/.gitkeep b/tests/fixtures/module-unification-addon/yarn/tests/acceptance/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/module-unification-addon/yarn/tests/dummy/.gitkeep b/tests/fixtures/module-unification-addon/yarn/tests/dummy/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/module-unification-addon/yarn/tests/dummy/config/environment.js b/tests/fixtures/module-unification-addon/yarn/tests/dummy/config/environment.js deleted file mode 100644 index 9d3feec603..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/tests/dummy/config/environment.js +++ /dev/null @@ -1,56 +0,0 @@ -'use strict'; - -module.exports = function(environment) { - let ENV = { - 'ember-resolver': { - features: { - EMBER_RESOLVER_MODULE_UNIFICATION: true - } - }, - modulePrefix: 'dummy', - environment, - rootURL: '/', - locationType: 'auto', - EmberENV: { - FEATURES: { - // Here you can enable experimental features on an ember canary build - // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true - EMBER_MODULE_UNIFICATION: true - }, - EXTEND_PROTOTYPES: { - // Prevent Ember Data from overriding Date.parse. - Date: false - } - }, - - APP: { - // Here you can pass flags/options to your application instance - // when it is created - } - }; - - if (environment === 'development') { - // ENV.APP.LOG_RESOLVER = true; - // ENV.APP.LOG_ACTIVE_GENERATION = true; - // ENV.APP.LOG_TRANSITIONS = true; - // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; - // ENV.APP.LOG_VIEW_LOOKUPS = true; - } - - if (environment === 'test') { - // Testem prefers this... - ENV.locationType = 'none'; - - // keep test console output quieter - ENV.APP.LOG_ACTIVE_GENERATION = false; - ENV.APP.LOG_VIEW_LOOKUPS = false; - - ENV.APP.rootElement = '#ember-testing'; - } - - if (environment === 'production') { - // here you can enable a production-specific feature - } - - return ENV; -}; diff --git a/tests/fixtures/module-unification-addon/yarn/tests/dummy/config/optional-features.json b/tests/fixtures/module-unification-addon/yarn/tests/dummy/config/optional-features.json deleted file mode 100644 index b1902623ae..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/tests/dummy/config/optional-features.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "jquery-integration": false -} diff --git a/tests/fixtures/module-unification-addon/yarn/tests/dummy/config/targets.js b/tests/fixtures/module-unification-addon/yarn/tests/dummy/config/targets.js deleted file mode 100644 index 1a7647d06b..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/tests/dummy/config/targets.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - browsers: [ - 'ie 11', - 'last 1 Chrome versions', - 'last 1 Firefox versions', - 'last 1 Safari versions' - ] -}; diff --git a/tests/fixtures/module-unification-addon/yarn/tests/dummy/public/robots.txt b/tests/fixtures/module-unification-addon/yarn/tests/dummy/public/robots.txt deleted file mode 100644 index f5916452e5..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/tests/dummy/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# http://www.robotstxt.org -User-agent: * -Disallow: diff --git a/tests/fixtures/module-unification-addon/yarn/tests/dummy/src/main.js b/tests/fixtures/module-unification-addon/yarn/tests/dummy/src/main.js deleted file mode 100644 index 84b092ff19..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/tests/dummy/src/main.js +++ /dev/null @@ -1,19 +0,0 @@ -import Application from "@ember/application"; -import Resolver from "./resolver"; -import loadInitializers from "ember-load-initializers"; -import config from "../config/environment"; - -const App = Application.extend({ - modulePrefix: config.modulePrefix, - podModulePrefix: config.podModulePrefix, - Resolver -}); - -loadInitializers(App, config.modulePrefix + "/src/init"); - -/* - * This line is added to support initializers in the `app/` directory - */ -loadInitializers(App, config.modulePrefix); - -export default App; diff --git a/tests/fixtures/module-unification-addon/yarn/tests/dummy/src/resolver.js b/tests/fixtures/module-unification-addon/yarn/tests/dummy/src/resolver.js deleted file mode 100644 index f0b8a1ccea..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/tests/dummy/src/resolver.js +++ /dev/null @@ -1,13 +0,0 @@ -import Resolver from 'ember-resolver/resolvers/fallback'; -import buildResolverConfig from 'ember-resolver/ember-config'; -import config from '../config/environment'; - -let moduleConfig = buildResolverConfig(config.modulePrefix); -/* - * If your application has custom types and collections, modify moduleConfig here - * to add support for them. - */ - -export default Resolver.extend({ - config: moduleConfig -}); diff --git a/tests/fixtures/module-unification-addon/yarn/tests/dummy/src/router.js b/tests/fixtures/module-unification-addon/yarn/tests/dummy/src/router.js deleted file mode 100644 index 7f2298b0dc..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/tests/dummy/src/router.js +++ /dev/null @@ -1,11 +0,0 @@ -import EmberRouter from "@ember/routing/router"; -import config from "../config/environment"; - -const Router = EmberRouter.extend({ - location: config.locationType, - rootURL: config.rootURL -}); - -Router.map(function() {}); - -export default Router; diff --git a/tests/fixtures/module-unification-addon/yarn/tests/dummy/src/ui/components/.gitkeep b/tests/fixtures/module-unification-addon/yarn/tests/dummy/src/ui/components/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/module-unification-addon/yarn/tests/dummy/src/ui/index.html b/tests/fixtures/module-unification-addon/yarn/tests/dummy/src/ui/index.html deleted file mode 100644 index e4e6c5d6e9..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/tests/dummy/src/ui/index.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - dummy - - - - {{content-for "head"}} - - - - - {{content-for "head-footer"}} - - - {{content-for "body"}} - - - - - {{content-for "body-footer"}} - - diff --git a/tests/fixtures/module-unification-addon/yarn/tests/dummy/src/ui/styles/app.css b/tests/fixtures/module-unification-addon/yarn/tests/dummy/src/ui/styles/app.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/module-unification-addon/yarn/tests/index.html b/tests/fixtures/module-unification-addon/yarn/tests/index.html deleted file mode 100644 index 5209b85232..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/tests/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - Dummy Tests - - - - {{content-for "head"}} - {{content-for "test-head"}} - - - - - - {{content-for "head-footer"}} - {{content-for "test-head-footer"}} - - - {{content-for "body"}} - {{content-for "test-body"}} - - - - - - - - {{content-for "body-footer"}} - {{content-for "test-body-footer"}} - - diff --git a/tests/fixtures/module-unification-addon/yarn/tests/test-helper.js b/tests/fixtures/module-unification-addon/yarn/tests/test-helper.js deleted file mode 100644 index 007bd6e506..0000000000 --- a/tests/fixtures/module-unification-addon/yarn/tests/test-helper.js +++ /dev/null @@ -1,8 +0,0 @@ -import Application from '../src/main'; -import config from '../config/environment'; -import { setApplication } from '@ember/test-helpers'; -import { start } from 'ember-qunit'; - -setApplication(Application.create(config.APP)); - -start(); diff --git a/tests/fixtures/module-unification-addon/yarn/vendor/.gitkeep b/tests/fixtures/module-unification-addon/yarn/vendor/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/module-unification-app/.eslintrc.js b/tests/fixtures/module-unification-app/.eslintrc.js deleted file mode 100644 index bb2c27ecb0..0000000000 --- a/tests/fixtures/module-unification-app/.eslintrc.js +++ /dev/null @@ -1,51 +0,0 @@ -module.exports = { - root: true, - parser: 'babel-eslint', - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module' - }, - plugins: [ - 'ember' - ], - extends: [ - 'eslint:recommended', - 'plugin:ember/recommended' - ], - env: { - browser: true - }, - rules: { - 'ember/no-jquery': 'error' - }, - overrides: [ - // node files - { - files: [ - '.eslintrc.js', - '.template-lintrc.js', - 'ember-cli-build.js', - 'index.js', - 'testem.js', - 'blueprints/*/index.js', - 'config/**/*.js', - 'tests/dummy/config/**/*.js' - ], - parserOptions: { - sourceType: 'script' - }, - env: { - browser: false, - node: true - }, - plugins: ['node'], - rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { - // add your custom rules and overrides for node files here - - // this can be removed once the following is fixed - // https://github.com/mysticatea/eslint-plugin-node/issues/77 - 'node/no-unpublished-require': 'off' - }) - } - ] -}; diff --git a/tests/fixtures/module-unification-app/npm/package.json b/tests/fixtures/module-unification-app/npm/package.json deleted file mode 100644 index 78f27f985c..0000000000 --- a/tests/fixtures/module-unification-app/npm/package.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "foo", - "version": "0.0.0", - "private": true, - "description": "Small description for foo goes here", - "repository": "", - "license": "MIT", - "author": "", - "directories": { - "doc": "doc", - "test": "tests" - }, - "scripts": { - "build": "ember build", - "lint:hbs": "ember-template-lint .", - "lint:js": "eslint .", - "start": "ember serve", - "test": "ember test" - }, - "devDependencies": { - "@ember/optional-features": "^0.7.0", - "babel-eslint": "^10.0.3", - "broccoli-asset-rev": "^3.0.0", - "ember-cli": "github:ember-cli/ember-cli", - "ember-cli-app-version": "^3.2.0", - "ember-cli-babel": "^7.11.0", - "ember-cli-dependency-checker": "^3.1.0", - "ember-cli-eslint": "^5.1.0", - "ember-cli-htmlbars": "^3.1.0", - "ember-cli-htmlbars-inline-precompile": "^3.0.0", - "ember-cli-inject-live-reload": "^2.0.1", - "ember-cli-sri": "^2.1.1", - "ember-cli-template-lint": "^1.0.0-beta.3", - "ember-cli-uglify": "^3.0.0", - "ember-data": "~3.13.0-beta.0", - "ember-export-application-global": "^2.0.0", - "ember-fetch": "^6.7.0", - "ember-load-initializers": "^2.1.0", - "ember-maybe-import-regenerator": "^0.1.6", - "ember-qunit": "^4.5.1", - "ember-resolver": "^5.2.1", - "ember-source": "<%= emberCanaryVersion %>", - "eslint-plugin-ember": "^7.0.0", - "eslint-plugin-node": "^9.2.0", - "loader.js": "^4.7.0", - "qunit-dom": "^0.9.0" - }, - "engines": { - "node": "8.* || >= 10.*" - } -} diff --git a/tests/fixtures/module-unification-app/npm/src/ui/routes/application/template.hbs b/tests/fixtures/module-unification-app/npm/src/ui/routes/application/template.hbs deleted file mode 100644 index 5230580f82..0000000000 --- a/tests/fixtures/module-unification-app/npm/src/ui/routes/application/template.hbs +++ /dev/null @@ -1,3 +0,0 @@ -

Welcome to Ember

- -{{outlet}} \ No newline at end of file diff --git a/tests/fixtures/module-unification-app/yarn/.travis.yml b/tests/fixtures/module-unification-app/yarn/.travis.yml deleted file mode 100644 index e8232192cb..0000000000 --- a/tests/fixtures/module-unification-app/yarn/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ ---- -language: node_js -node_js: - - "8" - -sudo: false -dist: trusty - -addons: - chrome: stable - -cache: - yarn: true - -env: - global: - # See https://git.io/vdao3 for details. - - JOBS=1 - -before_install: - - curl -o- -L https://yarnpkg.com/install.sh | bash - - export PATH=$HOME/.yarn/bin:$PATH - -install: - - yarn install --non-interactive - -script: - - yarn lint:hbs - - yarn lint:js - - yarn test diff --git a/tests/fixtures/module-unification-app/yarn/package.json b/tests/fixtures/module-unification-app/yarn/package.json deleted file mode 100644 index 551bb10096..0000000000 --- a/tests/fixtures/module-unification-app/yarn/package.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "foo", - "version": "0.0.0", - "private": true, - "description": "Small description for foo goes here", - "repository": "", - "license": "MIT", - "author": "", - "directories": { - "doc": "doc", - "test": "tests" - }, - "scripts": { - "build": "ember build", - "lint:hbs": "ember-template-lint .", - "lint:js": "eslint .", - "start": "ember serve", - "test": "ember test" - }, - "devDependencies": { - "@ember/optional-features": "^0.7.0", - "babel-eslint": "^10.0.3", - "broccoli-asset-rev": "^3.0.0", - "ember-cli": "github:ember-cli/ember-cli", - "ember-cli-app-version": "^3.2.0", - "ember-cli-babel": "^7.11.0", - "ember-cli-dependency-checker": "^3.1.0", - "ember-cli-eslint": "^5.1.0", - "ember-cli-htmlbars": "^3.1.0", - "ember-cli-htmlbars-inline-precompile": "^3.0.0", - "ember-cli-inject-live-reload": "^2.0.1", - "ember-cli-sri": "^2.1.1", - "ember-cli-template-lint": "^1.0.0-beta.3", - "ember-cli-uglify": "^3.0.0", - "ember-data": "~3.13.0-beta.0", - "ember-export-application-global": "^2.0.0", - "ember-fetch": "^6.7.0", - "ember-load-initializers": "^2.1.0", - "ember-maybe-import-regenerator": "^0.1.6", - "ember-qunit": "^4.5.1", - "ember-resolver": "^5.2.1", - "ember-source": "<%= emberCanaryVersion %>", - "ember-welcome-page": "^4.0.0", - "eslint-plugin-ember": "^7.0.0", - "eslint-plugin-node": "^9.2.0", - "loader.js": "^4.7.0", - "qunit-dom": "^0.9.0" - }, - "engines": { - "node": "8.* || >= 10.*" - } -} diff --git a/tests/fixtures/preprocessor-tests/app-with-addon-with-preprocessors-2/app/components/should-not-be-preprocessed.js b/tests/fixtures/preprocessor-tests/app-with-addon-with-preprocessors-2/app/components/should-not-be-preprocessed.js index 8d9ad15e5a..003ba9c8a7 100644 --- a/tests/fixtures/preprocessor-tests/app-with-addon-with-preprocessors-2/app/components/should-not-be-preprocessed.js +++ b/tests/fixtures/preprocessor-tests/app-with-addon-with-preprocessors-2/app/components/should-not-be-preprocessed.js @@ -1,5 +1,5 @@ import Component from '@ember/component'; export default Component.extend({ - foo: __PREPROCESSOR_REPLACEMENT_TOKEN__ + foo_in_app: __PREPROCESSOR_REPLACEMENT_TOKEN__ }); diff --git a/tests/fixtures/preprocessor-tests/app-with-addon-with-preprocessors-2/node_modules/ember-cool-addon/addon/components/clitest-example.js b/tests/fixtures/preprocessor-tests/app-with-addon-with-preprocessors-2/node_modules/ember-cool-addon/addon/components/clitest-example.js index 8d9ad15e5a..b83229f6d2 100644 --- a/tests/fixtures/preprocessor-tests/app-with-addon-with-preprocessors-2/node_modules/ember-cool-addon/addon/components/clitest-example.js +++ b/tests/fixtures/preprocessor-tests/app-with-addon-with-preprocessors-2/node_modules/ember-cool-addon/addon/components/clitest-example.js @@ -1,5 +1,5 @@ import Component from '@ember/component'; export default Component.extend({ - foo: __PREPROCESSOR_REPLACEMENT_TOKEN__ + foo_in_addon: __PREPROCESSOR_REPLACEMENT_TOKEN__ }); diff --git a/tests/fixtures/preprocessor-tests/app-with-addon-with-preprocessors-3/app/components/should-not-be-preprocessed.js b/tests/fixtures/preprocessor-tests/app-with-addon-with-preprocessors-3/app/components/should-not-be-preprocessed.js index 8d9ad15e5a..003ba9c8a7 100644 --- a/tests/fixtures/preprocessor-tests/app-with-addon-with-preprocessors-3/app/components/should-not-be-preprocessed.js +++ b/tests/fixtures/preprocessor-tests/app-with-addon-with-preprocessors-3/app/components/should-not-be-preprocessed.js @@ -1,5 +1,5 @@ import Component from '@ember/component'; export default Component.extend({ - foo: __PREPROCESSOR_REPLACEMENT_TOKEN__ + foo_in_app: __PREPROCESSOR_REPLACEMENT_TOKEN__ }); diff --git a/tests/fixtures/project-with-handlebars/package.json b/tests/fixtures/project-with-handlebars/package.json index 8a9407a41f..02566f3ea1 100644 --- a/tests/fixtures/project-with-handlebars/package.json +++ b/tests/fixtures/project-with-handlebars/package.json @@ -7,7 +7,7 @@ }, "devDependencies": { "ember-cli": "latest", - "ember-resolver": "^5.0.1", + "ember-resolver": "^7.0.0", "loader.js": "*" } } diff --git a/tests/fixtures/smoke-tests/js-testem-config/testem.js b/tests/fixtures/smoke-tests/js-testem-config/testem.js index a3cb08b46c..e60c6e0417 100644 --- a/tests/fixtures/smoke-tests/js-testem-config/testem.js +++ b/tests/fixtures/smoke-tests/js-testem-config/testem.js @@ -4,20 +4,16 @@ module.exports = { "framework": "qunit", "test_page": "tests/index.html?hidepassed", "disable_watching": true, - "launch_in_ci": [ - "Chrome" - ], - "launch_in_dev": [ - "Chrome" - ], + "launch_in_ci": ["Chrome"], + "launch_in_dev": ["Chrome"], "browser_args": { "Chrome": [ // --no-sandbox is needed when running Chrome inside a container - process.env.TRAVIS ? '--no-sandbox' : null, + process.env.CI ? '--no-sandbox' : null, "--headless", "--remote-debugging-port=0", - "--window-size=1440,900" + "--window-size=1440,900", ].filter(Boolean), - } + }, }; diff --git a/tests/fixtures/tasks/testem-config/testem-dummy.json b/tests/fixtures/tasks/testem-config/testem-dummy.json new file mode 100644 index 0000000000..303080ff39 --- /dev/null +++ b/tests/fixtures/tasks/testem-config/testem-dummy.json @@ -0,0 +1,9 @@ +{ + "framework": "qunit", + "launch": "Dummy", + "launchers": { + "Dummy" : { + "command": "exit" + } + } +} diff --git a/tests/fixtures/with-styles-mu/src/ui/styles/app.css b/tests/fixtures/with-styles-mu/src/ui/styles/app.css deleted file mode 100644 index 5540de6636..0000000000 --- a/tests/fixtures/with-styles-mu/src/ui/styles/app.css +++ /dev/null @@ -1,2 +0,0 @@ -@import "some-styles.css"; -@import "some-other-styles.css"; diff --git a/tests/fixtures/with-styles-mu/src/ui/styles/some-other-styles.css b/tests/fixtures/with-styles-mu/src/ui/styles/some-other-styles.css deleted file mode 100644 index 19840f92e3..0000000000 --- a/tests/fixtures/with-styles-mu/src/ui/styles/some-other-styles.css +++ /dev/null @@ -1 +0,0 @@ -.some-even-weirder-selector{cursor: crosshair;} diff --git a/tests/fixtures/with-styles-mu/src/ui/styles/some-styles.css b/tests/fixtures/with-styles-mu/src/ui/styles/some-styles.css deleted file mode 100644 index ac8323bcba..0000000000 --- a/tests/fixtures/with-styles-mu/src/ui/styles/some-styles.css +++ /dev/null @@ -1 +0,0 @@ -.some-weird-selector{cursor: crosshair;} diff --git a/tests/fixtures/with-unminified-styles-mu/src/ui/styles/app.css b/tests/fixtures/with-unminified-styles-mu/src/ui/styles/app.css deleted file mode 100644 index b2cd1ce731..0000000000 --- a/tests/fixtures/with-unminified-styles-mu/src/ui/styles/app.css +++ /dev/null @@ -1,18 +0,0 @@ -html { - line-height: 1.15; - -webkit-text-size-adjust: 100%; -} - -body { - margin: 0; -} - -h1 { - font-size: 2em; -} - -hr { - box-sizing: content-box; - height: 0; - overflow: visible; -} diff --git a/tests/helpers/acceptance.js b/tests/helpers/acceptance.js index 22b2482ec9..859601bbde 100644 --- a/tests/helpers/acceptance.js +++ b/tests/helpers/acceptance.js @@ -36,17 +36,23 @@ function applyCommand(command, name /*, ...flags*/) { let binaryPath = path.resolve(path.join(__dirname, '..', '..', 'bin', 'ember')); let args = [binaryPath, command, name, '--disable-analytics', '--watcher=node', '--skip-git', runCommandOptions]; - flags.forEach(function(flag) { + flags.forEach(function (flag) { args.splice(2, 0, flag); }); return runCommand.apply(undefined, args); } +/** + * @class PrivateTestHelpers + */ + /** * Use `createTestTargets` in the before hook to do the initial * setup of a project. This will ensure that we limit the amount of times * we go to the network to fetch dependencies. + * + * @method createTestTargets * @param {String} projectName The name of the project. Can be an app or addon. * @param {Object} options * @property {String} options.command The command you want to run @@ -77,6 +83,8 @@ function teardownTestTargets() { /** * Creates symbolic links from the dependency temp directories * to the project that is under test. + * + * @method linkDependencies * @param {String} projectName The name of the project under test * @return {String} The path to the hydrated fixture. */ diff --git a/tests/helpers/assert-version-lock.js b/tests/helpers/assert-version-lock.js index 96005d7adb..b82bd0310e 100644 --- a/tests/helpers/assert-version-lock.js +++ b/tests/helpers/assert-version-lock.js @@ -7,7 +7,7 @@ const semver = require('semver'); module.exports = function assertVersionLock(_deps) { let deps = _deps || {}; - Object.keys(deps).forEach(function(name) { + Object.keys(deps).forEach(function (name) { if (name !== 'ember-cli' && semver.valid(deps[name]) && semver.gtr('1.0.0', deps[name])) { // only valid if the version is fixed expect(semver.valid(deps[name]), `"${name}" has a valid version`).to.be.ok; diff --git a/tests/helpers/command-generator.js b/tests/helpers/command-generator.js index efe81cc92f..9a693ef0ac 100644 --- a/tests/helpers/command-generator.js +++ b/tests/helpers/command-generator.js @@ -13,6 +13,7 @@ const execa = require('../../lib/utilities/execa'); * bower.invoke('install', 'ember'); * ``` * + * @private * @class CommandGenerator * @constructor * @param {Path} program The path to the command. @@ -26,6 +27,7 @@ module.exports = class CommandGenerator { /** * The `invoke` method is responsible for building the final executable command. * + * @private * @method command * @param {String} [...arguments] Arguments to be passed into the command. * @param {Object} [options={}] The options passed into child_process.spawnSync. diff --git a/tests/helpers/convert-help-output-to-json.js b/tests/helpers/convert-help-output-to-json.js index 56d69f9e68..585439fd08 100644 --- a/tests/helpers/convert-help-output-to-json.js +++ b/tests/helpers/convert-help-output-to-json.js @@ -1,5 +1,5 @@ 'use strict'; -module.exports = function(output) { +module.exports = function (output) { return JSON.parse(output.substr(output.indexOf('{'))); }; diff --git a/tests/helpers/copy-fixture-files.js b/tests/helpers/copy-fixture-files.js index c9a087b7ad..e4b7f626ae 100644 --- a/tests/helpers/copy-fixture-files.js +++ b/tests/helpers/copy-fixture-files.js @@ -2,12 +2,9 @@ const fs = require('fs-extra'); const path = require('path'); -const RSVP = require('rsvp'); - -const copy = RSVP.denodeify(fs.copy); let rootPath = process.cwd(); module.exports = function copyFixtureFiles(sourceDir) { - return copy(path.join(rootPath, 'tests', 'fixtures', sourceDir), '.', { overwrite: true }); + return fs.copy(path.join(rootPath, 'tests', 'fixtures', sourceDir), '.', { overwrite: true }); }; diff --git a/tests/helpers/default-packager.js b/tests/helpers/default-packager.js index c0fd150117..b2a7ed4c46 100644 --- a/tests/helpers/default-packager.js +++ b/tests/helpers/default-packager.js @@ -234,7 +234,7 @@ function getDependencyFor(key, value) { * for and a function that takes a Broccoli tree and must return a Broccoli tree * as well. * - * @param {String} registryType i.e. 'template', 'js', 'css', 'src', 'all' + * @param {String} registryType i.e. 'template', 'js', 'css', 'all' * @param {Function} fn Transormation that is applied to the input tree */ function setupRegistryFor(registryType, fn) { diff --git a/tests/helpers/dist-checker.js b/tests/helpers/dist-checker.js new file mode 100644 index 0000000000..12e540e99b --- /dev/null +++ b/tests/helpers/dist-checker.js @@ -0,0 +1,86 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +// eslint-disable-next-line node/no-unpublished-require +const jsdom = require('jsdom'); + +class CustomResourceLoader extends jsdom.ResourceLoader { + constructor(distPath) { + super(); + this.distPath = distPath; + } + + fetch(url) { + let content = fs.readFileSync(path.join(this.distPath, url.replace('file://./', '')), 'utf8'); + return Promise.resolve(Buffer.from(content)); + } +} + +class DistChecker { + constructor(distPath) { + this.distPath = distPath; + } + + _evalHtml(options = {}) { + let html = fs.readFileSync(path.join(this.distPath, 'index.html'), 'utf8'); + this.dom = new jsdom.JSDOM(html, options); + this.scripts = this.dom.window.document.querySelectorAll('script'); + this.links = this.dom.window.document.querySelectorAll('link'); + } + + evalScripts(timeout = 15000) { + this._evalHtml({ + url: `file://.`, + runScripts: 'dangerously', + resources: new CustomResourceLoader(this.distPath), + }); + + // ember-data expects window.crypto to exists however JSDom does not + // impliment this: https://github.com/jsdom/jsdom/issues/1612 + this.dom.window.crypto = () => {}; + return new Promise((resolve, reject) => { + // reject if the scripts take longer than 15 seconds to load. + let timeoutId = setTimeout(reject, timeout); + + this.dom.window.addEventListener('load', () => { + clearTimeout(timeoutId); + return resolve(); + }); + }); + } + + get window() { + return this.dom.window; + } + + contains(fileType, token) { + if (!this.dom) { + this._evalHtml({}); + } + + if (fileType === 'js') { + for (let element of this.scripts) { + let src = element.getAttribute('src'); + let content = fs.readFileSync(path.join(this.distPath, src), 'utf8'); + + if (content.includes(token)) { + return true; + } + } + } else if (fileType === 'css') { + for (let element of this.links) { + let src = element.getAttribute('href'); + let content = fs.readFileSync(path.join(this.distPath, src), 'utf8'); + + if (content.includes(token)) { + return true; + } + } + } + + return false; + } +} + +module.exports = DistChecker; diff --git a/tests/helpers/ember.js b/tests/helpers/ember.js index 7c90f46e3d..e18b89b4a4 100644 --- a/tests/helpers/ember.js +++ b/tests/helpers/ember.js @@ -5,6 +5,7 @@ const MockAnalytics = require('./mock-analytics'); const cli = require('../../lib/cli'); const MockProcess = require('./mock-process'); const path = require('path'); +const willInterruptProcess = require('../../lib/utilities/will-interrupt-process'); /* Accepts a single array argument, that contains the @@ -65,6 +66,13 @@ module.exports = function ember(args, options) { args.push('--skipGit'); } + // Most tests don't npm install so let's disable lint fixing by default + if (!args.some((arg) => arg.startsWith('--lint-fix'))) { + args.push('--no-lint-fix'); + } + + willInterruptProcess.release(); + cliInstance = cli({ process: new MockProcess(), inputStream, diff --git a/tests/helpers/fixturify-project.js b/tests/helpers/fixturify-project.js index d942579a86..96c43bb1f1 100644 --- a/tests/helpers/fixturify-project.js +++ b/tests/helpers/fixturify-project.js @@ -1,6 +1,7 @@ 'use strict'; const path = require('path'); +const merge = require('ember-cli-lodash-subset').merge; const FixturifyProject = require('fixturify-project'); const Project = require('../../lib/models/project'); const MockCLI = require('./mock-cli'); @@ -13,20 +14,60 @@ class ProjectWithoutInternalAddons extends Project { } } -function prepareAddon(addon) { +function prepareAddon(addon, options) { addon.pkg.keywords.push('ember-addon'); addon.pkg['ember-addon'] = {}; - addon.files['index.js'] = 'module.exports = { name: require("./package").name };'; + addon.files['index.js'] = + options.addonEntryPoint || + `module.exports = { + name: require("./package").name, + allowCachingPerBundle: ${Boolean(options.allowCachingPerBundle)}, + ${options.additionalContent || ''} + };`; +} + +/** + * Gets a normalized object with provided defaults. If the 2nd argument is a function, + * we add this to the returned object with `callback` as its key. + * + * @name getOptionsObjectWithCallbackFunction + * @param {Object} defaultOptions The default options + * @param {Object|Function} optionsOrCallback The options object or callback function + * @returns {Object} The normalized options object + */ +function getOptionsObjectWithCallbackFunction(defaultOptions, optionsOrCallback) { + return Object.assign( + {}, + defaultOptions, + typeof optionsOrCallback === 'function' ? { callback: optionsOrCallback } : optionsOrCallback + ); +} + +// Essentially a copy of the function in node-fixturify-project, converted from TS to JS. +// We need this for use during toJSON(). +function parseScoped(name) { + let matched = name.match(/(@[^@/]+)\/(.*)/); + if (matched) { + return { + scope: matched[1], + name: matched[2], + }; + } + return null; } module.exports = class EmberCLIFixturifyProject extends FixturifyProject { writeSync() { super.writeSync(...arguments); - this._hasWrriten = true; + this._hasWritten = true; + } + + addFiles(filesObj) { + merge(this.files, filesObj); } buildProjectModel(ProjectClass = ProjectWithoutInternalAddons) { - if (this._hasWrriten !== false) { + if (!this._hasWritten) { this.writeSync(); } @@ -37,50 +78,316 @@ module.exports = class EmberCLIFixturifyProject extends FixturifyProject { return new ProjectClass(root, pkg, cli.ui, cli); } - addAddon(name, version = '0.0.0', cb) { - return this.addDependency(name, version, addon => { - prepareAddon(addon); + buildProjectModelForInRepoAddon(addonName, ProjectClass = ProjectWithoutInternalAddons) { + if (!this._hasWritten) { + this.writeSync(); + } - if (typeof cb === 'function') { - cb(addon); - } + let pkg = JSON.parse(this.files.lib[addonName]['package.json']); + let cli = new MockCLI(); + let root = path.join(this.root, this.name, 'lib', addonName); + + return new ProjectClass(root, pkg, cli.ui, cli); + } + + /** + * Add an entry for this object's `dependencies` list. When this object is written out, the + * dependency will also then write out appropriate files in this object's `node_modules' subdirectory. + * + * @param {String} name name of the dependency to add + * @param {String} version version of the dependency to add + * @param {Object|Function} optionsOrCallback options to configure the new FixturifyProject, or a callback function to call after creating + * the dependency's FixturifyProject. If the parameter is a function, it will be assumed to be a callback function. If instead + * the parameter is an object, a callback function can be provided using the property 'callback' in the object. + * @returns the new FixturifyProject + */ + addDependency(name, version, optionsOrCallback) { + const options = getOptionsObjectWithCallbackFunction(optionsOrCallback); + return super.addDependency(name, version, options.callback); + } + + /** + * Add a 'reference' entry to this object's `dependencies` list. A 'reference' dependency is + * an entry in `dependencies` where the caller knows the dependency's source files are being + * created elsewhere in the project tree, so no source files should be created locally in + * `node_modules`, which is the standard FixturifyProject (and node-fixturify-project) behavior. + * We do this by adding the necessary reference to `dependencies` during `toJSON`. + * + * This is used when two addons wish to share a single definition on disk for a dependency (various parts of + * ember-cli optimize processing based on paths on disk.) + * + * Because there is no FixturifyProject being created, no callback is given as in other methods. + * + * @param {String} name name of the dependency + * @param {String} version version of the dependency, defaults to '*'. For our purposes, '*' means + * "whatever version was specified elsewhere." + */ + addReferenceDependency(name, version = '*') { + if (!this._referenceDependencies) { + this._referenceDependencies = {}; + } + + this._referenceDependencies[name] = version; + } + + /** + * Add an entry to this object's `devDependencies` list. When this object is written out, the + * dependency will also then write out appropriate files in this object's `node_modules' subdirectory. + * + * @param {String} name name of the dev dependency to add + * @param {String} version version of the dev dependency to add + * @param {Object|Function} optionsOrCallback options to configure the new FixturifyProject, or a callback function to call after creating + * the dependency's FixturifyProject. If the parameter is a function, it will be assumed to be a callback function. If instead + * the parameter is an object, a callback function can be provided using the property 'callback' in the object. + * @returns the new FixturifyProject + */ + addDevDependency(name, version, optionsOrCallback) { + const options = getOptionsObjectWithCallbackFunction(optionsOrCallback); + return super.addDevDependency(name, version, options.callback); + } + + /** + * Add a 'reference' entry to this object's `devDependencies` list. A 'reference' devDependency is + * an entry in `devDependencies` where the caller knows the dependency's source files are being + * created elsewhere in the project tree, so no source files should be created locally in + * `node_modules`, which is the standard FixturifyProject (and node-fixturify-project) behavior. + * We do this by adding the necessary reference to `devDependencies` during `toJSON`. + * + * This is used when two addons wish to share a single definition on disk for a devDependency + * (various parts of ember-cli optimize processing based on paths on disk.) + * + * Because there is no FixturifyProject being created, no callback is given as in other methods. + * + * @param {String} name name of the devDependency + * @param {String} version version of the devDependency, defaults to '*'. For our purposes, '*' means + * "whatever version was specified elsewhere." + */ + addReferenceDevDependency(name, version = '*') { + if (!this._referenceDevDependencies) { + this._referenceDevDependencies = {}; + } + + this._referenceDevDependencies[name] = version; + } + + /** + * Add an addon to this object's `dependencies` list. The addon files will be written in + * this object's `node_modules/` directory when this object is written out. + * + * @param {String} name name of the addon + * @param {String} version version of the addon, defaults to '0.0.0' + * @param {Object|Function} optionsOrCallback an object consisting of properties and values to apply when creating + * the addon, or a callback function to pass the newly-created FixturifyProject to. Important options + * include 'allowCachingPerBundle' (true if the addon can be proxied, defaults to false) and 'callback' (if you want to include + * a callback function while also specifying other properties.) + * @returns {FixturifyProject} the newly-created addon + */ + addAddon(name, version = '0.0.0', optionsOrCallback) { + const options = getOptionsObjectWithCallbackFunction({ allowCachingPerBundle: false }, optionsOrCallback); + + return this.addDependency(name, version, { + ...options, + callback: (addon) => { + prepareAddon(addon, options); + + // call original `options.callback` if it exists + if (typeof options.callback === 'function') { + options.callback(addon); + } + }, }); } - addDevAddon(name, version = '0.0.0', cb) { - return this.addDevDependency(name, version, addon => { - prepareAddon(addon); - if (typeof cb === 'function') { - cb(addon); - } + /** + * Add an addon to this object's `devDependencies` list. The addon files will be written in + * this object's `node_modules/` directory when this object is written out. + * + * @param {String} name name of the addon + * @param {String} version version of the addon, defaults to '0.0.0' + * @param {Object|Function} optionsOrCallback an object consisting of properties and values to apply when creating + * the addon, or a callback function to pass the newly-created FixturifyProject to. Important options + * include 'allowCachingPerBundle' (true if the addon can be proxied, defaults to false) and 'callback' (if you want to include + * a callback function while also specifying other properties.) + * @returns {FixturifyProject} the newly-created addon + */ + addDevAddon(name, version = '0.0.0', optionsOrCallback) { + const options = getOptionsObjectWithCallbackFunction({ allowCachingPerBundle: false }, optionsOrCallback); + + return this.addDevDependency(name, version, { + ...options, + callback: (addon) => { + prepareAddon(addon, options); + + // call original `options.callback` if it exists + if (typeof options.callback === 'function') { + options.callback(addon); + } + }, }); } - addInRepoAddon(name, version = '0.0.0', cb) { - const inRepoAddon = new FixturifyProject(name, version, project => { - project.pkg.keywords.push('ember-addon'); - project.pkg['ember-addon'] = {}; - project.files['index.js'] = 'module.exports = { name: require("./package").name };'; + /** + * Add an addon to this object's `dependencies` list. The engine's addon files will be written in + * this object's `node_modules/` directory when this object is written out. + * + * @param {String} name name of the engine + * @param {String} version version of the engine, defaults to '0.0.0' + * @param {Object|Function} optionsOrCallback an object consisting of properties and values to apply when creating + * the engine, or a callback function to pass the newly-created FixturifyProject to. Important options + * include 'allowCachingPerBundle' (true if the engine can be proxied, defaults to false), 'enableLazyLoading' (true + * if the engine is to be lazily loaded, defaults to false) and 'callback' (if you want to include + * a callback function while also specifying other properties.) + * @returns {FixturifyProject} the newly-created engine addon + */ + addEngine(name, version = '0.0.0', options = { allowCachingPerBundle: false, enableLazyLoading: false }) { + const callback = (engine) => { + engine.pkg.keywords.push('ember-engine'); - if (typeof cb === 'function') { - cb(project); + // call original callback if it exists + if (typeof options.callback === 'function') { + options.callback(engine); + } + }; + + if (options.enableLazyLoading) { + return this.addAddon(name, version, { + ...options, + additionalContent: 'lazyLoading: { enabled: true },', + callback, + }); + } + + return this.addAddon(name, version, { ...options, callback }); + } + + /** + * Add an in-repo addon to this object. The addon files will be written in + * this object's `lib/` directory when this object is written out. + * + * @param {String} name name of the addon + * @param {String} version version of the addon, defaults to '0.0.0' + * @param {Object|Function} optionsOrCallback an object consisting of properties and values to apply when creating + * the addon, or a callback function to pass the newly-created FixturifyProject to. Important options + * include 'allowCachingPerBundle' (true if the addon can be proxied, defaults to false) and 'callback' (if you want to include + * a callback function while also specifying other properties.) + * @returns {FixturifyProject} the newly-created addon + */ + addInRepoAddon(name, version = '0.0.0', optionsOrCallback) { + const options = getOptionsObjectWithCallbackFunction({ allowCachingPerBundle: false }, optionsOrCallback); + + const inRepoAddon = new EmberCLIFixturifyProject(name, version, (addon) => { + prepareAddon(addon, options); + + if (typeof options.callback === 'function') { + options.callback(addon); } }); - // configure the current project to have an ember-addon configured at the appropriate path + // configure the current project to have an ember-addon configured at the + // appropriate path, i.e. under a common root directory (lib). + const addonRootDir = 'lib'; + + // Add to ember-addon.paths list let addon = (this.pkg['ember-addon'] = this.pkg['ember-addon'] || {}); addon.paths = addon.paths || []; - const addonPath = `lib/${name}`; - if (addon.paths.find(path => path.toLowerCase() === addonPath.toLowerCase())) { + const addonPath = `${addonRootDir}/${name}`; + + if (addon.paths.find((path) => path.toLowerCase() === addonPath.toLowerCase())) { throw new Error(`project: ${this.name} already contains the in-repo-addon: ${name}`); } addon.paths.push(addonPath); - this.files.lib = this.files.lib || {}; + this.files[addonRootDir] = this.files[addonRootDir] || {}; + + let addonJSON = inRepoAddon.toJSON(); + Object.assign(this.files[addonRootDir], addonJSON); + } + + /** + * Add an in-repo engine to this object. The engine files will be written in + * this object's `lib/` directory when this object is written out. + * + * @param {String} name name of the engine + * @param {String} version version of the engine, defaults to '0.0.0' + * @param {Object|Function} optionsOrCallback an object consisting of properties and values to apply when creating + * the engine, or a callback function to pass the newly-created FixturifyProject to. Important options + * include 'allowCachingPerBundle' (true if the addon can be proxied, defaults to false), 'enableLazyLoading' (true + * if the engine is to be lazily loaded, defaults to false) and 'callback' (if you want to include + * a callback function while also specifying other properties.) + * @returns {FixturifyProject} the newly-created addon + */ + addInRepoEngine(name, version = '0.0.0', options = { allowCachingPerBundle: false, enableLazyLoading: false }) { + const callback = (engine) => { + engine.pkg.keywords.push('ember-engine'); + + // call original callback if it exists + if (typeof options.callback === 'function') { + options.callback(engine); + } + }; + + if (options.enableLazyLoading) { + return this.addInRepoAddon(name, version, { + ...options, + additionalContent: 'lazyLoading: { enabled: true },', + callback, + }); + } + + return this.addInRepoAddon(name, version, { ...options, callback }); + } + + /** + * Convert the object into a data structure suitable for passing to `fixturify`. + * + * @param {String} key optional key. If specified, the object will be run through toJSON, then the given + * property extracted and returned. + * @returns {Object} the `toJSON` value of the object (wrapped) or the toJSON value of the specified field + * (not wrapped.) + */ + toJSON(key) { + if (key) { + return super.toJSON(key); + } + + let jsonData = super.toJSON(); + + let scoped = parseScoped(this.name); + + // Allowing for scoped names, get the object in the JSON structure that corresponds + // to this FixturifyProject. + let container = scoped ? jsonData[scoped.scope][scoped.name] : jsonData[this.name]; + + if (this._referenceDependencies || this._referenceDevDependencies) { + let pkg = JSON.parse(container['package.json']); + + if (this._referenceDependencies) { + if (!pkg.dependencies) { + pkg.dependencies = {}; + } + + Object.assign(pkg.dependencies, this._referenceDependencies); + } + + if (this._referenceDevDependencies) { + if (!pkg.devDependencies) { + pkg.devDependencies = {}; + } + + Object.assign(pkg.devDependencies, this._referenceDevDependencies); + } + + container['package.json'] = JSON.stringify(pkg, undefined, 2); + } + + // an optimization to remove any node_modules declaration that has nothing in it, + // to avoid creating extra directories for no reason. + if (container['node_modules'] && Object.keys(container['node_modules']).length === 0) { + delete container['node_modules']; + } - // insert inRepoAddon into files - Object.assign(this.files.lib, inRepoAddon.toJSON()); + return jsonData; } }; diff --git a/tests/helpers/generate-utils.js b/tests/helpers/generate-utils.js index deedf50f2f..fd538ab172 100644 --- a/tests/helpers/generate-utils.js +++ b/tests/helpers/generate-utils.js @@ -1,8 +1,6 @@ 'use strict'; -const RSVP = require('rsvp'); const fs = require('fs-extra'); -let outputFile = RSVP.denodeify(fs.outputFile); const ember = require('./ember'); function inRepoAddon(path) { @@ -10,7 +8,7 @@ function inRepoAddon(path) { } function tempBlueprint() { - return outputFile('blueprints/foo/files/__root__/foos/__name__.js', '/* whoah, empty foo! */'); + return fs.outputFile('blueprints/foo/files/__root__/foos/__name__.js', '/* whoah, empty foo! */'); } module.exports = { diff --git a/tests/helpers/has-global-yarn.js b/tests/helpers/has-global-yarn.js index 1c0a3dc690..679149e760 100644 --- a/tests/helpers/has-global-yarn.js +++ b/tests/helpers/has-global-yarn.js @@ -1,5 +1,6 @@ 'use strict'; +/* eslint-disable node/no-unpublished-require */ const which = require('which'); module.exports = which.sync('yarn', { nothrow: true }); diff --git a/tests/helpers/kill-cli-process.js b/tests/helpers/kill-cli-process.js index 4b59f3380a..aef4d0318c 100644 --- a/tests/helpers/kill-cli-process.js +++ b/tests/helpers/kill-cli-process.js @@ -1,6 +1,12 @@ 'use strict'; -module.exports = function(childProcess) { +module.exports = function (childProcess) { + // Calling `.kill` or `.send` when the process has already exited causes + // errors on Windows, when an `.exitCode` is non-null the process has exited + if (childProcess.exitCode !== null) { + return; + } + if (process.platform === 'win32') { childProcess.send({ kill: true }); } else { diff --git a/tests/helpers/log-on-failure.js b/tests/helpers/log-on-failure.js index a0fbef56ae..7bfca90439 100644 --- a/tests/helpers/log-on-failure.js +++ b/tests/helpers/log-on-failure.js @@ -4,12 +4,12 @@ let logSink; -beforeEach(function() { +beforeEach(function () { logSink = []; }); -afterEach(function() { - if (this.currentTest.state !== 'passed') { +afterEach(function () { + if (this.currentTest && this.currentTest.state !== 'passed') { // It would be preferable to attach the log output to the error object // (this.currentTest.err) and have Mocha report it somehow, so that the // error message and log output show up in the same place. This doesn't diff --git a/tests/helpers/mock-broccoli-watcher.js b/tests/helpers/mock-broccoli-watcher.js index d7ac45363e..0c9556160d 100644 --- a/tests/helpers/mock-broccoli-watcher.js +++ b/tests/helpers/mock-broccoli-watcher.js @@ -1,6 +1,5 @@ 'use strict'; -const RSVP = require('rsvp'); const EventEmitter = require('events').EventEmitter; const path = require('path'); @@ -8,7 +7,7 @@ class MockBroccoliWatcher extends EventEmitter { start() {} then() { - let promise = RSVP.resolve({ + let promise = Promise.resolve({ directory: path.resolve(__dirname, '../fixtures/express-server'), }); return promise.then.apply(promise, arguments); diff --git a/tests/helpers/mock-express-server.js b/tests/helpers/mock-express-server.js index 299d2e8d8b..2999bf9fd9 100644 --- a/tests/helpers/mock-express-server.js +++ b/tests/helpers/mock-express-server.js @@ -1,12 +1,11 @@ 'use strict'; -const RSVP = require('rsvp'); const EventEmitter = require('events').EventEmitter; const path = require('path'); class MockExpressServer extends EventEmitter { then() { - let promise = RSVP.resolve({ + let promise = Promise.resolve({ directory: path.resolve(__dirname, '../fixtures/express-server'), }); return promise.then.apply(promise, arguments); diff --git a/tests/helpers/mock-process.js b/tests/helpers/mock-process.js index 97cbe4b516..15f08718d3 100644 --- a/tests/helpers/mock-process.js +++ b/tests/helpers/mock-process.js @@ -14,7 +14,7 @@ module.exports = class MockProcess extends EventEmitter { new EventEmitter(), { isRaw: process.stdin.isRaw, - setRawMode: flag => { + setRawMode: (flag) => { stdin.isRaw = flag; }, }, diff --git a/tests/helpers/mock-project.js b/tests/helpers/mock-project.js index 18a421bbb1..97655e1aa3 100644 --- a/tests/helpers/mock-project.js +++ b/tests/helpers/mock-project.js @@ -27,7 +27,7 @@ class MockProject extends Project { require(file) { if (file === './server') { - return function() { + return function () { return { listen() { arguments[arguments.length - 1](); @@ -65,10 +65,6 @@ class MockProject extends Project { isEmberCLIAddon() { return false; } - - isModuleUnification() { - return false; - } } module.exports = MockProject; diff --git a/tests/helpers/mock-server-watcher.js b/tests/helpers/mock-server-watcher.js index 45c1c3888b..57519c379f 100644 --- a/tests/helpers/mock-server-watcher.js +++ b/tests/helpers/mock-server-watcher.js @@ -1,12 +1,11 @@ 'use strict'; -const RSVP = require('rsvp'); const EventEmitter = require('events').EventEmitter; const path = require('path'); class MockServerWatcher extends EventEmitter { then() { - let promise = RSVP.resolve({ + let promise = Promise.resolve({ directory: path.resolve(__dirname, '../fixtures/express-server'), }); return promise.then.apply(promise, arguments); diff --git a/tests/helpers/mock-watcher.js b/tests/helpers/mock-watcher.js index 244509a353..bef29f43ac 100644 --- a/tests/helpers/mock-watcher.js +++ b/tests/helpers/mock-watcher.js @@ -1,12 +1,11 @@ 'use strict'; -const RSVP = require('rsvp'); const EventEmitter = require('events').EventEmitter; const path = require('path'); class MockWatcher extends EventEmitter { then() { - let promise = RSVP.resolve({ + let promise = Promise.resolve({ directory: path.resolve(__dirname, '../fixtures/express-server'), }); return promise.then.apply(promise, arguments); diff --git a/tests/helpers/package-cache.js b/tests/helpers/package-cache.js index 757666d437..187762711b 100644 --- a/tests/helpers/package-cache.js +++ b/tests/helpers/package-cache.js @@ -25,6 +25,7 @@ let DEPENDENCY_KEYS = ['dependencies', 'devDependencies', 'peerDependencies', 'o /** * The `bower` command helper. * + * @private * @method bower * @param {String} subcommand The subcommand to be passed into bower. * @param {String} [...arguments] Arguments to be passed into the bower subcommand. @@ -36,6 +37,7 @@ let bower = new CommandGenerator('bower'); /** * The `npm` command helper. * + * @private * @method npm * @param {String} subcommand The subcommand to be passed into npm. * @param {String} [...arguments] Arguments to be passed into the npm subcommand. @@ -47,6 +49,7 @@ let npm = new CommandGenerator('npm'); /** * The `yarn` command helper. * + * @private * @method yarn * @param {String} subcommand The subcommand to be passed into yarn. * @param {String} [...arguments] Arguments to be passed into the yarn subcommand. @@ -87,6 +90,7 @@ let lookups = { * appropriate values based upon the context in which it is used. It's * a convenience helper to avoid littering lookups throughout the code. * + * @private * @method translate * @param {String} type Either 'bower', 'npm', or 'yarn'. * @param {String} lookup Either 'manifest', 'path', or 'upgrade'. @@ -221,6 +225,7 @@ function translate(type, lookup) { * code is responsible for ensuring that the cache size does not * grow unbounded. * + * @private * @class PackageCache * @constructor * @param {String} rootPath Root of the directory for `PackageCache`. @@ -229,13 +234,14 @@ module.exports = class PackageCache { constructor(rootPath) { this.rootPath = rootPath || originalWorkingDirectory; - this._conf = new Configstore('package-cache'); - - // Set it to where we want it to be. - this._conf.path = path.join(this.rootPath, 'tmp', 'package-cache.json'); - - // Initialize. - this._conf.all = this._conf.all; + let configPath = path.join(this.rootPath, 'tmp', 'package-cache.json'); + this._conf = new Configstore( + 'package-cache', + {}, + { + configPath, + } + ); this._cleanDirs(); } @@ -247,6 +253,7 @@ module.exports = class PackageCache { /** * The `__setupForTesting` modifies things in module scope. * + * @private * @method __setupForTesting */ __setupForTesting(stubs) { @@ -257,6 +264,7 @@ module.exports = class PackageCache { /** * The `__resetForTesting` puts things back in module scope. * + * @private * @method __resetForTesting */ __resetForTesting() { @@ -268,6 +276,7 @@ module.exports = class PackageCache { * Configstore and what exists on disk. Non-existent directories * are removed from `this.dirs`. * + * @private * @method _cleanDirs */ _cleanDirs() { @@ -287,6 +296,7 @@ module.exports = class PackageCache { * The `_readManifest` method reads the on-disk manifest for the current * cache and returns its value. * + * @private * @method _readManifest * @param {String} label The label for the cache. * @param {String} type The type of package cache. @@ -318,6 +328,7 @@ module.exports = class PackageCache { * and saves the manifest into it. If it is a yarn package cache it will remove * the existing lock file. * + * @private * @method _writeManifest * @param {String} label The label for the cache. * @param {String} type The type of package cache. @@ -355,6 +366,7 @@ module.exports = class PackageCache { * It is also responsible for removing these links prior to making any changes * to the specified cache. * + * @private * @method _removeLinks * @param {String} label The label for the cache. * @param {String} type The type of package cache. @@ -425,6 +437,7 @@ module.exports = class PackageCache { * * It is also responsible for restoring these links into the `PackageCache`. * + * @private * @method _restoreLinks * @param {String} label The label for the cache. * @param {String} type The type of package cache. @@ -471,6 +484,7 @@ module.exports = class PackageCache { * The `_checkManifest` method compares the desired manifest to that which * exists in the cache. * + * @private * @method _checkManifest * @param {String} label The label for the cache. * @param {String} type The type of package cache. @@ -511,6 +525,7 @@ module.exports = class PackageCache { * The `_install` method installs the contents of the manifest into the * specified package cache. * + * @private * @method _install * @param {String} label The label for the cache. * @param {String} type The type of package cache. @@ -529,6 +544,7 @@ module.exports = class PackageCache { * allowed to drift in a SemVer compatible manner. It ensures that CI is * always running against the latest versions of all dependencies. * + * @private * @method _upgrade * @param {String} label The label for the cache. * @param {String} type The type of package cache. @@ -539,9 +555,9 @@ module.exports = class PackageCache { return; } - // Only way to get repeatable behavior in npm: start over. - // We turn an `_upgrade` task into an `_install` task. - if (type === 'npm') { + if (!this._canUpgrade(label, type)) { + // Only way to get repeatable behavior in npm: start over. + // We turn an `_upgrade` task into an `_install` task. fs.removeSync(path.join(this.dirs[label], translate(type, 'path'))); return this._install(label, type); } @@ -553,6 +569,10 @@ module.exports = class PackageCache { upgraded[label] = true; } + _canUpgrade(label, type) { + return type === 'bower' || (type === 'yarn' && fs.existsSync(path.join(this.dirs[label], 'yarn.lock'))); + } + // PUBLIC API BELOW /** diff --git a/tests/helpers/per-bundle-addon-cache.js b/tests/helpers/per-bundle-addon-cache.js new file mode 100644 index 0000000000..4df1e4b4d6 --- /dev/null +++ b/tests/helpers/per-bundle-addon-cache.js @@ -0,0 +1,192 @@ +'use strict'; + +const FixturifyProject = require('./fixturify-project'); +const { TARGET_INSTANCE } = require('../../lib/models/per-bundle-addon-cache/target-instance'); +const isLazyEngine = require('../../lib/utilities/is-lazy-engine'); + +/** + * This collects all addons by name within a given host; it stops traversing when it + * encounters another host (i.e., a lazy engine). Within a given host we should expect + * at most 1 real addon, otherwise this is an error condition. We otherwise add all + * proxies to `config.proxies` + * + * @name getAllAddonsByNameWithinHost + * @param {Project|Addon} projectOrAddon + * @param {string} addonName + * @param {Object} [config] + * @returns {{proxies: Proxy[], realAddon: Addon}} + */ +function getAllAddonsByNameWithinHost(projectOrAddon, addonName, config = { proxies: [] }) { + if (!config.originalHost) { + config.originalHost = projectOrAddon; + } + + projectOrAddon.addons.forEach((addon) => { + if (addon.name === addonName) { + if (config.realAddon && !addon[TARGET_INSTANCE]) { + throw new Error( + `The real addon (\`${addon.name}\`) has already been set for a given host (\`${ + typeof config.originalHost.name === 'function' ? config.originalHost.name() : config.originalHost.name + }\`); the proxy for addon caching is not working correctly` + ); + } + + if (addon[TARGET_INSTANCE]) { + config.proxies.push(addon); + } else { + config.realAddon = addon; + } + } + + // stop traversing within another host + if (!isLazyEngine(addon)) { + getAllAddonsByNameWithinHost(addon, addonName, config); + } + }); + + return config; +} + +/** + * Returns whether all instances within a given host are equal (i.e., that there's a single + * "real addon") and all proxies have the `TARGET_INSTANCE` property that's strictly equal to + * the aforementioned real addon + * + * @name areAllInstancesEqualWithinHost + * @param {Project|Addon} projectOrAddon + * @param {string} addonName + * @returns {boolean} + */ +function areAllInstancesEqualWithinHost(projectOrAddon, addonName) { + const { realAddon, proxies } = getAllAddonsByNameWithinHost(projectOrAddon, addonName); + return proxies.length > 0 && proxies.every((proxy) => proxy[TARGET_INSTANCE] === realAddon); +} + +/** + * For a given project/addon, this counts addon instances within said project/addon; + * specifically we're interested in the number of "real" addon instances, and proxy + * objects. + * + * @name countAddons + * @param {Project|Addon} projectOrAddon + * @param {Object} [config] + * @returns {{byName: Object, proxyCount: number, realAddonInstanceCount: number}} + */ +function countAddons(projectOrAddon, config = { byName: {}, proxyCount: 0, realAddonInstanceCount: 0 }) { + projectOrAddon.addons.forEach((addon) => { + const addonName = addon.name; + + if (!config.byName[addonName]) { + config.byName[addonName] = { + addons: [], + proxyCount: 0, + realAddonInstanceCount: 0, + }; + } + + if (addon[TARGET_INSTANCE]) { + config.proxyCount++; + config.byName[addonName].proxyCount++; + } else { + config.realAddonInstanceCount++; + config.byName[addonName].realAddonInstanceCount++; + } + + config.byName[addonName].addons.push(addon); + countAddons(addon, config); + }); + + return config; +} + +/** + * Generate the file structure used for the cache-bundle-hosts and enable-cache tests. + * Puts it into the usual temporary location defined by ECFP. + * + * In this fixture, all the addon definitions are to be held in PROJECT/lib, even + * though the project itself doesn't directly depend on a few of them. This is so + * it's easier to create a single reference to a particular addon path, to enable + * the proxy code to function. + * + * @name createStandardCacheFixture + */ +function createStandardCacheFixture() { + let project = new FixturifyProject('test-ember-project', '1.0.0'); + + project.addInRepoAddon('test-addon-a', '1.0.0', { + callback: (addonA) => { + addonA.addInRepoAddon('test-addon-dep', '1.0.0'); + + // At this point, TAD has been run through toJSON inside of TAA. + // TAD itself has no issues. + // in TAA, we want to store all the inrepo addons, at any level, in + // PROJ/lib, so move TAD from TAA and change its path in TAA. + addonA.pkg['ember-addon'].paths = ['../test-addon-dep']; + project.files.lib = project.files.lib || {}; + project.files.lib['test-addon-dep'] = addonA.files.lib['test-addon-dep']; + delete addonA.files.lib; + }, + }); + + project.addInRepoEngine('lazy-engine-a', '1.0.0', { + enableLazyLoading: true, + callback: (lazyEngineA) => { + lazyEngineA.addInRepoAddon('test-engine-dep', '1.0.0'); + + // Similar to above + lazyEngineA.pkg['ember-addon'].paths = ['../test-engine-dep']; + project.files.lib['test-engine-dep'] = lazyEngineA.files.lib['test-engine-dep']; + delete lazyEngineA.files.lib; + }, + }); + + project.addInRepoEngine('lazy-engine-b', '1.0.0', { + enableLazyLoading: true, + callback: (lazyEngineB) => { + // These two addon definitions have already been moved to project, so just + // fix the ember-addon.paths and remove the files.lib entry. + lazyEngineB.pkg['ember-addon'].paths = ['../test-engine-dep', '../test-addon-dep']; + delete lazyEngineB.files.lib; + }, + }); + + project.addInRepoEngine('regular-engine-c', '1.0.0', { + callback: (regularEngineC) => { + regularEngineC.pkg['ember-addon'].paths = ['../test-engine-dep']; + delete regularEngineC.files.lib; + }, + }); + + return project; +} + +/** + * For help with testing, given a bundleHostName and an addon name, return + * a list of the addon cache entries that have that addon name. + * + * @name findAddonCacheEntriesByName + */ +function findAddonCacheEntriesByName(perBundleAddonCacheInstance, bundleHostPkgInfo, addonName) { + let bundleHostCacheEntry = perBundleAddonCacheInstance.bundleHostCache.get(bundleHostPkgInfo); + + if (!bundleHostCacheEntry) { + return null; + } + + let addonInstanceCache = bundleHostCacheEntry.addonInstanceCache; + let cacheEntries = Array.from(addonInstanceCache.values()); + let addonEntries = cacheEntries.filter((entry) => entry[TARGET_INSTANCE].name === addonName); + + return addonEntries; +} + +/** + * Simple utilities to help test the PerBundleAddonCache feature. + */ +module.exports = { + findAddonCacheEntriesByName, + createStandardCacheFixture, + getAllAddonsByNameWithinHost, + areAllInstancesEqualWithinHost, + countAddons, +}; diff --git a/tests/helpers/process-help-string.js b/tests/helpers/process-help-string.js index c596ecea30..ebb6deae3c 100644 --- a/tests/helpers/process-help-string.js +++ b/tests/helpers/process-help-string.js @@ -5,7 +5,7 @@ const chalk = require('chalk'); // eslint-disable-next-line node/no-unpublished-require const stripAnsi = require('strip-ansi'); -module.exports = function(helpString) { +module.exports = function (helpString) { // currently windows if (chalk.supportsColor) { return helpString; diff --git a/tests/helpers/proxy-server.js b/tests/helpers/proxy-server.js index dfda5e939b..50aef85b34 100644 --- a/tests/helpers/proxy-server.js +++ b/tests/helpers/proxy-server.js @@ -1,6 +1,7 @@ 'use strict'; const http = require('http'); +/* eslint-disable node/no-unpublished-require */ const WebSocketServer = require('websocket').server; class ProxyServer { @@ -21,15 +22,15 @@ class ProxyServer { }); let websocketEvents = (this.websocketEvents = []); - wsServer.on('connect', connection => { + wsServer.on('connect', (connection) => { websocketEvents.push('connect'); - connection.on('message', message => { + connection.on('message', (message) => { websocketEvents.push(`message: ${message.utf8Data}`); connection.sendUTF(message.utf8Data); }); - connection.on('error', error => { + connection.on('error', (error) => { websocketEvents.push(`error: ${error}`); }); diff --git a/tests/helpers/run-command.js b/tests/helpers/run-command.js index 7e99ce231a..c2942523ab 100644 --- a/tests/helpers/run-command.js +++ b/tests/helpers/run-command.js @@ -1,13 +1,19 @@ 'use strict'; -const Promise = require('rsvp').Promise; const chalk = require('chalk'); const spawn = require('child_process').spawn; const defaults = require('ember-cli-lodash-subset').defaults; const killCliProcess = require('./kill-cli-process'); const logOnFailure = require('./log-on-failure'); let debug = require('heimdalljs-logger')('run-command'); +const { captureExit, onExit } = require('capture-exit'); +// when running the full test suite, `process.exit` has already been captured +// however, when running specific files (e.g. `mocha some/path/to/file.js`) +// exit may not be captured before `runCommand` attempts to call `onExit` +captureExit(); + +let RUNS = []; module.exports = function run(/* command, args, options */) { let command = arguments[0]; let args = Array.prototype.slice.call(arguments, 1); @@ -40,13 +46,14 @@ module.exports = function run(/* command, args, options */) { }, }); - return new Promise(function(resolve, reject) { + let child; + const promise = new Promise(function (resolve, reject) { options.log(` Running: ${command} ${args.join(' ')} in: ${process.cwd()}`); let opts = {}; + args = [`--unhandled-rejections=strict`, `${command}`].concat(args); + command = 'node'; if (process.platform === 'win32') { - args = [`"${command}"`].concat(args); - command = 'node'; opts.windowsVerbatimArguments = true; opts.stdio = [null, null, null, 'ipc']; } @@ -54,38 +61,19 @@ module.exports = function run(/* command, args, options */) { opts.env = defaults(options.env, process.env); } - debug.info('command: %s, args: %o', command, args); - let child = spawn(command, args, opts); + debug.info('runCommand: %s, args: %o', command, args); + child = spawn(command, args, opts); + RUNS.push(child); + // ensure we tear down the child process on exit; + onExit(() => killCliProcess(child)); + let result = { output: [], errors: [], code: null, }; - if (options.onChildSpawned) { - let onChildSpawnedPromise = new Promise(function(childSpawnedResolve, childSpawnedReject) { - try { - options.onChildSpawned(child).then(childSpawnedResolve, childSpawnedReject); - } catch (err) { - childSpawnedReject(err); - } - }); - onChildSpawnedPromise.then( - function() { - if (options.killAfterChildSpawnedPromiseResolution) { - killCliProcess(child); - } - }, - function(err) { - result.testingError = err; - if (options.killAfterChildSpawnedPromiseResolution) { - killCliProcess(child); - } - } - ); - } - - child.stdout.on('data', function(data) { + child.stdout.on('data', function (data) { let string = data.toString(); options.onOutput(string, child); @@ -93,7 +81,7 @@ module.exports = function run(/* command, args, options */) { result.output.push(string); }); - child.stderr.on('data', function(data) { + child.stderr.on('data', function (data) { let string = data.toString(); options.onError(string, child); @@ -101,7 +89,7 @@ module.exports = function run(/* command, args, options */) { result.errors.push(string); }); - child.on('close', function(code, signal) { + child.on('close', function (code, signal) { result.code = code; result.signal = signal; @@ -112,4 +100,22 @@ module.exports = function run(/* command, args, options */) { } }); }); + + promise.kill = function () { + killCliProcess(child); + }; + + return promise; +}; + +module.exports.killAll = function () { + RUNS.forEach((run) => { + try { + killCliProcess(run); + } catch (e) { + console.error(e); + // during teardown, issues can arise, but teardown must complete it's operation + } + }); + RUNS.length = 0; }; diff --git a/tests/helpers/tmp.js b/tests/helpers/tmp.js index 3a213b96a8..17774f4001 100644 --- a/tests/helpers/tmp.js +++ b/tests/helpers/tmp.js @@ -1,20 +1,18 @@ 'use strict'; const fs = require('fs-extra'); -const RSVP = require('rsvp'); -let remove = RSVP.denodeify(fs.remove); let root = process.cwd(); -module.exports.setup = function(path) { +module.exports.setup = function (path) { process.chdir(root); - return remove(path).then(function() { + return fs.remove(path).then(function () { fs.mkdirsSync(path); }); }; -module.exports.teardown = function(path) { +module.exports.teardown = function (path) { process.chdir(root); - return remove(path); + return fs.remove(path); }; diff --git a/tests/integration/models/blueprint-test.js b/tests/integration/models/blueprint-test.js index 013d272999..c0057af883 100644 --- a/tests/integration/models/blueprint-test.js +++ b/tests/integration/models/blueprint-test.js @@ -8,17 +8,17 @@ const expect = require('chai').expect; const path = require('path'); const glob = require('glob'); const walkSync = require('walk-sync'); -const RSVP = require('rsvp'); +const util = require('util'); + const EOL = require('os').EOL; let root = process.cwd(); -let tmproot = path.join(root, 'tmp'); +let tempRoot = path.join(root, 'tmp'); const SilentError = require('silent-error'); const mkTmpDirIn = require('../../../lib/utilities/mk-tmp-dir-in'); const td = require('testdouble'); const Blueprint = require('../../../lib/models/blueprint'); -const Promise = RSVP.Promise; -const remove = RSVP.denodeify(fs.remove); +const remove = util.promisify(fs.remove); let localsCalled; let normalizeEntityNameCalled; @@ -110,22 +110,22 @@ let basicBlueprintFilesAfterBasic2 = [ 'test.txt', ]; -describe('Blueprint', function() { +describe('Blueprint', function () { const BasicBlueprintClass = require(basicBlueprint); let InstrumentedBasicBlueprint = BasicBlueprintClass.extend(instrumented); - beforeEach(function() { + beforeEach(function () { resetCalled(); }); - afterEach(function() { + afterEach(function () { td.reset(); }); - describe('.fileMapTokens', function() { - it('adds additional tokens from fileMapTokens hook', function() { + describe('.fileMapTokens', function () { + it('adds additional tokens from fileMapTokens hook', function () { let blueprint = Blueprint.lookup(basicBlueprint); - blueprint.fileMapTokens = function() { + blueprint.fileMapTokens = function () { return { __foo__() { return 'foo'; @@ -137,8 +137,8 @@ describe('Blueprint', function() { }); }); - describe('.generateFileMap', function() { - it('should not have locals in the fileMap', function() { + describe('.generateFileMap', function () { + it('should not have locals in the fileMap', function () { let blueprint = Blueprint.lookup(basicBlueprint); let fileMapVariables = { @@ -162,8 +162,8 @@ describe('Blueprint', function() { }); }); - describe('.lookup', function() { - it('uses an explicit path if one is given', function() { + describe('.lookup', function () { + it('uses an explicit path if one is given', function () { const expectedClass = require(basicBlueprint); let blueprint = Blueprint.lookup(basicBlueprint); @@ -172,7 +172,7 @@ describe('Blueprint', function() { expect(blueprint instanceof expectedClass).to.equal(true); }); - it('finds blueprints within given lookup paths', function() { + it('finds blueprints within given lookup paths', function () { const expectedClass = require(basicBlueprint); let blueprint = Blueprint.lookup('basic', { paths: [fixtureBlueprints], @@ -183,7 +183,7 @@ describe('Blueprint', function() { expect(blueprint instanceof expectedClass).to.equal(true); }); - it('finds blueprints in the ember-cli package', function() { + it('finds blueprints in the ember-cli package', function () { let expectedPath = path.resolve(defaultBlueprints, 'app'); let expectedClass = Blueprint; @@ -194,7 +194,7 @@ describe('Blueprint', function() { expect(blueprint instanceof expectedClass).to.equal(true); }); - it('can instantiate a blueprint that exports an object instead of a constructor', function() { + it('can instantiate a blueprint that exports an object instead of a constructor', function () { let blueprint = Blueprint.lookup('exporting-object', { paths: [fixtureBlueprints], }); @@ -203,13 +203,13 @@ describe('Blueprint', function() { expect(blueprint instanceof Blueprint).to.equal(true); }); - it('throws an error if no blueprint is found', function() { + it('throws an error if no blueprint is found', function () { expect(() => { Blueprint.lookup('foo'); }).to.throw('Unknown blueprint: foo'); }); - it('returns undefined if no blueprint is found and ignoredMissing is passed', function() { + it('returns undefined if no blueprint is found and ignoredMissing is passed', function () { let blueprint = Blueprint.lookup('foo', { ignoreMissing: true, }); @@ -218,395 +218,393 @@ describe('Blueprint', function() { }); }); - it('exists', function() { + it('exists', function () { let blueprint = new Blueprint(basicBlueprint); expect(!!blueprint).to.equal(true); }); - it('derives name from path', function() { + it('derives name from path', function () { let blueprint = new Blueprint(basicBlueprint); expect(blueprint.name).to.equal('basic'); }); - describe('filesPath', function() { - it('returns the blueprints default files path', function() { + describe('filesPath', function () { + it('returns the blueprints default files path', function () { let blueprint = new Blueprint(basicBlueprint); expect(blueprint.filesPath()).to.equal(path.join(basicBlueprint, 'files')); }); }); - describe('basic blueprint installation', function() { + describe('basic blueprint installation', function () { let blueprint; let ui; let project; let options; let tmpdir; - beforeEach(function() { - return mkTmpDirIn(tmproot).then(function(dir) { - tmpdir = dir; - blueprint = new InstrumentedBasicBlueprint(basicBlueprint); - ui = new MockUI(); - td.replace(ui, 'prompt'); - - project = new MockProject(); - options = { - ui, - project, - target: tmpdir, - }; - }); + beforeEach(async function () { + const dir = await mkTmpDirIn(tempRoot); + tmpdir = dir; + blueprint = new InstrumentedBasicBlueprint(basicBlueprint); + ui = new MockUI(); + td.replace(ui, 'prompt'); + + project = new MockProject(); + options = { + ui, + project, + target: tmpdir, + }; }); - afterEach(function() { - return remove(tmproot); + afterEach(async function () { + await remove(tempRoot); }); - it('installs basic files', function() { + it('installs basic files', async function () { expect(!!blueprint).to.equal(true); - return blueprint.install(options).then(function() { - let actualFiles = walkSync(tmpdir).sort(); - let output = ui.output.trim().split(EOL); + await blueprint.install(options); + + let actualFiles = walkSync(tmpdir).sort(); + let output = ui.output.trim().split(EOL); + + expect(output.shift()).to.match(/^installing/); + expect(output.shift()).to.match(/create.* .ember-cli/); + expect(output.shift()).to.match(/create.* .gitignore/); + expect(output.shift()).to.match(/create.* app[/\\]basics[/\\]mock-project.txt/); + expect(output.shift()).to.match(/create.* bar/); + expect(output.shift()).to.match(/create.* file-to-remove.txt/); + expect(output.shift()).to.match(/create.* foo.txt/); + expect(output.shift()).to.match(/create.* test.txt/); + expect(output.length).to.equal(0); + expect(actualFiles).to.deep.equal(basicBlueprintFiles); + expect(() => { + fs.readFile(path.join(tmpdir, 'test.txt'), 'utf-8', function (err, content) { + if (err) { + throw 'error'; + } + expect(content).to.match(/I AM TESTY/); + }); + }).not.to.throw(); + }); - expect(output.shift()).to.match(/^installing/); - expect(output.shift()).to.match(/create.* .ember-cli/); - expect(output.shift()).to.match(/create.* .gitignore/); - expect(output.shift()).to.match(/create.* app[/\\]basics[/\\]mock-project.txt/); - expect(output.shift()).to.match(/create.* bar/); - expect(output.shift()).to.match(/create.* file-to-remove.txt/); - expect(output.shift()).to.match(/create.* foo.txt/); - expect(output.shift()).to.match(/create.* test.txt/); - expect(output.length).to.equal(0); + it('re-installing identical files', async function () { + await blueprint.install(options); - expect(actualFiles).to.deep.equal(basicBlueprintFiles); + let output = ui.output.trim().split(EOL); + ui.output = ''; - expect(() => { - fs.readFile(path.join(tmpdir, 'test.txt'), 'utf-8', function(err, content) { - if (err) { - throw 'error'; - } - expect(content).to.match(/I AM TESTY/); - }); - }).not.to.throw(); - }); - }); + expect(output.shift()).to.match(/^installing/); + expect(output.shift()).to.match(/create.* .ember-cli/); + expect(output.shift()).to.match(/create.* .gitignore/); + expect(output.shift()).to.match(/create.* app[/\\]basics[/\\]mock-project.txt/); + expect(output.shift()).to.match(/create.* bar/); + expect(output.shift()).to.match(/create.* file-to-remove.txt/); + expect(output.shift()).to.match(/create.* foo.txt/); + expect(output.shift()).to.match(/create.* test.txt/); + expect(output.length).to.equal(0); - it('re-installing identical files', function() { - return blueprint - .install(options) - .then(function() { - let output = ui.output.trim().split(EOL); - ui.output = ''; - - expect(output.shift()).to.match(/^installing/); - expect(output.shift()).to.match(/create.* .ember-cli/); - expect(output.shift()).to.match(/create.* .gitignore/); - expect(output.shift()).to.match(/create.* app[/\\]basics[/\\]mock-project.txt/); - expect(output.shift()).to.match(/create.* bar/); - expect(output.shift()).to.match(/create.* file-to-remove.txt/); - expect(output.shift()).to.match(/create.* foo.txt/); - expect(output.shift()).to.match(/create.* test.txt/); - expect(output.length).to.equal(0); - - return blueprint.install(options); - }) - .then(function() { - let actualFiles = walkSync(tmpdir).sort(); - let output = ui.output.trim().split(EOL); - - expect(output.shift()).to.match(/^installing/); - expect(output.shift()).to.match(/identical.* .ember-cli/); - expect(output.shift()).to.match(/identical.* .gitignore/); - expect(output.shift()).to.match(/identical.* app[/\\]basics[/\\]mock-project.txt/); - expect(output.shift()).to.match(/identical.* bar/); - expect(output.shift()).to.match(/identical.* file-to-remove.txt/); - expect(output.shift()).to.match(/identical.* foo.txt/); - expect(output.shift()).to.match(/identical.* test.txt/); - expect(output.length).to.equal(0); - - expect(actualFiles).to.deep.equal(basicBlueprintFiles); - }); + await blueprint.install(options); + + let actualFiles = walkSync(tmpdir).sort(); + output = ui.output.trim().split(EOL); + + expect(output.shift()).to.match(/^installing/); + expect(output.shift()).to.match(/identical.* .ember-cli/); + expect(output.shift()).to.match(/identical.* .gitignore/); + expect(output.shift()).to.match(/identical.* app[/\\]basics[/\\]mock-project.txt/); + expect(output.shift()).to.match(/identical.* bar/); + expect(output.shift()).to.match(/identical.* file-to-remove.txt/); + expect(output.shift()).to.match(/identical.* foo.txt/); + expect(output.shift()).to.match(/identical.* test.txt/); + expect(output.length).to.equal(0); + + expect(actualFiles).to.deep.equal(basicBlueprintFiles); }); - it('re-installing conflicting files', function() { + it('re-installing conflicting files', async function () { td.when(ui.prompt(td.matchers.anything())).thenReturn( Promise.resolve({ answer: 'skip' }), Promise.resolve({ answer: 'overwrite' }) ); - return blueprint - .install(options) - .then(function() { - let output = ui.output.trim().split(EOL); - ui.output = ''; - - expect(output.shift()).to.match(/^installing/); - expect(output.shift()).to.match(/create.* .ember-cli/); - expect(output.shift()).to.match(/create.* .gitignore/); - expect(output.shift()).to.match(/create.* app[/\\]basics[/\\]mock-project.txt/); - expect(output.shift()).to.match(/create.* bar/); - expect(output.shift()).to.match(/create.* file-to-remove.txt/); - expect(output.shift()).to.match(/create.* foo.txt/); - expect(output.shift()).to.match(/create.* test.txt/); - expect(output.length).to.equal(0); - - let blueprintNew = Blueprint.lookup(basicNewBlueprint); - - return blueprintNew.install(options); - }) - .then(function() { - td.verify(ui.prompt(td.matchers.anything()), { times: 2 }); - - let actualFiles = walkSync(tmpdir).sort(); - // Prompts contain \n EOL - // Split output on \n since it will have the same affect as spliting on OS specific EOL - let output = ui.output.trim().split('\n'); - expect(output.shift()).to.match(/^installing/); - expect(output.shift()).to.match(/identical.* \.ember-cli/); - expect(output.shift()).to.match(/identical.* \.gitignore/); - expect(output.shift()).to.match(/skip.* foo.txt/); - expect(output.shift()).to.match(/overwrite.* test.txt/); - expect(output.shift()).to.match(/remove.* file-to-remove.txt/); - expect(output.length).to.equal(0); - - expect(actualFiles).to.deep.equal(basicBlueprintFilesAfterBasic2); - }); + await blueprint.install(options); + + let output = ui.output.trim().split(EOL); + ui.output = ''; + + expect(output.shift()).to.match(/^installing/); + expect(output.shift()).to.match(/create.* .ember-cli/); + expect(output.shift()).to.match(/create.* .gitignore/); + expect(output.shift()).to.match(/create.* app[/\\]basics[/\\]mock-project.txt/); + expect(output.shift()).to.match(/create.* bar/); + expect(output.shift()).to.match(/create.* file-to-remove.txt/); + expect(output.shift()).to.match(/create.* foo.txt/); + expect(output.shift()).to.match(/create.* test.txt/); + expect(output.length).to.equal(0); + + let blueprintNew = Blueprint.lookup(basicNewBlueprint); + + await blueprintNew.install(options); + + td.verify(ui.prompt(td.matchers.anything()), { times: 2 }); + + let actualFiles = walkSync(tmpdir).sort(); + // Prompts contain \n EOL + // Split output on \n since it will have the same affect as spliting on OS specific EOL + output = ui.output.trim().split('\n'); + expect(output.shift()).to.match(/^installing/); + expect(output.shift()).to.match(/identical.* \.ember-cli/); + expect(output.shift()).to.match(/identical.* \.gitignore/); + expect(output.shift()).to.match(/skip.* foo.txt/); + expect(output.shift()).to.match(/overwrite.* test.txt/); + expect(output.shift()).to.match(/remove.* file-to-remove.txt/); + expect(output.length).to.equal(0); + + expect(actualFiles).to.deep.equal(basicBlueprintFilesAfterBasic2); }); - it('installs path globPattern file', function() { + it('installs path globPattern file', async function () { options.targetFiles = ['foo.txt']; - return blueprint.install(options).then(function() { - let actualFiles = walkSync(tmpdir).sort(); - let globFiles = glob - .sync('**/foo.txt', { - cwd: tmpdir, - dot: true, - mark: true, - strict: true, - }) - .sort(); - let output = ui.output.trim().split(EOL); + await blueprint.install(options); + let actualFiles = walkSync(tmpdir).sort(); + let globFiles = glob + .sync('**/foo.txt', { + cwd: tmpdir, + dot: true, + mark: true, + strict: true, + }) + .sort(); + let output = ui.output.trim().split(EOL); - expect(output.shift()).to.match(/^installing/); - expect(output.shift()).to.match(/create.* foo.txt/); - expect(output.length).to.equal(0); + expect(output.shift()).to.match(/^installing/); + expect(output.shift()).to.match(/create.* foo.txt/); + expect(output.length).to.equal(0); - expect(actualFiles).to.deep.equal(globFiles); - }); + expect(actualFiles).to.deep.equal(globFiles); }); - it('installs multiple globPattern files', function() { + it('installs multiple globPattern files', async function () { options.targetFiles = ['foo.txt', 'test.txt']; - return blueprint.install(options).then(function() { - let actualFiles = walkSync(tmpdir).sort(); - let globFiles = glob - .sync(path.join('**', '*.txt'), { - cwd: tmpdir, - dot: true, - mark: true, - strict: true, - }) - .sort(); + await blueprint.install(options); + let actualFiles = walkSync(tmpdir).sort(); + let globFiles = glob + .sync(path.join('**', '*.txt'), { + cwd: tmpdir, + dot: true, + mark: true, + strict: true, + }) + .sort(); + let output = ui.output.trim().split(EOL); + + expect(output.shift()).to.match(/^installing/); + expect(output.shift()).to.match(/create.* foo.txt/); + expect(output.shift()).to.match(/create.* test.txt/); + expect(output.length).to.equal(0); + + expect(actualFiles).to.deep.equal(globFiles); + }); + + describe('called on an existing project', function () { + beforeEach(function () { + Blueprint.ignoredUpdateFiles.push('foo.txt'); + }); + + it('ignores files in ignoredUpdateFiles', async function () { + td.when(ui.prompt(), { ignoreExtraArgs: true }).thenReturn(Promise.resolve({ answer: 'skip' })); + await blueprint.install(options); + let output = ui.output.trim().split(EOL); + ui.output = ''; expect(output.shift()).to.match(/^installing/); + expect(output.shift()).to.match(/create.* .ember-cli/); + expect(output.shift()).to.match(/create.* .gitignore/); + expect(output.shift()).to.match(/create.* app[/\\]basics[/\\]mock-project.txt/); + expect(output.shift()).to.match(/create.* bar/); + expect(output.shift()).to.match(/create.* file-to-remove.txt/); expect(output.shift()).to.match(/create.* foo.txt/); expect(output.shift()).to.match(/create.* test.txt/); expect(output.length).to.equal(0); - expect(actualFiles).to.deep.equal(globFiles); - }); - }); + let blueprintNew = new Blueprint(basicNewBlueprint); - describe('called on an existing project', function() { - beforeEach(function() { - Blueprint.ignoredUpdateFiles.push('foo.txt'); - }); + options.project.isEmberCLIProject = function () { + return true; + }; - it('ignores files in ignoredUpdateFiles', function() { - td.when(ui.prompt(), { ignoreExtraArgs: true }).thenReturn(Promise.resolve({ answer: 'skip' })); + await blueprintNew.install(options); + + let actualFiles = walkSync(tmpdir).sort(); + // Prompts contain \n EOL + // Split output on \n since it will have the same affect as spliting on OS specific EOL + output = ui.output.trim().split('\n'); + expect(output.shift()).to.match(/^installing/); + expect(output.shift()).to.match(/identical.* \.ember-cli/); + expect(output.shift()).to.match(/identical.* \.gitignore/); + expect(output.shift()).to.match(/skip.* test.txt/); + expect(output.length).to.equal(0); - return blueprint - .install(options) - .then(function() { - let output = ui.output.trim().split(EOL); - ui.output = ''; - - expect(output.shift()).to.match(/^installing/); - expect(output.shift()).to.match(/create.* .ember-cli/); - expect(output.shift()).to.match(/create.* .gitignore/); - expect(output.shift()).to.match(/create.* app[/\\]basics[/\\]mock-project.txt/); - expect(output.shift()).to.match(/create.* bar/); - expect(output.shift()).to.match(/create.* file-to-remove.txt/); - expect(output.shift()).to.match(/create.* foo.txt/); - expect(output.shift()).to.match(/create.* test.txt/); - expect(output.length).to.equal(0); - - let blueprintNew = new Blueprint(basicNewBlueprint); - - options.project.isEmberCLIProject = function() { - return true; - }; - - return blueprintNew.install(options); - }) - .then(function() { - let actualFiles = walkSync(tmpdir).sort(); - // Prompts contain \n EOL - // Split output on \n since it will have the same affect as spliting on OS specific EOL - let output = ui.output.trim().split('\n'); - expect(output.shift()).to.match(/^installing/); - expect(output.shift()).to.match(/identical.* \.ember-cli/); - expect(output.shift()).to.match(/identical.* \.gitignore/); - expect(output.shift()).to.match(/skip.* test.txt/); - expect(output.length).to.equal(0); - - expect(actualFiles).to.deep.equal(basicBlueprintFiles); - }); + expect(actualFiles).to.deep.equal(basicBlueprintFiles); }); }); - describe('called on a new project', function() { - beforeEach(function() { + describe('called on a new project', function () { + beforeEach(function () { Blueprint.ignoredUpdateFiles.push('foo.txt'); }); - it('does not ignores files in ignoredUpdateFiles', function() { + it('does not ignores files in ignoredUpdateFiles', async function () { td.when(ui.prompt(), { ignoreExtraArgs: true }).thenReturn(Promise.resolve({ answer: 'skip' })); + await blueprint.install(options); + + let output = ui.output.trim().split(EOL); + ui.output = ''; - return blueprint - .install(options) - .then(function() { - let output = ui.output.trim().split(EOL); - ui.output = ''; - - expect(output.shift()).to.match(/^installing/); - expect(output.shift()).to.match(/create.* .ember-cli/); - expect(output.shift()).to.match(/create.* .gitignore/); - expect(output.shift()).to.match(/create.* app[/\\]basics[/\\]mock-project.txt/); - expect(output.shift()).to.match(/create.* bar/); - expect(output.shift()).to.match(/create.* file-to-remove.txt/); - expect(output.shift()).to.match(/create.* foo.txt/); - expect(output.shift()).to.match(/create.* test.txt/); - expect(output.length).to.equal(0); - - let blueprintNew = new Blueprint(basicNewBlueprint); - - options.project.isEmberCLIProject = function() { - return false; - }; - - return blueprintNew.install(options); - }) - .then(function() { - let actualFiles = walkSync(tmpdir).sort(); - // Prompts contain \n EOL - // Split output on \n since it will have the same affect as spliting on OS specific EOL - let output = ui.output.trim().split('\n'); - expect(output.shift()).to.match(/^installing/); - expect(output.shift()).to.match(/identical.* \.ember-cli/); - expect(output.shift()).to.match(/identical.* \.gitignore/); - expect(output.shift()).to.match(/skip.* foo.txt/); - expect(output.shift()).to.match(/skip.* test.txt/); - expect(output.length).to.equal(0); - - expect(actualFiles).to.deep.equal(basicBlueprintFiles); - }); + expect(output.shift()).to.match(/^installing/); + expect(output.shift()).to.match(/create.* .ember-cli/); + expect(output.shift()).to.match(/create.* .gitignore/); + expect(output.shift()).to.match(/create.* app[/\\]basics[/\\]mock-project.txt/); + expect(output.shift()).to.match(/create.* bar/); + expect(output.shift()).to.match(/create.* file-to-remove.txt/); + expect(output.shift()).to.match(/create.* foo.txt/); + expect(output.shift()).to.match(/create.* test.txt/); + expect(output.length).to.equal(0); + + let blueprintNew = new Blueprint(basicNewBlueprint); + + options.project.isEmberCLIProject = function () { + return false; + }; + + await blueprintNew.install(options); + + let actualFiles = walkSync(tmpdir).sort(); + // Prompts contain \n EOL + // Split output on \n since it will have the same affect as spliting on OS specific EOL + output = ui.output.trim().split('\n'); + expect(output.shift()).to.match(/^installing/); + expect(output.shift()).to.match(/identical.* \.ember-cli/); + expect(output.shift()).to.match(/identical.* \.gitignore/); + expect(output.shift()).to.match(/skip.* foo.txt/); + expect(output.shift()).to.match(/skip.* test.txt/); + expect(output.length).to.equal(0); + + expect(actualFiles).to.deep.equal(basicBlueprintFiles); }); }); - it('throws error when there is a trailing forward slash in entityName', function() { - options.entity = { name: 'foo/' }; - expect(() => { - blueprint.install(options); - }).to.throw( - /You specified "foo\/", but you can't use a trailing slash as an entity name with generators. Please re-run the command with "foo"./ - ); - - options.entity = { name: 'foo\\' }; - expect(() => { - blueprint.install(options); - }).to.throw( - /You specified "foo\\", but you can't use a trailing slash as an entity name with generators. Please re-run the command with "foo"./ - ); + it('throws error when there is a trailing forward slash in entityName', async function () { + try { + options.entity = { name: 'foo/' }; + await blueprint.install(options); + expect.fail('expected rejection'); + } catch (e) { + expect(e.message).to.match( + /You specified "foo\/", but you can't use a trailing slash as an entity name with generators. Please re-run the command with "foo"./ + ); + } + + try { + options.entity = { name: 'foo\\' }; + await blueprint.install(options); + expect.fail('expected rejection'); + } catch (e) { + expect(e.message).to.match( + /You specified "foo\\", but you can't use a trailing slash as an entity name with generators. Please re-run the command with "foo"./ + ); + } options.entity = { name: 'foo' }; - expect(() => { - blueprint.install(options); - }).not.to.throw(); + await blueprint.install(options); }); - it('throws error when an entityName is not provided', function() { - options.entity = {}; - expect(() => { - blueprint.install(options); - }).to.throw(SilentError, /The `ember generate ` command requires an entity name to be specified./); + it('throws error when an entityName is not provided', async function () { + try { + options.entity = {}; + await blueprint.install(options); + expect.fail('expected rejection)'); + } catch (e) { + expect(e).to.be.instanceof(SilentError); + expect(e.message).to.match( + /The `ember generate ` command requires an entity name to be specified./ + ); + } }); - it('throws error when an action does not exist', function() { + it('throws error when an action does not exist', async function () { blueprint._actions = {}; - return blueprint.install(options).catch(function(err) { - expect(err.message).to.equal('Tried to call action "write" but it does not exist'); + try { + await blueprint.install(options); + expect.fail('expected rejection'); + } catch (e) { + expect(e.message).to.equal('Tried to call action "write" but it does not exist'); + } + }); + + it('calls normalizeEntityName hook during install', async function () { + const wait = new Promise((resolve) => { + blueprint.normalizeEntityName = function () { + resolve(); + }; }); - }); - - it('calls normalizeEntityName hook during install', function(done) { - blueprint.normalizeEntityName = function() { - done(); - }; options.entity = { name: 'foo' }; - blueprint.install(options); + await blueprint.install(options); + await wait; }); - it('normalizeEntityName hook can modify the entity name', function() { - blueprint.normalizeEntityName = function() { + it('normalizeEntityName hook can modify the entity name', async function () { + blueprint.normalizeEntityName = function () { return 'foo'; }; options.entity = { name: 'bar' }; - return blueprint.install(options).then(function() { - let actualFiles = walkSync(tmpdir).sort(); + await blueprint.install(options); + let actualFiles = walkSync(tmpdir).sort(); - expect(actualFiles).to.contain('app/basics/foo.txt'); - expect(actualFiles).to.not.contain('app/basics/mock-project.txt'); - }); + expect(actualFiles).to.contain('app/basics/foo.txt'); + expect(actualFiles).to.not.contain('app/basics/mock-project.txt'); }); - it('calls normalizeEntityName before locals hook is called', function(done) { - blueprint.normalizeEntityName = function() { + it('calls normalizeEntityName before locals hook is called', async function () { + blueprint.normalizeEntityName = function () { return 'foo'; }; - blueprint.locals = function(options) { + let done; + const waitForLocals = new Promise((resolve) => (done = resolve)); + blueprint.locals = function (options) { expect(options.entity.name).to.equal('foo'); done(); }; options.entity = { name: 'bar' }; - blueprint.install(options); + await blueprint.install(options); + await waitForLocals; }); - it('calls appropriate hooks with correct arguments', function() { + it('calls appropriate hooks with correct arguments', async function () { options.entity = { name: 'foo' }; - return blueprint.install(options).then(function() { - expect(localsCalled).to.be.true; - expect(normalizeEntityNameCalled).to.be.true; - expect(fileMapTokensCalled).to.be.true; - expect(filesPathCalled).to.be.true; - expect(beforeInstallCalled).to.be.true; - expect(afterInstallCalled).to.be.true; - expect(beforeUninstallCalled).to.be.false; - expect(afterUninstallCalled).to.be.false; - }); + await blueprint.install(options); + expect(localsCalled).to.be.true; + expect(normalizeEntityNameCalled).to.be.true; + expect(fileMapTokensCalled).to.be.true; + expect(filesPathCalled).to.be.true; + expect(beforeInstallCalled).to.be.true; + expect(afterInstallCalled).to.be.true; + expect(beforeUninstallCalled).to.be.false; + expect(afterUninstallCalled).to.be.false; }); - it("doesn't throw when running uninstall without installing first", function() { + it("doesn't throw when running uninstall without installing first", function () { return blueprint.uninstall(options); }); }); - describe('basic blueprint uninstallation', function() { + describe('basic blueprint uninstallation', function () { const BasicBlueprintClass = require(basicBlueprint); let blueprint; let ui; @@ -619,86 +617,77 @@ describe('Blueprint', function() { options.ui = ui; } - beforeEach(function() { - return mkTmpDirIn(tmproot) - .then(function(dir) { - tmpdir = dir; - blueprint = new BasicBlueprintClass(basicBlueprint); - project = new MockProject(); - options = { - project, - target: tmpdir, - }; - refreshUI(); - return blueprint.install(options); - }) - .then(refreshUI); + beforeEach(async function () { + let dir = await mkTmpDirIn(tempRoot); + + tmpdir = dir; + blueprint = new BasicBlueprintClass(basicBlueprint); + project = new MockProject(); + options = { + project, + target: tmpdir, + }; + refreshUI(); + + await blueprint.install(options); + refreshUI(); }); - afterEach(function() { - return remove(tmproot); + afterEach(async function () { + await remove(tempRoot); }); - it('uninstalls basic files', function() { + it('uninstalls basic files', async function () { expect(!!blueprint).to.equal(true); - return blueprint.uninstall(options).then(function() { - let actualFiles = walkSync(tmpdir); - let output = ui.output.trim().split(EOL); + await blueprint.uninstall(options); + let actualFiles = walkSync(tmpdir); + let output = ui.output.trim().split(EOL); - expect(output.shift()).to.match(/^uninstalling/); - expect(output.shift()).to.match(/remove.* .ember-cli/); - expect(output.shift()).to.match(/remove.* .gitignore/); - expect(output.shift()).to.match(/remove.* app[/\\]basics[/\\]mock-project.txt/); - expect(output.shift()).to.match(/remove.* bar/); - expect(output.shift()).to.match(/remove.* file-to-remove.txt/); - expect(output.shift()).to.match(/remove.* foo.txt/); - expect(output.shift()).to.match(/remove.* test.txt/); - expect(output.length).to.equal(0); + expect(output.shift()).to.match(/^uninstalling/); + expect(output.shift()).to.match(/remove.* .ember-cli/); + expect(output.shift()).to.match(/remove.* .gitignore/); + expect(output.shift()).to.match(/remove.* app[/\\]basics[/\\]mock-project.txt/); + expect(output.shift()).to.match(/remove.* bar/); + expect(output.shift()).to.match(/remove.* file-to-remove.txt/); + expect(output.shift()).to.match(/remove.* foo.txt/); + expect(output.shift()).to.match(/remove.* test.txt/); + expect(output.length).to.equal(0); - expect(actualFiles.length).to.equal(0); + expect(actualFiles.length).to.equal(0); - fs.exists(path.join(tmpdir, 'test.txt'), function(exists) { - expect(exists).to.be.false; - }); - }); + expect(fs.existsSync(path.join(tmpdir, 'test.txt'))).to.be.false; }); - it("uninstall doesn't remove non-empty folders", function() { + it("uninstall doesn't remove non-empty folders", async function () { options.entity = { name: 'foo' }; - return blueprint - .install(options) - .then(function() { - let actualFiles = walkSync(tmpdir); + await blueprint.install(options); + let actualFiles = walkSync(tmpdir); - expect(actualFiles).to.contain('app/basics/foo.txt'); - expect(actualFiles).to.contain('app/basics/mock-project.txt'); + expect(actualFiles).to.contain('app/basics/foo.txt'); + expect(actualFiles).to.contain('app/basics/mock-project.txt'); - return blueprint.uninstall(options); - }) - .then(function() { - let actualFiles = walkSync(tmpdir); + await blueprint.uninstall(options); + actualFiles = walkSync(tmpdir); - expect(actualFiles).to.not.contain('app/basics/foo.txt'); - expect(actualFiles).to.contain('app/basics/mock-project.txt'); - }); + expect(actualFiles).to.not.contain('app/basics/foo.txt'); + expect(actualFiles).to.contain('app/basics/mock-project.txt'); }); - it("uninstall doesn't log remove messages when file does not exist", function() { + it("uninstall doesn't log remove messages when file does not exist", async function () { options.entity = { name: 'does-not-exist' }; - return blueprint.uninstall(options).then(function() { - let output = ui.output.trim().split(EOL); - expect(output.shift()).to.match(/^uninstalling/); - expect(output.shift()).to.match(/remove.* .ember-cli/); - expect(output.shift()).to.match(/remove.* .gitignore/); - expect(output.shift()).to.not.match(/remove.* app[/\\]basics[/\\]does-not-exist.txt/); - }); + await blueprint.uninstall(options); + let output = ui.output.trim().split(EOL); + expect(output.shift()).to.match(/^uninstalling/); + expect(output.shift()).to.match(/remove.* .ember-cli/); + expect(output.shift()).to.match(/remove.* .gitignore/); + expect(output.shift()).to.not.match(/remove.* app[/\\]basics[/\\]does-not-exist.txt/); }); }); - describe('instrumented blueprint uninstallation', function() { + describe('instrumented blueprint uninstallation', function () { let blueprint; let ui; let project; @@ -710,57 +699,54 @@ describe('Blueprint', function() { options.ui = ui; } - beforeEach(function() { - return mkTmpDirIn(tmproot) - .then(function(dir) { - tmpdir = dir; - blueprint = new InstrumentedBasicBlueprint(basicBlueprint); - project = new MockProject(); - options = { - project, - target: tmpdir, - }; - refreshUI(); - - return blueprint.install(options).then(resetCalled); - }) - .then(refreshUI); + beforeEach(async function () { + let dir = await mkTmpDirIn(tempRoot); + tmpdir = dir; + blueprint = new InstrumentedBasicBlueprint(basicBlueprint); + project = new MockProject(); + options = { + project, + target: tmpdir, + }; + refreshUI(); + await blueprint.install(options); + resetCalled(); + refreshUI(); }); - it('calls appropriate hooks with correct arguments', function() { + it('calls appropriate hooks with correct arguments', async function () { options.entity = { name: 'foo' }; - return blueprint.uninstall(options).then(function() { - expect(localsCalled).to.be.true; - expect(normalizeEntityNameCalled).to.be.true; - expect(fileMapTokensCalled).to.be.true; - expect(filesPathCalled).to.be.true; - expect(beforeUninstallCalled).to.be.true; - expect(afterUninstallCalled).to.be.true; + await blueprint.uninstall(options); + expect(localsCalled).to.be.true; + expect(normalizeEntityNameCalled).to.be.true; + expect(fileMapTokensCalled).to.be.true; + expect(filesPathCalled).to.be.true; + expect(beforeUninstallCalled).to.be.true; + expect(afterUninstallCalled).to.be.true; - expect(beforeInstallCalled).to.be.false; - expect(afterInstallCalled).to.be.false; - }); + expect(beforeInstallCalled).to.be.false; + expect(afterInstallCalled).to.be.false; }); }); - describe('addPackageToProject', function() { + describe('addPackageToProject', function () { let blueprint; - beforeEach(function() { + beforeEach(function () { blueprint = new Blueprint(basicBlueprint); }); - it('passes a packages array for addPackagesToProject', function() { - blueprint.addPackagesToProject = function(packages) { + it('passes a packages array for addPackagesToProject', function () { + blueprint.addPackagesToProject = function (packages) { expect(packages).to.deep.equal([{ name: 'foo-bar' }]); }; blueprint.addPackageToProject('foo-bar'); }); - it('passes a packages array with target for addPackagesToProject', function() { - blueprint.addPackagesToProject = function(packages) { + it('passes a packages array with target for addPackagesToProject', function () { + blueprint.addPackagesToProject = function (packages) { expect(packages).to.deep.equal([{ name: 'foo-bar', target: '^123.1.12' }]); }; @@ -768,26 +754,26 @@ describe('Blueprint', function() { }); }); - describe('addPackagesToProject', function() { + describe('addPackagesToProject', function () { let blueprint; let ui; let NpmInstallTask; let taskNameLookedUp; - beforeEach(function() { + beforeEach(function () { blueprint = new Blueprint(basicBlueprint); ui = new MockUI(); - blueprint.taskFor = function(name) { + blueprint.taskFor = function (name) { taskNameLookedUp = name; return new NpmInstallTask(); }; }); - afterEach(function() { - return remove(tmproot); + afterEach(async function () { + await remove(tempRoot); }); - it('looks up the `npm-install` task', function() { + it('looks up the `npm-install` task', function () { NpmInstallTask = Task.extend({ run() {}, }); @@ -797,7 +783,7 @@ describe('Blueprint', function() { expect(taskNameLookedUp).to.equal('npm-install'); }); - it('calls the task with package names', function() { + it('calls the task with package names', function () { let packages; NpmInstallTask = Task.extend({ @@ -811,7 +797,7 @@ describe('Blueprint', function() { expect(packages).to.deep.equal(['foo-bar', 'bar-foo']); }); - it('calls the task with package names and versions', function() { + it('calls the task with package names and versions', function () { let packages; NpmInstallTask = Task.extend({ @@ -820,12 +806,15 @@ describe('Blueprint', function() { }, }); - blueprint.addPackagesToProject([{ name: 'foo-bar', target: '^123.1.12' }, { name: 'bar-foo', target: '0.0.7' }]); + blueprint.addPackagesToProject([ + { name: 'foo-bar', target: '^123.1.12' }, + { name: 'bar-foo', target: '0.0.7' }, + ]); expect(packages).to.deep.equal(['foo-bar@^123.1.12', 'bar-foo@0.0.7']); }); - it('writes information to the ui log for a single package', function() { + it('writes information to the ui log for a single package', function () { blueprint.ui = ui; blueprint.addPackagesToProject([{ name: 'foo-bar', target: '^123.1.12' }]); @@ -835,17 +824,20 @@ describe('Blueprint', function() { expect(output).to.match(/install package.*foo-bar/); }); - it('writes information to the ui log for multiple packages', function() { + it('writes information to the ui log for multiple packages', function () { blueprint.ui = ui; - blueprint.addPackagesToProject([{ name: 'foo-bar', target: '^123.1.12' }, { name: 'bar-foo', target: '0.0.7' }]); + blueprint.addPackagesToProject([ + { name: 'foo-bar', target: '^123.1.12' }, + { name: 'bar-foo', target: '0.0.7' }, + ]); let output = ui.output.trim(); expect(output).to.match(/install packages.*foo-bar, bar-foo/); }); - it('does not error if ui is not present', function() { + it('does not error if ui is not present', function () { delete blueprint.ui; blueprint.addPackagesToProject([{ name: 'foo-bar', target: '^123.1.12' }]); @@ -855,7 +847,7 @@ describe('Blueprint', function() { expect(output).to.not.match(/install package.*foo-bar/); }); - it('runs task with --save-dev', function() { + it('runs task with --save-dev', function () { let saveDev; NpmInstallTask = Task.extend({ @@ -864,12 +856,15 @@ describe('Blueprint', function() { }, }); - blueprint.addPackagesToProject([{ name: 'foo-bar', target: '^123.1.12' }, { name: 'bar-foo', target: '0.0.7' }]); + blueprint.addPackagesToProject([ + { name: 'foo-bar', target: '^123.1.12' }, + { name: 'bar-foo', target: '0.0.7' }, + ]); expect(!!saveDev).to.equal(true); }); - it('does not use verbose mode with the task', function() { + it('does not use verbose mode with the task', function () { let verbose; NpmInstallTask = Task.extend({ @@ -878,39 +873,42 @@ describe('Blueprint', function() { }, }); - blueprint.addPackagesToProject([{ name: 'foo-bar', target: '^123.1.12' }, { name: 'bar-foo', target: '0.0.7' }]); + blueprint.addPackagesToProject([ + { name: 'foo-bar', target: '^123.1.12' }, + { name: 'bar-foo', target: '0.0.7' }, + ]); expect(verbose).to.equal(false); }); }); - describe('removePackageFromProject', function() { + describe('removePackageFromProject', function () { let blueprint; let NpmUninstallTask; let taskNameLookedUp; let project; - beforeEach(function() { + beforeEach(function () { project = new MockProject(); blueprint = new Blueprint(basicBlueprint); blueprint.project = project; - blueprint.taskFor = function(name) { + blueprint.taskFor = function (name) { taskNameLookedUp = name; return new NpmUninstallTask(); }; }); - afterEach(function() { - return remove(tmproot); + afterEach(async function () { + await remove(tempRoot); }); - it('looks up the `npm-uninstall` task', function() { + it('looks up the `npm-uninstall` task', function () { NpmUninstallTask = Task.extend({ run() {}, }); - project.dependencies = function() { + project.dependencies = function () { return { 'foo-bar': '1.0.0', }; @@ -921,30 +919,30 @@ describe('Blueprint', function() { }); }); - describe('removePackagesFromProject', function() { + describe('removePackagesFromProject', function () { let blueprint; let ui; let NpmUninstallTask; let taskNameLookedUp; let project; - beforeEach(function() { + beforeEach(function () { project = new MockProject(); blueprint = new Blueprint(basicBlueprint); ui = new MockUI(); blueprint.project = project; - blueprint.taskFor = function(name) { + blueprint.taskFor = function (name) { taskNameLookedUp = name; return new NpmUninstallTask(); }; }); - afterEach(function() { - return remove(tmproot); + afterEach(function () { + return remove(tempRoot); }); - it('looks up the `npm-uninstall` task', function() { + it('looks up the `npm-uninstall` task', function () { NpmUninstallTask = Task.extend({ run() {}, }); @@ -954,7 +952,7 @@ describe('Blueprint', function() { expect(taskNameLookedUp).to.equal('npm-uninstall'); }); - it('calls the task with only existing packages', function() { + it('calls the task with only existing packages', function () { let packages; NpmUninstallTask = Task.extend({ @@ -963,7 +961,7 @@ describe('Blueprint', function() { }, }); - project.dependencies = function() { + project.dependencies = function () { return { 'foo-bar': '1.0.0', 'bar-zoo': '2.0.0', @@ -975,7 +973,7 @@ describe('Blueprint', function() { expect(packages).to.deep.equal(['foo-bar']); }); - it('skips uninstall if no matching package exists', function() { + it('skips uninstall if no matching package exists', function () { let packages; NpmUninstallTask = Task.extend({ @@ -984,7 +982,7 @@ describe('Blueprint', function() { }, }); - project.dependencies = function() { + project.dependencies = function () { return { 'foo-baz': '1.0.0', 'bar-zoo': '2.0.0', @@ -996,7 +994,7 @@ describe('Blueprint', function() { expect(packages).to.deep.equal(undefined); }); - it('calls the task with package names', function() { + it('calls the task with package names', function () { let packages; NpmUninstallTask = Task.extend({ @@ -1005,7 +1003,7 @@ describe('Blueprint', function() { }, }); - project.dependencies = function() { + project.dependencies = function () { return { 'foo-bar': '1.0.0', 'bar-foo': '2.0.0', @@ -1017,10 +1015,10 @@ describe('Blueprint', function() { expect(packages).to.deep.equal(['foo-bar', 'bar-foo']); }); - it('writes information to the ui log for a single package', function() { + it('writes information to the ui log for a single package', function () { blueprint.ui = ui; - project.dependencies = function() { + project.dependencies = function () { return { 'foo-bar': '1.0.0', }; @@ -1033,10 +1031,10 @@ describe('Blueprint', function() { expect(output).to.match(/uninstall package.*foo-bar/); }); - it('writes information to the ui log for multiple packages', function() { + it('writes information to the ui log for multiple packages', function () { blueprint.ui = ui; - project.dependencies = function() { + project.dependencies = function () { return { 'foo-bar': '1.0.0', 'bar-foo': '2.0.0', @@ -1050,7 +1048,7 @@ describe('Blueprint', function() { expect(output).to.match(/uninstall packages.*foo-bar, bar-foo/); }); - it('does not error if ui is not present', function() { + it('does not error if ui is not present', function () { delete blueprint.ui; blueprint.removePackagesFromProject([{ name: 'foo-bar' }]); @@ -1060,7 +1058,7 @@ describe('Blueprint', function() { expect(output).to.not.match(/uninstall package.*foo-bar/); }); - it('runs task with --save-dev', function() { + it('runs task with --save-dev', function () { let saveDev; NpmUninstallTask = Task.extend({ @@ -1069,7 +1067,7 @@ describe('Blueprint', function() { }, }); - project.dependencies = function() { + project.dependencies = function () { return { 'foo-bar': '1.0.0', 'bar-foo': '2.0.0', @@ -1081,7 +1079,7 @@ describe('Blueprint', function() { expect(!!saveDev).to.equal(true); }); - it('does not use verbose mode with the task', function() { + it('does not use verbose mode with the task', function () { let verbose; NpmUninstallTask = Task.extend({ @@ -1090,7 +1088,7 @@ describe('Blueprint', function() { }, }); - project.dependencies = function() { + project.dependencies = function () { return { 'foo-bar': '1.0.0', 'bar-foo': '2.0.0', @@ -1103,50 +1101,50 @@ describe('Blueprint', function() { }); }); - describe('addBowerPackageToProject', function() { + describe('addBowerPackageToProject', function () { let blueprint; let ui; let BowerInstallTask; - beforeEach(function() { + beforeEach(function () { blueprint = new Blueprint(basicBlueprint); ui = new MockUI(); blueprint.ui = ui; - blueprint.taskFor = function() { + blueprint.taskFor = function () { return new BowerInstallTask(); }; }); - afterEach(function() { - return remove(tmproot); + afterEach(async function () { + await remove(tempRoot); }); - it('passes a packages array for addBowerPackagesToProject', function() { - blueprint.addBowerPackagesToProject = function(packages) { + it('passes a packages array for addBowerPackagesToProject', function () { + blueprint.addBowerPackagesToProject = function (packages) { expect(packages).to.deep.equal([{ name: 'foo-bar', source: 'foo-bar', target: '*' }]); }; blueprint.addBowerPackageToProject('foo-bar'); }); - it('passes a packages array with target for addBowerPackagesToProject', function() { - blueprint.addBowerPackagesToProject = function(packages) { + it('passes a packages array with target for addBowerPackagesToProject', function () { + blueprint.addBowerPackagesToProject = function (packages) { expect(packages).to.deep.equal([{ name: 'foo-bar', source: 'foo-bar', target: '1.0.0' }]); }; blueprint.addBowerPackageToProject('foo-bar', '1.0.0'); }); - it('correctly handles local package naming, with a numbered pkg version', function() { - blueprint.addBowerPackagesToProject = function(packages) { + it('correctly handles local package naming, with a numbered pkg version', function () { + blueprint.addBowerPackagesToProject = function (packages) { expect(packages).to.deep.equal([{ name: 'foo-bar-local', target: '1.0.0', source: 'foo-bar' }]); }; blueprint.addBowerPackageToProject('foo-bar-local', 'foo-bar#1.0.0'); }); - it('correctly handles local package naming, with a non-versioned package', function() { - blueprint.addBowerPackagesToProject = function(packages) { + it('correctly handles local package naming, with a non-versioned package', function () { + blueprint.addBowerPackagesToProject = function (packages) { expect(packages).to.deep.equal([ { name: 'foo-bar-local', target: '*', source: 'https://twitter.github.io/bootstrap/assets/bootstrap' }, ]); @@ -1156,24 +1154,24 @@ describe('Blueprint', function() { }); }); - describe('addBowerPackagesToProject', function() { + describe('addBowerPackagesToProject', function () { let blueprint; let BowerInstallTask; let taskNameLookedUp; - beforeEach(function() { + beforeEach(function () { blueprint = new Blueprint(basicBlueprint); - blueprint.taskFor = function(name) { + blueprint.taskFor = function (name) { taskNameLookedUp = name; return new BowerInstallTask(); }; }); - afterEach(function() { - return remove(tmproot); + afterEach(async function () { + await remove(tempRoot); }); - it('looks up the `bower-install` task', function() { + it('looks up the `bower-install` task', function () { BowerInstallTask = Task.extend({ run() {}, }); @@ -1182,7 +1180,7 @@ describe('Blueprint', function() { expect(taskNameLookedUp).to.equal('bower-install'); }); - it('calls the task with the package names', function() { + it('calls the task with the package names', function () { let packages; BowerInstallTask = Task.extend({ @@ -1196,7 +1194,7 @@ describe('Blueprint', function() { expect(packages).to.deep.equal(['foo-bar=foo-bar', 'bar-foo=bar-foo']); }); - it('uses the provided target (version, range, sha, etc)', function() { + it('uses the provided target (version, range, sha, etc)', function () { let packages; BowerInstallTask = Task.extend({ @@ -1213,7 +1211,7 @@ describe('Blueprint', function() { expect(packages).to.deep.equal(['foo-bar=foo-bar#~1.0.0', 'bar-foo=bar-foo#0.7.0']); }); - it('properly parses a variety of bower package endpoints', function() { + it('properly parses a variety of bower package endpoints', function () { let packages; BowerInstallTask = Task.extend({ @@ -1238,7 +1236,7 @@ describe('Blueprint', function() { ]); }); - it('uses uses verbose mode with the task', function() { + it('uses uses verbose mode with the task', function () { let verbose; BowerInstallTask = Task.extend({ @@ -1256,27 +1254,27 @@ describe('Blueprint', function() { }); }); - describe('addAddonToProject', function() { + describe('addAddonToProject', function () { let blueprint; - beforeEach(function() { + beforeEach(function () { blueprint = new Blueprint(basicBlueprint); }); - afterEach(function() { - return remove(tmproot); + afterEach(async function () { + await remove(tempRoot); }); - it('passes a packages array for addAddonsToProject', function() { - blueprint.addAddonsToProject = function(options) { + it('passes a packages array for addAddonsToProject', function () { + blueprint.addAddonsToProject = function (options) { expect(options.packages).to.deep.equal(['foo-bar']); }; blueprint.addAddonToProject('foo-bar'); }); - it('passes a packages array with target for addAddonsToProject', function() { - blueprint.addAddonsToProject = function(options) { + it('passes a packages array with target for addAddonsToProject', function () { + blueprint.addAddonsToProject = function (options) { expect(options.packages).to.deep.equal([{ name: 'foo-bar', target: '^123.1.12' }]); }; @@ -1284,26 +1282,26 @@ describe('Blueprint', function() { }); }); - describe('addAddonsToProject', function() { + describe('addAddonsToProject', function () { let blueprint; let ui; let AddonInstallTask; let taskNameLookedUp; - beforeEach(function() { + beforeEach(function () { blueprint = new Blueprint(basicBlueprint); ui = new MockUI(); - blueprint.taskFor = function(name) { + blueprint.taskFor = function (name) { taskNameLookedUp = name; return new AddonInstallTask(); }; }); - afterEach(function() { - return remove(tmproot); + afterEach(async function () { + await remove(tempRoot); }); - it('looks up the `addon-install` task', function() { + it('looks up the `addon-install` task', function () { AddonInstallTask = Task.extend({ run() {}, }); @@ -1313,7 +1311,7 @@ describe('Blueprint', function() { expect(taskNameLookedUp).to.equal('addon-install'); }); - it('calls the task with package name', function() { + it('calls the task with package name', function () { let pkg; AddonInstallTask = Task.extend({ @@ -1327,7 +1325,7 @@ describe('Blueprint', function() { expect(pkg).to.deep.equal(['foo-bar', 'baz-bat']); }); - it('calls the task with correctly parsed options', function() { + it('calls the task with correctly parsed options', function () { let pkg, args, bluOpts; AddonInstallTask = Task.extend({ @@ -1356,7 +1354,7 @@ describe('Blueprint', function() { expect(bluOpts).to.equal('-foo'); }); - it('writes information to the ui log for a single package', function() { + it('writes information to the ui log for a single package', function () { blueprint.ui = ui; blueprint.addAddonsToProject({ @@ -1373,7 +1371,7 @@ describe('Blueprint', function() { expect(output).to.match(/install addon.*foo-bar/); }); - it('writes information to the ui log for multiple packages', function() { + it('writes information to the ui log for multiple packages', function () { blueprint.ui = ui; blueprint.addAddonsToProject({ @@ -1392,7 +1390,7 @@ describe('Blueprint', function() { expect(output).to.match(/install addons.*foo-bar@1.0.0,.*stuff-things,.*baz-bat@0.0.1/); }); - it('does not error if ui is not present', function() { + it('does not error if ui is not present', function () { delete blueprint.ui; blueprint.addAddonsToProject({ @@ -1410,55 +1408,54 @@ describe('Blueprint', function() { }); }); - describe('load', function() { - it('loads and returns a blueprint object', function() { + describe('load', function () { + it('loads and returns a blueprint object', function () { let blueprint = Blueprint.load(basicBlueprint); expect(blueprint).to.be.an('object'); expect(blueprint.name).to.equal('basic'); }); - it('loads only blueprints with an index.js', function() { + it('loads only blueprints with an index.js', function () { expect(Blueprint.load(path.join(fixtureBlueprints, '.notablueprint'))).to.not.exist; }); }); - describe('lookupBlueprint', function() { + describe('lookupBlueprint', function () { let blueprint; let tmpdir; let project; - beforeEach(function() { - return mkTmpDirIn(tmproot).then(function(dir) { - tmpdir = dir; - blueprint = new Blueprint(basicBlueprint); - project = new MockProject(); - // normally provided by `install`, but mocked here for testing - project.root = tmpdir; - blueprint.project = project; - project.blueprintLookupPaths = function() { - return [fixtureBlueprints]; - }; - }); + beforeEach(async function () { + let dir = await mkTmpDirIn(tempRoot); + tmpdir = dir; + blueprint = new Blueprint(basicBlueprint); + project = new MockProject(); + // normally provided by `install`, but mocked here for testing + project.root = tmpdir; + blueprint.project = project; + project.blueprintLookupPaths = function () { + return [fixtureBlueprints]; + }; }); - afterEach(function() { - return remove(tmproot); + afterEach(async function () { + await remove(tempRoot); }); - it('can lookup other Blueprints from the project blueprintLookupPaths', function() { + it('can lookup other Blueprints from the project blueprintLookupPaths', function () { let result = blueprint.lookupBlueprint('basic_2'); expect(result.description).to.equal('Another basic blueprint'); }); - it('can find internal blueprints', function() { + it('can find internal blueprints', function () { let result = blueprint.lookupBlueprint('blueprint'); expect(result.description).to.equal('Generates a blueprint and definition.'); }); }); - describe('._generateFileMapVariables', function() { + describe('._generateFileMapVariables', function () { let blueprint; let project; let moduleName; @@ -1467,7 +1464,7 @@ describe('Blueprint', function() { let result; let expectation; - beforeEach(function() { + beforeEach(function () { blueprint = new Blueprint(basicBlueprint); project = new MockProject(); moduleName = project.name(); @@ -1494,13 +1491,13 @@ describe('Blueprint', function() { }; }); - it('should create the correct default fileMapVariables', function() { + it('should create the correct default fileMapVariables', function () { result = blueprint._generateFileMapVariables(moduleName, locals, options); expect(result).to.eql(expectation); }); - it('should use the moduleName method argument for moduleName', function() { + it('should use the moduleName method argument for moduleName', function () { moduleName = 'foo'; expectation.dasherizedModuleName = 'foo'; @@ -1509,7 +1506,7 @@ describe('Blueprint', function() { expect(result).to.eql(expectation); }); - it('should use the locals method argument for its locals value', function() { + it('should use the locals method argument for its locals value', function () { locals = { foo: 'bar' }; expectation.locals = locals; @@ -1518,7 +1515,7 @@ describe('Blueprint', function() { expect(result).to.eql(expectation); }); - it('should use the option.originBlueprintName value as its originBlueprintName if included in the options hash', function() { + it('should use the option.originBlueprintName value as its originBlueprintName if included in the options hash', function () { options.originBlueprintName = 'foo'; expectation.originBlueprintName = 'foo'; @@ -1527,8 +1524,8 @@ describe('Blueprint', function() { expect(result).to.eql(expectation); }); - it("should include a podPath if the project's podModulePrefix is defined", function() { - blueprint.project.config = function() { + it("should include a podPath if the project's podModulePrefix is defined", function () { + blueprint.project.config = function () { return { podModulePrefix: 'foo/bar', }; @@ -1541,10 +1538,10 @@ describe('Blueprint', function() { expect(result).to.eql(expectation); }); - it('should include an inAddon and inDummy flag of true if the project is an addon', function() { + it('should include an inAddon and inDummy flag of true if the project is an addon', function () { options.dummy = true; - blueprint.project.isEmberCLIAddon = function() { + blueprint.project.isEmberCLIAddon = function () { return true; }; @@ -1556,7 +1553,7 @@ describe('Blueprint', function() { expect(result).to.eql(expectation); }); - it('should include an inAddon and inRepoAddon flag of true if options.inRepoAddon is true', function() { + it('should include an inAddon and inRepoAddon flag of true if options.inRepoAddon is true', function () { options.inRepoAddon = true; expectation.inRepoAddon = true; @@ -1567,7 +1564,7 @@ describe('Blueprint', function() { expect(result).to.eql(expectation); }); - it('should include an in flag of true if options.in is true', function() { + it('should include an in flag of true if options.in is true', function () { options.in = true; expectation.in = true; @@ -1577,7 +1574,7 @@ describe('Blueprint', function() { expect(result).to.eql(expectation); }); - it('should have a hasPathToken flag of true if the blueprint hasPathToken is true', function() { + it('should have a hasPathToken flag of true if the blueprint hasPathToken is true', function () { blueprint.hasPathToken = true; expectation.hasPathToken = true; @@ -1588,22 +1585,22 @@ describe('Blueprint', function() { }); }); - describe('._locals', function() { + describe('._locals', function () { let blueprint; let project; let options; let result; let expectation; - beforeEach(function() { + beforeEach(function () { blueprint = new Blueprint(basicBlueprint); project = new MockProject(); - blueprint._generateFileMapVariables = function() { + blueprint._generateFileMapVariables = function () { return {}; }; - blueprint.generateFileMap = function() { + blueprint.generateFileMap = function () { return {}; }; @@ -1622,28 +1619,26 @@ describe('Blueprint', function() { }; }); - it('should return a default object if no custom options are passed', function() { - result = blueprint._locals(options); + it('should return a default object if no custom options are passed', async function () { + result = await blueprint._locals(options); - result.then(function(locals) { - expect(locals).to.eql(expectation); - }); + expect(result).to.deep.include(expectation); }); - it('it should call the locals method with the correct arguments', function() { - blueprint.locals = function(opts) { + it('it should call the locals method with the correct arguments', function () { + blueprint.locals = function (opts) { expect(opts).to.equal(options); }; blueprint._locals(options); }); - it('should call _generateFileMapVariables with the correct arguments', function() { - blueprint.locals = function() { + it('should call _generateFileMapVariables with the correct arguments', function () { + blueprint.locals = function () { return { foo: 'bar' }; }; - blueprint._generateFileMapVariables = function(modName, lcls, opts) { + blueprint._generateFileMapVariables = function (modName, lcls, opts) { expect(modName).to.equal('mock-project'); expect(lcls).to.eql({ foo: 'bar' }); expect(opts).to.eql(opts); @@ -1652,19 +1647,19 @@ describe('Blueprint', function() { blueprint._locals(options); }); - it('should call generateFileMap with the correct arguments', function() { - blueprint._generateFileMapVariables = function() { + it('should call generateFileMap with the correct arguments', function () { + blueprint._generateFileMapVariables = function () { return { bar: 'baz' }; }; - blueprint.generateFileMap = function(fileMapVariables) { + blueprint.generateFileMap = function (fileMapVariables) { expect(fileMapVariables).to.eql({ bar: 'baz' }); }; blueprint._locals(options); }); - it('should use the options.entity.name as its moduleName if its value is defined', function() { + it('should use the options.entity.name as its moduleName if its value is defined', async function () { options.entity = { name: 'foo', }; @@ -1674,39 +1669,33 @@ describe('Blueprint', function() { expectation.dasherizedModuleName = 'foo'; expectation.decamelizedModuleName = 'foo'; - result = blueprint._locals(options); + result = await blueprint._locals(options); - result.then(function(locals) { - expect(locals).to.eql(expectation); - }); + expect(result).to.deep.include(expectation); }); - it('should update its fileMap values to match the generateFileMap result', function() { - blueprint.generateFileMap = function() { + it('should update its fileMap values to match the generateFileMap result', async function () { + blueprint.generateFileMap = function () { return { foo: 'bar' }; }; expectation.fileMap = { foo: 'bar' }; - result = blueprint._locals(options); + result = await blueprint._locals(options); - result.then(function(locals) { - expect(locals).to.eql(expectation); - }); + expect(result).to.deep.include(expectation); }); - it('should return an object containing custom local values', function() { - blueprint.locals = function() { + it('should return an object containing custom local values', async function () { + blueprint.locals = function () { return { foo: 'bar' }; }; expectation.foo = 'bar'; - result = blueprint._locals(options); + result = await blueprint._locals(options); - result.then(function(locals) { - expect(locals).to.eql(expectation); - }); + expect(result).to.deep.include(expectation); }); }); }); diff --git a/tests/integration/tasks/build-test.js b/tests/integration/tasks/build-test.js index 451088dd9e..f7c3012522 100644 --- a/tests/integration/tasks/build-test.js +++ b/tests/integration/tasks/build-test.js @@ -7,45 +7,43 @@ let expect = chai.expect; let file = chai.file; const walkSync = require('walk-sync'); const BuildTask = require('../../../lib/tasks/build'); -const RSVP = require('rsvp'); const MockProject = require('../../helpers/mock-project'); const MockAnalytics = require('../../helpers/mock-analytics'); const MockProcess = require('../../helpers/mock-process'); const copyFixtureFiles = require('../../helpers/copy-fixture-files'); const mkTmpDirIn = require('../../../lib/utilities/mk-tmp-dir-in'); const willInterruptProcess = require('../../../lib/utilities/will-interrupt-process'); -let remove = RSVP.denodeify(fs.remove); let root = process.cwd(); let tmproot = path.join(root, 'tmp'); -describe('build task test', function() { +describe('build task test', function () { let project, ui, _process; - beforeEach(function() { + beforeEach(function () { _process = new MockProcess(); willInterruptProcess.capture(_process); return mkTmpDirIn(tmproot) - .then(function(tmpdir) { + .then(function (tmpdir) { process.chdir(tmpdir); }) - .then(function() { + .then(function () { return copyFixtureFiles('tasks/builder'); }) - .then(function() { + .then(function () { project = new MockProject(); ui = project.ui; }); }); - afterEach(function() { + afterEach(function () { willInterruptProcess.release(); process.chdir(root); delete process.env.BROCCOLI_VIZ; - return remove(tmproot); + return fs.remove(tmproot); }); - it('can build', function() { + it('can build', function () { let outputPath = 'dist'; let task = new BuildTask({ analytics: new MockAnalytics(), @@ -64,7 +62,7 @@ describe('build task test', function() { }); }); - it('generates valid visualization output', function() { + it('generates valid visualization output', function () { process.env.BROCCOLI_VIZ = '1'; let outputPath = 'dist'; @@ -79,7 +77,7 @@ describe('build task test', function() { environment: 'development', }; - return task.run(runOptions).then(function() { + return task.run(runOptions).then(function () { let vizOutputPath = 'instrumentation.build.0.json'; expect(file(vizOutputPath)).to.exist; @@ -94,7 +92,7 @@ describe('build task test', function() { }); }); - it('it displays environment', function() { + it('it displays environment', function () { let outputPath = 'dist'; let task = new BuildTask({ analytics: new MockAnalytics(), diff --git a/tests/integration/utilities/clean-remove-test.js b/tests/integration/utilities/clean-remove-test.js index 7cb33b4664..1db35fdd26 100644 --- a/tests/integration/utilities/clean-remove-test.js +++ b/tests/integration/utilities/clean-remove-test.js @@ -4,19 +4,15 @@ const expect = require('../../chai').expect; const cleanRemove = require('../../../lib/utilities/clean-remove'); const temp = require('temp'); const path = require('path'); -const RSVP = require('rsvp'); const fs = require('fs-extra'); -let outputFile = RSVP.denodeify(fs.outputFile); -let stat = RSVP.denodeify(fs.stat); - -describe('clean-remove', function() { +describe('clean-remove', function () { let tempDir; let originalCwd = process.cwd(); let fileInfo; let nestedPath = 'nested1/nested2'; - beforeEach(function() { + beforeEach(function () { tempDir = temp.mkdirSync('clean-remove'); process.chdir(tempDir); @@ -25,55 +21,37 @@ describe('clean-remove', function() { }; }); - afterEach(function() { + afterEach(function () { process.chdir(originalCwd); fs.removeSync(tempDir); }); - it('removes empty folders', function() { + it('removes empty folders', async function () { let displayPath = path.join(nestedPath, 'file.txt'); fileInfo.outputPath = path.join(tempDir, displayPath); fileInfo.displayPath = displayPath; - return outputFile(displayPath, '') - .then(function() { - return stat(displayPath).then(function(stats) { - expect(stats).to.be.ok; - }); - }) - .then(function() { - return cleanRemove(fileInfo); - }) - .then(function() { - return expect(stat('nested1')).to.be.rejected; - }); + await fs.outputFile(displayPath, ''); + let stats = await fs.stat(displayPath); + expect(stats).to.be.ok; + await cleanRemove(fileInfo); + return expect(fs.stat('nested1')).to.be.rejected; }); - it('preserves filled folders', function() { + it('preserves filled folders', async function () { let removedDisplayPath = path.join(nestedPath, 'file.txt'); let preservedDisplayPath = path.join(nestedPath, 'file2.txt'); fileInfo.outputPath = path.join(tempDir, removedDisplayPath); fileInfo.displayPath = removedDisplayPath; - return outputFile(removedDisplayPath, '') - .then(function() { - return outputFile(preservedDisplayPath, ''); - }) - .then(function() { - return stat(preservedDisplayPath).then(function(stats) { - expect(stats).to.be.ok; - }); - }) - .then(function() { - return cleanRemove(fileInfo); - }) - .then(function() { - return expect(stat(removedDisplayPath)).to.be.rejected; - }) - .then(function() { - return stat(preservedDisplayPath).then(function(stats) { - expect(stats).to.be.ok; - }); - }); + await fs.outputFile(removedDisplayPath, ''); + await fs.outputFile(preservedDisplayPath, ''); + + expect(await fs.stat(preservedDisplayPath)).to.be.ok; + + await cleanRemove(fileInfo); + await expect(fs.stat(removedDisplayPath)).to.be.rejected; + + expect(await fs.stat(preservedDisplayPath)).to.be.ok; }); }); diff --git a/tests/runner.js b/tests/runner.js index 6025be20da..13d0ba3e7c 100644 --- a/tests/runner.js +++ b/tests/runner.js @@ -5,8 +5,8 @@ captureExit.captureExit(); const glob = require('glob'); const Mocha = require('mocha'); -const RSVP = require('rsvp'); const fs = require('fs-extra'); +const expect = require('./chai').expect; if (process.env.EOLNEWLINE) { require('os').EOL = '\n'; @@ -17,7 +17,8 @@ fs.removeSync('.deps-tmp'); let root = 'tests/{unit,integration,acceptance}'; let optionOrFile = process.argv[2]; // default to `tap` reporter in CI otherwise default to `spec` -let reporter = process.env.MOCHA_REPORTER || (process.env.CI ? 'tap' : 'spec'); +let isCI = process.env.CI || process.env.GITHUB_ACTIONS; +let reporter = process.env.MOCHA_REPORTER || (isCI ? 'tap' : 'spec'); let mocha = new Mocha({ timeout: 5000, reporter, @@ -48,8 +49,16 @@ function addFiles(mocha, files) { } function runMocha() { + let ROOT = process.cwd(); + + // ensure that at the end of every test, we are in the correct current + // working directory + mocha.suite.afterEach(function () { + expect(process.cwd()).to.equal(ROOT); + }); + console.time('Mocha Tests Running Time'); - mocha.run(failures => { + mocha.run((failures) => { console.timeEnd('Mocha Tests Running Time'); // eslint-disable-next-line no-process-exit @@ -57,9 +66,9 @@ function runMocha() { }); } -RSVP.resolve() +Promise.resolve() .then(() => runMocha()) - .catch(error => { + .catch((error) => { console.error(error); console.error(error.stack); diff --git a/tests/unit/analytics-test.js b/tests/unit/analytics-test.js index 01056595bd..6934db4088 100644 --- a/tests/unit/analytics-test.js +++ b/tests/unit/analytics-test.js @@ -7,8 +7,8 @@ const MockProject = require('../helpers/mock-project'); let command; let called = false; -describe('analytics', function() { - beforeEach(function() { +describe('analytics', function () { + beforeEach(function () { let analytics = { track() { called = true; @@ -21,7 +21,7 @@ describe('analytics', function() { }); let project = new MockProject(); - project.isEmberCLIProject = function() { + project.isEmberCLIProject = function () { return true; }; @@ -32,11 +32,11 @@ describe('analytics', function() { }); }); - afterEach(function() { + afterEach(function () { command = null; }); - it('track gets invoked on command.validateAndRun()', async function() { + it('track gets invoked on command.validateAndRun()', async function () { await command.validateAndRun([]); expect(called, 'expected analytics.track to be called').to.be.true; }); diff --git a/tests/unit/blueprints/addon-test.js b/tests/unit/blueprints/addon-test.js index 60dc9123ee..e0129e9a01 100644 --- a/tests/unit/blueprints/addon-test.js +++ b/tests/unit/blueprints/addon-test.js @@ -4,38 +4,38 @@ const Blueprint = require('../../../lib/models/blueprint'); const MockProject = require('../../helpers/mock-project'); const expect = require('chai').expect; -describe('blueprint - addon', function() { - describe('Blueprint.lookup', function() { +describe('blueprint - addon', function () { + describe('Blueprint.lookup', function () { let blueprint; - beforeEach(function() { + beforeEach(function () { blueprint = Blueprint.lookup('addon'); }); - describe('entityName', function() { + describe('entityName', function () { let mockProject; - beforeEach(function() { + beforeEach(function () { mockProject = new MockProject(); - mockProject.isEmberCLIProject = function() { + mockProject.isEmberCLIProject = function () { return true; }; blueprint.project = mockProject; }); - afterEach(function() { + afterEach(function () { mockProject = null; }); - it('throws error when current project is an existing ember-cli project', function() { + it('throws error when current project is an existing ember-cli project', function () { expect(() => blueprint.normalizeEntityName('foo')).to.throw( 'Generating an addon in an existing ember-cli project is not supported.' ); }); - it('works when current project is an existing ember-cli addon', function() { - mockProject.isEmberCLIAddon = function() { + it('works when current project is an existing ember-cli addon', function () { + mockProject.isEmberCLIAddon = function () { return true; }; @@ -44,15 +44,15 @@ describe('blueprint - addon', function() { ); }); - it('keeps existing behavior by calling Blueprint.normalizeEntityName', function() { + it('keeps existing behavior by calling Blueprint.normalizeEntityName', function () { expect(() => blueprint.normalizeEntityName('foo/')).to.throw(/trailing slash/); }); }); }); - describe('direct blueprint require', function() { + describe('direct blueprint require', function () { let blueprint; - beforeEach(function() { + beforeEach(function () { blueprint = require('../../../blueprints/addon'); blueprint.options = { entity: { name: 'my-cool-addon' }, @@ -63,53 +63,26 @@ describe('blueprint - addon', function() { blueprint.path = 'test-blueprint-path'; }); - describe('generatePackageJson', function() { - it('works', function() { - let output = blueprint.updatePackageJson(JSON.stringify({})); - // string to test ordering - expect(output).to.equal( - '\ -{\n\ - "name": "my-cool-addon",\n\ - "description": "The default blueprint for ember-cli addons.",\n\ - "keywords": [\n\ - "ember-addon"\n\ - ],\n\ - "scripts": {\n\ - "test:all": "ember try:each"\n\ - },\n\ - "dependencies": {},\n\ - "devDependencies": {\n\ - "ember-disable-prototype-extensions": "^1.1.3",\n\ - "ember-source-channel-url": "^2.0.1",\n\ - "ember-try": "^1.2.1"\n\ - },\n\ - "ember-addon": {\n\ - "configPath": "tests/dummy/config"\n\ - }\n\ -}\n' - ); - }); - - it('removes the `private` property', function() { + describe('generatePackageJson', function () { + it('removes the `private` property', function () { let output = blueprint.updatePackageJson(JSON.stringify({})); expect(JSON.parse(output).private).to.be.undefined; }); - it('overwrites `name`', function() { + it('overwrites `name`', function () { let output = blueprint.updatePackageJson(JSON.stringify({ name: 'OMG' })); expect(JSON.parse(output).name).to.eql('my-cool-addon'); }); - it('overwrites `description`', function() { + it('overwrites `description`', function () { let output = blueprint.updatePackageJson(JSON.stringify({ description: 'OMG' })); let json = JSON.parse(output); expect(json.description).to.equal('The default blueprint for ember-cli addons.'); }); - it('moves `ember-cli-babel` from devDependencies to dependencies', function() { + it('moves `ember-cli-babel` from devDependencies to dependencies', function () { let output = blueprint.updatePackageJson( JSON.stringify({ devDependencies: { @@ -125,7 +98,7 @@ describe('blueprint - addon', function() { expect(json.devDependencies).to.not.have.property('ember-cli-babel'); }); - it('moves `ember-cli-htmlbars` from devDependencies to dependencies', function() { + it('moves `ember-cli-htmlbars` from devDependencies to dependencies', function () { let output = blueprint.updatePackageJson( JSON.stringify({ devDependencies: { @@ -141,7 +114,7 @@ describe('blueprint - addon', function() { expect(json.devDependencies).to.not.have.property('ember-cli-htmlbars'); }); - it('does not push multiple `ember-addon` keywords', function() { + it('does not push multiple `ember-addon` keywords', function () { let output = blueprint.updatePackageJson( JSON.stringify({ keywords: ['ember-addon'], @@ -151,7 +124,7 @@ describe('blueprint - addon', function() { expect(json.keywords).to.deep.equal(['ember-addon']); }); - it('overwrites any version of `ember-disable-prototype-extensions`', function() { + it('overwrites any version of `ember-disable-prototype-extensions`', function () { let output = blueprint.updatePackageJson( JSON.stringify({ devDependencies: { @@ -164,7 +137,7 @@ describe('blueprint - addon', function() { expect(json.devDependencies['ember-disable-prototype-extensions']).to.equal('^1.1.3'); }); - it('adds `scripts.test:all`', function() { + it('adds `scripts.test:all`', function () { let output = blueprint.updatePackageJson( JSON.stringify({ scripts: {}, @@ -172,10 +145,10 @@ describe('blueprint - addon', function() { ); let json = JSON.parse(output); - expect(json.scripts['test:all']).to.equal('ember try:each'); + expect(json.scripts['test:ember-compatibility']).to.equal('ember try:each'); }); - it('overwrites `ember-addon.configPath`', function() { + it('overwrites `ember-addon.configPath`', function () { let output = blueprint.updatePackageJson( JSON.stringify({ 'ember-addon': { @@ -188,7 +161,7 @@ describe('blueprint - addon', function() { expect(json['ember-addon'].configPath).to.equal('tests/dummy/config'); }); - it('preserves dependency ordering', function() { + it('preserves dependency ordering', function () { let output = blueprint.updatePackageJson( JSON.stringify({ dependencies: { @@ -207,6 +180,7 @@ describe('blueprint - addon', function() { delete json.devDependencies['eslint-plugin-node']; delete json.devDependencies['ember-try']; delete json.devDependencies['ember-source-channel-url']; + delete json.devDependencies['@embroider/test-setup']; expect(json.dependencies).to.deep.equal({ a: '1', b: '1' }); expect(json.devDependencies).to.deep.equal({ a: '1', b: '1' }); }); diff --git a/tests/unit/blueprints/in-repo-addon-test.js b/tests/unit/blueprints/in-repo-addon-test.js index c0ca1be2a0..a378d44e05 100644 --- a/tests/unit/blueprints/in-repo-addon-test.js +++ b/tests/unit/blueprints/in-repo-addon-test.js @@ -11,57 +11,54 @@ const td = require('testdouble'); const expect = require('ember-cli-blueprint-test-helpers/chai').expect; const file = require('ember-cli-blueprint-test-helpers/chai').file; -const { isExperimentEnabled } = require('../../../lib/experiments'); -describe('Acceptance: ember generate and destroy in-repo-addon', function() { +describe('Acceptance: ember generate and destroy in-repo-addon', function () { setupTestHooks(this, { cliPath: path.resolve(`${__dirname}/../../..`), }); - it('in-repo-addon fooBar', function() { + it('in-repo-addon fooBar', function () { let args = ['in-repo-addon', 'fooBar']; - const path = isExperimentEnabled('MODULE_UNIFICATION') ? 'packages' : 'lib'; - return emberNew() - .then(function() { + .then(function () { expect(fs.readJsonSync('package.json')['ember-addon']).to.be.undefined; }) - .then(function() { + .then(function () { return emberGenerate(args); }) - .then(function() { - expect(file(`${path}/foo-bar/package.json`)).to.exist; - expect(file(`${path}/foo-bar/index.js`)).to.exist; + .then(function () { + expect(file('lib/foo-bar/package.json')).to.exist; + expect(file('lib/foo-bar/index.js')).to.exist; - expect(fs.readJsonSync(`${path}/foo-bar/package.json`)).to.deep.equal({ + expect(fs.readJsonSync('lib/foo-bar/package.json')).to.deep.equal({ name: 'foo-bar', keywords: ['ember-addon'], }); expect(fs.readJsonSync('package.json')['ember-addon']).to.deep.equal({ - paths: [`${path}/foo-bar`], + paths: ['lib/foo-bar'], }); }) - .then(function() { + .then(function () { return emberDestroy(args); }) - .then(function() { - expect(file(`${path}/foo-bar/package.json`)).to.not.exist; - expect(file(`${path}/foo-bar/index.js`)).to.not.exist; + .then(function () { + expect(file('lib/foo-bar/package.json')).to.not.exist; + expect(file('lib/foo-bar/index.js')).to.not.exist; expect(fs.readJsonSync('package.json')['ember-addon']['paths']).to.be.undefined; }); }); }); -describe('Unit: in-repo-addon blueprint', function() { +describe('Unit: in-repo-addon blueprint', function () { let blueprint; let readJsonSync; let writeFileSync; let options; - beforeEach(function() { + beforeEach(function () { blueprint = require('../../../blueprints/in-repo-addon'); blueprint.project = { root: 'test-project-root', @@ -77,11 +74,11 @@ describe('Unit: in-repo-addon blueprint', function() { writeFileSync = td.replace(blueprint, '_writeFileSync'); }); - afterEach(function() { + afterEach(function () { td.reset(); }); - it('adds to paths', function() { + it('adds to paths', function () { td.when(readJsonSync(), { ignoreExtraArgs: true }).thenReturn({}); blueprint.afterInstall(options); @@ -103,7 +100,7 @@ describe('Unit: in-repo-addon blueprint', function() { ); }); - it('ignores if already exists', function() { + it('ignores if already exists', function () { td.when(readJsonSync(), { ignoreExtraArgs: true }).thenReturn({ 'ember-addon': { paths: ['lib/test-entity-name'], @@ -129,7 +126,7 @@ describe('Unit: in-repo-addon blueprint', function() { ); }); - it('removes from paths', function() { + it('removes from paths', function () { td.when(readJsonSync(), { ignoreExtraArgs: true }).thenReturn({ 'ember-addon': { paths: ['lib/test-entity-name', 'lib/test-entity-name-2'], @@ -155,7 +152,7 @@ describe('Unit: in-repo-addon blueprint', function() { ); }); - it('removes paths if last one', function() { + it('removes paths if last one', function () { td.when(readJsonSync(), { ignoreExtraArgs: true }).thenReturn({ 'ember-addon': { paths: ['lib/test-entity-name'], @@ -175,7 +172,7 @@ describe('Unit: in-repo-addon blueprint', function() { }\n'); }); - it('alphabetizes paths', function() { + it('alphabetizes paths', function () { td.when(readJsonSync(), { ignoreExtraArgs: true }).thenReturn({ 'ember-addon': { paths: ['lib/test-entity-name-2'], diff --git a/tests/unit/blueprints/lib-test.js b/tests/unit/blueprints/lib-test.js index 9411a6868b..e975d44fbc 100644 --- a/tests/unit/blueprints/lib-test.js +++ b/tests/unit/blueprints/lib-test.js @@ -10,28 +10,28 @@ let modifyPackages = blueprintHelpers.modifyPackages; const expect = require('ember-cli-blueprint-test-helpers/chai').expect; const dir = require('chai-files').dir; -describe('Acceptance: ember generate and destroy lib', function() { +describe('Acceptance: ember generate and destroy lib', function () { setupTestHooks(this, { cliPath: path.resolve(`${__dirname}/../../..`), }); - it('lib foo', async function() { + it('lib foo', async function () { let args = ['lib', 'foo']; await emberNew(); - await emberGenerateDestroy(args, file => { + await emberGenerateDestroy(args, (file) => { expect(dir('lib')).to.exist; expect(file('lib/.eslintrc.js')).to.not.exist; expect(file('lib/.jshintrc')).to.not.exist; }); }); - it('lib foo with ember-cli-jshint', async function() { + it('lib foo with ember-cli-jshint', async function () { let args = ['lib', 'foo']; await emberNew(); await modifyPackages([{ name: 'ember-cli-jshint', dev: true }]); - await emberGenerateDestroy(args, file => { + await emberGenerateDestroy(args, (file) => { expect(dir('lib')).to.exist; expect(file('lib/.jshintrc')).to.not.exist; }); diff --git a/tests/unit/blueprints/packages-test.js b/tests/unit/blueprints/packages-test.js deleted file mode 100644 index 67f5ae3d6d..0000000000 --- a/tests/unit/blueprints/packages-test.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -const path = require('path'); -const blueprintHelpers = require('ember-cli-blueprint-test-helpers/helpers'); -let setupTestHooks = blueprintHelpers.setupTestHooks; -let emberNew = blueprintHelpers.emberNew; -let emberGenerateDestroy = blueprintHelpers.emberGenerateDestroy; -let modifyPackages = blueprintHelpers.modifyPackages; - -const expect = require('ember-cli-blueprint-test-helpers/chai').expect; -const dir = require('chai-files').dir; - -describe('Acceptance: ember generate and destroy packages', function() { - setupTestHooks(this, { - cliPath: path.resolve(`${__dirname}/../../..`), - }); - - it('packages foo', function() { - let args = ['packages', 'foo']; - - return emberNew().then(() => - emberGenerateDestroy(args, file => { - expect(dir('packages')).to.exist; - expect(file('lib/.eslintrc.js')).to.not.exist; - expect(file('packages/.jshintrc')).to.not.exist; - }) - ); - }); - - it('packages foo with ember-cli-jshint', function() { - let args = ['packages', 'foo']; - - return emberNew() - .then(() => modifyPackages([{ name: 'ember-cli-jshint', dev: true }])) - .then(() => - emberGenerateDestroy(args, file => { - expect(dir('packages')).to.exist; - expect(file('packages/.jshintrc')).to.not.exist; - }) - ); - }); -}); diff --git a/tests/unit/blueprints/server-test.js b/tests/unit/blueprints/server-test.js index 11da650997..89aeabc549 100644 --- a/tests/unit/blueprints/server-test.js +++ b/tests/unit/blueprints/server-test.js @@ -11,12 +11,12 @@ const chai = require('ember-cli-blueprint-test-helpers/chai'); let expect = chai.expect; let file = chai.file; -describe('Acceptance: ember generate and destroy server', function() { +describe('Acceptance: ember generate and destroy server', function () { setupTestHooks(this, { cliPath: path.resolve(`${__dirname}/../../..`), }); - it('server', async function() { + it('server', async function () { let args = ['server']; await emberNew(); @@ -26,7 +26,7 @@ describe('Acceptance: ember generate and destroy server', function() { // TODO: assert that `morgan` and `glob` dependencies were installed }); - it('server with ember-cli-jshint', async function() { + it('server with ember-cli-jshint', async function () { let args = ['server']; await emberNew(); diff --git a/tests/unit/broccoli/addon/linting-test.js b/tests/unit/broccoli/addon/linting-test.js index e0e6f8d880..6d18f159d6 100644 --- a/tests/unit/broccoli/addon/linting-test.js +++ b/tests/unit/broccoli/addon/linting-test.js @@ -1,6 +1,5 @@ 'use strict'; -const co = require('co'); const broccoliTestHelper = require('broccoli-test-helper'); const expect = require('chai').expect; @@ -11,239 +10,215 @@ const Addon = require('../../../../lib/models/addon'); const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; -describe('Addon - linting', function() { +describe('Addon - linting', function () { let input, output, addon, lintTrees; - beforeEach( - co.wrap(function*() { - input = yield createTempDir(); - let MockAddon = Addon.extend({ - name: 'first', - root: input.path(), - }); - lintTrees = []; - let cli = new MockCLI(); - let pkg = { name: 'ember-app-test' }; - - let project = new Project(input.path(), pkg, cli.ui, cli); - project.addons = [ - { - name: 'fake-linter-addon', - lintTree(type, tree) { - lintTrees.push(tree); - }, - }, - ]; - - addon = new MockAddon(project, project); - }) - ); - - afterEach( - co.wrap(function*() { - yield input.dispose(); - yield output.dispose(); - }) - ); - - it( - 'calls lintTree on project addons for app directory', - co.wrap(function*() { - input.write({ - app: { - 'derp.js': '// slerpy', - }, - }); - - addon.jshintAddonTree(); - expect(lintTrees.length).to.equal(1); - - output = yield buildOutput(lintTrees[0]); - - expect(output.read()).to.deep.equal({ - app: { - 'derp.js': '// slerpy', - }, - }); - }) - ); - - it( - 'calls lintTree on project addons for addon directory', - co.wrap(function*() { - input.write({ - addon: { - 'derp.js': '// slerpy', - }, - }); - - addon.jshintAddonTree(); - expect(lintTrees.length).to.equal(2); - - output = yield buildOutput(lintTrees[0]); - - expect(output.read()).to.deep.equal({ - addon: { - 'derp.js': '// slerpy', - }, - }); - - yield output.dispose(); - - output = yield buildOutput(lintTrees[1]); - - expect(output.read()).to.deep.equal({ - addon: { - templates: {}, - }, - }); - }) - ); - - it( - 'calls lintTree on project addons for addon directory with only templates', - co.wrap(function*() { - input.write({ - addon: { - templates: { - 'foo.hbs': '{{huzzzah}}', - }, - }, - }); - - addon.jshintAddonTree(); - expect(lintTrees.length).to.equal(2); - - output = yield buildOutput(lintTrees[0]); - - expect(output.read()).to.deep.equal({}); - - yield output.dispose(); - - output = yield buildOutput(lintTrees[1]); - - expect(output.read()).to.deep.equal({ - addon: { - templates: { - 'foo.hbs': '{{huzzzah}}', - }, - }, - }); - }) - ); - - it( - 'calls lintTree on project addons for addon directory with templates', - co.wrap(function*() { - input.write({ - addon: { - 'derp.js': '// slerpy', - templates: { - 'foo.hbs': '{{huzzzah}}', - }, - }, - }); - - addon.jshintAddonTree(); - expect(lintTrees.length).to.equal(2); - - output = yield buildOutput(lintTrees[0]); - - expect(output.read()).to.deep.equal({ - addon: { - 'derp.js': '// slerpy', - }, - }); - - yield output.dispose(); - - output = yield buildOutput(lintTrees[1]); - - expect(output.read()).to.deep.equal({ - addon: { - templates: { - 'foo.hbs': '{{huzzzah}}', - }, - }, - }); - }) - ); - - it( - 'calls lintTree on project addons for addon-test-support directory', - co.wrap(function*() { - input.write({ - 'addon-test-support': { - 'derp.js': '// slerpy', - }, - }); + beforeEach(async function () { + input = await createTempDir(); + let MockAddon = Addon.extend({ + name: 'first', + root: input.path(), + packageRoot: input.path(), + }); + lintTrees = []; + let cli = new MockCLI(); + let pkg = { name: 'ember-app-test' }; + + let project = new Project(input.path(), pkg, cli.ui, cli); + project.addons = [ + { + name: 'fake-linter-addon', + lintTree(type, tree) { + lintTrees.push(tree); + }, + }, + ]; - addon.jshintAddonTree(); - expect(lintTrees.length).to.equal(1); + addon = new MockAddon(project, project); + }); + + afterEach(async function () { + await input.dispose(); + await output.dispose(); + }); - output = yield buildOutput(lintTrees[0]); + it('calls lintTree on project addons for app directory', async function () { + input.write({ + app: { + 'derp.js': '// slerpy', + }, + }); - expect(output.read()).to.deep.equal({ - 'addon-test-support': { - 'derp.js': '// slerpy', - }, - }); - }) - ); - - it( - 'calls lintTree on project addons for test-support directory', - co.wrap(function*() { - input.write({ - 'test-support': { - 'derp.js': '// slerpy', - }, - }); - - addon.jshintAddonTree(); - expect(lintTrees.length).to.equal(1); + addon.jshintAddonTree(); + expect(lintTrees.length).to.equal(1); + + output = await buildOutput(lintTrees[0]); - output = yield buildOutput(lintTrees[0]); - - expect(output.read()).to.deep.equal({ - 'test-support': { - 'derp.js': '// slerpy', - }, - }); - }) - ); - - it( - 'calls lintTree for trees in an addon', - co.wrap(function*() { - addon.project.addons[0].lintTree = function(type, tree) { - return tree; - }; - let addonRootContents = { - app: { - 'app-foo.js': '// hoo-foo', - }, - addon: { - 'addon-bar.js': '// bar-dar', - templates: { - 'addon-templates-quux.hbs': '{{! quux-books }}', - }, - }, - 'addon-test-support': { - 'addon-test-support-baz.js': '// baz-jazz', - }, - 'test-support': { - 'test-support-qux.js': '// qux-bucks', - }, - }; - input.write(addonRootContents); - - output = yield buildOutput(addon.jshintAddonTree()); - expect(output.read()).to.deep.equal({ - first: { - tests: addonRootContents, - }, - }); - }) - ); + expect(output.read()).to.deep.equal({ + app: { + 'derp.js': '// slerpy', + }, + }); + }); + + it('calls lintTree on project addons for addon directory', async function () { + input.write({ + addon: { + 'derp.js': '// slerpy', + }, + }); + + addon.jshintAddonTree(); + expect(lintTrees.length).to.equal(2); + + output = await buildOutput(lintTrees[0]); + + expect(output.read()).to.deep.equal({ + addon: { + 'derp.js': '// slerpy', + }, + }); + + await output.dispose(); + + output = await buildOutput(lintTrees[1]); + + expect(output.read()).to.deep.equal({ + addon: { + templates: {}, + }, + }); + }); + + it('calls lintTree on project addons for addon directory with only templates', async function () { + input.write({ + addon: { + templates: { + 'foo.hbs': '{{huzzzah}}', + }, + }, + }); + + addon.jshintAddonTree(); + expect(lintTrees.length).to.equal(2); + + output = await buildOutput(lintTrees[0]); + + expect(output.read()).to.deep.equal({}); + + await output.dispose(); + + output = await buildOutput(lintTrees[1]); + + expect(output.read()).to.deep.equal({ + addon: { + templates: { + 'foo.hbs': '{{huzzzah}}', + }, + }, + }); + }); + + it('calls lintTree on project addons for addon directory with templates', async function () { + input.write({ + addon: { + 'derp.js': '// slerpy', + templates: { + 'foo.hbs': '{{huzzzah}}', + }, + }, + }); + + addon.jshintAddonTree(); + expect(lintTrees.length).to.equal(2); + + output = await buildOutput(lintTrees[0]); + + expect(output.read()).to.deep.equal({ + addon: { + 'derp.js': '// slerpy', + }, + }); + + await output.dispose(); + + output = await buildOutput(lintTrees[1]); + + expect(output.read()).to.deep.equal({ + addon: { + templates: { + 'foo.hbs': '{{huzzzah}}', + }, + }, + }); + }); + + it('calls lintTree on project addons for addon-test-support directory', async function () { + input.write({ + 'addon-test-support': { + 'derp.js': '// slerpy', + }, + }); + + addon.jshintAddonTree(); + expect(lintTrees.length).to.equal(1); + + output = await buildOutput(lintTrees[0]); + + expect(output.read()).to.deep.equal({ + 'addon-test-support': { + 'derp.js': '// slerpy', + }, + }); + }); + + it('calls lintTree on project addons for test-support directory', async function () { + input.write({ + 'test-support': { + 'derp.js': '// slerpy', + }, + }); + + addon.jshintAddonTree(); + expect(lintTrees.length).to.equal(1); + + output = await buildOutput(lintTrees[0]); + + expect(output.read()).to.deep.equal({ + 'test-support': { + 'derp.js': '// slerpy', + }, + }); + }); + + it('calls lintTree for trees in an addon', async function () { + addon.project.addons[0].lintTree = function (type, tree) { + return tree; + }; + let addonRootContents = { + app: { + 'app-foo.js': '// hoo-foo', + }, + addon: { + 'addon-bar.js': '// bar-dar', + templates: { + 'addon-templates-quux.hbs': '{{! quux-books }}', + }, + }, + 'addon-test-support': { + 'addon-test-support-baz.js': '// baz-jazz', + }, + 'test-support': { + 'test-support-qux.js': '// qux-bucks', + }, + }; + input.write(addonRootContents); + + output = await buildOutput(addon.jshintAddonTree()); + expect(output.read()).to.deep.equal({ + first: { + tests: addonRootContents, + }, + }); + }); }); diff --git a/tests/unit/broccoli/addon/module-name-test.js b/tests/unit/broccoli/addon/module-name-test.js index f90b24bbaf..9a4daf7316 100644 --- a/tests/unit/broccoli/addon/module-name-test.js +++ b/tests/unit/broccoli/addon/module-name-test.js @@ -1,6 +1,5 @@ 'use strict'; -const co = require('co'); const path = require('path'); const broccoliTestHelper = require('broccoli-test-helper'); const expect = require('chai').expect; @@ -12,66 +11,60 @@ const Addon = require('../../../../lib/models/addon'); const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; -describe('Addon - moduleName', function() { +describe('Addon - moduleName', function () { let input, output, addon; - beforeEach( - co.wrap(function*() { - input = yield createTempDir(); - let MockAddon = Addon.extend({ - root: input.path(), - name: 'fake-addon', - moduleName() { - return 'totes-not-fake-addon'; - }, - }); - let cli = new MockCLI(); - let pkg = { name: 'ember-app-test' }; - let project = new Project(input.path(), pkg, cli.ui, cli); + beforeEach(async function () { + input = await createTempDir(); + let MockAddon = Addon.extend({ + root: input.path(), + packageRoot: input.path(), + name: 'fake-addon', + moduleName() { + return 'totes-not-fake-addon'; + }, + }); + let cli = new MockCLI(); + let pkg = { name: 'ember-app-test' }; + let project = new Project(input.path(), pkg, cli.ui, cli); - addon = new MockAddon(project, project); + addon = new MockAddon(project, project); - // override the registry so it just returns the input for everything - // and doesn't whine about not finding template preprocessors - addon.registry.load = () => [ - { - toTree(t) { - return t; - }, + // override the registry so it just returns the input for everything + // and doesn't whine about not finding template preprocessors + addon.registry.load = () => [ + { + toTree(t) { + return t; }, - ]; - }) - ); + }, + ]; + }); - afterEach( - co.wrap(function*() { - yield input.dispose(); - yield output.dispose(); - }) - ); + afterEach(async function () { + await input.dispose(); + await output.dispose(); + }); - it( - 'uses the module name function', - co.wrap(function*() { - input.write({ - addon: { - 'herp.js': '// slerpy', - templates: { - 'derp.hbs': '', - }, + it('uses the module name function', async function () { + input.write({ + addon: { + 'herp.js': '// slerpy', + templates: { + 'derp.hbs': '', }, - }); + }, + }); - output = yield buildOutput(addon.treeForAddon(path.join(addon.root, '/addon'))); + output = await buildOutput(addon.treeForAddon(path.join(addon.root, '/addon'))); - expect(output.read()).to.deep.equal({ - 'totes-not-fake-addon': { - 'herp.js': '// slerpy', - templates: { - 'derp.hbs': '', - }, + expect(output.read()).to.deep.equal({ + 'totes-not-fake-addon': { + 'herp.js': '// slerpy', + templates: { + 'derp.hbs': '', }, - }); - }) - ); + }, + }); + }); }); diff --git a/tests/unit/broccoli/builder-test.js b/tests/unit/broccoli/builder-test.js index 4920ec6048..ffaba584c4 100644 --- a/tests/unit/broccoli/builder-test.js +++ b/tests/unit/broccoli/builder-test.js @@ -1,9 +1,7 @@ 'use strict'; -const co = require('co'); const broccoliTestHelper = require('broccoli-test-helper'); const expect = require('chai').expect; -const ci = require('ci-info'); const MockProject = require('../../helpers/mock-project'); const Builder = require('../../../lib/models/builder'); @@ -11,35 +9,29 @@ const Builder = require('../../../lib/models/builder'); const { createTempDir, fromBuilder } = broccoliTestHelper; const ROOT = process.cwd(); -describe('Builder - broccoli tests', function() { +describe('Builder - broccoli tests', function () { let projectRoot, builderOutputPath, output, project, builder; - beforeEach( - co.wrap(function*() { - projectRoot = yield createTempDir(); - builderOutputPath = yield createTempDir(); - - project = new MockProject({ root: projectRoot.path() }); - }) - ); - - afterEach( - co.wrap(function*() { - yield projectRoot.dispose(); - yield builderOutputPath.dispose(); - yield output.dispose(); - - // this is needed because lib/utilities/find-build-file.js does a - // `process.chdir` when it looks for the `ember-cli-build.js` - process.chdir(ROOT); - }) - ); - - (ci.APPVEYOR ? it.skip : it)( - 'falls back to broccoli-builder@0.18 when legacy plugins exist in build', - co.wrap(function*() { - projectRoot.write({ - 'ember-cli-build.js': ` + beforeEach(async function () { + projectRoot = await createTempDir(); + builderOutputPath = await createTempDir(); + + project = new MockProject({ root: projectRoot.path() }); + }); + + afterEach(async function () { + // this is needed because lib/utilities/find-build-file.js does a + // `process.chdir` when it looks for the `ember-cli-build.js` + process.chdir(ROOT); + + await projectRoot.dispose(); + await builderOutputPath.dispose(); + await output.dispose(); + }); + + it('falls back to broccoli-builder@0.18 when legacy plugins exist in build', async function () { + projectRoot.write({ + 'ember-cli-build.js': ` const fs = require('fs'); const os = require('os'); const crypto = require('crypto'); @@ -75,35 +67,34 @@ describe('Builder - broccoli tests', function() { return new LegacyPlugin(__dirname + '/app'); } `, - app: { - 'hello.txt': '// hello!', - }, - }); - - builder = new Builder({ - project, - ui: project.ui, - onProcessInterrupt: { - addHandler() {}, - removeHandler() {}, - }, - outputPath: builderOutputPath.path(), - }); - - output = fromBuilder(builder); - yield output.build(); - - expect(output.read()).to.deep.equal({ + app: { 'hello.txt': '// hello!', - }); - - expect(builder.broccoliBuilderFallback).to.be.true; - expect(builder.ui.output).to.include( - 'WARNING: Invalid Broccoli2 node detected, falling back to broccoli-builder. Broccoli error:' - ); - expect(builder.ui.output).to.include( - 'LegacyPlugin: The .read/.rebuild API is no longer supported as of Broccoli 1.0. Plugins must now derive from broccoli-plugin. https://github.com/broccolijs/broccoli/blob/master/docs/broccoli-1-0-plugin-api.md' - ); - }) - ); + }, + }); + + builder = new Builder({ + project, + ui: project.ui, + onProcessInterrupt: { + addHandler() {}, + removeHandler() {}, + }, + outputPath: builderOutputPath.path(), + }); + + output = fromBuilder(builder); + await output.build(); + + expect(output.read()).to.deep.equal({ + 'hello.txt': '// hello!', + }); + + expect(builder.broccoliBuilderFallback).to.be.true; + expect(builder.ui.output).to.include( + 'WARNING: Invalid Broccoli2 node detected, falling back to broccoli-builder. Broccoli error:' + ); + expect(builder.ui.output).to.include( + 'LegacyPlugin: The .read/.rebuild API is no longer supported as of Broccoli 1.0. Plugins must now derive from broccoli-plugin. https://github.com/broccolijs/broccoli/blob/master/docs/broccoli-1-0-plugin-api.md' + ); + }); }); diff --git a/tests/unit/broccoli/default-packager/additional-assets-test.js b/tests/unit/broccoli/default-packager/additional-assets-test.js index 181a48f46c..1583d674e2 100644 --- a/tests/unit/broccoli/default-packager/additional-assets-test.js +++ b/tests/unit/broccoli/default-packager/additional-assets-test.js @@ -1,6 +1,5 @@ 'use strict'; -const co = require('co'); const expect = require('chai').expect; const Funnel = require('broccoli-funnel'); const DefaultPackager = require('../../../../lib/broccoli/default-packager'); @@ -11,7 +10,7 @@ const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; const setupRegistryFor = defaultPackagerHelpers.setupRegistryFor; -describe('Default Packager: Additional Assets', function() { +describe('Default Packager: Additional Assets', function () { let input, output; let MODULES = { @@ -48,7 +47,7 @@ describe('Default Packager: Additional Assets', function() { return { a: 1 }; }, - registry: setupRegistryFor('template', function(tree) { + registry: setupRegistryFor('template', function (tree) { return new Funnel(tree, { getDestinationPath(relativePath) { return relativePath.replace(/hbs$/g, 'js'); @@ -59,121 +58,109 @@ describe('Default Packager: Additional Assets', function() { addons: [], }; - before( - co.wrap(function*() { - input = yield createTempDir(); - - input.write(MODULES); - }) - ); - - after( - co.wrap(function*() { - yield input.dispose(); - }) - ); - - afterEach( - co.wrap(function*() { - yield output.dispose(); - }) - ); - - it( - 'caches packaged javascript tree', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', - - distPaths: { - appJsFile: '/assets/the-best-app-ever.js', - vendorJsFile: '/assets/vendor.js', - }, + before(async function () { + input = await createTempDir(); - registry: setupRegistryFor('template', function(tree) { - return new Funnel(tree, { - getDestinationPath(relativePath) { - return relativePath.replace(/hbs$/g, 'js'); - }, - }); - }), - - additionalAssetPaths: [ - { - src: 'vendor/font-awesome/fonts', - file: 'FontAwesome.otf', - dest: 'fonts', - }, - { - src: 'vendor/font-awesome/fonts', - file: 'FontAwesome.woff', - dest: 'fonts', + input.write(MODULES); + }); + + after(async function () { + await input.dispose(); + }); + + afterEach(async function () { + await output.dispose(); + }); + + it('caches packaged javascript tree', async function () { + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', + env: 'development', + + distPaths: { + appJsFile: '/assets/the-best-app-ever.js', + vendorJsFile: '/assets/vendor.js', + }, + + registry: setupRegistryFor('template', function (tree) { + return new Funnel(tree, { + getDestinationPath(relativePath) { + return relativePath.replace(/hbs$/g, 'js'); }, - ], + }); + }), + + additionalAssetPaths: [ + { + src: 'vendor/font-awesome/fonts', + file: 'FontAwesome.otf', + dest: 'fonts', + }, + { + src: 'vendor/font-awesome/fonts', + file: 'FontAwesome.woff', + dest: 'fonts', + }, + ], - project, - }); + project, + }); - expect(defaultPackager._cachedProcessedAdditionalAssets).to.equal(null); + expect(defaultPackager._cachedProcessedAdditionalAssets).to.equal(null); - output = yield buildOutput(defaultPackager.importAdditionalAssets(input.path())); + output = await buildOutput(defaultPackager.importAdditionalAssets(input.path())); - expect(defaultPackager._cachedProcessedAdditionalAssets).to.not.equal(null); - expect(defaultPackager._cachedProcessedAdditionalAssets._annotation).to.equal( - 'vendor/font-awesome/fonts/{FontAwesome.otf,FontAwesome.woff} => fonts/{FontAwesome.otf,FontAwesome.woff}' - ); - }) - ); + expect(defaultPackager._cachedProcessedAdditionalAssets).to.not.equal(null); + expect(defaultPackager._cachedProcessedAdditionalAssets._annotation).to.equal( + 'vendor/font-awesome/fonts/{FontAwesome.otf,FontAwesome.woff} => fonts/{FontAwesome.otf,FontAwesome.woff}' + ); + }); - it( - 'imports additional assets properly', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', + it('imports additional assets properly', async function () { + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', + env: 'development', - distPaths: { - appJsFile: '/assets/the-best-app-ever.js', - vendorJsFile: '/assets/vendor.js', - }, + distPaths: { + appJsFile: '/assets/the-best-app-ever.js', + vendorJsFile: '/assets/vendor.js', + }, - registry: setupRegistryFor('template', function(tree) { - return new Funnel(tree, { - getDestinationPath(relativePath) { - return relativePath.replace(/hbs$/g, 'js'); - }, - }); - }), - - additionalAssetPaths: [ - { - src: 'vendor/font-awesome/fonts', - file: 'FontAwesome.otf', - dest: 'fonts', - }, - { - src: 'vendor/font-awesome/fonts', - file: 'FontAwesome.woff', - dest: 'fonts', + registry: setupRegistryFor('template', function (tree) { + return new Funnel(tree, { + getDestinationPath(relativePath) { + return relativePath.replace(/hbs$/g, 'js'); }, - ], + }); + }), + + additionalAssetPaths: [ + { + src: 'vendor/font-awesome/fonts', + file: 'FontAwesome.otf', + dest: 'fonts', + }, + { + src: 'vendor/font-awesome/fonts', + file: 'FontAwesome.woff', + dest: 'fonts', + }, + ], - project, - }); + project, + }); - expect(defaultPackager._cachedProcessedAdditionalAssets).to.equal(null); + expect(defaultPackager._cachedProcessedAdditionalAssets).to.equal(null); - output = yield buildOutput(defaultPackager.importAdditionalAssets(input.path())); + output = await buildOutput(defaultPackager.importAdditionalAssets(input.path())); - let outputFiles = output.read(); + let outputFiles = output.read(); - expect(outputFiles).to.deep.equal({ - fonts: { - 'FontAwesome.otf': '', - 'FontAwesome.woff': '', - }, - }); - }) - ); + expect(outputFiles).to.deep.equal({ + fonts: { + 'FontAwesome.otf': '', + 'FontAwesome.woff': '', + }, + }); + }); }); diff --git a/tests/unit/broccoli/default-packager/bower-test.js b/tests/unit/broccoli/default-packager/bower-test.js index 06c8589f53..b6ecaf7ea5 100644 --- a/tests/unit/broccoli/default-packager/bower-test.js +++ b/tests/unit/broccoli/default-packager/bower-test.js @@ -1,6 +1,5 @@ 'use strict'; -const co = require('co'); const expect = require('chai').expect; const DefaultPackager = require('../../../../lib/broccoli/default-packager'); const broccoliTestHelper = require('broccoli-test-helper'); @@ -8,7 +7,7 @@ const broccoliTestHelper = require('broccoli-test-helper'); const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; -describe('Default Packager: Bower', function() { +describe('Default Packager: Bower', function () { let input; let BOWER_PACKAGES = { @@ -29,59 +28,46 @@ describe('Default Packager: Bower', function() { }, }; - before( - co.wrap(function*() { - input = yield createTempDir(); - - input.write(BOWER_PACKAGES); - }) - ); - - after( - co.wrap(function*() { - yield input.dispose(); - }) - ); - - it( - 'caches packaged bower tree', - co.wrap(function*() { - let defaultPackager = new DefaultPackager(); - - expect(defaultPackager._cachedBower).to.equal(null); - - yield buildOutput(defaultPackager.packageBower(input.path())); - - expect(defaultPackager._cachedBower).to.not.equal(null); - expect(defaultPackager._cachedBower._annotation).to.equal('Packaged Bower'); - }) - ); - - it( - 'packages bower files with default folder', - co.wrap(function*() { - let defaultPackager = new DefaultPackager(); - - let packagedBower = yield buildOutput(defaultPackager.packageBower(input.path())); - let output = packagedBower.read(); - - expect(output).to.deep.equal({ - bower_components: BOWER_PACKAGES, - }); - }) - ); - - it( - 'packages bower files with custom folder', - co.wrap(function*() { - let defaultPackager = new DefaultPackager(); - - let packagedBower = yield buildOutput(defaultPackager.packageBower(input.path(), 'foobar')); - let output = packagedBower.read(); - - expect(output).to.deep.equal({ - foobar: BOWER_PACKAGES, - }); - }) - ); + before(async function () { + input = await createTempDir(); + + input.write(BOWER_PACKAGES); + }); + + after(async function () { + await input.dispose(); + }); + + it('caches packaged bower tree', async function () { + let defaultPackager = new DefaultPackager(); + + expect(defaultPackager._cachedBower).to.equal(null); + + await buildOutput(defaultPackager.packageBower(input.path())); + + expect(defaultPackager._cachedBower).to.not.equal(null); + expect(defaultPackager._cachedBower._annotation).to.equal('Packaged Bower'); + }); + + it('packages bower files with default folder', async function () { + let defaultPackager = new DefaultPackager(); + + let packagedBower = await buildOutput(defaultPackager.packageBower(input.path())); + let output = packagedBower.read(); + + expect(output).to.deep.equal({ + bower_components: BOWER_PACKAGES, + }); + }); + + it('packages bower files with custom folder', async function () { + let defaultPackager = new DefaultPackager(); + + let packagedBower = await buildOutput(defaultPackager.packageBower(input.path(), 'foobar')); + let output = packagedBower.read(); + + expect(output).to.deep.equal({ + foobar: BOWER_PACKAGES, + }); + }); }); diff --git a/tests/unit/broccoli/default-packager/config-test.js b/tests/unit/broccoli/default-packager/config-test.js index 60d340f86b..27f7c2d1d0 100644 --- a/tests/unit/broccoli/default-packager/config-test.js +++ b/tests/unit/broccoli/default-packager/config-test.js @@ -1,6 +1,5 @@ 'use strict'; -const co = require('co'); const expect = require('chai').expect; const DefaultPackager = require('../../../../lib/broccoli/default-packager'); const broccoliTestHelper = require('broccoli-test-helper'); @@ -8,7 +7,7 @@ const broccoliTestHelper = require('broccoli-test-helper'); const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; -describe('Default Packager: Config', function() { +describe('Default Packager: Config', function () { let input, output; let name = 'the-best-app-ever'; let env = 'development'; @@ -28,94 +27,79 @@ describe('Default Packager: Config', function() { }, }; - before( - co.wrap(function*() { - input = yield createTempDir(); - - input.write(CONFIG); - }) - ); - - after( - co.wrap(function*() { - yield input.dispose(); - }) - ); - - afterEach( - co.wrap(function*() { - yield output.dispose(); - }) - ); - - it( - 'caches packaged config tree', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name, - project, - env, - }); - - expect(defaultPackager._cachedConfig).to.equal(null); - - output = yield buildOutput(defaultPackager.packageConfig()); - - expect(defaultPackager._cachedConfig).to.not.equal(null); - expect(defaultPackager._cachedConfig._annotation).to.equal('Packaged Config'); - }) - ); - - it( - 'packages config files w/ tests disabled', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name, - project, - env, - areTestsEnabled: false, - }); - - output = yield buildOutput(defaultPackager.packageConfig()); - - let outputFiles = output.read(); - - expect(outputFiles).to.deep.equal({ - [name]: { - config: { - environments: { - 'development.json': '{"a":1}', - }, + before(async function () { + input = await createTempDir(); + + input.write(CONFIG); + }); + + after(async function () { + await input.dispose(); + }); + + afterEach(async function () { + await output.dispose(); + }); + + it('caches packaged config tree', async function () { + let defaultPackager = new DefaultPackager({ + name, + project, + env, + }); + + expect(defaultPackager._cachedConfig).to.equal(null); + + output = await buildOutput(defaultPackager.packageConfig()); + + expect(defaultPackager._cachedConfig).to.not.equal(null); + expect(defaultPackager._cachedConfig._annotation).to.equal('Packaged Config'); + }); + + it('packages config files w/ tests disabled', async function () { + let defaultPackager = new DefaultPackager({ + name, + project, + env, + areTestsEnabled: false, + }); + + output = await buildOutput(defaultPackager.packageConfig()); + + let outputFiles = output.read(); + + expect(outputFiles).to.deep.equal({ + [name]: { + config: { + environments: { + 'development.json': '{"a":1}', }, }, - }); - }) - ); - - it( - 'packages config files w/ tests enabled', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name, - project, - env, - areTestsEnabled: true, - }); - - output = yield buildOutput(defaultPackager.packageConfig()); - - let outputFiles = output.read(); - - expect(outputFiles).to.deep.equal({ - [name]: { - config: { - environments: { - 'development.json': '{"a":1}', - 'test.json': '{"a":1}', - }, + }, + }); + }); + + it('packages config files w/ tests enabled', async function () { + let defaultPackager = new DefaultPackager({ + name, + project, + env, + areTestsEnabled: true, + }); + + output = await buildOutput(defaultPackager.packageConfig()); + + let outputFiles = output.read(); + + expect(outputFiles).to.deep.equal({ + [name]: { + config: { + environments: { + 'development.json': '{"a":1}', + 'test.json': '{"a":1}', }, }, - }); - }) - ); + }, + }); + }); }); diff --git a/tests/unit/broccoli/default-packager/ember-cli-internal-test.js b/tests/unit/broccoli/default-packager/ember-cli-internal-test.js index 5ca8c9bd08..2d201efb6f 100644 --- a/tests/unit/broccoli/default-packager/ember-cli-internal-test.js +++ b/tests/unit/broccoli/default-packager/ember-cli-internal-test.js @@ -1,6 +1,5 @@ 'use strict'; -const co = require('co'); const expect = require('chai').expect; const DefaultPackager = require('../../../../lib/broccoli/default-packager'); const broccoliTestHelper = require('broccoli-test-helper'); @@ -8,7 +7,7 @@ const broccoliTestHelper = require('broccoli-test-helper'); const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; -describe('Default Packager: Ember CLI Internal', function() { +describe('Default Packager: Ember CLI Internal', function () { let input, output; let CONFIG = { @@ -30,216 +29,192 @@ describe('Default Packager: Ember CLI Internal', function() { }, }; - before( - co.wrap(function*() { - input = yield createTempDir(); - - input.write(CONFIG); - }) - ); - - after( - co.wrap(function*() { - yield input.dispose(); - }) - ); - - afterEach( - co.wrap(function*() { - yield output.dispose(); - }) - ); - - it( - 'caches packaged ember cli internal tree', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - env: 'development', - name: 'the-best-app-ever', - project, - autoRun: false, - areTestsEnabled: true, - storeConfigInMeta: false, - isModuleUnificationEnabled: false, - }); - - expect(defaultPackager._cachedEmberCliInternalTree).to.equal(null); - - output = yield buildOutput(defaultPackager.packageEmberCliInternalFiles()); - - expect(defaultPackager._cachedEmberCliInternalTree).to.not.equal(null); - expect(defaultPackager._cachedEmberCliInternalTree._annotation).to.equal('Packaged Ember CLI Internal Files'); - }) - ); - - it( - 'packages internal files properly', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - env: 'development', - name: 'the-best-app-ever', - project, - autoRun: false, - areTestsEnabled: true, - storeConfigInMeta: false, - isModuleUnificationEnabled: false, - }); - - expect(defaultPackager._cachedEmberCliInternalTree).to.equal(null); - - output = yield buildOutput(defaultPackager.packageEmberCliInternalFiles()); - - let outputFiles = output.read(); - - let emberCliFiles = outputFiles.vendor['ember-cli']; - - expect(Object.keys(emberCliFiles)).to.deep.equal([ - 'app-boot.js', - 'app-config.js', - 'app-prefix.js', - 'app-suffix.js', - 'test-support-prefix.js', - 'test-support-suffix.js', - 'tests-prefix.js', - 'tests-suffix.js', - 'vendor-prefix.js', - 'vendor-suffix.js', - ]); - }) - ); - - it( - 'populates the contents of internal files correctly', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - env: 'development', - name: 'the-best-app-ever', - project, - autoRun: false, - areTestsEnabled: false, - storeConfigInMeta: false, - isModuleUnificationEnabled: false, - }); - - expect(defaultPackager._cachedEmberCliInternalTree).to.equal(null); - - output = yield buildOutput(defaultPackager.packageEmberCliInternalFiles()); - - let outputFiles = output.read(); - - let emberCliFiles = outputFiles.vendor['ember-cli']; - - let appBootFileContent = emberCliFiles['app-boot.js'].trim(); - let appConfigFileContent = emberCliFiles['app-config.js'].trim(); - let appPrefixFileContent = emberCliFiles['app-prefix.js'].trim(); - let appSuffixFileContent = emberCliFiles['app-suffix.js'].trim(); - - let testPrefixFileContent = emberCliFiles['tests-prefix.js'].trim(); - let testSuffixFileContent = emberCliFiles['tests-suffix.js'].trim(); - let testSupportPrefixFileContent = emberCliFiles['test-support-prefix.js'].trim(); - let testSupportSuffixFileContent = emberCliFiles['test-support-suffix.js'].trim(); - - let vendorPrefixFileContent = emberCliFiles['vendor-prefix.js'].trim(); - let vendorSuffixFileContent = emberCliFiles['vendor-suffix.js'].trim(); - - expect(appBootFileContent).to.equal(''); - expect(appConfigFileContent).to.contain(`'default': {"modulePrefix":"the-best-app-ever"}`); - expect(appPrefixFileContent).to.contain(`'use strict';`); - expect(appSuffixFileContent).to.equal(''); - - expect(testPrefixFileContent).to.contain(`'use strict';`); - expect(testSuffixFileContent).to.contain( - `require('the-best-app-ever/tests/test-helper');\nEmberENV.TESTS_FILE_LOADED = true;` - ); - expect(testSupportPrefixFileContent).to.equal(''); - expect(testSupportSuffixFileContent).to.contain( - 'runningTests = true;\n\nif (window.Testem) {\n window.Testem.hookIntoTestFramework();\n}' - ); - - expect(vendorPrefixFileContent).to.contain('window.EmberENV = {};\nvar runningTests = false;'); - expect(vendorSuffixFileContent).to.equal(''); - }) - ); - - it( - 'populates the contents of internal files correctly when `storeConfigInMeta` is enabled', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - env: 'development', - name: 'the-best-app-ever', - project, - autoRun: false, - areTestsEnabled: true, - storeConfigInMeta: true, - isModuleUnificationEnabled: false, - }); - - expect(defaultPackager._cachedEmberCliInternalTree).to.equal(null); - - output = yield buildOutput(defaultPackager.packageEmberCliInternalFiles()); - - let outputFiles = output.read(); - - let emberCliFiles = outputFiles.vendor['ember-cli']; - let appConfigFileContent = emberCliFiles['app-config.js'].trim(); - - expect(appConfigFileContent).to.contain(`var rawConfig = document.querySelector(`); - expect(appConfigFileContent).to.contain(`var config = JSON.parse(decodeURIComponent(rawConfig));`); - }) - ); - - it( - 'populates the contents of internal files correctly, including content from add-ons', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - env: 'development', - name: 'the-best-app-ever', - project: { - addons: [ - { - contentFor(type) { - if (type === 'app-prefix') { - return 'CUSTOM APP PREFIX CODE'; - } - }, + before(async function () { + input = await createTempDir(); + + input.write(CONFIG); + }); + + after(async function () { + await input.dispose(); + }); + + afterEach(async function () { + await output.dispose(); + }); + + it('caches packaged ember cli internal tree', async function () { + let defaultPackager = new DefaultPackager({ + env: 'development', + name: 'the-best-app-ever', + project, + autoRun: false, + areTestsEnabled: true, + storeConfigInMeta: false, + }); + + expect(defaultPackager._cachedEmberCliInternalTree).to.equal(null); + + output = await buildOutput(defaultPackager.packageEmberCliInternalFiles()); + + expect(defaultPackager._cachedEmberCliInternalTree).to.not.equal(null); + expect(defaultPackager._cachedEmberCliInternalTree._annotation).to.equal('Packaged Ember CLI Internal Files'); + }); + + it('packages internal files properly', async function () { + let defaultPackager = new DefaultPackager({ + env: 'development', + name: 'the-best-app-ever', + project, + autoRun: false, + areTestsEnabled: true, + storeConfigInMeta: false, + }); + + expect(defaultPackager._cachedEmberCliInternalTree).to.equal(null); + + output = await buildOutput(defaultPackager.packageEmberCliInternalFiles()); + + let outputFiles = output.read(); + + let emberCliFiles = outputFiles.vendor['ember-cli']; + + expect(Object.keys(emberCliFiles)).to.deep.equal([ + 'app-boot.js', + 'app-config.js', + 'app-prefix.js', + 'app-suffix.js', + 'test-support-prefix.js', + 'test-support-suffix.js', + 'tests-prefix.js', + 'tests-suffix.js', + 'vendor-prefix.js', + 'vendor-suffix.js', + ]); + }); + + it('populates the contents of internal files correctly', async function () { + let defaultPackager = new DefaultPackager({ + env: 'development', + name: 'the-best-app-ever', + project, + autoRun: false, + areTestsEnabled: false, + storeConfigInMeta: false, + }); + + expect(defaultPackager._cachedEmberCliInternalTree).to.equal(null); + + output = await buildOutput(defaultPackager.packageEmberCliInternalFiles()); + + let outputFiles = output.read(); + + let emberCliFiles = outputFiles.vendor['ember-cli']; + + let appBootFileContent = emberCliFiles['app-boot.js'].trim(); + let appConfigFileContent = emberCliFiles['app-config.js'].trim(); + let appPrefixFileContent = emberCliFiles['app-prefix.js'].trim(); + let appSuffixFileContent = emberCliFiles['app-suffix.js'].trim(); + + let testPrefixFileContent = emberCliFiles['tests-prefix.js'].trim(); + let testSuffixFileContent = emberCliFiles['tests-suffix.js'].trim(); + let testSupportPrefixFileContent = emberCliFiles['test-support-prefix.js'].trim(); + let testSupportSuffixFileContent = emberCliFiles['test-support-suffix.js'].trim(); + + let vendorPrefixFileContent = emberCliFiles['vendor-prefix.js'].trim(); + let vendorSuffixFileContent = emberCliFiles['vendor-suffix.js'].trim(); + + expect(appBootFileContent).to.equal(''); + expect(appConfigFileContent).to.contain(`'default': {"modulePrefix":"the-best-app-ever"}`); + expect(appPrefixFileContent).to.contain(`'use strict';`); + expect(appSuffixFileContent).to.equal(''); + + expect(testPrefixFileContent).to.contain(`'use strict';`); + expect(testSuffixFileContent).to.contain( + `require('the-best-app-ever/tests/test-helper');\nEmberENV.TESTS_FILE_LOADED = true;` + ); + expect(testSupportPrefixFileContent).to.equal(''); + expect(testSupportSuffixFileContent).to.contain( + 'runningTests = true;\n\nif (window.Testem) {\n window.Testem.hookIntoTestFramework();\n}' + ); + + expect(vendorPrefixFileContent).to.contain( + 'window.EmberENV = (function(EmberENV, extra) {\n for (var key in extra) {\n EmberENV[key] = extra[key];\n }\n\n return EmberENV;\n})(window.EmberENV || {}, {});\n\nvar runningTests = false;' + ); + expect(vendorSuffixFileContent).to.equal(''); + }); + + it('populates the contents of internal files correctly when `storeConfigInMeta` is enabled', async function () { + let defaultPackager = new DefaultPackager({ + env: 'development', + name: 'the-best-app-ever', + project, + autoRun: false, + areTestsEnabled: true, + storeConfigInMeta: true, + }); + + expect(defaultPackager._cachedEmberCliInternalTree).to.equal(null); + + output = await buildOutput(defaultPackager.packageEmberCliInternalFiles()); + + let outputFiles = output.read(); + + let emberCliFiles = outputFiles.vendor['ember-cli']; + let appConfigFileContent = emberCliFiles['app-config.js'].trim(); + + expect(appConfigFileContent).to.contain(`var rawConfig = document.querySelector(`); + expect(appConfigFileContent).to.contain(`var config = JSON.parse(decodeURIComponent(rawConfig));`); + }); + + it('populates the contents of internal files correctly, including content from add-ons', async function () { + let defaultPackager = new DefaultPackager({ + env: 'development', + name: 'the-best-app-ever', + project: { + addons: [ + { + contentFor(type) { + if (type === 'app-prefix') { + return 'CUSTOM APP PREFIX CODE'; + } }, - { - contentFor(type) { - if (type === 'app-boot') { - return 'CUSTOM APP BOOT CODE'; - } - }, + }, + { + contentFor(type) { + if (type === 'app-boot') { + return 'CUSTOM APP BOOT CODE'; + } }, - ], - - configPath() { - return `${input.path()}/config/environment`; }, + ], - config() { - return { - modulePrefix: 'the-best-app-ever', - }; - }, + configPath() { + return `${input.path()}/config/environment`; + }, + + config() { + return { + modulePrefix: 'the-best-app-ever', + }; }, - autoRun: false, - areTestsEnabled: true, - storeConfigInMeta: false, - isModuleUnificationEnabled: false, - }); + }, + autoRun: false, + areTestsEnabled: true, + storeConfigInMeta: false, + }); - expect(defaultPackager._cachedEmberCliInternalTree).to.equal(null); + expect(defaultPackager._cachedEmberCliInternalTree).to.equal(null); - output = yield buildOutput(defaultPackager.packageEmberCliInternalFiles()); + output = await buildOutput(defaultPackager.packageEmberCliInternalFiles()); - let outputFiles = output.read(); + let outputFiles = output.read(); - let emberCliFiles = outputFiles.vendor['ember-cli']; - let appBootFileContent = emberCliFiles['app-boot.js'].trim(); - let appPrefixFileContent = emberCliFiles['app-prefix.js'].trim(); + let emberCliFiles = outputFiles.vendor['ember-cli']; + let appBootFileContent = emberCliFiles['app-boot.js'].trim(); + let appPrefixFileContent = emberCliFiles['app-prefix.js'].trim(); - expect(appBootFileContent).to.equal('CUSTOM APP BOOT CODE'); - expect(appPrefixFileContent).to.equal(`'use strict';\n\nCUSTOM APP PREFIX CODE`); - }) - ); + expect(appBootFileContent).to.equal('CUSTOM APP BOOT CODE'); + expect(appPrefixFileContent).to.equal(`'use strict';\n\nCUSTOM APP PREFIX CODE`); + }); }); diff --git a/tests/unit/broccoli/default-packager/external-test.js b/tests/unit/broccoli/default-packager/external-test.js index c6200c5673..c78c938982 100644 --- a/tests/unit/broccoli/default-packager/external-test.js +++ b/tests/unit/broccoli/default-packager/external-test.js @@ -1,6 +1,5 @@ 'use strict'; -const co = require('co'); const expect = require('chai').expect; const DefaultPackager = require('../../../../lib/broccoli/default-packager'); const broccoliTestHelper = require('broccoli-test-helper'); @@ -8,7 +7,7 @@ const broccoliTestHelper = require('broccoli-test-helper'); const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; -describe('Default Packager: External', function() { +describe('Default Packager: External', function () { let input, output; let EXTERNAL = { @@ -21,69 +20,60 @@ describe('Default Packager: External', function() { }, }; - before( - co.wrap(function*() { - input = yield createTempDir(); - - input.write(EXTERNAL); - }) - ); - - after( - co.wrap(function*() { - yield input.dispose(); - }) - ); - - afterEach( - co.wrap(function*() { - yield output.dispose(); - }) - ); - - it( - 'applies transforms to an external tree', - co.wrap(function*() { - let customTransformsMap = new Map(); - - customTransformsMap.set('amd', { - files: ['vendor/auth0-js.js', 'vendor/auth0-lock.js', 'vendor/auth0-lock-passwordless.js'], - callback(tree, options) { - const stew = require('broccoli-stew'); - - return stew.map(tree, (content, relativePath) => { - const name = options[relativePath].as; - - return `${name} was transformed`; - }); + before(async function () { + input = await createTempDir(); + + input.write(EXTERNAL); + }); + + after(async function () { + await input.dispose(); + }); + + afterEach(async function () { + await output.dispose(); + }); + + it('applies transforms to an external tree', async function () { + let customTransformsMap = new Map(); + + customTransformsMap.set('amd', { + files: ['vendor/auth0-js.js', 'vendor/auth0-lock.js', 'vendor/auth0-lock-passwordless.js'], + callback(tree, options) { + const stew = require('broccoli-stew'); + + return stew.map(tree, (content, relativePath) => { + const name = options[relativePath].as; + + return `${name} was transformed`; + }); + }, + processOptions() {}, + options: { + 'vendor/auth0-js.js': { + as: 'auth0', + }, + 'vendor/auth0-lock-passwordless.js': { + as: 'auth0-lock-passwordless', }, - processOptions() {}, - options: { - 'vendor/auth0-js.js': { - as: 'auth0', - }, - 'vendor/auth0-lock-passwordless.js': { - as: 'auth0-lock-passwordless', - }, - 'vendor/auth0-lock.js': { - as: 'auth0-lock', - }, + 'vendor/auth0-lock.js': { + as: 'auth0-lock', }, - }); + }, + }); - let defaultPackager = new DefaultPackager({ - customTransformsMap, - }); + let defaultPackager = new DefaultPackager({ + customTransformsMap, + }); - output = yield buildOutput(defaultPackager.applyCustomTransforms(input.path())); + output = await buildOutput(defaultPackager.applyCustomTransforms(input.path())); - let outputFiles = output.read(); + let outputFiles = output.read(); - expect(outputFiles.vendor).to.deep.equal({ - 'auth0-js.js': 'auth0 was transformed', - 'auth0-lock.js': 'auth0-lock was transformed', - 'auth0-lock-passwordless.js': 'auth0-lock-passwordless was transformed', - }); - }) - ); + expect(outputFiles.vendor).to.deep.equal({ + 'auth0-js.js': 'auth0 was transformed', + 'auth0-lock.js': 'auth0-lock was transformed', + 'auth0-lock-passwordless.js': 'auth0-lock-passwordless was transformed', + }); + }); }); diff --git a/tests/unit/broccoli/default-packager/index-test.js b/tests/unit/broccoli/default-packager/index-test.js index 5e3f94b376..d1e08a20af 100644 --- a/tests/unit/broccoli/default-packager/index-test.js +++ b/tests/unit/broccoli/default-packager/index-test.js @@ -1,15 +1,13 @@ 'use strict'; -const co = require('co'); const expect = require('chai').expect; const DefaultPackager = require('../../../../lib/broccoli/default-packager'); const broccoliTestHelper = require('broccoli-test-helper'); -const { isExperimentEnabled } = require('../../../../lib/experiments'); const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; -describe('Default Packager: Index', function() { +describe('Default Packager: Index', function () { let input, output; let project = { @@ -30,240 +28,109 @@ describe('Default Packager: Index', function() { let META_TAG = '/best-url-ever/\n'; - before( - co.wrap(function*() { - input = yield createTempDir(); + before(async function () { + input = await createTempDir(); - let indexContent = ` + let indexContent = ` {{rootURL}}{{content-for "head"}} {{content-for "head-footer"}} {{content-for "body"}} {{content-for "body-footer"}} `; - input.write({ - 'addon-tree-output': {}, - 'the-best-app-ever': { - 'router.js': 'router.js', - 'app.js': 'app.js', - 'index.html': indexContent, - config: { - 'environment.js': 'environment.js', - }, - templates: {}, - }, - }); - }) - ); - - after( - co.wrap(function*() { - yield input.dispose(); - }) - ); - - afterEach( - co.wrap(function*() { - yield output.dispose(); - }) - ); - - it( - 'caches processed index tree', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', - - autoRun: true, - storeConfigInMeta: true, - isModuleUnificationEnabled: false, - areTestsEnabled: true, - - distPaths: { - appHtmlFile: 'index.html', + input.write({ + 'addon-tree-output': {}, + 'the-best-app-ever': { + 'router.js': 'router.js', + 'app.js': 'app.js', + 'index.html': indexContent, + config: { + 'environment.js': 'environment.js', }, + templates: {}, + }, + }); + }); - project, - }); + after(async function () { + await input.dispose(); + }); - expect(defaultPackager._cachedProcessedIndex).to.equal(null); + afterEach(async function () { + await output.dispose(); + }); - output = yield buildOutput(defaultPackager.processIndex(input.path())); + it('caches processed index tree', async function () { + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', + env: 'development', - expect(defaultPackager._cachedProcessedIndex).to.not.equal(null); - }) - ); + autoRun: true, + storeConfigInMeta: true, + areTestsEnabled: true, - it( - 'works with a custom path', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', + distPaths: { + appHtmlFile: 'index.html', + }, - autoRun: true, - storeConfigInMeta: true, - isModuleUnificationEnabled: false, - areTestsEnabled: true, + project, + }); - distPaths: { - appHtmlFile: 'custom/index.html', - }, + expect(defaultPackager._cachedProcessedIndex).to.equal(null); - project, - }); + output = await buildOutput(defaultPackager.processIndex(input.path())); - expect(defaultPackager._cachedProcessedIndex).to.equal(null); + expect(defaultPackager._cachedProcessedIndex).to.not.equal(null); + }); - output = yield buildOutput(defaultPackager.processIndex(input.path())); + it('works with a custom path', async function () { + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', + env: 'development', - let outputFiles = output.read(); - let indexContent = decodeURIComponent(outputFiles.custom['index.html'].trim()); + autoRun: true, + storeConfigInMeta: true, + areTestsEnabled: true, - expect(indexContent).to.equal(META_TAG); - }) - ); + distPaths: { + appHtmlFile: 'custom/index.html', + }, - it( - 'populates `index.html` according to settings', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', + project, + }); - autoRun: true, - storeConfigInMeta: true, - isModuleUnificationEnabled: false, - areTestsEnabled: true, + expect(defaultPackager._cachedProcessedIndex).to.equal(null); - distPaths: { - appHtmlFile: 'index.html', - }, + output = await buildOutput(defaultPackager.processIndex(input.path())); + + let outputFiles = output.read(); + let indexContent = decodeURIComponent(outputFiles.custom['index.html'].trim()); + + expect(indexContent).to.equal(META_TAG); + }); - project, - }); - - expect(defaultPackager._cachedProcessedIndex).to.equal(null); - - output = yield buildOutput(defaultPackager.processIndex(input.path())); - - let outputFiles = output.read(); - let indexContent = decodeURIComponent(outputFiles['index.html'].trim()); - - expect(indexContent).to.equal(META_TAG); - }) - ); - - if (isExperimentEnabled('MODULE_UNIFICATION')) { - describe('with module unification', function() { - let input, output; - - before( - co.wrap(function*() { - input = yield createTempDir(); - - let indexContent = ` - {{rootURL}}{{content-for "head"}} - {{content-for "head-footer"}} - {{content-for "body"}} - {{content-for "body-footer"}} - `; - input.write({ - 'addon-tree-output': {}, - 'the-best-app-ever': { - 'router.js': 'router.js', - 'app.js': 'app.js', - 'index.html': indexContent, - config: { - 'environment.js': 'environment.js', - }, - templates: {}, - }, - src: { - ui: { - 'index.html': 'src', - }, - }, - }); - }) - ); - - after( - co.wrap(function*() { - yield input.dispose(); - }) - ); - - afterEach( - co.wrap(function*() { - yield output.dispose(); - }) - ); - - it( - 'prefers `src/ui/index.html` over `app/index.html`', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', - - autoRun: true, - storeConfigInMeta: true, - isModuleUnificationEnabled: true, - areTestsEnabled: true, - - distPaths: { - appHtmlFile: 'index.html', - }, - - project, - }); - - output = yield buildOutput(defaultPackager.processIndex(input.path())); - - let outputFiles = output.read(); - let indexContent = decodeURIComponent(outputFiles['index.html'].trim()); - - expect(indexContent).to.equal('src'); - }) - ); - - it( - 'works if only `src/ui/index.html` exists', - co.wrap(function*() { - input.dispose(); - input.write({ - 'addon-tree-output': {}, - src: { - ui: { - 'index.html': 'src', - }, - }, - }); - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', - - autoRun: true, - storeConfigInMeta: true, - isModuleUnificationEnabled: true, - areTestsEnabled: true, - - distPaths: { - appHtmlFile: 'index.html', - }, - - project, - }); - - output = yield buildOutput(defaultPackager.processIndex(input.path())); - - let outputFiles = output.read(); - let indexContent = decodeURIComponent(outputFiles['index.html'].trim()); - - expect(indexContent).to.equal('src'); - }) - ); + it('populates `index.html` according to settings', async function () { + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', + env: 'development', + + autoRun: true, + storeConfigInMeta: true, + areTestsEnabled: true, + + distPaths: { + appHtmlFile: 'index.html', + }, + + project, }); - } + + expect(defaultPackager._cachedProcessedIndex).to.equal(null); + + output = await buildOutput(defaultPackager.processIndex(input.path())); + + let outputFiles = output.read(); + let indexContent = decodeURIComponent(outputFiles['index.html'].trim()); + + expect(indexContent).to.equal(META_TAG); + }); }); diff --git a/tests/unit/broccoli/default-packager/javascript-test.js b/tests/unit/broccoli/default-packager/javascript-test.js index f9a01c14f3..dac2cc0a8b 100644 --- a/tests/unit/broccoli/default-packager/javascript-test.js +++ b/tests/unit/broccoli/default-packager/javascript-test.js @@ -1,19 +1,16 @@ 'use strict'; -const co = require('co'); const expect = require('chai').expect; const Funnel = require('broccoli-funnel'); const DefaultPackager = require('../../../../lib/broccoli/default-packager'); const broccoliTestHelper = require('broccoli-test-helper'); const defaultPackagerHelpers = require('../../../helpers/default-packager'); -const { isExperimentEnabled } = require('../../../../lib/experiments'); const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; -const setupRegistry = defaultPackagerHelpers.setupRegistry; const setupRegistryFor = defaultPackagerHelpers.setupRegistryFor; -describe('Default Packager: Javascript', function() { +describe('Default Packager: Javascript', function () { let input, output; let scriptOutputFiles = { @@ -94,7 +91,7 @@ describe('Default Packager: Javascript', function() { return { a: 1 }; }, - registry: setupRegistryFor('template', function(tree) { + registry: setupRegistryFor('template', function (tree) { return new Funnel(tree, { getDestinationPath(relativePath) { return relativePath.replace(/hbs$/g, 'js'); @@ -105,386 +102,194 @@ describe('Default Packager: Javascript', function() { addons: [], }; - before( - co.wrap(function*() { - input = yield createTempDir(); - - input.write(MODULES); - }) - ); - - after( - co.wrap(function*() { - yield input.dispose(); - }) - ); - - afterEach( - co.wrap(function*() { - yield output.dispose(); - }) - ); - - it( - 'caches packaged javascript tree', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', - - distPaths: { - appJsFile: '/assets/the-best-app-ever.js', - vendorJsFile: '/assets/vendor.js', - }, - - registry: setupRegistryFor('template', function(tree) { - return new Funnel(tree, { - getDestinationPath(relativePath) { - return relativePath.replace(/hbs$/g, 'js'); - }, - }); - }), + before(async function () { + input = await createTempDir(); - customTransformsMap: new Map(), + input.write(MODULES); + }); - scriptOutputFiles, - project, - }); + after(async function () { + await input.dispose(); + }); - expect(defaultPackager._cachedJavascript).to.equal(null); + afterEach(async function () { + await output.dispose(); + }); - output = yield buildOutput(defaultPackager.packageJavascript(input.path())); + it('caches packaged javascript tree', async function () { + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', + env: 'development', - expect(defaultPackager._cachedJavascript).to.not.equal(null); - expect(defaultPackager._cachedJavascript._annotation).to.equal('Packaged Javascript'); - }) - ); + distPaths: { + appJsFile: '/assets/the-best-app-ever.js', + vendorJsFile: '/assets/vendor.js', + }, - it( - 'packages javascript files with sourcemaps on', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', + registry: setupRegistryFor('template', function (tree) { + return new Funnel(tree, { + getDestinationPath(relativePath) { + return relativePath.replace(/hbs$/g, 'js'); + }, + }); + }), - distPaths: { - appJsFile: '/assets/the-best-app-ever.js', - vendorJsFile: '/assets/vendor.js', - }, + customTransformsMap: new Map(), - registry: setupRegistryFor('template', function(tree) { - return new Funnel(tree, { - getDestinationPath(relativePath) { - return relativePath.replace(/hbs$/g, 'js'); - }, - }); - }), + scriptOutputFiles, + project, + }); - customTransformsMap: new Map(), + expect(defaultPackager._cachedJavascript).to.equal(null); - scriptOutputFiles, - project, - }); + output = await buildOutput(defaultPackager.packageJavascript(input.path())); - output = yield buildOutput(defaultPackager.packageJavascript(input.path())); - - let outputFiles = output.read(); - - expect(Object.keys(outputFiles.assets)).to.deep.equal([ - 'the-best-app-ever.js', - 'the-best-app-ever.map', - 'vendor.js', - 'vendor.map', - ]); - }) - ); - - it( - 'packages javascript files with sourcemaps off', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', - - distPaths: { - appJsFile: '/assets/the-best-app-ever.js', - vendorJsFile: '/assets/vendor.js', - }, + expect(defaultPackager._cachedJavascript).to.not.equal(null); + expect(defaultPackager._cachedJavascript._annotation).to.equal('Packaged Javascript'); + }); - registry: setupRegistryFor('template', function(tree) { - return new Funnel(tree, { - getDestinationPath(relativePath) { - return relativePath.replace(/hbs$/g, 'js'); - }, - }); - }), + it('packages javascript files with sourcemaps on', async function () { + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', + env: 'development', - sourcemaps: { - enabled: false, - }, + distPaths: { + appJsFile: '/assets/the-best-app-ever.js', + vendorJsFile: '/assets/vendor.js', + }, - customTransformsMap: new Map(), + registry: setupRegistryFor('template', function (tree) { + return new Funnel(tree, { + getDestinationPath(relativePath) { + return relativePath.replace(/hbs$/g, 'js'); + }, + }); + }), - scriptOutputFiles, - project, - }); + customTransformsMap: new Map(), - output = yield buildOutput(defaultPackager.packageJavascript(input.path())); + scriptOutputFiles, + project, + }); - let outputFiles = output.read(); + output = await buildOutput(defaultPackager.packageJavascript(input.path())); - expect(Object.keys(outputFiles.assets)).to.deep.equal(['the-best-app-ever.js', 'vendor.js']); - }) - ); + let outputFiles = output.read(); - it( - 'processes javascript according to the registry', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', + expect(Object.keys(outputFiles.assets)).to.deep.equal([ + 'the-best-app-ever.js', + 'the-best-app-ever.map', + 'vendor.js', + 'vendor.map', + ]); + }); - registry: setupRegistryFor('js', function(tree) { - return new Funnel(tree, { - getDestinationPath(relativePath) { - return relativePath.replace(/js/g, 'jsx'); - }, - }); - }), + it('packages javascript files with sourcemaps off', async function () { + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', + env: 'development', - project: { addons: [] }, - }); + distPaths: { + appJsFile: '/assets/the-best-app-ever.js', + vendorJsFile: '/assets/vendor.js', + }, - expect(defaultPackager._cachedProcessedJavascript).to.equal(null); + registry: setupRegistryFor('template', function (tree) { + return new Funnel(tree, { + getDestinationPath(relativePath) { + return relativePath.replace(/hbs$/g, 'js'); + }, + }); + }), - output = yield buildOutput(defaultPackager.processJavascript(input.path())); + sourcemaps: { + enabled: false, + }, - let outputFiles = output.read(); + customTransformsMap: new Map(), - expect(outputFiles['the-best-app-ever']).to.deep.equal({ - 'app.jsx': 'app.js', - components: { - 'x-foo.jsx': 'export default class {}', - }, - routes: { - 'application.jsx': 'export default class {}', - }, - config: { - 'environment.jsx': 'environment.js', - }, - 'router.jsx': 'router.js', - }); - }) - ); - - it( - 'runs pre/post-process add-on hooks', - co.wrap(function*() { - let addonPreprocessTreeHookCalled = false; - let addonPostprocessTreeHookCalled = false; - - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - - registry: setupRegistryFor('js', tree => tree), - - // avoid using `testdouble.js` here on purpose; it does not have a "proxy" - // option, where a function call would be registered and the original - // would be returned - project: { - addons: [ - { - preprocessTree(type, tree) { - addonPreprocessTreeHookCalled = true; - - return tree; - }, - postprocessTree(type, tree) { - addonPostprocessTreeHookCalled = true; - - return tree; - }, - }, - ], - }, - }); + scriptOutputFiles, + project, + }); - expect(defaultPackager._cachedProcessedJavascript).to.equal(null); + output = await buildOutput(defaultPackager.packageJavascript(input.path())); - output = yield buildOutput(defaultPackager.processJavascript(input.path())); + let outputFiles = output.read(); - expect(addonPreprocessTreeHookCalled).to.equal(true); - expect(addonPostprocessTreeHookCalled).to.equal(true); - }) - ); -}); + expect(Object.keys(outputFiles.assets)).to.deep.equal(['the-best-app-ever.js', 'vendor.js']); + }); -if (isExperimentEnabled('MODULE_UNIFICATION')) { - // there is a little code duplication here (mainly the ceremony around - // setting up the folder structure and disposing of it after the tests are - // executed; once we enable MU flag by defaul, we should clean this up a tad - describe('with module unification layout', function() { - let inputMU, outputMU; - let addonPreprocessTreeHookCalled = false; - let addonPostprocessTreeHookCalled = false; + it('processes javascript according to the registry', async function () { + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', - let MU_LAYOUT = { - 'addon-tree-output': {}, - public: {}, - tests: {}, - vendor: {}, - src: { - 'main.js': '', - 'resolver.js': '', - 'router.js': '', - ui: { - components: { - 'login-form': { - 'component-test.js': ' // login-form-component-test', - 'component.js': '', - 'template.hbs': '', - }, + registry: setupRegistryFor('js', function (tree) { + return new Funnel(tree, { + getDestinationPath(relativePath) { + return relativePath.replace(/js/g, 'jsx'); }, - 'index.html': '', - routes: { - application: { - 'template.hbs': '', - }, - }, - styles: { - 'app.css': 'html { height: 100%; }', - }, - }, + }); + }), + + project: { addons: [] }, + }); + + expect(defaultPackager._cachedProcessedJavascript).to.equal(null); + + output = await buildOutput(defaultPackager.processJavascript(input.path())); + + let outputFiles = output.read(); + + expect(outputFiles['the-best-app-ever']).to.deep.equal({ + 'app.jsx': 'app.js', + components: { + 'x-foo.jsx': 'export default class {}', }, - }; - before( - co.wrap(function*() { - inputMU = yield createTempDir(); - - inputMU.write(MU_LAYOUT); - }) - ); - - after( - co.wrap(function*() { - yield inputMU.dispose(); - }) - ); - - afterEach( - co.wrap(function*() { - yield outputMU.dispose(); - }) - ); - - it( - 'processes javascript according to the registry', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - - distPaths: { - appJsFile: '/assets/the-best-app-ever.js', - appCssFile: '/assets/the-best-app-ever.css', - vendorJsFile: '/assets/vendor.js', - }, + routes: { + 'application.jsx': 'export default class {}', + }, + config: { + 'environment.jsx': 'environment.js', + }, + 'router.jsx': 'router.js', + }); + }); - registry: setupRegistry({ - js: tree => tree, - }), - - isModuleUnificationEnabled: true, - - // avoid using `testdouble.js` here on purpose; it does not have a "proxy" - // option, where a function call would be registered and the original - // would be returned - project: { - addons: [ - { - preprocessTree(type, tree) { - expect(type).to.equal('src'); - return tree; - }, - postprocessTree(type, tree) { - expect(type).to.equal('src'); - return tree; - }, - }, - ], - }, - }); + it('runs pre/post-process add-on hooks', async function () { + let addonPreprocessTreeHookCalled = false; + let addonPostprocessTreeHookCalled = false; - expect(defaultPackager._cachedProcessedSrc).to.equal(null); + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', - outputMU = yield buildOutput(defaultPackager.processJavascriptSrc(inputMU.path())); + registry: setupRegistryFor('js', (tree) => tree), - let outputFiles = outputMU.read(); + // avoid using `testdouble.js` here on purpose; it does not have a "proxy" + // option, where a function call would be registered and the original + // would be returned + project: { + addons: [ + { + preprocessTree(type, tree) { + addonPreprocessTreeHookCalled = true; - expect(outputFiles['the-best-app-ever']).to.deep.equal({ - src: { - 'main.js': '', - 'resolver.js': '', - 'router.js': '', - ui: { - components: { - 'login-form': { - 'component.js': '', - }, - }, + return tree; }, - }, - }); - }) - ); - - it( - 'runs pre/post-process add-on hooks', - co.wrap(function*() { - addonPreprocessTreeHookCalled = false; - addonPostprocessTreeHookCalled = false; - - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - - distPaths: { - appJsFile: '/assets/the-best-app-ever.js', - appCssFile: '/assets/the-best-app-ever.css', - vendorJsFile: '/assets/vendor.js', - }, + postprocessTree(type, tree) { + addonPostprocessTreeHookCalled = true; - registry: setupRegistry({ - js: tree => tree, - }), - - isModuleUnificationEnabled: true, - - // avoid using `testdouble.js` here on purpose; it does not have a "proxy" - // option, where a function call would be registered and the original - // would be returned - project: { - addons: [ - { - preprocessTree(type, tree) { - expect(type).to.equal('src'); - addonPreprocessTreeHookCalled = true; - - return tree; - }, - postprocessTree(type, tree) { - expect(type).to.equal('src'); - addonPostprocessTreeHookCalled = true; - - return tree; - }, - }, - ], + return tree; + }, }, - }); + ], + }, + }); - outputMU = yield buildOutput(defaultPackager.processJavascriptSrc(inputMU.path())); + expect(defaultPackager._cachedProcessedJavascript).to.equal(null); - expect(addonPreprocessTreeHookCalled).to.equal(true); - expect(addonPostprocessTreeHookCalled).to.equal(true); - }) - ); + output = await buildOutput(defaultPackager.processJavascript(input.path())); + + expect(addonPreprocessTreeHookCalled).to.equal(true); + expect(addonPostprocessTreeHookCalled).to.equal(true); }); -} +}); diff --git a/tests/unit/broccoli/default-packager/process-test.js b/tests/unit/broccoli/default-packager/process-test.js index 9e298a9d25..d9a6ae7391 100644 --- a/tests/unit/broccoli/default-packager/process-test.js +++ b/tests/unit/broccoli/default-packager/process-test.js @@ -1,18 +1,16 @@ 'use strict'; -const co = require('co'); const expect = require('chai').expect; const Funnel = require('broccoli-funnel'); const DefaultPackager = require('../../../../lib/broccoli/default-packager'); const broccoliTestHelper = require('broccoli-test-helper'); const defaultPackagerHelpers = require('../../../helpers/default-packager'); -const { isExperimentEnabled } = require('../../../../lib/experiments'); const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; const setupRegistryFor = defaultPackagerHelpers.setupRegistryFor; -describe('Default Packager: Process Javascript', function() { +describe('Default Packager: Process Javascript', function () { let input, output; let scriptOutputFiles = { @@ -53,7 +51,7 @@ describe('Default Packager: Process Javascript', function() { return { a: 1 }; }, - registry: setupRegistryFor('template', function(tree) { + registry: setupRegistryFor('template', function (tree) { return new Funnel(tree, { getDestinationPath(relativePath) { return relativePath.replace(/hbs$/g, 'js'); @@ -73,145 +71,53 @@ describe('Default Packager: Process Javascript', function() { ], }; - before( - co.wrap(function*() { - input = yield createTempDir(); - - input.write(MODULES); - }) - ); - - after( - co.wrap(function*() { - yield input.dispose(); - }) - ); - - afterEach( - co.wrap(function*() { - if (output) { - yield output.dispose(); - } - }) - ); - - it( - 'caches packaged application tree', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', - - distPaths: { - appJsFile: '/assets/the-best-app-ever.js', - vendorJsFile: '/assets/vendor.js', - }, + before(async function () { + input = await createTempDir(); - registry: setupRegistryFor('template', function(tree) { - return new Funnel(tree, { - getDestinationPath(relativePath) { - return relativePath.replace(/hbs$/g, 'js'); - }, - }); - }), + input.write(MODULES); + }); - customTransformsMap: new Map(), + after(async function () { + await input.dispose(); + }); - scriptOutputFiles, - project, - }); + afterEach(async function () { + if (output) { + await output.dispose(); + } + }); - expect(defaultPackager._cachedProcessedAppAndDependencies).to.equal(null); - - output = yield buildOutput(defaultPackager.processAppAndDependencies(input.path())); - - expect(defaultPackager._cachedProcessedAppAndDependencies).to.not.equal(null); - expect(defaultPackager._cachedProcessedAppAndDependencies._annotation).to.equal( - 'Processed Application and Dependencies' - ); - }) - ); - - if (isExperimentEnabled('MODULE_UNIFICATION')) { - it( - 'merges src with with app', - co.wrap(function*() { - let input = yield createTempDir(); - - input.write({ - 'addon-tree-output': {}, - 'the-best-app-ever': { - 'router.js': 'router.js', - 'app.js': 'app.js', - components: { - 'x-foo.js': 'export default class {}', - }, - routes: { - 'application.js': 'export default class {}', - }, - config: { - 'environment.js': 'environment.js', - }, - templates: {}, - }, - vendor: {}, - src: { - 'main.js': '', - 'resolver.js': '', - 'router.js': '', - ui: { - components: { - 'login-form': { - 'component.js': '', - 'template.hbs': '', - }, - }, - 'index.html': '', - routes: { - application: { - 'template.hbs': '', - }, - }, - styles: { - 'app.css': '', - }, - }, - }, - }); + it('caches packaged application tree', async function () { + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', + env: 'development', - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', + distPaths: { + appJsFile: '/assets/the-best-app-ever.js', + vendorJsFile: '/assets/vendor.js', + }, - distPaths: { - appJsFile: '/assets/the-best-app-ever.js', - vendorJsFile: '/assets/vendor.js', + registry: setupRegistryFor('template', function (tree) { + return new Funnel(tree, { + getDestinationPath(relativePath) { + return relativePath.replace(/hbs$/g, 'js'); }, - - isModuleUnificationEnabled: true, - - registry: setupRegistryFor('template', function(tree) { - return new Funnel(tree, { - getDestinationPath(relativePath) { - return relativePath.replace(/hbs$/g, 'js'); - }, - }); - }), - - customTransformsMap: new Map(), - - scriptOutputFiles, - project, }); + }), + + customTransformsMap: new Map(), - output = yield buildOutput(defaultPackager.processAppAndDependencies(input.path())); + scriptOutputFiles, + project, + }); - let outputFiles = output.read(); + expect(defaultPackager._cachedProcessedAppAndDependencies).to.equal(null); - expect(Object.keys(outputFiles)).to.deep.equal(['addon-tree-output', 'src', 'the-best-app-ever', 'vendor']); + output = await buildOutput(defaultPackager.processAppAndDependencies(input.path())); - input.dispose(); - }) + expect(defaultPackager._cachedProcessedAppAndDependencies).to.not.equal(null); + expect(defaultPackager._cachedProcessedAppAndDependencies._annotation).to.equal( + 'Processed Application and Dependencies' ); - } + }); }); diff --git a/tests/unit/broccoli/default-packager/public-test.js b/tests/unit/broccoli/default-packager/public-test.js index 56e163eb17..b205eb341e 100644 --- a/tests/unit/broccoli/default-packager/public-test.js +++ b/tests/unit/broccoli/default-packager/public-test.js @@ -1,6 +1,5 @@ 'use strict'; -const co = require('co'); const expect = require('chai').expect; const DefaultPackager = require('../../../../lib/broccoli/default-packager'); const broccoliTestHelper = require('broccoli-test-helper'); @@ -8,7 +7,7 @@ const broccoliTestHelper = require('broccoli-test-helper'); const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; -describe('Default Packager: Public', function() { +describe('Default Packager: Public', function () { let input, output; let PUBLIC = { @@ -22,49 +21,37 @@ describe('Default Packager: Public', function() { }, }; - before( - co.wrap(function*() { - input = yield createTempDir(); + before(async function () { + input = await createTempDir(); - input.write(PUBLIC); - }) - ); + input.write(PUBLIC); + }); - after( - co.wrap(function*() { - yield input.dispose(); - }) - ); + after(async function () { + await input.dispose(); + }); - afterEach( - co.wrap(function*() { - yield output.dispose(); - }) - ); + afterEach(async function () { + await output.dispose(); + }); - it( - 'caches packaged public tree', - co.wrap(function*() { - let defaultPackager = new DefaultPackager(); + it('caches packaged public tree', async function () { + let defaultPackager = new DefaultPackager(); - expect(defaultPackager._cachedPublic).to.equal(null); + expect(defaultPackager._cachedPublic).to.equal(null); - output = yield buildOutput(defaultPackager.packagePublic(input.path())); + output = await buildOutput(defaultPackager.packagePublic(input.path())); - expect(defaultPackager._cachedPublic).to.not.equal(null); - }) - ); + expect(defaultPackager._cachedPublic).to.not.equal(null); + }); - it( - 'packages public files', - co.wrap(function*() { - let defaultPackager = new DefaultPackager(); + it('packages public files', async function () { + let defaultPackager = new DefaultPackager(); - output = yield buildOutput(defaultPackager.packagePublic(input.path())); + output = await buildOutput(defaultPackager.packagePublic(input.path())); - let outputFiles = output.read(); + let outputFiles = output.read(); - expect(outputFiles).to.deep.equal(PUBLIC.public); - }) - ); + expect(outputFiles).to.deep.equal(PUBLIC.public); + }); }); diff --git a/tests/unit/broccoli/default-packager/styles-test.js b/tests/unit/broccoli/default-packager/styles-test.js index 5504ddd324..ccda790ff3 100644 --- a/tests/unit/broccoli/default-packager/styles-test.js +++ b/tests/unit/broccoli/default-packager/styles-test.js @@ -1,9 +1,7 @@ 'use strict'; -const co = require('co'); const expect = require('chai').expect; const Funnel = require('broccoli-funnel'); -const { isExperimentEnabled } = require('../../../../lib/experiments'); const DefaultPackager = require('../../../../lib/broccoli/default-packager'); const broccoliTestHelper = require('broccoli-test-helper'); const defaultPackagerHelpers = require('../../../helpers/default-packager'); @@ -12,7 +10,7 @@ const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; const setupRegistryFor = defaultPackagerHelpers.setupRegistryFor; -describe('Default Packager: Styles', function() { +describe('Default Packager: Styles', function () { let input, output; let styleOutputFiles = { @@ -63,458 +61,312 @@ describe('Default Packager: Styles', function() { }, }; - before( - co.wrap(function*() { - input = yield createTempDir(); - - input.write(MODULES); - }) - ); - - after( - co.wrap(function*() { - yield input.dispose(); - }) - ); - - afterEach( - co.wrap(function*() { - if (output) { - yield output.dispose(); - } - }) - ); - - it( - 'caches packaged styles tree', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', - - distPaths: { - appCssFile: '/assets/the-best-app-ever.css', - vendorCssFile: '/assets/vendor.css', - }, - - registry: setupRegistryFor('css', function(tree) { - return new Funnel(tree, { - getDestinationPath(relativePath) { - return relativePath.replace(/scss$/g, 'css'); - }, - }); - }), - - minifyCSS: { - enabled: true, - options: { processImport: false }, - }, + before(async function () { + input = await createTempDir(); - styleOutputFiles, + input.write(MODULES); + }); - project: { addons: [] }, - }); + after(async function () { + await input.dispose(); + }); - expect(defaultPackager._cachedProcessedStyles).to.equal(null); + afterEach(async function () { + if (output) { + await output.dispose(); + } + }); - output = yield buildOutput(defaultPackager.packageStyles(input.path())); + it('caches packaged styles tree', async function () { + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', + env: 'development', - expect(defaultPackager._cachedProcessedStyles).to.not.equal(null); - expect(defaultPackager._cachedProcessedStyles._annotation).to.equal('Packaged Styles'); - }) - ); - - it( - 'does not minify css files when minification is disabled', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', - - distPaths: { - appCssFile: { app: '/assets/the-best-app-ever.css' }, - vendorCssFile: '/assets/vendor.css', - }, - - registry: { - load: () => [], - }, + distPaths: { + appCssFile: '/assets/the-best-app-ever.css', + vendorCssFile: '/assets/vendor.css', + }, - minifyCSS: { - enabled: false, - options: { - processImport: false, - relativeTo: 'assets', + registry: setupRegistryFor('css', function (tree) { + return new Funnel(tree, { + getDestinationPath(relativePath) { + return relativePath.replace(/scss$/g, 'css'); }, - }, + }); + }), - styleOutputFiles, + minifyCSS: { + enabled: true, + options: { processImport: false }, + }, - project: { addons: [] }, - }); + styleOutputFiles, - expect(defaultPackager._cachedProcessedStyles).to.equal(null); + project: { addons: [] }, + }); - output = yield buildOutput(defaultPackager.packageStyles(input.path())); + expect(defaultPackager._cachedProcessedStyles).to.equal(null); - let outputFiles = output.read(); + output = await buildOutput(defaultPackager.packageStyles(input.path())); - expect(Object.keys(outputFiles.assets)).to.deep.equal(['extra.css', 'the-best-app-ever.css', 'vendor.css']); - expect(outputFiles.assets['vendor.css'].trim()).to.equal('body { height: 100%; }'); - expect(outputFiles.assets['the-best-app-ever.css'].trim()).to.equal( - '@import "extra.css";\nhtml { height: 100%; }' - ); - }) - ); + expect(defaultPackager._cachedProcessedStyles).to.not.equal(null); + expect(defaultPackager._cachedProcessedStyles._annotation).to.equal('Packaged Styles'); + }); - it( - 'minifies css files when minification is enabled', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', + it('does not minify css files when minification is disabled', async function () { + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', + env: 'development', - distPaths: { - appCssFile: { app: '/assets/the-best-app-ever.css' }, - vendorCssFile: '/assets/vendor.css', - }, + distPaths: { + appCssFile: { app: '/assets/the-best-app-ever.css' }, + vendorCssFile: '/assets/vendor.css', + }, - registry: { - load: () => [], - }, + registry: { + load: () => [], + }, - minifyCSS: { - enabled: true, - options: { - processImport: false, - relativeTo: 'assets', - }, + minifyCSS: { + enabled: false, + options: { + processImport: false, + relativeTo: 'assets', }, + }, - styleOutputFiles, + styleOutputFiles, - project: { addons: [] }, - }); + project: { addons: [] }, + }); - expect(defaultPackager._cachedProcessedStyles).to.equal(null); + expect(defaultPackager._cachedProcessedStyles).to.equal(null); - output = yield buildOutput(defaultPackager.packageStyles(input.path())); + output = await buildOutput(defaultPackager.packageStyles(input.path())); - let outputFiles = output.read(); + let outputFiles = output.read(); - expect(Object.keys(outputFiles.assets)).to.deep.equal(['extra.css', 'the-best-app-ever.css', 'vendor.css']); - expect(outputFiles.assets['vendor.css'].trim()).to.match(/^\S+$/, 'css file is minified'); - expect(outputFiles.assets['the-best-app-ever.css'].trim()).to.match(/^@import \S+$/, 'css file is minified'); - }) - ); + expect(Object.keys(outputFiles.assets)).to.deep.equal(['extra.css', 'the-best-app-ever.css', 'vendor.css']); + expect(outputFiles.assets['vendor.css'].trim()).to.equal('body { height: 100%; }'); + expect(outputFiles.assets['the-best-app-ever.css'].trim()).to.equal('@import "extra.css";\nhtml { height: 100%; }'); + }); - it( - 'processes css according to the registry', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', + it('minifies css files when minification is enabled', async function () { + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', + env: 'development', - distPaths: { - appCssFile: { app: '/assets/the-best-app-ever.css' }, - vendorCssFile: '/assets/vendor.css', - }, + distPaths: { + appCssFile: { app: '/assets/the-best-app-ever.css' }, + vendorCssFile: '/assets/vendor.css', + }, - registry: setupRegistryFor('css', function(tree, inputPath, outputPath, options) { - return new Funnel(tree, { - getDestinationPath(relativePath) { - if (relativePath.includes('app.css')) { - return options.outputPaths.app.replace(/css$/g, 'zss'); - } + registry: { + load: () => [], + }, - return relativePath; - }, - }); - }), - - minifyCSS: { - enabled: true, - options: { - processImport: false, - relativeTo: 'assets', - }, + minifyCSS: { + enabled: true, + options: { + processImport: false, + relativeTo: 'assets', }, + }, - styleOutputFiles, + styleOutputFiles, - project: { addons: [] }, - }); + project: { addons: [] }, + }); - expect(defaultPackager._cachedProcessedStyles).to.equal(null); + expect(defaultPackager._cachedProcessedStyles).to.equal(null); - output = yield buildOutput(defaultPackager.packageStyles(input.path())); + output = await buildOutput(defaultPackager.packageStyles(input.path())); - let outputFiles = output.read(); + let outputFiles = output.read(); - expect(Object.keys(outputFiles.assets)).to.deep.equal(['the-best-app-ever.zss', 'vendor.css']); - }) - ); + expect(Object.keys(outputFiles.assets)).to.deep.equal(['extra.css', 'the-best-app-ever.css', 'vendor.css']); + expect(outputFiles.assets['vendor.css'].trim()).to.match(/^\S+$/, 'css file is minified'); + expect(outputFiles.assets['the-best-app-ever.css'].trim()).to.match(/^@import \S+$/, 'css file is minified'); + }); - it( - 'inlines css imports', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', + it('processes css according to the registry', async function () { + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', + env: 'development', - distPaths: { - appCssFile: { app: '/assets/the-best-app-ever.css' }, - vendorCssFile: '/assets/vendor.css', - }, + distPaths: { + appCssFile: { app: '/assets/the-best-app-ever.css' }, + vendorCssFile: '/assets/vendor.css', + }, - registry: { - load: () => [], - }, + registry: setupRegistryFor('css', function (tree, inputPath, outputPath, options) { + return new Funnel(tree, { + getDestinationPath(relativePath) { + if (relativePath.includes('app.css')) { + return options.outputPaths.app.replace(/css$/g, 'zss'); + } - minifyCSS: { - enabled: true, - options: { - processImport: true, - relativeTo: 'assets', + return relativePath; }, + }); + }), + + minifyCSS: { + enabled: true, + options: { + processImport: false, + relativeTo: 'assets', }, + }, - styleOutputFiles, - - project: { addons: [] }, - }); + styleOutputFiles, - expect(defaultPackager._cachedProcessedStyles).to.equal(null); + project: { addons: [] }, + }); - output = yield buildOutput(defaultPackager.packageStyles(input.path())); + expect(defaultPackager._cachedProcessedStyles).to.equal(null); - let outputFiles = output.read(); + output = await buildOutput(defaultPackager.packageStyles(input.path())); - expect(outputFiles.assets['the-best-app-ever.css'].trim()).to.not.include('@import'); - expect(outputFiles.assets['the-best-app-ever.css'].trim()).to.equal('body{position:relative}html{height:100%}'); - }) - ); + let outputFiles = output.read(); - it( - 'runs pre/post-process add-on hooks', - co.wrap(function*() { - let addonPreprocessTreeHookCalled = false; - let addonPostprocessTreeHookCalled = false; + expect(Object.keys(outputFiles.assets)).to.deep.equal(['the-best-app-ever.zss', 'vendor.css']); + }); - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', + it('inlines css imports', async function () { + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', + env: 'development', - distPaths: { - appCssFile: { app: '/assets/the-best-app-ever.css' }, - vendorCssFile: '/assets/vendor.css', - }, + distPaths: { + appCssFile: { app: '/assets/the-best-app-ever.css' }, + vendorCssFile: '/assets/vendor.css', + }, - registry: { - load: () => [], - }, + registry: { + load: () => [], + }, - minifyCSS: { - enabled: true, - options: { - processImport: false, - relativeTo: 'assets', - }, + minifyCSS: { + enabled: true, + options: { + processImport: true, + relativeTo: 'assets', }, + }, - styleOutputFiles, + styleOutputFiles, - // avoid using `testdouble.js` here on purpose; it does not have a "proxy" - // option, where a function call would be registered and the original - // would be returned - project: { - addons: [ - { - preprocessTree(type, tree) { - addonPreprocessTreeHookCalled = true; + project: { addons: [] }, + }); - return tree; - }, - postprocessTree(type, tree) { - addonPostprocessTreeHookCalled = true; + expect(defaultPackager._cachedProcessedStyles).to.equal(null); - return tree; - }, - }, - ], - }, - }); - - expect(defaultPackager._cachedProcessedStyles).to.equal(null); - - output = yield buildOutput(defaultPackager.packageStyles(input.path())); - - expect(addonPreprocessTreeHookCalled).to.equal(true); - expect(addonPostprocessTreeHookCalled).to.equal(true); - }) - ); - - it( - 'prevents duplicate inclusion, maintains order: CSS', - co.wrap(function*() { - let importFilesMap = { - '/assets/vendor.css': [ - 'bower_components/1.css', - 'bower_components/2.css', - 'bower_components/3.css', - 'bower_components/1.css', - ], - }; - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - env: 'development', - - distPaths: { - appCssFile: { app: '/assets/the-best-app-ever.css' }, - vendorCssFile: '/assets/vendor.css', - }, + output = await buildOutput(defaultPackager.packageStyles(input.path())); - registry: { - load: () => [], - }, + let outputFiles = output.read(); - minifyCSS: { - enabled: true, - options: { - processImport: false, - relativeTo: 'assets', - }, - }, + expect(outputFiles.assets['the-best-app-ever.css'].trim()).to.not.include('@import'); + expect(outputFiles.assets['the-best-app-ever.css'].trim()).to.equal('body{position:relative}html{height:100%}'); + }); - styleOutputFiles: importFilesMap, + it('runs pre/post-process add-on hooks', async function () { + let addonPreprocessTreeHookCalled = false; + let addonPostprocessTreeHookCalled = false; - project: { addons: [] }, - }); + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', + env: 'development', - expect(defaultPackager._cachedProcessedStyles).to.equal(null); + distPaths: { + appCssFile: { app: '/assets/the-best-app-ever.css' }, + vendorCssFile: '/assets/vendor.css', + }, - output = yield buildOutput(defaultPackager.packageStyles(input.path())); + registry: { + load: () => [], + }, - let outputFiles = output.read(); + minifyCSS: { + enabled: true, + options: { + processImport: false, + relativeTo: 'assets', + }, + }, - expect(outputFiles.assets['vendor.css']).to.equal('.third{position:absolute}'); - }) - ); + styleOutputFiles, - if (isExperimentEnabled('MODULE_UNIFICATION')) { - describe('with module unification layout', function() { - let importFilesMap = { - '/assets/vendor.css': ['vendor/font-awesome/css/font-awesome.css'], - }; - let input, output, processedSrc; + // avoid using `testdouble.js` here on purpose; it does not have a "proxy" + // option, where a function call would be registered and the original + // would be returned + project: { + addons: [ + { + preprocessTree(type, tree) { + addonPreprocessTreeHookCalled = true; - let MU_LAYOUT = { - vendor: { - 'font-awesome': { - css: { - 'font-awesome.css': 'body { height: 100%; }', - }, - }, - }, - src: { - 'main.js': '', - 'resolver.js': '', - 'router.js': '', - ui: { - components: { - 'login-form': { - 'component.js': '', - 'template.hbs': '', - }, - }, - 'index.html': '', - routes: { - application: { - 'template.hbs': '', - }, + return tree; }, - styles: { - 'app.css': 'html { height: 100%; }', + postprocessTree(type, tree) { + addonPostprocessTreeHookCalled = true; + + return tree; }, }, - }, - }; - let processedSrcFolder = { - 'the-best-app-ever': { - src: MU_LAYOUT, - assets: { - 'the-best-app-ever.css': 'html { height: 100%; }', - }, - }, - }; - - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', + ], + }, + }); - distPaths: { - appCssFile: { app: '/assets/the-best-app-ever.css' }, - vendorCssFile: '/assets/vendor.css', - }, + expect(defaultPackager._cachedProcessedStyles).to.equal(null); + + output = await buildOutput(defaultPackager.packageStyles(input.path())); + + expect(addonPreprocessTreeHookCalled).to.equal(true); + expect(addonPostprocessTreeHookCalled).to.equal(true); + }); + + it('prevents duplicate inclusion, maintains order: CSS', async function () { + let importFilesMap = { + '/assets/vendor.css': [ + 'bower_components/1.css', + 'bower_components/2.css', + 'bower_components/3.css', + 'bower_components/1.css', + ], + }; + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', + env: 'development', + + distPaths: { + appCssFile: { app: '/assets/the-best-app-ever.css' }, + vendorCssFile: '/assets/vendor.css', + }, - registry: { - load: () => [], - }, + registry: { + load: () => [], + }, - minifyCSS: { - enabled: true, - options: { - processImport: false, - relativeTo: 'assets', - }, + minifyCSS: { + enabled: true, + options: { + processImport: false, + relativeTo: 'assets', }, + }, - styleOutputFiles: importFilesMap, - - isModuleUnificationEnabled: true, - - project: { addons: [] }, - }); - - before( - co.wrap(function*() { - input = yield createTempDir(); - processedSrc = yield createTempDir(); - - input.write(MU_LAYOUT); - processedSrc.write(processedSrcFolder); - }) - ); - - after( - co.wrap(function*() { - yield input.dispose(); - yield processedSrc.dispose(); - }) - ); + styleOutputFiles: importFilesMap, - afterEach( - co.wrap(function*() { - yield output.dispose(); - }) - ); + project: { addons: [] }, + }); - it( - 'processes styles according to the registry', - co.wrap(function*() { - defaultPackager._cachedProcessedSrc = processedSrc.path(); + expect(defaultPackager._cachedProcessedStyles).to.equal(null); - output = yield buildOutput(defaultPackager.packageStyles(input.path())); + output = await buildOutput(defaultPackager.packageStyles(input.path())); - let outputFiles = output.read(); + let outputFiles = output.read(); - expect(outputFiles).to.deep.equal({ - assets: { - 'the-best-app-ever.css': 'html{height:100%}', - 'vendor.css': 'body{height:100%}', - }, - }); - }) - ); - }); - } + expect(outputFiles.assets['vendor.css']).to.equal('.third{position:absolute}'); + }); }); diff --git a/tests/unit/broccoli/default-packager/templates-test.js b/tests/unit/broccoli/default-packager/templates-test.js index 136d19308f..6e16f29ecd 100644 --- a/tests/unit/broccoli/default-packager/templates-test.js +++ b/tests/unit/broccoli/default-packager/templates-test.js @@ -1,6 +1,5 @@ 'use strict'; -const co = require('co'); const expect = require('chai').expect; const Funnel = require('broccoli-funnel'); const DefaultPackager = require('../../../../lib/broccoli/default-packager'); @@ -11,7 +10,7 @@ const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; const setupRegistryFor = defaultPackagerHelpers.setupRegistryFor; -describe('Default Packager: Templates', function() { +describe('Default Packager: Templates', function () { let input, output; let TEMPLATES = { @@ -25,135 +24,120 @@ describe('Default Packager: Templates', function() { }, }; - before( - co.wrap(function*() { - input = yield createTempDir(); - - input.write(TEMPLATES); - }) - ); - - after( - co.wrap(function*() { - if (input) { - yield input.dispose(); - } - }) - ); - - afterEach( - co.wrap(function*() { - if (output) { - yield output.dispose(); - } - }) - ); - - it( - 'caches processed templates tree', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - - registry: setupRegistryFor('template', function(tree) { - return new Funnel(tree, { - getDestinationPath(relativePath) { - return relativePath.replace(/hbs$/g, 'js'); - }, - }); - }), + before(async function () { + input = await createTempDir(); - project: { addons: [] }, - }); + input.write(TEMPLATES); + }); - expect(defaultPackager._cachedProcessedTemplates).to.equal(null); + after(async function () { + if (input) { + await input.dispose(); + } + }); - output = yield buildOutput(defaultPackager.processTemplates(input.path())); + afterEach(async function () { + if (output) { + await output.dispose(); + } + }); - expect(defaultPackager._cachedProcessedTemplates).to.not.equal(null); - }) - ); + it('caches processed templates tree', async function () { + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', - it( - 'processes templates according to the registry', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', + registry: setupRegistryFor('template', function (tree) { + return new Funnel(tree, { + getDestinationPath(relativePath) { + return relativePath.replace(/hbs$/g, 'js'); + }, + }); + }), - registry: setupRegistryFor('template', function(tree) { - return new Funnel(tree, { - getDestinationPath(relativePath) { - return relativePath.replace(/hbs$/g, 'js'); - }, - }); - }), - - project: { addons: [] }, - }); - - expect(defaultPackager._cachedProcessedTemplates).to.equal(null); - - output = yield buildOutput(defaultPackager.processTemplates(input.path())); - - let outputFiles = output.read(); - - expect(outputFiles['the-best-app-ever']).to.deep.equal({ - templates: { - 'application.js': '', - 'error.js': '', - 'index.js': '', - 'loading.js': '', - }, - }); - }) - ); - - it( - 'runs pre/post-process add-on hooks', - co.wrap(function*() { - let addonPreprocessTreeHookCalled = false; - let addonPostprocessTreeHookCalled = false; - - let defaultPackager = new DefaultPackager({ - name: 'the-best-app-ever', - - registry: setupRegistryFor('template', function(tree) { - return new Funnel(tree, { - getDestinationPath(relativePath) { - return relativePath.replace(/hbs$/g, 'js'); + project: { addons: [] }, + }); + + expect(defaultPackager._cachedProcessedTemplates).to.equal(null); + + output = await buildOutput(defaultPackager.processTemplates(input.path())); + + expect(defaultPackager._cachedProcessedTemplates).to.not.equal(null); + }); + + it('processes templates according to the registry', async function () { + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', + + registry: setupRegistryFor('template', function (tree) { + return new Funnel(tree, { + getDestinationPath(relativePath) { + return relativePath.replace(/hbs$/g, 'js'); + }, + }); + }), + + project: { addons: [] }, + }); + + expect(defaultPackager._cachedProcessedTemplates).to.equal(null); + + output = await buildOutput(defaultPackager.processTemplates(input.path())); + + let outputFiles = output.read(); + + expect(outputFiles['the-best-app-ever']).to.deep.equal({ + templates: { + 'application.js': '', + 'error.js': '', + 'index.js': '', + 'loading.js': '', + }, + }); + }); + + it('runs pre/post-process add-on hooks', async function () { + let addonPreprocessTreeHookCalled = false; + let addonPostprocessTreeHookCalled = false; + + let defaultPackager = new DefaultPackager({ + name: 'the-best-app-ever', + + registry: setupRegistryFor('template', function (tree) { + return new Funnel(tree, { + getDestinationPath(relativePath) { + return relativePath.replace(/hbs$/g, 'js'); + }, + }); + }), + + // avoid using `testdouble.js` here on purpose; it does not have a "proxy" + // option, where a function call would be registered and the original + // would be returned + project: { + addons: [ + { + preprocessTree(type, tree) { + expect(type).to.equal('template'); + addonPreprocessTreeHookCalled = true; + + return tree; }, - }); - }), - - // avoid using `testdouble.js` here on purpose; it does not have a "proxy" - // option, where a function call would be registered and the original - // would be returned - project: { - addons: [ - { - preprocessTree(type, tree) { - expect(type).to.equal('template'); - addonPreprocessTreeHookCalled = true; - - return tree; - }, - postprocessTree(type, tree) { - expect(type).to.equal('template'); - addonPostprocessTreeHookCalled = true; - - return tree; - }, + postprocessTree(type, tree) { + expect(type).to.equal('template'); + addonPostprocessTreeHookCalled = true; + + return tree; }, - ], - }, - }); + }, + ], + }, + }); - expect(defaultPackager._cachedProcessedTemplates).to.equal(null); + expect(defaultPackager._cachedProcessedTemplates).to.equal(null); - output = yield buildOutput(defaultPackager.processTemplates(input.path())); + output = await buildOutput(defaultPackager.processTemplates(input.path())); - expect(addonPreprocessTreeHookCalled).to.equal(true); - expect(addonPostprocessTreeHookCalled).to.equal(true); - }) - ); + expect(addonPreprocessTreeHookCalled).to.equal(true); + expect(addonPostprocessTreeHookCalled).to.equal(true); + }); }); diff --git a/tests/unit/broccoli/default-packager/tests-test.js b/tests/unit/broccoli/default-packager/tests-test.js index 6c8fd152aa..e2265f36a7 100644 --- a/tests/unit/broccoli/default-packager/tests-test.js +++ b/tests/unit/broccoli/default-packager/tests-test.js @@ -1,10 +1,8 @@ 'use strict'; -const co = require('co'); const stew = require('broccoli-stew'); const Funnel = require('broccoli-funnel'); const expect = require('chai').expect; -const { isExperimentEnabled } = require('../../../../lib/experiments'); const DefaultPackager = require('../../../../lib/broccoli/default-packager'); const broccoliTestHelper = require('broccoli-test-helper'); const defaultPackagerHelpers = require('../../../helpers/default-packager'); @@ -13,7 +11,7 @@ const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; const setupRegistryFor = defaultPackagerHelpers.setupRegistryFor; -describe('Default Packager: Tests', function() { +describe('Default Packager: Tests', function () { let input, output; let name = 'the-best-app-ever'; let env = 'development'; @@ -110,613 +108,452 @@ describe('Default Packager: Tests', function() { // files as the input tree, but the contents will be // different lintTree(type, tree) { - return stew.map(tree, string => string.toUpperCase()); + return stew.map(tree, (string) => string.toUpperCase()); }, }, ], }; - before( - co.wrap(function*() { - input = yield createTempDir(); - - input.write(TESTS); - }) - ); - - after( - co.wrap(function*() { - yield input.dispose(); - }) - ); - - afterEach( - co.wrap(function*() { - yield output.dispose(); - }) - ); - - it( - 'caches packaged tests tree', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - project, - name, - env, - areTestsEnabled: true, - - distPaths: { - testJsFile: '/assets/tests.js', - testSupportJsFile: { - testSupport: '/assets/test-support.js', - testLoader: '/assets/test-loader.js', - }, - testSupportCssFile: '/assets/test-support.css', + before(async function () { + input = await createTempDir(); + + input.write(TESTS); + }); + + after(async function () { + await input.dispose(); + }); + + afterEach(async function () { + await output.dispose(); + }); + + it('caches packaged tests tree', async function () { + let defaultPackager = new DefaultPackager({ + project, + name, + env, + areTestsEnabled: true, + + distPaths: { + testJsFile: '/assets/tests.js', + testSupportJsFile: { + testSupport: '/assets/test-support.js', + testLoader: '/assets/test-loader.js', }, + testSupportCssFile: '/assets/test-support.css', + }, - customTransformsMap: new Map(), + customTransformsMap: new Map(), - vendorTestStaticStyles: [], - legacyTestFilesToAppend: [], + vendorTestStaticStyles: [], + legacyTestFilesToAppend: [], - registry: setupRegistryFor('js', tree => tree), - }); + registry: setupRegistryFor('js', (tree) => tree), + }); - expect(defaultPackager._cachedTests).to.equal(null); + expect(defaultPackager._cachedTests).to.equal(null); - output = yield buildOutput(defaultPackager.packageTests(input.path())); + output = await buildOutput(defaultPackager.packageTests(input.path())); - expect(defaultPackager._cachedTests).to.not.equal(null); - }) - ); + expect(defaultPackager._cachedTests).to.not.equal(null); + }); - it( - 'packages test files (with sourcemaps)', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - project, - name, - env, - areTestsEnabled: true, + it('packages test files (with sourcemaps)', async function () { + let defaultPackager = new DefaultPackager({ + project, + name, + env, + areTestsEnabled: true, - distPaths: { - testJsFile: '/assets/tests.js', - testSupportJsFile: { - testSupport: '/assets/test-support.js', - testLoader: '/assets/test-loader.js', - }, - testSupportCssFile: '/assets/test-support.css', + distPaths: { + testJsFile: '/assets/tests.js', + testSupportJsFile: { + testSupport: '/assets/test-support.js', + testLoader: '/assets/test-loader.js', }, + testSupportCssFile: '/assets/test-support.css', + }, - customTransformsMap: new Map(), - - vendorTestStaticStyles: [], - legacyTestFilesToAppend: [], - - registry: setupRegistryFor('js', tree => tree), - }); - - output = yield buildOutput(defaultPackager.packageTests(input.path())); - - let outputFiles = output.read(); - - expect(Object.keys(outputFiles.tests)).to.deep.equal(['index.html']); - - expect(Object.keys(outputFiles.assets)).to.deep.equal([ - 'test-support.js', - 'test-support.map', - 'tests.js', - 'tests.map', - ]); - - expect(Object.keys(outputFiles)).to.deep.equal(['assets', 'testem.js', 'tests']); - - expect(outputFiles.assets['tests.js']).to.include('login-test.js'); - expect(outputFiles.assets['tests.js']).to.include('login-test.lint.js'); - expect(outputFiles.assets['tests.js']).to.include('test-helper'); - expect(outputFiles.assets['tests.js']).to.include(`define('the-best-app-ever/config/environment'`); - expect(outputFiles.assets['tests.js']).to.include(`require('the-best-app-ever/tests/test-helper');`); - expect(outputFiles.assets['tests.js']).to.include('EmberENV.TESTS_FILE_LOADED = true;'); - }) - ); - - it( - 'packages test files (without sourcemaps)', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - project, - name, - env, - areTestsEnabled: true, - sourcemaps: { enabled: false }, - - distPaths: { - testJsFile: '/assets/tests.js', - testSupportJsFile: { - testSupport: '/assets/test-support.js', - testLoader: '/assets/test-loader.js', - }, - testSupportCssFile: '/assets/test-support.css', + customTransformsMap: new Map(), + + vendorTestStaticStyles: [], + legacyTestFilesToAppend: [], + + registry: setupRegistryFor('js', (tree) => tree), + }); + + output = await buildOutput(defaultPackager.packageTests(input.path())); + + let outputFiles = output.read(); + + expect(Object.keys(outputFiles.tests)).to.deep.equal(['index.html']); + + expect(Object.keys(outputFiles.assets)).to.deep.equal([ + 'test-support.js', + 'test-support.map', + 'tests.js', + 'tests.map', + ]); + + expect(Object.keys(outputFiles)).to.deep.equal(['assets', 'testem.js', 'tests']); + + expect(outputFiles.assets['tests.js']).to.include('login-test.js'); + expect(outputFiles.assets['tests.js']).to.include('login-test.lint.js'); + expect(outputFiles.assets['tests.js']).to.include('test-helper'); + expect(outputFiles.assets['tests.js']).to.include(`define('the-best-app-ever/config/environment'`); + expect(outputFiles.assets['tests.js']).to.include(`require('the-best-app-ever/tests/test-helper');`); + expect(outputFiles.assets['tests.js']).to.include('EmberENV.TESTS_FILE_LOADED = true;'); + }); + + it('packages test files (without sourcemaps)', async function () { + let defaultPackager = new DefaultPackager({ + project, + name, + env, + areTestsEnabled: true, + sourcemaps: { enabled: false }, + + distPaths: { + testJsFile: '/assets/tests.js', + testSupportJsFile: { + testSupport: '/assets/test-support.js', + testLoader: '/assets/test-loader.js', }, + testSupportCssFile: '/assets/test-support.css', + }, - customTransformsMap: new Map(), + customTransformsMap: new Map(), - vendorTestStaticStyles: [], - legacyTestFilesToAppend: [], + vendorTestStaticStyles: [], + legacyTestFilesToAppend: [], - registry: setupRegistryFor('js', tree => tree), - }); + registry: setupRegistryFor('js', (tree) => tree), + }); - output = yield buildOutput(defaultPackager.packageTests(input.path())); + output = await buildOutput(defaultPackager.packageTests(input.path())); - let outputFiles = output.read(); + let outputFiles = output.read(); - expect(Object.keys(outputFiles.tests)).to.deep.equal(['index.html']); + expect(Object.keys(outputFiles.tests)).to.deep.equal(['index.html']); - expect(Object.keys(outputFiles.assets)).to.deep.equal(['test-support.js', 'tests.js']); + expect(Object.keys(outputFiles.assets)).to.deep.equal(['test-support.js', 'tests.js']); - expect(Object.keys(outputFiles)).to.deep.equal(['assets', 'testem.js', 'tests']); + expect(Object.keys(outputFiles)).to.deep.equal(['assets', 'testem.js', 'tests']); - expect(outputFiles.assets['tests.js']).to.include('login-test.js'); - expect(outputFiles.assets['tests.js']).to.include('login-test.lint.js'); - expect(outputFiles.assets['tests.js']).to.include('test-helper'); - expect(outputFiles.assets['tests.js']).to.include(`define('the-best-app-ever/config/environment'`); - expect(outputFiles.assets['tests.js']).to.include(`require('the-best-app-ever/tests/test-helper');`); - expect(outputFiles.assets['tests.js']).to.include('EmberENV.TESTS_FILE_LOADED = true;'); - }) - ); + expect(outputFiles.assets['tests.js']).to.include('login-test.js'); + expect(outputFiles.assets['tests.js']).to.include('login-test.lint.js'); + expect(outputFiles.assets['tests.js']).to.include('test-helper'); + expect(outputFiles.assets['tests.js']).to.include(`define('the-best-app-ever/config/environment'`); + expect(outputFiles.assets['tests.js']).to.include(`require('the-best-app-ever/tests/test-helper');`); + expect(outputFiles.assets['tests.js']).to.include('EmberENV.TESTS_FILE_LOADED = true;'); + }); - it( - 'does not process `addon-test-support` folder', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - project, - name, - env, - areTestsEnabled: true, + it('does not process `addon-test-support` folder', async function () { + let defaultPackager = new DefaultPackager({ + project, + name, + env, + areTestsEnabled: true, - distPaths: { - testJsFile: '/assets/tests.js', - testSupportJsFile: { - testSupport: '/assets/test-support.js', - testLoader: '/assets/test-loader.js', - }, - testSupportCssFile: '/assets/test-support.css', + distPaths: { + testJsFile: '/assets/tests.js', + testSupportJsFile: { + testSupport: '/assets/test-support.js', + testLoader: '/assets/test-loader.js', }, + testSupportCssFile: '/assets/test-support.css', + }, - customTransformsMap: new Map(), + customTransformsMap: new Map(), - vendorTestStaticStyles: [], - legacyTestFilesToAppend: [], + vendorTestStaticStyles: [], + legacyTestFilesToAppend: [], - registry: setupRegistryFor('js', function(tree) { - return new Funnel(tree, { - getDestinationPath(relativePath) { - return relativePath.replace(/js/g, 'js-test'); - }, - }); - }), - }); + registry: setupRegistryFor('js', function (tree) { + return new Funnel(tree, { + getDestinationPath(relativePath) { + return relativePath.replace(/js/g, 'js-test'); + }, + }); + }), + }); - output = yield buildOutput(defaultPackager.processTests(input.path())); + output = await buildOutput(defaultPackager.processTests(input.path())); - let outputFiles = output.read(); + let outputFiles = output.read(); - expect(outputFiles).to.deep.equal({ - 'addon-test-support': { - '@ember': { - 'test-helpers': { - 'global.js': - 'define("@ember/test-helpers/global", ["exports"], function(exports) { Object.defineProperty(exports, "__esModule", { value: true }); });', - }, + expect(outputFiles).to.deep.equal({ + 'addon-test-support': { + '@ember': { + 'test-helpers': { + 'global.js': + 'define("@ember/test-helpers/global", ["exports"], function(exports) { Object.defineProperty(exports, "__esModule", { value: true }); });', }, }, - [name]: { - tests: { - acceptance: { - 'login-test.js-test': ' // login-test.js', - 'logout-test.js-test': '', - }, - lint: { - 'login-test.lint.js-test': ' // login-test.lint.js', - 'logout-test.lint.js-test': '', - }, - helpers: { - 'resolver.js-test': '', - 'start-app.js-test': '', - }, - 'index.html': 'index', - integration: { - components: { - 'login-form-test.js-test': '', - 'user-menu-test.js-test': '', - }, + }, + [name]: { + tests: { + acceptance: { + 'login-test.js-test': ' // login-test.js', + 'logout-test.js-test': '', + }, + lint: { + 'login-test.lint.js-test': ' // login-test.lint.js', + 'logout-test.lint.js-test': '', + }, + helpers: { + 'resolver.js-test': '', + 'start-app.js-test': '', + }, + 'index.html': 'index', + integration: { + components: { + 'login-form-test.js-test': '', + 'user-menu-test.js-test': '', }, - 'test-helper.js-test': '// test-helper.js', - unit: { - services: { - 'session-test.js-test': '', - }, + }, + 'test-helper.js-test': '// test-helper.js', + unit: { + services: { + 'session-test.js-test': '', }, }, }, - }); - }) - ); - - it( - 'processes tests files according to the registry', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - project, - name, - env, - areTestsEnabled: true, - - distPaths: { - testJsFile: '/assets/tests.js', - testSupportJsFile: { - testSupport: '/assets/test-support.js', - testLoader: '/assets/test-loader.js', - }, - testSupportCssFile: '/assets/test-support.css', + }, + }); + }); + + it('processes tests files according to the registry', async function () { + let defaultPackager = new DefaultPackager({ + project, + name, + env, + areTestsEnabled: true, + + distPaths: { + testJsFile: '/assets/tests.js', + testSupportJsFile: { + testSupport: '/assets/test-support.js', + testLoader: '/assets/test-loader.js', }, + testSupportCssFile: '/assets/test-support.css', + }, - customTransformsMap: new Map(), + customTransformsMap: new Map(), - vendorTestStaticStyles: [], - legacyTestFilesToAppend: [], + vendorTestStaticStyles: [], + legacyTestFilesToAppend: [], - registry: setupRegistryFor('js', function(tree) { - return new Funnel(tree, { - getDestinationPath(relativePath) { - return relativePath.replace(/js/g, 'js-test'); - }, - }); - }), - }); + registry: setupRegistryFor('js', function (tree) { + return new Funnel(tree, { + getDestinationPath(relativePath) { + return relativePath.replace(/js/g, 'js-test'); + }, + }); + }), + }); - output = yield buildOutput(defaultPackager.processTests(input.path())); + output = await buildOutput(defaultPackager.processTests(input.path())); - let outputFiles = output.read(); + let outputFiles = output.read(); - expect(outputFiles).to.deep.equal({ - 'addon-test-support': { - '@ember': { - 'test-helpers': { - 'global.js': - 'define("@ember/test-helpers/global", ["exports"], function(exports) { Object.defineProperty(exports, "__esModule", { value: true }); });', - }, + expect(outputFiles).to.deep.equal({ + 'addon-test-support': { + '@ember': { + 'test-helpers': { + 'global.js': + 'define("@ember/test-helpers/global", ["exports"], function(exports) { Object.defineProperty(exports, "__esModule", { value: true }); });', }, }, - [name]: { - tests: { - acceptance: { - 'login-test.js-test': ' // login-test.js', - 'logout-test.js-test': '', - }, - lint: { - 'login-test.lint.js-test': ' // login-test.lint.js', - 'logout-test.lint.js-test': '', - }, - helpers: { - 'resolver.js-test': '', - 'start-app.js-test': '', - }, - 'index.html': 'index', - integration: { - components: { - 'login-form-test.js-test': '', - 'user-menu-test.js-test': '', - }, - }, - 'test-helper.js-test': '// test-helper.js', - unit: { - services: { - 'session-test.js-test': '', - }, - }, + }, + [name]: { + tests: { + acceptance: { + 'login-test.js-test': ' // login-test.js', + 'logout-test.js-test': '', }, - }, - }); - }) - ); - - it( - 'emits dist/assets/tests.js by default', - co.wrap(function*() { - let emptyInput = yield createTempDir(); - let emptyTestFolder = { - 'addon-tree-output': {}, - bower_components: {}, - 'the-best-app-ever': { - 'router.js': 'router.js', - 'app.js': 'app.js', - components: { - 'x-foo.js': 'export default class {}', + lint: { + 'login-test.lint.js-test': ' // login-test.lint.js', + 'logout-test.lint.js-test': '', }, - routes: { - 'application.js': 'export default class {}', + helpers: { + 'resolver.js-test': '', + 'start-app.js-test': '', }, - config: { - 'environment.js': 'environment.js', + 'index.html': 'index', + integration: { + components: { + 'login-form-test.js-test': '', + 'user-menu-test.js-test': '', + }, }, - templates: {}, - }, - vendor: { - 'ember-cli': { - 'app-boot.js': 'app-boot.js', - 'app-config.js': 'app-config.js', - 'app-prefix.js': 'app-prefix.js', - 'app-suffix.js': 'app-suffix.js', - 'test-support-prefix.js': 'test-support-prefix.js', - 'test-support-suffix.js': 'test-support-suffix.js', - 'tests-prefix.js': 'tests-prefix.js', - 'tests-suffix.js': 'tests-suffix.js', - 'vendor-prefix.js': 'vendor-prefix.js', - 'vendor-suffix.js': 'vendor-suffix.js', + 'test-helper.js-test': '// test-helper.js', + unit: { + services: { + 'session-test.js-test': '', + }, }, }, - tests: { - 'test-helper.js': '// test-helper.js', + }, + }); + }); + + it('emits dist/assets/tests.js by default', async function () { + let emptyInput = await createTempDir(); + let emptyTestFolder = { + 'addon-tree-output': {}, + bower_components: {}, + 'the-best-app-ever': { + 'router.js': 'router.js', + 'app.js': 'app.js', + components: { + 'x-foo.js': 'export default class {}', }, - }; - - emptyInput.write(emptyTestFolder); - - let project = { - configPath() { - return `${emptyInput.path()}/the-best-app-ever/config/environment`; + routes: { + 'application.js': 'export default class {}', }, - - config() { - return { a: 1 }; + config: { + 'environment.js': 'environment.js', }, - - addons: [], - }; - - let defaultPackager = new DefaultPackager({ - project, - name, - env, - areTestsEnabled: true, - - distPaths: { - testJsFile: '/assets/tests.js', - testSupportJsFile: { - testSupport: '/assets/test-support.js', - testLoader: '/assets/test-loader.js', - }, - testSupportCssFile: '/assets/test-support.css', + templates: {}, + }, + vendor: { + 'ember-cli': { + 'app-boot.js': 'app-boot.js', + 'app-config.js': 'app-config.js', + 'app-prefix.js': 'app-prefix.js', + 'app-suffix.js': 'app-suffix.js', + 'test-support-prefix.js': 'test-support-prefix.js', + 'test-support-suffix.js': 'test-support-suffix.js', + 'tests-prefix.js': 'tests-prefix.js', + 'tests-suffix.js': 'tests-suffix.js', + 'vendor-prefix.js': 'vendor-prefix.js', + 'vendor-suffix.js': 'vendor-suffix.js', }, + }, + tests: { + 'test-helper.js': '// test-helper.js', + }, + }; - customTransformsMap: new Map(), - - vendorTestStaticStyles: [], - legacyTestFilesToAppend: [], - - registry: setupRegistryFor('js', tree => tree), - }); + emptyInput.write(emptyTestFolder); - output = yield buildOutput(defaultPackager.packageTests(input.path())); + let project = { + configPath() { + return `${emptyInput.path()}/the-best-app-ever/config/environment`; + }, - let outputFiles = output.read(); + config() { + return { a: 1 }; + }, - expect(Object.keys(outputFiles.tests)).to.deep.equal(['index.html']); + addons: [], + }; - expect(Object.keys(outputFiles.assets)).to.deep.equal([ - 'test-support.js', - 'test-support.map', - 'tests.js', - 'tests.map', - ]); + let defaultPackager = new DefaultPackager({ + project, + name, + env, + areTestsEnabled: true, - expect(Object.keys(outputFiles)).to.deep.equal(['assets', 'testem.js', 'tests']); + distPaths: { + testJsFile: '/assets/tests.js', + testSupportJsFile: { + testSupport: '/assets/test-support.js', + testLoader: '/assets/test-loader.js', + }, + testSupportCssFile: '/assets/test-support.css', + }, - emptyInput.dispose(); - }) - ); + customTransformsMap: new Map(), - it( - 'lintTree results do not "win" over app tests', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - project, - name, - env, - areTestsEnabled: true, + vendorTestStaticStyles: [], + legacyTestFilesToAppend: [], - distPaths: { - testJsFile: '/assets/tests.js', - testSupportJsFile: { - testSupport: '/assets/test-support.js', - testLoader: '/assets/test-loader.js', - }, - testSupportCssFile: '/assets/test-support.css', - }, + registry: setupRegistryFor('js', (tree) => tree), + }); - customTransformsMap: new Map(), + output = await buildOutput(defaultPackager.packageTests(input.path())); - vendorTestStaticStyles: [], - legacyTestFilesToAppend: [], + let outputFiles = output.read(); - registry: setupRegistryFor('js', tree => tree), - }); + expect(Object.keys(outputFiles.tests)).to.deep.equal(['index.html']); - output = yield buildOutput(defaultPackager.packageTests(input.path())); + expect(Object.keys(outputFiles.assets)).to.deep.equal([ + 'test-support.js', + 'test-support.map', + 'tests.js', + 'tests.map', + ]); - let outputFiles = output.read(); + expect(Object.keys(outputFiles)).to.deep.equal(['assets', 'testem.js', 'tests']); - // confirm this contains the original value - // unmodified by the `lintTree` added above - expect(outputFiles.assets['tests.js']).to.include('// login-test.js'); - }) - ); + emptyInput.dispose(); + }); - it( - 'maintains the concatenation order', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - project, - name, - env, - areTestsEnabled: true, + it('lintTree results do not "win" over app tests', async function () { + let defaultPackager = new DefaultPackager({ + project, + name, + env, + areTestsEnabled: true, - distPaths: { - testJsFile: '/assets/tests.js', - testSupportJsFile: { - testSupport: '/assets/test-support.js', - testLoader: '/assets/test-loader.js', - }, - testSupportCssFile: '/assets/test-support.css', + distPaths: { + testJsFile: '/assets/tests.js', + testSupportJsFile: { + testSupport: '/assets/test-support.js', + testLoader: '/assets/test-loader.js', }, + testSupportCssFile: '/assets/test-support.css', + }, - customTransformsMap: new Map(), + customTransformsMap: new Map(), - vendorTestStaticStyles: ['vendor/custom/a.css', 'vendor/custom/b.css'], - legacyTestFilesToAppend: ['vendor/custom/a.js', 'vendor/custom/b.js'], + vendorTestStaticStyles: [], + legacyTestFilesToAppend: [], - registry: setupRegistryFor('js', tree => tree), - }); + registry: setupRegistryFor('js', (tree) => tree), + }); - output = yield buildOutput(defaultPackager.packageTests(input.path())); + output = await buildOutput(defaultPackager.packageTests(input.path())); - let outputFiles = output.read(); + let outputFiles = output.read(); - expect(outputFiles.assets['test-support.js']).to.include('a.js\nb.js'); - expect(outputFiles.assets['test-support.css']).to.include('a.css\nb.css'); - }) - ); + // confirm this contains the original value + // unmodified by the `lintTree` added above + expect(outputFiles.assets['tests.js']).to.include('// login-test.js'); + }); - if (isExperimentEnabled('MODULE_UNIFICATION')) { - describe('with module unification layout', function() { - let input, output; + it('maintains the concatenation order', async function () { + let defaultPackager = new DefaultPackager({ + project, + name, + env, + areTestsEnabled: true, - let MU_LAYOUT = { - vendor: { - 'ember-cli': { - 'app-boot.js': 'app-boot.js', - 'app-config.js': 'app-config.js', - 'app-prefix.js': 'app-prefix.js', - 'app-suffix.js': 'app-suffix.js', - 'test-support-prefix.js': 'test-support-prefix.js', - 'test-support-suffix.js': 'test-support-suffix.js', - 'tests-prefix.js': 'tests-prefix.js', - 'tests-suffix.js': 'tests-suffix.js', - 'vendor-prefix.js': 'vendor-prefix.js', - 'vendor-suffix.js': 'vendor-suffix.js', - }, - }, - tests: { - 'addon-test-support': {}, - acceptance: { - 'login-test.js': ' // login-test.js', - 'logout-test.js': '', - }, - lint: { - 'login-test.lint.js': ' // login-test.lint.js', - 'logout-test.lint.js': '', - }, - 'index.html': 'index', - }, - src: { - 'main.js': '', - 'resolver.js': '', - 'router.js': '', - ui: { - components: { - 'login-form': { - 'component-test.js': ' // login-form-component-test', - 'component.js': '', - 'template.hbs': '', - }, - }, - 'index.html': '', - routes: { - application: { - 'template.hbs': '', - }, - }, - styles: { - 'app.css': 'html { height: 100%; }', - }, - }, + distPaths: { + testJsFile: '/assets/tests.js', + testSupportJsFile: { + testSupport: '/assets/test-support.js', + testLoader: '/assets/test-loader.js', }, - }; - - before( - co.wrap(function*() { - input = yield createTempDir(); - - input.write(MU_LAYOUT); - }) - ); - - after( - co.wrap(function*() { - yield input.dispose(); - }) - ); - - afterEach( - co.wrap(function*() { - yield output.dispose(); - }) - ); - - it( - 'packages test files', - co.wrap(function*() { - let defaultPackager = new DefaultPackager({ - project, - name, - env, - areTestsEnabled: true, - - distPaths: { - testJsFile: '/assets/tests.js', - testSupportJsFile: { - testSupport: '/assets/test-support.js', - testLoader: '/assets/test-loader.js', - }, - testSupportCssFile: '/assets/test-support.css', - }, - - customTransformsMap: new Map(), - - isModuleUnificationEnabled: true, - - vendorTestStaticStyles: [], - legacyTestFilesToAppend: [], - - registry: setupRegistryFor('js', tree => tree), - }); + testSupportCssFile: '/assets/test-support.css', + }, - output = yield buildOutput(defaultPackager.packageTests(input.path())); + customTransformsMap: new Map(), - let outputFiles = output.read(); + vendorTestStaticStyles: ['vendor/custom/a.css', 'vendor/custom/b.css'], + legacyTestFilesToAppend: ['vendor/custom/a.js', 'vendor/custom/b.js'], - expect(Object.keys(outputFiles.tests)).to.deep.equal(['index.html']); + registry: setupRegistryFor('js', (tree) => tree), + }); - expect(Object.keys(outputFiles.assets)).to.deep.equal([ - 'test-support.js', - 'test-support.map', - 'tests.js', - 'tests.map', - ]); + output = await buildOutput(defaultPackager.packageTests(input.path())); - expect(Object.keys(outputFiles)).to.deep.equal(['assets', 'testem.js', 'tests']); + let outputFiles = output.read(); - expect(outputFiles.assets['tests.js']).to.include('login-form-component-test'); - expect(outputFiles.assets['tests.js']).to.include('login-test.js'); - expect(outputFiles.assets['tests.js']).to.include('login-test.lint.js'); - expect(outputFiles.assets['tests.js']).to.include('test-helper'); - expect(outputFiles.assets['tests.js']).to.include(`define('the-best-app-ever/config/environment'`); - expect(outputFiles.assets['tests.js']).to.include(`require('the-best-app-ever/tests/test-helper');`); - expect(outputFiles.assets['tests.js']).to.include('EmberENV.TESTS_FILE_LOADED = true;'); - }) - ); - }); - } + expect(outputFiles.assets['test-support.js']).to.include('a.js\nb.js'); + expect(outputFiles.assets['test-support.css']).to.include('a.css\nb.css'); + }); }); diff --git a/tests/unit/broccoli/default-packager/vendor-test.js b/tests/unit/broccoli/default-packager/vendor-test.js index 12ffa8eae9..21e10cb5c6 100644 --- a/tests/unit/broccoli/default-packager/vendor-test.js +++ b/tests/unit/broccoli/default-packager/vendor-test.js @@ -1,6 +1,5 @@ 'use strict'; -const co = require('co'); const expect = require('chai').expect; const DefaultPackager = require('../../../../lib/broccoli/default-packager'); const broccoliTestHelper = require('broccoli-test-helper'); @@ -8,7 +7,7 @@ const broccoliTestHelper = require('broccoli-test-helper'); const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; -describe('Default Packager: Vendor', function() { +describe('Default Packager: Vendor', function () { let input, output; let VENDOR_PACKAGES = { @@ -43,52 +42,40 @@ describe('Default Packager: Vendor', function() { }, }; - before( - co.wrap(function*() { - input = yield createTempDir(); + before(async function () { + input = await createTempDir(); - input.write(VENDOR_PACKAGES); - }) - ); + input.write(VENDOR_PACKAGES); + }); - after( - co.wrap(function*() { - yield input.dispose(); - }) - ); + after(async function () { + await input.dispose(); + }); - afterEach( - co.wrap(function*() { - yield output.dispose(); - }) - ); + afterEach(async function () { + await output.dispose(); + }); - it( - 'caches packaged vendor tree', - co.wrap(function*() { - let defaultPackager = new DefaultPackager(); + it('caches packaged vendor tree', async function () { + let defaultPackager = new DefaultPackager(); - expect(defaultPackager._cachedVendor).to.equal(null); + expect(defaultPackager._cachedVendor).to.equal(null); - output = yield buildOutput(defaultPackager.packageVendor(input.path())); + output = await buildOutput(defaultPackager.packageVendor(input.path())); - expect(defaultPackager._cachedVendor).to.not.equal(null); - expect(defaultPackager._cachedVendor._annotation).to.equal('Packaged Vendor'); - }) - ); + expect(defaultPackager._cachedVendor).to.not.equal(null); + expect(defaultPackager._cachedVendor._annotation).to.equal('Packaged Vendor'); + }); - it( - 'packages vendor files', - co.wrap(function*() { - let defaultPackager = new DefaultPackager(); + it('packages vendor files', async function () { + let defaultPackager = new DefaultPackager(); - output = yield buildOutput(defaultPackager.packageVendor(input.path())); + output = await buildOutput(defaultPackager.packageVendor(input.path())); - let outputFiles = output.read(); + let outputFiles = output.read(); - expect(outputFiles).to.deep.equal({ - vendor: VENDOR_PACKAGES, - }); - }) - ); + expect(outputFiles).to.deep.equal({ + vendor: VENDOR_PACKAGES, + }); + }); }); diff --git a/tests/unit/broccoli/ember-addon-test.js b/tests/unit/broccoli/ember-addon-test.js index 89c5da2ebd..87d55cd833 100644 --- a/tests/unit/broccoli/ember-addon-test.js +++ b/tests/unit/broccoli/ember-addon-test.js @@ -7,7 +7,7 @@ const EmberApp = require('../../../lib/broccoli/ember-app'); const expect = require('chai').expect; const MockCLI = require('../../helpers/mock-cli'); -describe('EmberAddon', function() { +describe('EmberAddon', function () { let project, emberAddon, projectPath; function setupProject(rootPath) { @@ -15,22 +15,22 @@ describe('EmberAddon', function() { let cli = new MockCLI(); project = new Project(rootPath, packageContents, cli.ui, cli); - project.require = function() { - return function() {}; + project.require = function () { + return function () {}; }; - project.initializeAddons = function() { + project.initializeAddons = function () { this.addons = []; }; return project; } - beforeEach(function() { + beforeEach(function () { projectPath = path.resolve(__dirname, '../../fixtures/addon/simple'); project = setupProject(projectPath); }); - it('should merge options with defaults to depth', function() { + it('should merge options with defaults to depth', function () { emberAddon = new EmberAddon( { project, @@ -67,11 +67,11 @@ describe('EmberAddon', function() { }); }); - it('should contain env', function() { + it('should contain env', function () { expect(EmberAddon.env).to.be.a('function'); }); - it('should contain return the correct environment', function() { + it('should contain return the correct environment', function () { expect(EmberAddon.env()).to.eql(EmberApp.env()); }); }); diff --git a/tests/unit/broccoli/ember-app-test.js b/tests/unit/broccoli/ember-app-test.js index 53768e5dce..65673a1f8f 100644 --- a/tests/unit/broccoli/ember-app-test.js +++ b/tests/unit/broccoli/ember-app-test.js @@ -1,7 +1,7 @@ 'use strict'; -const co = require('co'); const path = require('path'); +const FixturifyProject = require('../../helpers/fixturify-project'); const Project = require('../../../lib/models/project'); const expect = require('chai').expect; const td = require('testdouble'); @@ -21,11 +21,11 @@ const Addon = require('../../../lib/models/addon'); function mockTemplateRegistry(app) { let oldLoad = app.registry.load; - app.registry.load = function(type) { + app.registry.load = function (type) { if (type === 'template') { return [ { - toTree: tree => tree, + toTree: (tree) => tree, }, ]; } @@ -33,7 +33,7 @@ function mockTemplateRegistry(app) { }; } -describe('EmberApp', function() { +describe('EmberApp', function () { let project, projectPath, app, addon; function setupProject(rootPath) { @@ -41,58 +41,50 @@ describe('EmberApp', function() { let cli = new MockCLI(); project = new Project(rootPath, packageContents, cli.ui, cli); - project.require = function() { - return function() {}; + project.require = function () { + return function () {}; }; - project.initializeAddons = function() { + project.initializeAddons = function () { this.addons = []; }; return project; } - beforeEach(function() { + beforeEach(function () { projectPath = path.resolve(__dirname, '../../fixtures/addon/simple'); project = setupProject(projectPath); }); if (isExperimentEnabled('PACKAGER')) { - describe('packager hook', function() { + describe('packager hook', function () { let js, input, output; - before( - co.wrap(function*() { - js = yield createTempDir(); - js.write({ - fake: { - javascript: '// javascript.js', - }, - }); - }) - ); + before(async function () { + js = await createTempDir(); + js.write({ + fake: { + javascript: '// javascript.js', + }, + }); + }); - beforeEach( - co.wrap(function*() { - input = yield createTempDir(); - }) - ); + beforeEach(async function () { + input = await createTempDir(); + }); - afterEach( - co.wrap(function*() { - yield input.dispose(); - if (output) { - yield output.dispose(); - } - }) - ); + afterEach(async function () { + await input.dispose(); + if (output) { + await output.dispose(); + } + }); - after( - co.wrap(function*() { - yield js.dispose(); - }) - ); + after(async function () { + await js.dispose(); + }); - it('sets `_isPackageHookSupplied` to `false` if `package` hook is not a function', function() { + it('sets `_isPackageHookSupplied` to `false` if `package` hook is not a function', function () { let app = new EmberApp({ project, package: false, @@ -101,7 +93,7 @@ describe('EmberApp', function() { expect(app._isPackageHookSupplied).to.equal(false); }); - it('sets `_isPackageHookSupplied` to `false` if `package` hook is not supplied', function() { + it('sets `_isPackageHookSupplied` to `false` if `package` hook is not supplied', function () { let app = new EmberApp({ project, }); @@ -109,7 +101,7 @@ describe('EmberApp', function() { expect(app._isPackageHookSupplied).to.equal(false); }); - it('sets `_isPackageHookSupplied` to `true` if `package` hook is supplied', function() { + it('sets `_isPackageHookSupplied` to `true` if `package` hook is supplied', function () { let app = new EmberApp({ project, package: () => input.path(), @@ -118,633 +110,559 @@ describe('EmberApp', function() { expect(app._isPackageHookSupplied).to.equal(true); }); - it( - 'overrides the output of the build', - co.wrap(function*() { - input.write({ - fake: { - dist: { - 'foo.js': '// foo.js', - }, - }, - }); - - let app = new EmberApp({ - project, - package: () => input.path(), - }); - mockTemplateRegistry(app); - - output = yield buildOutput(app.toTree()); - - let outputFiles = output.read(); - - expect(outputFiles).to.deep.equal({ - fake: { - dist: { - 'foo.js': '// foo.js', - }, - }, - }); - }) - ); - - it( - 'receives a full tree as an argument', - co.wrap(function*() { - let appStyles = yield createTempDir(); - appStyles.write({ - 'app.css': '// css styles', - }); - input.write({ - fake: { - dist: { - 'foo.js': '// foo.js', - }, - }, - }); - - let app = new EmberApp({ - project, - package: tree => mergeTrees([tree, input.path()]), - trees: { - styles: appStyles.path(), - }, - }); - mockTemplateRegistry(app); - - app.getAppJavascript = () => js.path(); - - output = yield buildOutput(app.toTree()); - - let outputFiles = output.read(); - - expect(outputFiles).to.deep.equal({ - 'addon-tree-output': {}, - fake: { - dist: { - 'foo.js': '// foo.js', - }, - javascript: '// javascript.js', - }, - app: { - styles: { - 'app.css': '// css styles', - }, - }, - 'test-project': { - templates: {}, - }, - tests: { - '.gitkeep': '', - 'addon-test-support': {}, - lint: {}, - }, - public: {}, - vendor: { - '.gitkeep': '', + it('overrides the output of the build', async function () { + input.write({ + fake: { + dist: { + 'foo.js': '// foo.js', }, - }); - }) - ); - - it( - 'prints a warning if `package` is not a function and falls back to default packaging', - co.wrap(function*() { - let app = new EmberApp({ - project, - package: {}, - }); - - app.project.ui.writeWarnLine = td.function(); - - app.getAppJavascript = td.function(); - app.getAddonTemplates = td.function(); - app.getStyles = td.function(); - app.getTests = td.function(); - app.getExternalTree = td.function(); - app.getSrc = td.function(); - app._legacyAddonCompile = td.function(); - app._defaultPackager = { - packagePublic: td.function(), - packageJavascript: td.function(), - packageStyles: td.function(), - processIndex: td.function(), - importAdditionalAssets: td.function(), - packageTests: td.function(), - }; - - output = yield buildOutput(app.toTree()); - - td.verify(app.getAppJavascript(false)); - td.verify(app.getStyles()); - td.verify(app.getTests()); - td.verify(app.getExternalTree()); - td.verify(app.getSrc()); - td.verify( - app.project.ui.writeWarnLine('`package` hook must be a function, falling back to default packaging.') - ); - }) - ); - - it( - 'receives transpiled ES current app tree', - co.wrap(function*() { - let app = new EmberApp({ - project, - package: tree => tree, - }); - mockTemplateRegistry(app); + }, + }); - input.write({ - fake: { - dist: { - 'foo.js': '// foo.js', - }, - }, - }); - app.registry.add('js', { - name: 'fake-js-preprocessor', - ext: 'js', - toTree() { - return input.path(); - }, - }); + let app = new EmberApp({ + project, + package: () => input.path(), + }); + mockTemplateRegistry(app); - output = yield buildOutput(app.toTree()); + output = await buildOutput(app.toTree()); - let outputFiles = output.read(); + let outputFiles = output.read(); - expect(outputFiles.fake).to.deep.equal({ + expect(outputFiles).to.deep.equal({ + fake: { dist: { 'foo.js': '// foo.js', }, - }); - }) - ); - }); - } + }, + }); + }); - describe('getStyles()', function() { - it( - 'can handle empty styles folders', - co.wrap(function*() { - let appStyles = yield createTempDir(); + it('receives a full tree as an argument', async function () { + let appStyles = await createTempDir(); appStyles.write({ 'app.css': '// css styles', }); + input.write({ + fake: { + dist: { + 'foo.js': '// foo.js', + }, + }, + }); let app = new EmberApp({ project, + package: (tree) => mergeTrees([tree, input.path()]), trees: { styles: appStyles.path(), }, }); + mockTemplateRegistry(app); + + app.getAppJavascript = () => js.path(); - app.addonTreesFor = () => []; + output = await buildOutput(app.toTree()); - let output = yield buildOutput(app.getStyles()); let outputFiles = output.read(); expect(outputFiles).to.deep.equal({ + 'addon-tree-output': {}, + fake: { + dist: { + 'foo.js': '// foo.js', + }, + javascript: '// javascript.js', + }, app: { styles: { 'app.css': '// css styles', }, }, + 'test-project': { + templates: {}, + }, + tests: { + '.gitkeep': '', + 'addon-test-support': {}, + lint: {}, + }, + public: {}, + vendor: { + '.gitkeep': '', + }, }); + }); - yield output.dispose(); - }) - ); + it('prints a warning if `package` is not a function and falls back to default packaging', async function () { + let app = new EmberApp({ + project, + package: {}, + }); - it( - 'can handle empty addon styles folders', - co.wrap(function*() { - let appOptions = { project }; + app.project.ui.writeWarnLine = td.function(); - if (isExperimentEnabled('MODULE_UNIFICATION')) { - appOptions.trees = { src: {} }; - } + app.getAppJavascript = td.function(); + app.getAddonTemplates = td.function(); + app.getStyles = td.function(); + app.getTests = td.function(); + app.getExternalTree = td.function(); + app._legacyAddonCompile = td.function(); + app._defaultPackager = { + packagePublic: td.function(), + packageJavascript: td.function(), + packageStyles: td.function(), + processIndex: td.function(), + importAdditionalAssets: td.function(), + packageTests: td.function(), + }; - let app = new EmberApp(appOptions); + output = await buildOutput(app.toTree()); - let AddonFoo = Addon.extend({ - root: 'foo', - name: 'foo', - }); - let addonFoo = new AddonFoo(app, project); - app.project.addons.push(addonFoo); + td.verify(app.getAppJavascript(false)); + td.verify(app.getStyles()); + td.verify(app.getTests()); + td.verify(app.getExternalTree()); + td.verify( + app.project.ui.writeWarnLine('`package` hook must be a function, falling back to default packaging.') + ); + }); - let output = yield buildOutput(app.getStyles()); - let outputFiles = output.read(); + it('receives transpiled ES current app tree', async function () { + let app = new EmberApp({ + project, + package: (tree) => tree, + }); + mockTemplateRegistry(app); - let expectedOutput; - if (isExperimentEnabled('MODULE_UNIFICATION')) { - expectedOutput = { - src: { - ui: { - styles: {}, - }, + input.write({ + fake: { + dist: { + 'foo.js': '// foo.js', }, - }; - } else { - expectedOutput = {}; - } - expect(outputFiles).to.deep.equal(expectedOutput); + }, + }); + app.registry.add('js', { + name: 'fake-js-preprocessor', + ext: 'js', + toTree() { + return input.path(); + }, + }); - yield output.dispose(); - }) - ); + output = await buildOutput(app.toTree()); - it( - 'add `app/styles` folder from add-ons', - co.wrap(function*() { - let addonFooStyles = yield createTempDir(); + let outputFiles = output.read(); - addonFooStyles.write({ - app: { - styles: { - 'foo.css': 'foo', - }, + expect(outputFiles.fake).to.deep.equal({ + dist: { + 'foo.js': '// foo.js', }, }); + }); + }); + } - let appOptions = { project }; + describe('getStyles()', function () { + it('can handle empty styles folders', async function () { + let appStyles = await createTempDir(); + appStyles.write({ + 'app.css': '// css styles', + }); - if (isExperimentEnabled('MODULE_UNIFICATION')) { - appOptions.trees = { src: {} }; - } + let app = new EmberApp({ + project, + trees: { + styles: appStyles.path(), + }, + }); + + app.addonTreesFor = () => []; - let app = new EmberApp(appOptions); + let output = await buildOutput(app.getStyles()); + let outputFiles = output.read(); - let AddonFoo = Addon.extend({ - root: 'foo', - name: 'foo', - treeForStyles() { - return addonFooStyles.path(); + expect(outputFiles).to.deep.equal({ + app: { + styles: { + 'app.css': '// css styles', }, - }); - let addonFoo = new AddonFoo(app, project); - app.project.addons.push(addonFoo); + }, + }); - let output = yield buildOutput(app.getStyles()); - let outputFiles = output.read(); + await output.dispose(); + }); - let expectedOutput; - if (isExperimentEnabled('MODULE_UNIFICATION')) { - expectedOutput = { - src: { - ui: { - styles: { - 'foo.css': 'foo', - }, - }, - }, - }; - } else { - expectedOutput = { - app: { - styles: { - 'foo.css': 'foo', - }, - }, - }; - } - expect(outputFiles).to.deep.equal(expectedOutput); + it('can handle empty addon styles folders', async function () { + let appOptions = { project }; - yield addonFooStyles.dispose(); - yield output.dispose(); - }) - ); + let app = new EmberApp(appOptions); + + let AddonFoo = Addon.extend({ + root: 'foo', + packageRoot: 'foo', + name: 'foo', + }); + let addonFoo = new AddonFoo(app, project); + app.project.addons.push(addonFoo); - it( - 'returns add-ons styles files', - co.wrap(function*() { - let addonFooStyles = yield createTempDir(); - let addonBarStyles = yield createTempDir(); + let output = await buildOutput(app.getStyles()); + let outputFiles = output.read(); - // `ember-basic-dropdown` - addonFooStyles.write({ - app: { - styles: { - 'foo.css': 'foo', - }, + let expectedOutput = {}; + expect(outputFiles).to.deep.equal(expectedOutput); + + await output.dispose(); + }); + + it('add `app/styles` folder from add-ons', async function () { + let addonFooStyles = await createTempDir(); + + addonFooStyles.write({ + app: { + styles: { + 'foo.css': 'foo', }, - }); - // `ember-bootstrap` - addonBarStyles.write({ - baztrap: { - 'baztrap.css': '// baztrap.css', + }, + }); + + let appOptions = { project }; + + let app = new EmberApp(appOptions); + + let AddonFoo = Addon.extend({ + root: 'foo', + packageRoot: 'foo', + name: 'foo', + treeForStyles() { + return addonFooStyles.path(); + }, + }); + let addonFoo = new AddonFoo(app, project); + app.project.addons.push(addonFoo); + + let output = await buildOutput(app.getStyles()); + let outputFiles = output.read(); + + let expectedOutput = { + app: { + styles: { + 'foo.css': 'foo', }, - }); + }, + }; + expect(outputFiles).to.deep.equal(expectedOutput); - let app = new EmberApp({ - project, - }); - app.addonTreesFor = function() { - return [addonFooStyles.path(), addonBarStyles.path()]; - }; + await addonFooStyles.dispose(); + await output.dispose(); + }); - let output = yield buildOutput(app.getStyles()); - let outputFiles = output.read(); + it('returns add-ons styles files', async function () { + let addonFooStyles = await createTempDir(); + let addonBarStyles = await createTempDir(); - expect(outputFiles).to.deep.equal({ - app: { - styles: { - 'foo.css': 'foo', - }, + // `ember-basic-dropdown` + addonFooStyles.write({ + app: { + styles: { + 'foo.css': 'foo', }, - baztrap: { - 'baztrap.css': '// baztrap.css', + }, + }); + // `ember-bootstrap` + addonBarStyles.write({ + baztrap: { + 'baztrap.css': '// baztrap.css', + }, + }); + + let app = new EmberApp({ + project, + }); + app.addonTreesFor = function () { + return [addonFooStyles.path(), addonBarStyles.path()]; + }; + + let output = await buildOutput(app.getStyles()); + let outputFiles = output.read(); + + expect(outputFiles).to.deep.equal({ + app: { + styles: { + 'foo.css': 'foo', }, - }); + }, + baztrap: { + 'baztrap.css': '// baztrap.css', + }, + }); - yield addonFooStyles.dispose(); - yield addonBarStyles.dispose(); - yield output.dispose(); - }) - ); + await addonFooStyles.dispose(); + await addonBarStyles.dispose(); + await output.dispose(); + }); - it( - 'does not fail if add-ons do not export styles', - co.wrap(function*() { - let app = new EmberApp({ - project, - }); - app.addonTreesFor = () => []; + it('does not fail if add-ons do not export styles', async function () { + let app = new EmberApp({ + project, + }); + app.addonTreesFor = () => []; - let output = yield buildOutput(app.getStyles()); - let outputFiles = output.read(); + let output = await buildOutput(app.getStyles()); + let outputFiles = output.read(); - expect(outputFiles).to.deep.equal({}); + expect(outputFiles).to.deep.equal({}); - yield output.dispose(); - }) - ); + await output.dispose(); + }); }); - describe('getPublic()', function() { - it( - 'returns public files for app and add-ons', - co.wrap(function*() { - let input = yield createTempDir(); - let addonFooPublic = yield createTempDir(); - let addonBarPublic = yield createTempDir(); + describe('getPublic()', function () { + it('returns public files for app and add-ons', async function () { + let input = await createTempDir(); + let addonFooPublic = await createTempDir(); + let addonBarPublic = await createTempDir(); - input.write({ + input.write({ + 'crossdomain.xml': '', + 'robots.txt': '', + }); + addonFooPublic.write({ + foo: 'foo', + }); + addonBarPublic.write({ + bar: 'bar', + }); + + app = new EmberApp({ + project, + }); + + app.trees.public = input.path(); + app.addonTreesFor = function () { + return [addonFooPublic.path(), addonBarPublic.path()]; + }; + + let output = await buildOutput(app.getPublic()); + let outputFiles = output.read(); + + expect(outputFiles).to.deep.equal({ + public: { 'crossdomain.xml': '', 'robots.txt': '', - }); - addonFooPublic.write({ foo: 'foo', - }); - addonBarPublic.write({ bar: 'bar', - }); + }, + }); - app = new EmberApp({ - project, - }); + await input.dispose(); + await addonFooPublic.dispose(); + await addonBarPublic.dispose(); + await output.dispose(); + }); - app.trees.public = input.path(); - app.addonTreesFor = function() { - return [addonFooPublic.path(), addonBarPublic.path()]; - }; + it('does not fail if app or add-ons have the same `public` folder structure', async function () { + let input = await createTempDir(); + let addonFooPublic = await createTempDir(); + let addonBarPublic = await createTempDir(); - let output = yield buildOutput(app.getPublic()); - let outputFiles = output.read(); + input.write({ + 'crossdomain.xml': '', + 'robots.txt': '', + }); + addonFooPublic.write({ + bar: 'bar', + foo: 'foo', + }); + addonBarPublic.write({ + bar: 'bar', + }); - expect(outputFiles).to.deep.equal({ - public: { - 'crossdomain.xml': '', - 'robots.txt': '', - foo: 'foo', - bar: 'bar', - }, - }); + app = new EmberApp({ + project, + }); - yield input.dispose(); - yield addonFooPublic.dispose(); - yield addonBarPublic.dispose(); - yield output.dispose(); - }) - ); + app.trees.public = input.path(); + app.addonTreesFor = function () { + return [addonFooPublic.path(), addonBarPublic.path()]; + }; - it( - 'does not fail if app or add-ons have the same `public` folder structure', - co.wrap(function*() { - let input = yield createTempDir(); - let addonFooPublic = yield createTempDir(); - let addonBarPublic = yield createTempDir(); + let output = await buildOutput(app.getPublic()); + let outputFiles = output.read(); - input.write({ + expect(outputFiles).to.deep.equal({ + public: { 'crossdomain.xml': '', 'robots.txt': '', - }); - addonFooPublic.write({ - bar: 'bar', foo: 'foo', - }); - addonBarPublic.write({ bar: 'bar', - }); - - app = new EmberApp({ - project, - }); - - app.trees.public = input.path(); - app.addonTreesFor = function() { - return [addonFooPublic.path(), addonBarPublic.path()]; - }; - - let output = yield buildOutput(app.getPublic()); - let outputFiles = output.read(); - - expect(outputFiles).to.deep.equal({ - public: { - 'crossdomain.xml': '', - 'robots.txt': '', - foo: 'foo', - bar: 'bar', - }, - }); + }, + }); - yield input.dispose(); - yield addonFooPublic.dispose(); - yield addonBarPublic.dispose(); - yield output.dispose(); - }) - ); + await input.dispose(); + await addonFooPublic.dispose(); + await addonBarPublic.dispose(); + await output.dispose(); + }); }); - describe('getAddonTemplates()', function() { - it( - 'returns add-ons template files', - co.wrap(function*() { - let input = yield createTempDir(); - let addonFooTemplates = yield createTempDir(); - let addonBarTemplates = yield createTempDir(); + describe('getAddonTemplates()', function () { + it('returns add-ons template files', async function () { + let input = await createTempDir(); + let addonFooTemplates = await createTempDir(); + let addonBarTemplates = await createTempDir(); - addonFooTemplates.write({ - 'foo.hbs': 'foo', - }); - addonBarTemplates.write({ - 'bar.hbs': 'bar', - }); + addonFooTemplates.write({ + 'foo.hbs': 'foo', + }); + addonBarTemplates.write({ + 'bar.hbs': 'bar', + }); - let app = new EmberApp({ - project, - }); - app.trees.templates = input.path(); - app.addonTreesFor = function() { - return [addonFooTemplates.path(), addonBarTemplates.path()]; - }; + let app = new EmberApp({ + project, + }); + app.trees.templates = input.path(); + app.addonTreesFor = function () { + return [addonFooTemplates.path(), addonBarTemplates.path()]; + }; - let output = yield buildOutput(app.getAddonTemplates()); - let outputFiles = output.read(); + let output = await buildOutput(app.getAddonTemplates()); + let outputFiles = output.read(); - expect(outputFiles['test-project'].templates).to.deep.equal({ - 'foo.hbs': 'foo', - 'bar.hbs': 'bar', - }); + expect(outputFiles['test-project'].templates).to.deep.equal({ + 'foo.hbs': 'foo', + 'bar.hbs': 'bar', + }); - yield input.dispose(); - yield addonFooTemplates.dispose(); - yield addonBarTemplates.dispose(); - yield output.dispose(); - }) - ); + await input.dispose(); + await addonFooTemplates.dispose(); + await addonBarTemplates.dispose(); + await output.dispose(); + }); }); - describe('getTests()', function() { - it( - 'returns all test files `hinting` is enabled', - co.wrap(function*() { - let input = yield createTempDir(); - let addonLint = yield createTempDir(); - let addonFooTestSupport = yield createTempDir(); - let addonBarTestSupport = yield createTempDir(); - - input.write({ - acceptance: { - 'login-test.js': '', - 'logout-test.js': '', - }, - }); - addonFooTestSupport.write({ - 'foo-helper.js': 'foo', - }); - addonBarTestSupport.write({ - 'bar-helper.js': 'bar', - }); - addonLint.write({ - 'login-test.lint.js': '', - 'logout-test.lint.js': '', - }); + describe('getTests()', function () { + it('returns all test files `hinting` is enabled', async function () { + let input = await createTempDir(); + let addonLint = await createTempDir(); + let addonFooTestSupport = await createTempDir(); + let addonBarTestSupport = await createTempDir(); + + input.write({ + acceptance: { + 'login-test.js': '', + 'logout-test.js': '', + }, + }); + addonFooTestSupport.write({ + 'foo-helper.js': 'foo', + }); + addonBarTestSupport.write({ + 'bar-helper.js': 'bar', + }); + addonLint.write({ + 'login-test.lint.js': '', + 'logout-test.lint.js': '', + }); - let app = new EmberApp({ - project, - }); - app.trees.tests = input.path(); - app.addonLintTree = (type, tree) => { - if (type === 'tests') { - return addonLint.path(); - } + let app = new EmberApp({ + project, + }); + app.trees.tests = input.path(); + app.addonLintTree = (type, tree) => { + if (type === 'tests') { + return addonLint.path(); + } - return tree; - }; - app.addonTreesFor = function(type) { - if (type === 'test-support') { - return [addonFooTestSupport.path(), addonBarTestSupport.path()]; - } + return tree; + }; + app.addonTreesFor = function (type) { + if (type === 'test-support') { + return [addonFooTestSupport.path(), addonBarTestSupport.path()]; + } - return []; - }; + return []; + }; - let output = yield buildOutput(app.getTests()); - let outputFiles = output.read(); + let output = await buildOutput(app.getTests()); + let outputFiles = output.read(); - expect(outputFiles.tests).to.deep.equal({ - 'addon-test-support': {}, - lint: { - 'login-test.lint.js': '', - 'logout-test.lint.js': '', - }, - acceptance: { - 'login-test.js': '', - 'logout-test.js': '', - }, - 'foo-helper.js': 'foo', - 'bar-helper.js': 'bar', - }); + expect(outputFiles.tests).to.deep.equal({ + 'addon-test-support': {}, + lint: { + 'login-test.lint.js': '', + 'logout-test.lint.js': '', + }, + acceptance: { + 'login-test.js': '', + 'logout-test.js': '', + }, + 'foo-helper.js': 'foo', + 'bar-helper.js': 'bar', + }); - yield input.dispose(); - yield addonFooTestSupport.dispose(); - yield addonBarTestSupport.dispose(); - yield addonLint.dispose(); - yield output.dispose(); - }) - ); + await input.dispose(); + await addonFooTestSupport.dispose(); + await addonBarTestSupport.dispose(); + await addonLint.dispose(); + await output.dispose(); + }); - it( - 'returns test files w/o lint tests if `hinting` is disabled', - co.wrap(function*() { - let input = yield createTempDir(); - let addonFooTestSupport = yield createTempDir(); - let addonBarTestSupport = yield createTempDir(); + it('returns test files w/o lint tests if `hinting` is disabled', async function () { + let input = await createTempDir(); + let addonFooTestSupport = await createTempDir(); + let addonBarTestSupport = await createTempDir(); - input.write({ - acceptance: { - 'login-test.js': '', - 'logout-test.js': '', - }, - }); - addonFooTestSupport.write({ - 'foo-helper.js': 'foo', - }); - addonBarTestSupport.write({ - 'bar-helper.js': 'bar', - }); + input.write({ + acceptance: { + 'login-test.js': '', + 'logout-test.js': '', + }, + }); + addonFooTestSupport.write({ + 'foo-helper.js': 'foo', + }); + addonBarTestSupport.write({ + 'bar-helper.js': 'bar', + }); - let app = new EmberApp({ - project, - hinting: false, - }); - app.trees.tests = input.path(); - app.addonTreesFor = function(type) { - if (type === 'test-support') { - return [addonFooTestSupport.path(), addonBarTestSupport.path()]; - } + let app = new EmberApp({ + project, + hinting: false, + }); + app.trees.tests = input.path(); + app.addonTreesFor = function (type) { + if (type === 'test-support') { + return [addonFooTestSupport.path(), addonBarTestSupport.path()]; + } - return []; - }; + return []; + }; - let output = yield buildOutput(app.getTests()); - let outputFiles = output.read(); + let output = await buildOutput(app.getTests()); + let outputFiles = output.read(); - expect(outputFiles.tests).to.deep.equal({ - 'addon-test-support': {}, - acceptance: { - 'login-test.js': '', - 'logout-test.js': '', - }, - 'foo-helper.js': 'foo', - 'bar-helper.js': 'bar', - }); + expect(outputFiles.tests).to.deep.equal({ + 'addon-test-support': {}, + acceptance: { + 'login-test.js': '', + 'logout-test.js': '', + }, + 'foo-helper.js': 'foo', + 'bar-helper.js': 'bar', + }); - yield input.dispose(); - yield addonFooTestSupport.dispose(); - yield addonBarTestSupport.dispose(); - yield output.dispose(); - }) - ); + await input.dispose(); + await addonFooTestSupport.dispose(); + await addonBarTestSupport.dispose(); + await output.dispose(); + }); }); - describe('constructor', function() { - it('should override project.configPath if configPath option is specified', function() { - project.configPath = function() { + describe('constructor', function () { + it('should override project.configPath if configPath option is specified', function () { + project.configPath = function () { return 'original value'; }; @@ -758,7 +676,22 @@ describe('EmberApp', function() { expect(project.configPath().slice(-expected.length)).to.equal(expected); }); - it('should set bowerDirectory for app', function() { + it('should update project.config() if configPath option is specified', function () { + project.require = function (path) { + return () => ({ path }); + }; + + expect(project.config('development')).to.deep.equal({}); + + new EmberApp({ + project, + configPath: path.join('..', '..', 'app-import', 'config', 'environment'), + }); + + expect(project.configPath()).to.contain(path.join('app-import', 'config', 'environment')); + }); + + it('should set bowerDirectory for app', function () { let app = new EmberApp({ project, }); @@ -767,7 +700,7 @@ describe('EmberApp', function() { expect(app.bowerDirectory).to.equal('bower_components'); }); - it('should merge options with defaults to depth', function() { + it('should merge options with defaults to depth', function () { let app = new EmberApp( { project, @@ -804,7 +737,7 @@ describe('EmberApp', function() { }); }); - it('should do the right thing when merging default object options', function() { + it('should do the right thing when merging default object options', function () { let app = new EmberApp( { project, @@ -835,7 +768,7 @@ describe('EmberApp', function() { }); }); - it('should watch vendor if it exists', function() { + it('should watch vendor if it exists', function () { let app = new EmberApp({ project, }); @@ -843,7 +776,7 @@ describe('EmberApp', function() { expect(app.options.trees.vendor.__broccoliGetInfo__()).to.have.property('watched', true); }); - describe('Addons included hook', function() { + describe('Addons included hook', function () { let includedWasCalled; let setupPreprocessorRegistryWasCalled; let addonsAppIncluded, addonsApp; @@ -862,15 +795,15 @@ describe('EmberApp', function() { }, }; - beforeEach(function() { + beforeEach(function () { setupPreprocessorRegistryWasCalled = includedWasCalled = 0; addonsApp = null; addonsAppIncluded = null; - project.initializeAddons = function() {}; + project.initializeAddons = function () {}; project.addons = [addon]; }); - it('should set the app on the addons', function() { + it('should set the app on the addons', function () { expect(includedWasCalled).to.eql(0); let app = new EmberApp({ project, @@ -885,56 +818,21 @@ describe('EmberApp', function() { }); }); - describe('loader.js missing', function() { - it('does not error when loader.js is present in registry.availablePlugins', function() { - expect(() => { - new EmberApp({ - project, - }); - }).to.not.throw(/loader.js addon is missing/); - }); - - it('throws an error when loader.js is not present in registry.availablePlugins', function() { - expect(() => { - new EmberApp({ - project, - registry: { - add() {}, - availablePlugins: {}, - }, - }); - }).to.throw(/loader.js addon is missing/); - }); - - it('does not throw an error if _ignoreMissingLoader is set', function() { - expect(() => { - new EmberApp({ - project, - registry: { - add() {}, - availablePlugins: {}, - }, - _ignoreMissingLoader: true, - }); - }).to.not.throw(/loader.js addon is missing/); - }); - }); - - describe('ember-resolver npm vs Bower', function() { - it('does not load ember-resolver.js as bower dep when ember-resolver is present in registry.availablePlugins', function() { + describe('ember-resolver npm vs Bower', function () { + it('does not load ember-resolver.js as bower dep when ember-resolver is present in registry.availablePlugins', function () { let app = new EmberApp({ project }); expect(app.vendorFiles['ember-resolver']).to.equal(undefined); }); - it('keeps ember-resolver.js in vendorFiles when npm ember-resolver is not installed, but is present in bower.json', function() { - project.bowerDependencies = function() { + it('keeps ember-resolver.js in vendorFiles when npm ember-resolver is not installed, but is present in bower.json', function () { + project.bowerDependencies = function () { return { ember: {}, 'ember-resolver': {} }; }; let app = new EmberApp({ project, registry: { add() {}, - availablePlugins: { 'loader.js': {} }, + availablePlugins: {}, }, }); expect(app.vendorFiles['ember-resolver.js'][0]).to.equal( @@ -942,15 +840,15 @@ describe('EmberApp', function() { ); }); - it('removes ember-resolver.js from vendorFiles when not in bower.json and npm ember-resolver not installed', function() { - project.bowerDependencies = function() { + it('removes ember-resolver.js from vendorFiles when not in bower.json and npm ember-resolver not installed', function () { + project.bowerDependencies = function () { return { ember: {} }; }; let app = new EmberApp({ project, registry: { add() {}, - availablePlugins: { 'loader.js': {} }, + availablePlugins: {}, }, }); @@ -958,8 +856,8 @@ describe('EmberApp', function() { }); }); - describe('options.babel.sourceMaps', function() { - it('disables babel sourcemaps by default', function() { + describe('options.babel.sourceMaps', function () { + it('disables babel sourcemaps by default', function () { let app = new EmberApp({ project, }); @@ -967,7 +865,7 @@ describe('EmberApp', function() { expect(app.options.babel.sourceMaps).to.be.false; }); - it('can enable babel sourcemaps with the option', function() { + it('can enable babel sourcemaps with the option', function () { let app = new EmberApp({ project, babel: { @@ -979,8 +877,8 @@ describe('EmberApp', function() { }); }); - describe('options.fingerprint.exclude', function() { - it('excludeds testem in fingerprint exclude', function() { + describe('options.fingerprint.exclude', function () { + it('excludeds testem in fingerprint exclude', function () { let app = new EmberApp({ project, fingerprint: { @@ -993,9 +891,9 @@ describe('EmberApp', function() { }); }); - describe('addons', function() { - describe('included hook', function() { - it('included hook is called properly on instantiation', function() { + describe('addons', function () { + describe('included hook', function () { + it('included hook is called properly on instantiation', function () { let called = false; let passedApp; @@ -1007,7 +905,7 @@ describe('EmberApp', function() { treeFor() {}, }; - project.initializeAddons = function() { + project.initializeAddons = function () { this.addons = [addon]; }; @@ -1019,10 +917,10 @@ describe('EmberApp', function() { expect(passedApp).to.equal(app); }); - it('does not throw an error if the addon does not implement `included`', function() { + it('does not throw an error if the addon does not implement `included`', function () { delete addon.included; - project.initializeAddons = function() { + project.initializeAddons = function () { this.addons = [addon]; }; @@ -1034,14 +932,14 @@ describe('EmberApp', function() { }); }); - describe('addonTreesFor', function() { - beforeEach(function() { + describe('addonTreesFor', function () { + beforeEach(function () { addon = { included() {}, treeFor() {}, }; - project.initializeAddons = function() { + project.initializeAddons = function () { this.addons = [addon]; }; @@ -1050,15 +948,15 @@ describe('EmberApp', function() { }); }); - it('addonTreesFor returns an empty array if no addons return a tree', function() { + it('addonTreesFor returns an empty array if no addons return a tree', function () { expect(app.addonTreesFor('blah')).to.deep.equal([]); }); - it('addonTreesFor calls treesFor on the addon', function() { + it('addonTreesFor calls treesFor on the addon', function () { let sampleAddon = project.addons[0]; let actualTreeName; - sampleAddon.treeFor = function(name) { + sampleAddon.treeFor = function (name) { actualTreeName = name; return 'blazorz'; @@ -1068,7 +966,7 @@ describe('EmberApp', function() { expect(actualTreeName).to.equal('blah'); }); - it('addonTreesFor does not throw an error if treeFor is not defined', function() { + it('addonTreesFor does not throw an error if treeFor is not defined', function () { delete addon.treeFor; app = new EmberApp({ @@ -1080,8 +978,8 @@ describe('EmberApp', function() { }).not.to.throw(/addon must implement the `treeFor`/); }); - describe('addonTreesFor is called properly', function() { - beforeEach(function() { + describe('addonTreesFor is called properly', function () { + beforeEach(function () { app = new EmberApp({ project, }); @@ -1090,10 +988,10 @@ describe('EmberApp', function() { td.when(app.addonTreesFor(), { ignoreExtraArgs: true }).thenReturn(['batman']); }); - it('getAppJavascript calls addonTreesFor', function() { + it('getAppJavascript calls addonTreesFor', function () { app.getAppJavascript(); - let args = td.explain(app.addonTreesFor).calls.map(function(call) { + let args = td.explain(app.addonTreesFor).calls.map(function (call) { return call.args[0]; }); @@ -1102,8 +1000,8 @@ describe('EmberApp', function() { }); }); - describe('toArray', function() { - it('excludes `tests` tree from resulting array if the tree is not present', function() { + describe('toArray', function () { + it('excludes `tests` tree from resulting array if the tree is not present', function () { app = new EmberApp({ project, trees: { @@ -1123,15 +1021,15 @@ describe('EmberApp', function() { }); }); - describe('toTree', function() { - beforeEach(function() { + describe('toTree', function () { + beforeEach(function () { addon = { included() {}, treeFor() {}, postprocessTree: td.function(), }; - project.initializeAddons = function() { + project.initializeAddons = function () { this.addons = [addon]; }; @@ -1142,7 +1040,7 @@ describe('EmberApp', function() { }); }); - it('calls postProcessTree if defined', function() { + it('calls postProcessTree if defined', function () { app.toArray = td.function(); app._legacyPackage = td.function(); @@ -1152,7 +1050,7 @@ describe('EmberApp', function() { addon.postprocessTree( 'all', td.matchers.argThat( - t => t.constructor === BroccoliMergeTrees && t._inputNodes.length === 1 && t._inputNodes[0] === 'bar' + (t) => t.constructor === BroccoliMergeTrees && t._inputNodes.length === 1 && t._inputNodes[0] === 'bar' ) ) ).thenReturn('derp'); @@ -1160,7 +1058,7 @@ describe('EmberApp', function() { expect(app.toTree()).to.equal('derp'); }); - it('calls addonPostprocessTree', function() { + it('calls addonPostprocessTree', function () { app.toArray = td.function(); app.addonPostprocessTree = td.function(); app._legacyPackage = td.function(); @@ -1171,7 +1069,7 @@ describe('EmberApp', function() { app.addonPostprocessTree( 'all', td.matchers.argThat( - t => t.constructor === BroccoliMergeTrees && t._inputNodes.length === 1 && t._inputNodes[0] === 'bar' + (t) => t.constructor === BroccoliMergeTrees && t._inputNodes.length === 1 && t._inputNodes[0] === 'bar' ) ) ).thenReturn('blap'); @@ -1179,7 +1077,7 @@ describe('EmberApp', function() { expect(app.toTree()).to.equal('blap'); }); - it('calls each addon postprocessTree hook', function() { + it('calls each addon postprocessTree hook', function () { mockTemplateRegistry(app); app.index = td.function(); @@ -1195,7 +1093,7 @@ describe('EmberApp', function() { expect(app.toTree()).to.equal('blap'); - let args = td.explain(addon.postprocessTree).calls.map(function(call) { + let args = td.explain(addon.postprocessTree).calls.map(function (call) { return call.args[0]; }); @@ -1203,27 +1101,27 @@ describe('EmberApp', function() { }); }); - describe('addons can be disabled', function() { - beforeEach(function() { + describe('addons can be disabled', function () { + beforeEach(function () { projectPath = path.resolve(__dirname, '../../fixtures/addon/env-addons'); const packageContents = require(path.join(projectPath, 'package.json')); let cli = new MockCLI(); project = new Project(projectPath, packageContents, cli.ui, cli); }); - afterEach(function() { + afterEach(function () { process.env.EMBER_ENV = undefined; }); - describe('isEnabled is called properly', function() { - describe('with environment', function() { + describe('isEnabled is called properly', function () { + describe('with environment', function () { let emberFooEnvAddonFixture; - beforeEach(function() { + beforeEach(function () { emberFooEnvAddonFixture = require(path.resolve(projectPath, 'node_modules/ember-foo-env-addon/index.js')); }); - it('development', function() { + it('development', function () { process.env.EMBER_ENV = 'development'; let app = new EmberApp({ project }); @@ -1233,7 +1131,7 @@ describe('EmberApp', function() { expect(app.project.addons.length).to.equal(8); }); - it('foo', function() { + it('foo', function () { process.env.EMBER_ENV = 'foo'; let app = new EmberApp({ project }); @@ -1245,8 +1143,8 @@ describe('EmberApp', function() { }); }); - describe('blacklist', function() { - it('prevents addons to be added to the project', function() { + describe('blacklist', function () { + it('prevents addons to be added to the project', function () { process.env.EMBER_ENV = 'foo'; let app = new EmberApp({ @@ -1261,7 +1159,7 @@ describe('EmberApp', function() { expect(app.project.addons.length).to.equal(8); }); - it('throws if unavailable addon is specified', function() { + it('throws if unavailable addon is specified', function () { function load() { process.env.EMBER_ENV = 'foo'; @@ -1277,8 +1175,8 @@ describe('EmberApp', function() { }); }); - describe('whitelist', function() { - it('prevents non-whitelisted addons to be added to the project', function() { + describe('whitelist', function () { + it('prevents non-whitelisted addons to be added to the project', function () { process.env.EMBER_ENV = 'foo'; let app = new EmberApp({ @@ -1293,7 +1191,7 @@ describe('EmberApp', function() { expect(app.project.addons.length).to.equal(1); }); - it('throws if unavailable addon is specified', function() { + it('throws if unavailable addon is specified', function () { function load() { process.env.EMBER_ENV = 'foo'; app = new EmberApp({ @@ -1308,8 +1206,8 @@ describe('EmberApp', function() { }); }); - describe('blacklist wins over whitelist', function() { - it('prevents addon to be added to the project', function() { + describe('blacklist wins over whitelist', function () { + it('prevents addon to be added to the project', function () { process.env.EMBER_ENV = 'foo'; app = new EmberApp({ project, @@ -1324,13 +1222,86 @@ describe('EmberApp', function() { }); }); - describe('addonLintTree', function() { - beforeEach(function() { + describe('addon instance bundle caching validation (when used within the project)', function () { + let fixturifyProject; + + beforeEach(function () { + fixturifyProject = new FixturifyProject('awesome-proj', '1.0.0'); + fixturifyProject.addDevDependency('ember-cli', '*'); + }); + + afterEach(function () { + fixturifyProject.dispose(); + }); + + it('throws an error if an addon `whitelist` is specified', function () { + fixturifyProject.addInRepoAddon('foo', '1.0.0', { allowCachingPerBundle: true }); + fixturifyProject.addInRepoAddon('foo-bar', '1.0.0', { + callback: (inRepoAddon) => { + inRepoAddon.pkg['ember-addon'].paths = ['../foo']; + }, + }); + + fixturifyProject.writeSync(); + + let projectWithBundleCaching = fixturifyProject.buildProjectModel(); + projectWithBundleCaching.initializeAddons(); + + expect(() => { + new EmberApp({ + project: projectWithBundleCaching, + addons: { + whitelist: ['foo'], + }, + }); + }).to.throw( + [ + '[ember-cli] addon bundle caching is disabled for apps that specify an addon `whitelist`', + '', + 'All addons using bundle caching:', + projectWithBundleCaching.addons.find((addon) => addon.name === 'foo').packageRoot, + ].join('\n') + ); + }); + + it('throws an error if an addon `blacklist` is specified', function () { + fixturifyProject.addInRepoAddon('foo', '1.0.0', { allowCachingPerBundle: true }); + fixturifyProject.addInRepoAddon('foo-bar', '1.0.0', { + callback: (inRepoAddon) => { + inRepoAddon.pkg['ember-addon'].paths = ['../foo']; + }, + }); + + fixturifyProject.writeSync(); + + let projectWithBundleCaching = fixturifyProject.buildProjectModel(); + projectWithBundleCaching.initializeAddons(); + + expect(() => { + new EmberApp({ + project: projectWithBundleCaching, + addons: { + blacklist: ['foo'], + }, + }); + }).to.throw( + [ + '[ember-cli] addon bundle caching is disabled for apps that specify an addon `blacklist`', + '', + 'All addons using bundle caching:', + projectWithBundleCaching.addons.find((addon) => addon.name === 'foo').packageRoot, + ].join('\n') + ); + }); + }); + + describe('addonLintTree', function () { + beforeEach(function () { addon = { lintTree: td.function(), }; - project.initializeAddons = function() { + project.initializeAddons = function () { this.addons = [addon]; }; @@ -1339,11 +1310,11 @@ describe('EmberApp', function() { }); }); - it('does not throw an error if lintTree is not defined', function() { + it('does not throw an error if lintTree is not defined', function () { app.addonLintTree(); }); - it('calls lintTree on the addon', function() { + it('calls lintTree on the addon', function () { app.addonLintTree('blah', 'blam'); td.verify(addon.lintTree('blah', 'blam')); @@ -1351,25 +1322,25 @@ describe('EmberApp', function() { }); }); - describe('import', function() { - beforeEach(function() { + describe('import', function () { + beforeEach(function () { app = new EmberApp({ project, }); }); - afterEach(function() { + afterEach(function () { process.env.EMBER_ENV = undefined; }); - it('appends dependencies to vendor by default', function() { + it('appends dependencies to vendor by default', function () { app.import('vendor/moment.js'); let outputFile = app._scriptOutputFiles['/assets/vendor.js']; expect(outputFile).to.be.instanceof(Array); expect(outputFile.indexOf('vendor/moment.js')).to.equal(outputFile.length - 1); }); - it('appends dependencies', function() { + it('appends dependencies', function () { app.import('vendor/moment.js', { type: 'vendor' }); let outputFile = app._scriptOutputFiles['/assets/vendor.js']; @@ -1378,7 +1349,7 @@ describe('EmberApp', function() { expect(outputFile.indexOf('vendor/moment.js')).to.equal(outputFile.length - 1); }); - it('prepends dependencies', function() { + it('prepends dependencies', function () { app.import('vendor/es5-shim.js', { type: 'vendor', prepend: true }); let outputFile = app._scriptOutputFiles['/assets/vendor.js']; @@ -1387,7 +1358,7 @@ describe('EmberApp', function() { expect(outputFile.indexOf('vendor/es5-shim.js')).to.equal(0); }); - it('prepends dependencies to outputFile', function() { + it('prepends dependencies to outputFile', function () { app.import('vendor/moment.js', { outputFile: 'moment.js', prepend: true }); let outputFile = app._scriptOutputFiles['moment.js']; @@ -1396,7 +1367,7 @@ describe('EmberApp', function() { expect(outputFile.indexOf('vendor/moment.js')).to.equal(0); }); - it('appends dependencies to outputFile', function() { + it('appends dependencies to outputFile', function () { app.import('vendor/moment.js', { outputFile: 'moment.js' }); let outputFile = app._scriptOutputFiles['moment.js']; @@ -1405,7 +1376,7 @@ describe('EmberApp', function() { expect(outputFile.indexOf('vendor/moment.js')).to.equal(outputFile.length - 1); }); - it('defaults to development if production is not set', function() { + it('defaults to development if production is not set', function () { process.env.EMBER_ENV = 'production'; app.import({ development: 'vendor/jquery.js', @@ -1415,7 +1386,7 @@ describe('EmberApp', function() { expect(outputFile.indexOf('vendor/jquery.js')).to.equal(outputFile.length - 1); }); - it('honors explicitly set to null in environment', function() { + it('honors explicitly set to null in environment', function () { process.env.EMBER_ENV = 'production'; // set EMBER_ENV before creating the project @@ -1431,7 +1402,7 @@ describe('EmberApp', function() { expect(app._scriptOutputFiles['/assets/vendor.js']).to.not.contain('vendor/jquery.js'); }); - it('normalizes asset path correctly', function() { + it('normalizes asset path correctly', function () { app.import('vendor\\path\\to\\lib.js', { type: 'vendor' }); app.import('vendor/path/to/lib2.js', { type: 'vendor' }); @@ -1439,7 +1410,7 @@ describe('EmberApp', function() { expect(app._scriptOutputFiles['/assets/vendor.js']).to.contain('vendor/path/to/lib2.js'); }); - it('option.using throws exception given invalid inputs', function() { + it('option.using throws exception given invalid inputs', function () { // `using` is looped over if given, we should ensure this throws an exception with proper error message expect(() => { app.import('vendor/path/to/lib1.js', { using: 1 }); @@ -1459,11 +1430,11 @@ describe('EmberApp', function() { }); }); - describe('vendorFiles', function() { + describe('vendorFiles', function () { let defaultVendorFiles = ['jquery.js', 'ember.js', 'app-shims.js']; - describe('handlebars.js', function() { - it('does not app.import handlebars if not present in bower.json', function() { + describe('handlebars.js', function () { + it('does not app.import handlebars if not present in bower.json', function () { let app = new EmberApp({ project, }); @@ -1471,7 +1442,7 @@ describe('EmberApp', function() { expect(app.vendorFiles).not.to.include.keys('handlebars.js'); }); - it('includes handlebars if present in bower.json', function() { + it('includes handlebars if present in bower.json', function () { projectPath = path.resolve(__dirname, '../../fixtures/project-with-handlebars'); project = setupProject(projectPath); @@ -1482,7 +1453,7 @@ describe('EmberApp', function() { expect(app.vendorFiles).to.include.keys('handlebars.js'); }); - it('includes handlebars if present in provided `vendorFiles`', function() { + it('includes handlebars if present in provided `vendorFiles`', function () { let app = new EmberApp({ project, vendorFiles: { @@ -1494,14 +1465,14 @@ describe('EmberApp', function() { }); }); - it('defines vendorFiles by default', function() { + it('defines vendorFiles by default', function () { app = new EmberApp({ project, }); expect(Object.keys(app.vendorFiles)).to.deep.equal(defaultVendorFiles); }); - it('redefines a location of a vendor asset', function() { + it('redefines a location of a vendor asset', function () { app = new EmberApp({ project, @@ -1512,7 +1483,7 @@ describe('EmberApp', function() { expect(app.vendorFiles['ember.js']).to.equal('vendor/ember.js'); }); - it('defines vendorFiles in order even when option for it is passed', function() { + it('defines vendorFiles in order even when option for it is passed', function () { app = new EmberApp({ project, @@ -1523,17 +1494,17 @@ describe('EmberApp', function() { expect(Object.keys(app.vendorFiles)).to.deep.equal(defaultVendorFiles); }); - it('does not include jquery if the app has `@ember/jquery` installed', function() { - project.initializeAddons = function() { + it('does not include jquery if the app has `@ember/jquery` installed', function () { + project.initializeAddons = function () { this.addons = [{ name: '@ember/jquery' }]; }; app = new EmberApp({ project }); - let filesWithoutJQuery = defaultVendorFiles.filter(e => e !== 'jquery.js'); + let filesWithoutJQuery = defaultVendorFiles.filter((e) => e !== 'jquery.js'); expect(Object.keys(app.vendorFiles)).to.deep.equal(filesWithoutJQuery); }); - it('does not include jquery if the app has `@ember/optional-features` with the `jquery-integration` FF turned off', function() { - project.initializeAddons = function() { + it('does not include jquery if the app has `@ember/optional-features` with the `jquery-integration` FF turned off', function () { + project.initializeAddons = function () { this.addons = [ { name: 'ember-source', @@ -1548,11 +1519,11 @@ describe('EmberApp', function() { ]; }; app = new EmberApp({ project, vendorFiles: { 'ember-testing.js': null } }); - let filesWithoutJQuery = defaultVendorFiles.filter(e => e !== 'jquery.js'); + let filesWithoutJQuery = defaultVendorFiles.filter((e) => e !== 'jquery.js'); expect(Object.keys(app.vendorFiles)).to.deep.equal(filesWithoutJQuery); }); - it('removes dependency in vendorFiles', function() { + it('removes dependency in vendorFiles', function () { app = new EmberApp({ project, @@ -1566,7 +1537,7 @@ describe('EmberApp', function() { expect(vendorFiles).to.not.contain('handlebars.js'); }); - it('defaults to ember.debug.js if exists in bower_components', function() { + it('defaults to ember.debug.js if exists in bower_components', function () { let root = path.resolve(__dirname, '../../fixtures/app/with-default-ember-debug'); app = new EmberApp({ @@ -1577,7 +1548,7 @@ describe('EmberApp', function() { expect(files.development).to.equal('bower_components/ember/ember.debug.js'); }); - it('switches the default ember.debug.js to ember.js if it does not exist', function() { + it('switches the default ember.debug.js to ember.js if it does not exist', function () { let root = path.resolve(__dirname, '../../fixtures/app/without-ember-debug'); app = new EmberApp({ @@ -1588,7 +1559,7 @@ describe('EmberApp', function() { expect(files.development).to.equal('bower_components/ember/ember.js'); }); - it('does not clobber an explicitly configured ember development file', function() { + it('does not clobber an explicitly configured ember development file', function () { app = new EmberApp({ project, @@ -1603,7 +1574,7 @@ describe('EmberApp', function() { }); }); - it('fails with invalid type', function() { + it('fails with invalid type', function () { let app = new EmberApp({ project, }); @@ -1615,8 +1586,8 @@ describe('EmberApp', function() { ); }); - describe('_initOptions', function() { - it('sets the tests directory as watched when tests are enabled', function() { + describe('_initOptions', function () { + it('sets the tests directory as watched when tests are enabled', function () { let app = new EmberApp({ project, }); @@ -1627,7 +1598,7 @@ describe('EmberApp', function() { expect(app.options.trees.tests).to.be.an.instanceOf(WatchedDir); }); - it('sets the tests directory as unwatched when tests are disabled', function() { + it('sets the tests directory as unwatched when tests are disabled', function () { let app = new EmberApp({ project, }); @@ -1640,8 +1611,8 @@ describe('EmberApp', function() { }); }); - describe('_resolveLocal', function() { - it('resolves a path relative to the project root', function() { + describe('_resolveLocal', function () { + it('resolves a path relative to the project root', function () { let app = new EmberApp({ project, }); @@ -1651,17 +1622,17 @@ describe('EmberApp', function() { }); }); - describe('_concatFiles()', function() { - beforeEach(function() { + describe('_concatFiles()', function () { + beforeEach(function () { app = new EmberApp({ project }); }); - describe('concat order', function() { - beforeEach(function() { + describe('concat order', function () { + beforeEach(function () { mockTemplateRegistry(app); }); - it('correctly orders concats from app.styles()', function() { + it('correctly orders concats from app.styles()', function () { app.import('files/b.css'); app.import('files/c.css'); app.import('files/a.css', { prepend: true }); @@ -1675,7 +1646,7 @@ describe('EmberApp', function() { ]); }); - it('correctly orders concats from app.testFiles()', function() { + it('correctly orders concats from app.testFiles()', function () { app.import('files/b.js', { type: 'test' }); app.import('files/c.js', { type: 'test' }); app.import('files/a.js', { type: 'test' }); @@ -1697,16 +1668,16 @@ describe('EmberApp', function() { }); }); - describe('deprecations', function() { - it('shows ember-cli-shims deprecation', function() { + describe('deprecations', function () { + it('shows ember-cli-shims deprecation', function () { let root = path.resolve(__dirname, '../../fixtures/app/npm'); let project = setupProject(root); - project.require = function() { + project.require = function () { return { version: '5.0.0', }; }; - project.initializeAddons = function() { + project.initializeAddons = function () { this.addons = [ { name: 'ember-cli-babel', @@ -1724,9 +1695,9 @@ describe('EmberApp', function() { ); }); - describe('jQuery integration', function() { - it('shows deprecation', function() { - project.initializeAddons = function() { + describe('jQuery integration', function () { + it('shows deprecation', function () { + project.initializeAddons = function () { this.addons = [{ name: 'ember-source', paths: {} }]; }; app = new EmberApp({ project }); @@ -1736,8 +1707,8 @@ describe('EmberApp', function() { ); }); - it('does not show deprecation if the app has `@ember/jquery` installed', function() { - project.initializeAddons = function() { + it('does not show deprecation if the app has `@ember/jquery` installed', function () { + project.initializeAddons = function () { this.addons = [{ name: 'ember-source', paths: {} }, { name: '@ember/jquery' }]; }; app = new EmberApp({ project }); @@ -1746,8 +1717,8 @@ describe('EmberApp', function() { ); }); - it('does not show deprecation if the app has `@ember/optional-features` with the `jquery-integration` FF turned off', function() { - project.initializeAddons = function() { + it('does not show deprecation if the app has `@ember/optional-features` with the `jquery-integration` FF turned off', function () { + project.initializeAddons = function () { this.addons = [ { name: 'ember-source', paths: {} }, { diff --git a/tests/unit/broccoli/ember-app/app-and-dependencies-test.js b/tests/unit/broccoli/ember-app/app-and-dependencies-test.js index a9be2fa7c9..cf5c2714fb 100644 --- a/tests/unit/broccoli/ember-app/app-and-dependencies-test.js +++ b/tests/unit/broccoli/ember-app/app-and-dependencies-test.js @@ -1,6 +1,5 @@ 'use strict'; -const co = require('co'); const broccoliTestHelper = require('broccoli-test-helper'); const expect = require('chai').expect; @@ -11,26 +10,24 @@ const Project = require('../../../../lib/models/project'); const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; const walkSync = require('walk-sync'); -const { isExperimentEnabled } = require('../../../../lib/experiments'); -describe('EmberApp#appAndDependencies', function() { +describe('EmberApp#appAndDependencies', function () { let input, output; - beforeEach( - co.wrap(function*() { - process.env.EMBER_ENV = 'development'; + beforeEach(async function () { + process.env.EMBER_ENV = 'development'; - input = yield createTempDir(); + input = await createTempDir(); - input.write({ - node_modules: { - 'fake-template-preprocessor': { - 'package.json': JSON.stringify({ - name: 'fake-template-preprocessor', - main: 'index.js', - keywords: ['ember-addon'], - }), - 'index.js': ` + input.write({ + node_modules: { + 'fake-template-preprocessor': { + 'package.json': JSON.stringify({ + name: 'fake-template-preprocessor', + main: 'index.js', + keywords: ['ember-addon'], + }), + 'index.js': ` module.exports = { name: 'fake-template-preprocessor', setupPreprocessorRegistry(type, registry) { @@ -43,25 +40,22 @@ describe('EmberApp#appAndDependencies', function() { } }; `, - }, }, - config: { - 'environment.js': `module.exports = function() { return { modulePrefix: 'test-app' }; };`, - }, - }); - }) - ); + }, + config: { + 'environment.js': `module.exports = function() { return { modulePrefix: 'test-app' }; };`, + }, + }); + }); - afterEach( - co.wrap(function*() { - delete process.env.EMBER_ENV; - yield input.dispose(); + afterEach(async function () { + delete process.env.EMBER_ENV; + await input.dispose(); - if (output) { - yield output.dispose(); - } - }) - ); + if (output) { + await output.dispose(); + } + }); function createApp(options) { options = options || {}; @@ -95,69 +89,19 @@ describe('EmberApp#appAndDependencies', function() { }); } - it( - 'moduleNormalizerDisabled', - co.wrap(function*() { - input.write({ - node_modules: { - 'my-addon': { - addon: { - 'index.js': `define('amd', function() {});`, - }, - 'package.json': JSON.stringify({ - name: 'my-addon', - main: 'index.js', - keywords: ['ember-addon'], - }), - 'index.js': ` - module.exports = { - name: 'my-addon', - setupPreprocessorRegistry(type, registry) { - registry.add('template', { ext: 'hbs', toTree(tree) { return tree; } }); - registry.add('js', { ext: 'js', toTree(tree) { return tree; } }); - }, - } - `, + it('moduleNormalizerDisabled', async function () { + input.write({ + node_modules: { + 'my-addon': { + addon: { + 'index.js': `define('amd', function() {});`, }, - }, - }); - - let app = createApp({ - moduleNormalizerDisabled: true, - }); - - let addon = app.project.findAddonByName('my-addon'); - - addon.treeForAddon = tree => { - const Funnel = require('broccoli-funnel'); - return new Funnel(tree, { - destDir: 'modules/my-addon', - }); - }; - - output = yield buildOutput(app.getExternalTree()); - let actualFiles = getFiles(output.path()); - - expect(actualFiles).to.contain('addon-tree-output/modules/my-addon/index.js'); - }) - ); - - if (isExperimentEnabled('DELAYED_TRANSPILATION')) { - it( - 'amdFunnelDisabled', - co.wrap(function*() { - input.write({ - node_modules: { - 'my-addon': { - addon: { - 'index.js': `define('amd', function() {});`, - }, - 'package.json': JSON.stringify({ - name: 'my-addon', - main: 'index.js', - keywords: ['ember-addon'], - }), - 'index.js': ` + 'package.json': JSON.stringify({ + name: 'my-addon', + main: 'index.js', + keywords: ['ember-addon'], + }), + 'index.js': ` module.exports = { name: 'my-addon', setupPreprocessorRegistry(type, registry) { @@ -166,122 +110,26 @@ describe('EmberApp#appAndDependencies', function() { }, } `, - }, - }, - }); - - let app = createApp({ - amdFunnelDisabled: true, - }); - - let tree; - - app.registry.add('js', { - ext: 'js', - toTree(_tree) { - tree = _tree; - return _tree; - }, - }); - - app.addonTree(); - app._legacyAddonCompile('addon', 'addon-tree-output'); - - output = yield buildOutput(tree); - let actualFiles = getFiles(output.path()); - - expect(actualFiles).to.deep.equal(['my-addon/index.js']); - }) - ); - - it( - 'uses preprocessor that is marked by default', - co.wrap(function*() { - let app = createApp(); - - app.registry.add('js', { - ext: 'js', - name: 'addon1', - isDefaultForType: true, - toTree(tree) { - return tree; - }, - }); - - app.registry.add('js', { - ext: 'js', - name: 'addon2', - toTree(tree) { - expect(true).to.equal(false); - return tree; - }, - }); - - yield buildOutput(app.addonTree()); - }) - ); - - it( - 'uses all registered preprocessors if none is marked by default', - co.wrap(function*() { - let count = 0; - let app = createApp(); - - app.registry.add('js', { - ext: 'js', - name: 'addon1', - toTree(tree) { - count++; - return tree; - }, - }); - - app.registry.add('js', { - ext: 'js', - name: 'addon2', - toTree(tree) { - count++; - return tree; - }, - }); - - app.addonTree(); - - yield buildOutput(app._legacyAddonCompile('addon', 'addon-tree-output')); + }, + }, + }); - expect(count).to.equal(2); - }) - ); + let app = createApp({ + moduleNormalizerDisabled: true, + }); - it( - 'throws an exception if more than one preprocessor is marked as default', - co.wrap(function*() { - let exceptionMessage; - let app = createApp(); + let addon = app.project.findAddonByName('my-addon'); - app.registry.add('template', { - ext: 'hbs', - name: 'faulty-addon', - isDefaultForType: true, - toTree(tree) { - return tree; - }, - }); + addon.treeForAddon = (tree) => { + const Funnel = require('broccoli-funnel'); + return new Funnel(tree, { + destDir: 'modules/my-addon', + }); + }; - yield co(function*() { - app.addonTree(); + output = await buildOutput(app.getExternalTree()); + let actualFiles = getFiles(output.path()); - output = yield buildOutput(app._legacyAddonCompile('addon', 'addon-tree-output')); - }) - .catch(e => { - exceptionMessage = e.message; - }) - .then(() => { - expect(exceptionMessage).to.equal( - `There are multiple preprocessor plugins marked as default for 'template': fake-template-preprocessor, faulty-addon` - ); - }); - }) - ); - } + expect(actualFiles).to.contain('addon-tree-output/modules/my-addon/index.js'); + }); }); diff --git a/tests/unit/broccoli/ember-app/import-test.js b/tests/unit/broccoli/ember-app/import-test.js index e50bc794e4..dd9606e299 100644 --- a/tests/unit/broccoli/ember-app/import-test.js +++ b/tests/unit/broccoli/ember-app/import-test.js @@ -1,6 +1,5 @@ 'use strict'; -const co = require('co'); const path = require('path'); const broccoliTestHelper = require('broccoli-test-helper'); const expect = require('chai').expect; @@ -16,7 +15,7 @@ const getDependencyFor = defaultPackagerHelpers.getDependencyFor; const setupProject = defaultPackagerHelpers.setupProject; const validateDefaultPackagedDist = defaultPackagerHelpers.validateDefaultPackagedDist; -describe('EmberApp: Bower Dependencies', function() { +describe('EmberApp: Bower Dependencies', function () { let applicationDirectory, output; let moment = getDependencyFor('moment', { @@ -25,148 +24,132 @@ describe('EmberApp: Bower Dependencies', function() { 'moment.min.js': 'window.moment = "verysmallmoment"', }); - beforeEach( - co.wrap(function*() { - applicationDirectory = yield createTempDir(); - }) - ); + beforeEach(async function () { + applicationDirectory = await createTempDir(); + }); - afterEach( - co.wrap(function*() { - yield applicationDirectory.dispose(); - yield output.dispose(); - }) - ); + afterEach(async function () { + await applicationDirectory.dispose(); + await output.dispose(); + }); /* * Both Ember.js and jQuery are packaged by default (when distriburted * through bower). `ember-source` takes precedent over bower though. */ - it( - 'are not packaged unless explicitly imported', - co.wrap(function*() { - // Given - applicationDirectory.write( - getDefaultUnpackagedDist('the-best-app-ever', { - bowerComponents: Object.assign({}, moment), - }) - ); - - let applicationInstance = new EmberApp({ - name: 'the-best-app-ever', - project: setupProject(path.join(applicationDirectory.path(), 'the-best-app-ever')), - }); - - // When - let packagedApplicationJs = applicationInstance._defaultPackager.packageJavascript(applicationDirectory.path()); - - // Then - output = yield buildOutput(packagedApplicationJs); - let results = output.read(); - - expect(() => { - validateDefaultPackagedDist('the-best-app-ever', results); - }).not.to.throw(); - expect(results.assets['vendor.js']).to.contain('window.Ember = {'); - expect(results.assets['vendor.js']).to.contain('window.$ = function() {'); - expect(results.assets['vendor.js']).to.not.contain('window.moment'); - }) - ); - - it( - 'are packaged when explicitly imported', - co.wrap(function*() { - // Given - applicationDirectory.write( - getDefaultUnpackagedDist('the-best-app-ever', { - bowerComponents: Object.assign({}, moment), - }) - ); - let applicationInstance = new EmberApp({ - name: 'the-best-app-ever', - project: setupProject(path.join(applicationDirectory.path(), 'the-best-app-ever')), - }); - applicationInstance.import('bower_components/moment/moment.js'); - - // When - let packagedApplicationJs = applicationInstance._defaultPackager.packageJavascript(applicationDirectory.path()); - - // Then - output = yield buildOutput(packagedApplicationJs); - let results = output.read(); - - expect(() => { - validateDefaultPackagedDist('the-best-app-ever', results); - }).not.to.throw(); - expect(results.assets['vendor.js']).to.contain('window.Ember = {'); - expect(results.assets['vendor.js']).to.contain('window.$ = function() {'); - expect(results.assets['vendor.js']).to.contain('window.moment'); - }) - ); - - it( - 'are packaged when explicitly imported for production', - co.wrap(function*() { - // Given - applicationDirectory.write( - getDefaultUnpackagedDist('the-best-app-ever', { - bowerComponents: Object.assign({}, moment), - }) - ); - - let applicationInstance = new EmberApp({ - name: 'the-best-app-ever', - project: setupProject(path.join(applicationDirectory.path(), 'the-best-app-ever')), - }); - applicationInstance.env = 'production'; - applicationInstance.import({ - development: 'bower_components/moment/moment.js', - production: 'bower_components/moment/moment.min.js', - }); - - // When - let packagedApplicationJs = applicationInstance._defaultPackager.packageJavascript(applicationDirectory.path()); - - // Then - output = yield buildOutput(packagedApplicationJs); - let results = output.read(); - - expect(() => { - validateDefaultPackagedDist('the-best-app-ever', results); - }).not.to.throw(); - expect(results.assets['vendor.js']).to.contain('verysmallmoment'); - }) - ); - - it( - 'are packaged when explicitly imported for development', - co.wrap(function*() { - // Given - applicationDirectory.write( - getDefaultUnpackagedDist('the-best-app-ever', { - bowerComponents: Object.assign({}, moment), - }) - ); - let applicationInstance = new EmberApp({ - name: 'the-best-app-ever', - project: setupProject(path.join(applicationDirectory.path(), 'the-best-app-ever')), - }); - applicationInstance.import({ - development: 'bower_components/moment/moment.js', - production: 'bower_components/moment/moment.min.js', - }); - - // When - let packagedApplicationJs = applicationInstance._defaultPackager.packageJavascript(applicationDirectory.path()); - - // Then - output = yield buildOutput(packagedApplicationJs); - let results = output.read(); - - expect(() => { - validateDefaultPackagedDist('the-best-app-ever', results); - }).not.to.throw(); - expect(results.assets['vendor.js']).to.contain('window.moment'); - }) - ); + it('are not packaged unless explicitly imported', async function () { + // Given + applicationDirectory.write( + getDefaultUnpackagedDist('the-best-app-ever', { + bowerComponents: Object.assign({}, moment), + }) + ); + + let applicationInstance = new EmberApp({ + name: 'the-best-app-ever', + project: setupProject(path.join(applicationDirectory.path(), 'the-best-app-ever')), + }); + + // When + let packagedApplicationJs = applicationInstance._defaultPackager.packageJavascript(applicationDirectory.path()); + + // Then + output = await buildOutput(packagedApplicationJs); + let results = output.read(); + + expect(() => { + validateDefaultPackagedDist('the-best-app-ever', results); + }).not.to.throw(); + expect(results.assets['vendor.js']).to.contain('window.Ember = {'); + expect(results.assets['vendor.js']).to.contain('window.$ = function() {'); + expect(results.assets['vendor.js']).to.not.contain('window.moment'); + }); + + it('are packaged when explicitly imported', async function () { + // Given + applicationDirectory.write( + getDefaultUnpackagedDist('the-best-app-ever', { + bowerComponents: Object.assign({}, moment), + }) + ); + let applicationInstance = new EmberApp({ + name: 'the-best-app-ever', + project: setupProject(path.join(applicationDirectory.path(), 'the-best-app-ever')), + }); + applicationInstance.import('bower_components/moment/moment.js'); + + // When + let packagedApplicationJs = applicationInstance._defaultPackager.packageJavascript(applicationDirectory.path()); + + // Then + output = await buildOutput(packagedApplicationJs); + let results = output.read(); + + expect(() => { + validateDefaultPackagedDist('the-best-app-ever', results); + }).not.to.throw(); + expect(results.assets['vendor.js']).to.contain('window.Ember = {'); + expect(results.assets['vendor.js']).to.contain('window.$ = function() {'); + expect(results.assets['vendor.js']).to.contain('window.moment'); + }); + + it('are packaged when explicitly imported for production', async function () { + // Given + applicationDirectory.write( + getDefaultUnpackagedDist('the-best-app-ever', { + bowerComponents: Object.assign({}, moment), + }) + ); + + let applicationInstance = new EmberApp({ + name: 'the-best-app-ever', + project: setupProject(path.join(applicationDirectory.path(), 'the-best-app-ever')), + }); + applicationInstance.env = 'production'; + applicationInstance.import({ + development: 'bower_components/moment/moment.js', + production: 'bower_components/moment/moment.min.js', + }); + + // When + let packagedApplicationJs = applicationInstance._defaultPackager.packageJavascript(applicationDirectory.path()); + + // Then + output = await buildOutput(packagedApplicationJs); + let results = output.read(); + + expect(() => { + validateDefaultPackagedDist('the-best-app-ever', results); + }).not.to.throw(); + expect(results.assets['vendor.js']).to.contain('verysmallmoment'); + }); + + it('are packaged when explicitly imported for development', async function () { + // Given + applicationDirectory.write( + getDefaultUnpackagedDist('the-best-app-ever', { + bowerComponents: Object.assign({}, moment), + }) + ); + let applicationInstance = new EmberApp({ + name: 'the-best-app-ever', + project: setupProject(path.join(applicationDirectory.path(), 'the-best-app-ever')), + }); + applicationInstance.import({ + development: 'bower_components/moment/moment.js', + production: 'bower_components/moment/moment.min.js', + }); + + // When + let packagedApplicationJs = applicationInstance._defaultPackager.packageJavascript(applicationDirectory.path()); + + // Then + output = await buildOutput(packagedApplicationJs); + let results = output.read(); + + expect(() => { + validateDefaultPackagedDist('the-best-app-ever', results); + }).not.to.throw(); + expect(results.assets['vendor.js']).to.contain('window.moment'); + }); }); diff --git a/tests/unit/broccoli/merge-trees-test.js b/tests/unit/broccoli/merge-trees-test.js index b491413e46..b9f23f53a1 100644 --- a/tests/unit/broccoli/merge-trees-test.js +++ b/tests/unit/broccoli/merge-trees-test.js @@ -4,30 +4,30 @@ const expect = require('chai').expect; const mergeTrees = require('../../../lib/broccoli/merge-trees'); -describe('broccoli/merge-trees', function() { +describe('broccoli/merge-trees', function () { let originalUpstreamMergeTrees; - beforeEach(function() { + beforeEach(function () { originalUpstreamMergeTrees = mergeTrees._upstreamMergeTrees; - mergeTrees._upstreamMergeTrees = function() { + mergeTrees._upstreamMergeTrees = function () { return {}; }; }); - afterEach(function() { + afterEach(function () { // reset the shared EMPTY_MERGE_TREE to ensure // we end up back in a consistent state mergeTrees._overrideEmptyTree(null); mergeTrees._upstreamMergeTrees = originalUpstreamMergeTrees; }); - it('returns the first item when merging single item array', function() { + it('returns the first item when merging single item array', function () { let actual = mergeTrees(['foo']); expect(actual).to.equal('foo'); }); - it('returns a constant "empty tree" when passed an empty array', function() { + it('returns a constant "empty tree" when passed an empty array', function () { let expected = {}; mergeTrees._overrideEmptyTree(expected); @@ -39,11 +39,11 @@ describe('broccoli/merge-trees', function() { expect(second).to.equal(expected); }); - it('passes all inputTrees through when non-empty', function() { + it('passes all inputTrees through when non-empty', function () { let expected = ['foo', 'bar']; let actual; - mergeTrees._upstreamMergeTrees = function(inputTrees) { + mergeTrees._upstreamMergeTrees = function (inputTrees) { actual = inputTrees; return {}; }; @@ -52,13 +52,13 @@ describe('broccoli/merge-trees', function() { expect(actual).to.deep.equal(expected); }); - it('filters out empty trees from inputs', function() { + it('filters out empty trees from inputs', function () { let expected = ['bar', 'baz']; let actual; mergeTrees._overrideEmptyTree('foo'); - mergeTrees._upstreamMergeTrees = function(inputTrees) { + mergeTrees._upstreamMergeTrees = function (inputTrees) { actual = inputTrees; return {}; }; @@ -67,13 +67,13 @@ describe('broccoli/merge-trees', function() { expect(actual).to.deep.equal(expected); }); - it('removes duplicate trees with the last duplicate being the remainder', function() { + it('removes duplicate trees with the last duplicate being the remainder', function () { let treeA = {}; let treeB = {}; let expected = [treeB, treeA]; let actual; - mergeTrees._upstreamMergeTrees = function(inputTrees) { + mergeTrees._upstreamMergeTrees = function (inputTrees) { actual = inputTrees; return {}; }; diff --git a/tests/unit/broccoli/template-precompilation-test.js b/tests/unit/broccoli/template-precompilation-test.js index 619a9b9915..c1aeddc894 100644 --- a/tests/unit/broccoli/template-precompilation-test.js +++ b/tests/unit/broccoli/template-precompilation-test.js @@ -15,7 +15,7 @@ const EmberApp = require('../../../lib/broccoli/ember-app'); const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; -describe('template preprocessors', function() { +describe('template preprocessors', function () { let input, output, addon; class FakeTemplateColocator extends BroccoliPlugin { @@ -33,7 +33,7 @@ describe('template preprocessors', function() { let root = entries[0]; let files = walkSync(path.join(inputPath, root), { directories: false }); - files.forEach(file => { + files.forEach((file) => { let fullInputPath = path.join(inputPath, root, file); let fullOutputPath = path.join(this.outputPath, root, file); @@ -55,11 +55,12 @@ describe('template preprocessors', function() { } } - describe('Addon', function() { - beforeEach(async function() { + describe('Addon', function () { + beforeEach(async function () { input = await createTempDir(); let MockAddon = Addon.extend({ root: input.path(), + packageRoot: input.path(), name: 'fake-addon', }); let cli = new MockCLI(); @@ -77,12 +78,12 @@ describe('template preprocessors', function() { }); }); - afterEach(async function() { + afterEach(async function () { await input.dispose(); await output.dispose(); }); - it('the template preprocessor receives access to all files', async function() { + it('the template preprocessor receives access to all files', async function () { addon.registry.add('template', { name: 'fake-template-compiler', ext: 'hbs', @@ -116,22 +117,22 @@ describe('template preprocessors', function() { }); }); - describe('EmberApp', function() { + describe('EmberApp', function () { let project; - beforeEach(async function() { + beforeEach(async function () { input = await createTempDir(); let cli = new MockCLI(); let pkg = { name: 'fake-app-test', devDependencies: { 'ember-cli': '*' } }; project = new Project(input.path(), pkg, cli.ui, cli); }); - afterEach(async function() { + afterEach(async function () { await input.dispose(); await output.dispose(); }); - it('the template preprocessor receives access to all files', async function() { + it('the template preprocessor receives access to all files', async function () { input.write({ app: { components: { diff --git a/tests/unit/cli/cli-test.js b/tests/unit/cli/cli-test.js index 91b07b66c5..680a0f2cdb 100644 --- a/tests/unit/cli/cli-test.js +++ b/tests/unit/cli/cli-test.js @@ -5,7 +5,7 @@ const MockUI = require('console-ui/mock'); const MockAnalytics = require('../../helpers/mock-analytics'); const td = require('testdouble'); const Command = require('../../../lib/models/command'); -const Promise = require('rsvp').Promise; +const CLI = require('../../../lib/cli/cli'); let ui; let analytics; @@ -14,8 +14,6 @@ let isWithinProject; let project; let willInterruptProcess; -let CLI; - // helper to similate running the CLI function ember(args) { let cli = new CLI({ @@ -35,7 +33,7 @@ function ember(args) { settings: {}, project, }) - .then(function(value) { + .then(function (value) { td.verify(stopInstr('init'), { times: 1 }); td.verify(startInstr('command'), { times: 1 }); td.verify(stopInstr('command', td.matchers.anything(), td.matchers.isA(Array)), { times: 1 }); @@ -66,13 +64,12 @@ function stubRun(name) { return td.replace(commands[name].prototype, 'run', td.function()); } -describe('Unit: CLI', function() { - beforeEach(function() { +describe('Unit: CLI', function () { + beforeEach(function () { willInterruptProcess = require('../../../lib/utilities/will-interrupt-process'); td.replace(willInterruptProcess, 'addHandler', td.function()); td.replace(willInterruptProcess, 'removeHandler', td.function()); - CLI = require('../../../lib/cli/cli'); ui = new MockUI(); analytics = new MockAnalytics(); commands = {}; @@ -91,7 +88,7 @@ describe('Unit: CLI', function() { }; }); - afterEach(function() { + afterEach(function () { td.reset(); delete process.env.EMBER_ENV; @@ -100,14 +97,10 @@ describe('Unit: CLI', function() { this.timeout(10000); - it('exists', function() { - expect(CLI).to.be.ok; - }); - - it('ember', function() { + it('ember', function () { let help = stubValidateAndRun('help'); - return ember().then(function() { + return ember().then(function () { td.verify(help(), { ignoreExtraArgs: true, times: 1 }); let output = ui.output.trim(); expect(output).to.equal('', 'expected no extra output'); @@ -127,7 +120,7 @@ describe('Unit: CLI', function() { }); */ - it('callHelp', function() { + it('callHelp', function () { let cli = new CLI({ ui, analytics, @@ -162,7 +155,7 @@ describe('Unit: CLI', function() { td.verify(init(), { ignoreExtraArgs: true, times: 0 }); }); - it('errors correctly if the init hook errors', function() { + it('errors correctly if the init hook errors', function () { stubValidateAndRun('help'); let cli = new CLI({ @@ -186,7 +179,7 @@ describe('Unit: CLI', function() { settings: {}, project, }) - .then(function() { + .then(function () { td.verify(startInstr('command'), { times: 0 }); td.verify(stopInstr('command'), { times: 0 }); td.verify(startInstr('shutdown'), { times: 1 }); @@ -194,8 +187,49 @@ describe('Unit: CLI', function() { }); }); - describe('custom addon command', function() { - it('beforeRun can return a promise', function() { + it('"run" method must throw error if no evironment provided', function () { + stubValidateAndRun('help'); + + let cli = new CLI({ + ui, + analytics, + testing: true, + }); + + let wasResolved = false; + cli + .run() + .then(() => { + wasResolved = true; + }) + .catch((err) => { + expect(err.toString()).to.be.equal('Error: Unable to execute "run" command without environment argument'); + }) + .finally(() => { + expect(wasResolved).to.be.false; + }); + }); + + it('errors correctly if "run" method not called before "maybeMakeCommand" execution', function () { + stubValidateAndRun('help'); + + let cli = new CLI({ + ui, + analytics, + testing: true, + }); + + try { + cli.maybeMakeCommand('foo', ['bar']); + } catch (err) { + expect(err.toString()).to.be.equal( + 'Error: Unable to make command without environment, you have to execute "run" method first.' + ); + } + }); + + describe('custom addon command', function () { + it('beforeRun can return a promise', function () { let CustomCommand = Command.extend({ name: 'custom', @@ -208,8 +242,8 @@ describe('Unit: CLI', function() { beforeRun() { let command = this; - return new Promise(function(resolve) { - setTimeout(function() { + return new Promise(function (resolve) { + setTimeout(function () { command._beforeRunFinished = true; resolve(); }, 5); @@ -223,7 +257,7 @@ describe('Unit: CLI', function() { }, }); - project.eachAddonCommand = function(callback) { + project.eachAddonCommand = function (callback) { callback('custom-addon', { custom: CustomCommand, }); @@ -233,13 +267,13 @@ describe('Unit: CLI', function() { }); }); - describe('command interruption handler', function() { + describe('command interruption handler', function () { let onCommandInterrupt; - beforeEach(function() { + beforeEach(function () { onCommandInterrupt = td.matchers.isA(Function); }); - it('sets up handler before command run', function() { + it('sets up handler before command run', function () { const CustomCommand = Command.extend({ name: 'custom', @@ -254,7 +288,7 @@ describe('Unit: CLI', function() { }, }); - project.eachAddonCommand = function(callback) { + project.eachAddonCommand = function (callback) { callback('custom-addon', { CustomCommand, }); @@ -263,32 +297,32 @@ describe('Unit: CLI', function() { return ember(['custom']); }); - it('cleans up handler after command finished', function() { + it('cleans up handler after command finished', function () { stubValidateAndRun('serve'); - return ember(['serve']).finally(function() { + return ember(['serve']).finally(function () { td.verify(willInterruptProcess.removeHandler(onCommandInterrupt)); }); }); }); - describe('help', function() { - ['--help', '-h'].forEach(function(command) { - it(`ember ${command}`, function() { + describe('help', function () { + ['--help', '-h'].forEach(function (command) { + it(`ember ${command}`, function () { let help = stubValidateAndRun('help'); - return ember([command]).then(function() { + return ember([command]).then(function () { td.verify(help(), { ignoreExtraArgs: true, times: 1 }); let output = ui.output.trim(); expect(output).to.equal('', 'expected no extra output'); }); }); - it(`ember new ${command}`, function() { + it(`ember new ${command}`, function () { let help = stubCallHelp(); stubValidateAndRunHelp('new'); - return ember(['new', command]).then(function() { + return ember(['new', command]).then(function () { td.verify(help(), { ignoreExtraArgs: true, times: 1 }); let output = ui.output.trim(); expect(output).to.equal('', 'expected no extra output'); @@ -297,11 +331,11 @@ describe('Unit: CLI', function() { }); }); - ['--version', '-v'].forEach(function(command) { - it(`ember ${command}`, function() { + ['--version', '-v'].forEach(function (command) { + it(`ember ${command}`, function () { let version = stubValidateAndRun('version'); - return ember([command]).then(function() { + return ember([command]).then(function () { let output = ui.output.trim(); expect(output).to.equal('', 'expected no extra output'); td.verify(version(), { ignoreExtraArgs: true, times: 1 }); @@ -309,32 +343,32 @@ describe('Unit: CLI', function() { }); }); - describe('server', function() { - ['server', 's'].forEach(function(command) { - it(`ember ${command} --port 9999`, function() { + describe('server', function () { + ['server', 's'].forEach(function (command) { + it(`ember ${command} --port 9999`, function () { let server = stubRun('serve'); - return ember([command, '--port', '9999']).then(function() { + return ember([command, '--port', '9999']).then(function () { let captor = td.matchers.captor(); td.verify(server(captor.capture()), { ignoreExtraArgs: true, times: 1 }); expect(captor.value.port, 'port').to.equal(9999); }); }); - it(`ember ${command} --host localhost`, function() { + it(`ember ${command} --host localhost`, function () { let server = stubRun('serve'); - return ember(['server', '--host', 'localhost']).then(function() { + return ember(['server', '--host', 'localhost']).then(function () { let captor = td.matchers.captor(); td.verify(server(captor.capture()), { ignoreExtraArgs: true, times: 1 }); expect(captor.value.host, 'host').to.equal('localhost'); }); }); - it(`ember ${command} --port 9292 --host localhost`, function() { + it(`ember ${command} --port 9292 --host localhost`, function () { let server = stubRun('serve'); - return ember([command, '--port', '9292', '--host', 'localhost']).then(function() { + return ember([command, '--port', '9292', '--host', 'localhost']).then(function () { let captor = td.matchers.captor(); td.verify(server(captor.capture()), { ignoreExtraArgs: true, times: 1 }); expect(captor.value.host, 'host').to.equal('localhost'); @@ -342,71 +376,71 @@ describe('Unit: CLI', function() { }); }); - it(`ember ${command} --proxy http://localhost:3000/`, function() { + it(`ember ${command} --proxy http://localhost:3000/`, function () { let server = stubRun('serve'); - return ember([command, '--proxy', 'http://localhost:3000/']).then(function() { + return ember([command, '--proxy', 'http://localhost:3000/']).then(function () { let captor = td.matchers.captor(); td.verify(server(captor.capture()), { ignoreExtraArgs: true, times: 1 }); expect(captor.value.proxy, 'proxy').to.equal('http://localhost:3000/'); }); }); - it(`ember ${command} --proxy https://localhost:3009/ --insecure-proxy`, function() { + it(`ember ${command} --proxy https://localhost:3009/ --insecure-proxy`, function () { let server = stubRun('serve'); - return ember([command, '--insecure-proxy']).then(function() { + return ember([command, '--insecure-proxy']).then(function () { let captor = td.matchers.captor(); td.verify(server(captor.capture()), { ignoreExtraArgs: true, times: 1 }); expect(captor.value.insecureProxy, 'insecureProxy').to.equal(true); }); }); - it(`ember ${command} --proxy https://localhost:3009/ --no-insecure-proxy`, function() { + it(`ember ${command} --proxy https://localhost:3009/ --no-insecure-proxy`, function () { let server = stubRun('serve'); - return ember([command, '--no-insecure-proxy']).then(function() { + return ember([command, '--no-insecure-proxy']).then(function () { let captor = td.matchers.captor(); td.verify(server(captor.capture()), { ignoreExtraArgs: true, times: 1 }); expect(captor.value.insecureProxy, 'insecureProxy').to.equal(false); }); }); - it(`ember ${command} --watcher events`, function() { + it(`ember ${command} --watcher events`, function () { let server = stubRun('serve'); - return ember([command, '--watcher', 'events']).then(function() { + return ember([command, '--watcher', 'events']).then(function () { let captor = td.matchers.captor(); td.verify(server(captor.capture()), { ignoreExtraArgs: true, times: 1 }); expect(captor.value.watcher, 'watcher').to.match(/node|polling|watchman/); }); }); - it(`ember ${command} --watcher polling`, function() { + it(`ember ${command} --watcher polling`, function () { let server = stubRun('serve'); - return ember([command, '--watcher', 'polling']).then(function() { + return ember([command, '--watcher', 'polling']).then(function () { let captor = td.matchers.captor(); td.verify(server(captor.capture()), { ignoreExtraArgs: true, times: 1 }); expect(captor.value.watcher, 'watcher').to.equal('polling'); }); }); - it(`ember ${command}`, function() { + it(`ember ${command}`, function () { let server = stubRun('serve'); - return ember([command]).then(function() { + return ember([command]).then(function () { let captor = td.matchers.captor(); td.verify(server(captor.capture()), { ignoreExtraArgs: true, times: 1 }); expect(captor.value.watcher, 'watcher').to.match(/node|polling|watchman/); }); }); - ['production', 'development', 'foo'].forEach(function(env) { - it(`ember ${command} --environment ${env}`, function() { + ['production', 'development', 'foo'].forEach(function (env) { + it(`ember ${command} --environment ${env}`, function () { let server = stubRun('serve'); - return ember([command, '--environment', env]).then(function() { + return ember([command, '--environment', env]).then(function () { let captor = td.matchers.captor(); td.verify(server(captor.capture()), { ignoreExtraArgs: true, times: 1 }); expect(captor.value.environment, 'environment').to.equal(env); @@ -414,12 +448,12 @@ describe('Unit: CLI', function() { }); }); - ['development', 'foo'].forEach(function(env) { - it(`ember ${command} --environment ${env}`, function() { + ['development', 'foo'].forEach(function (env) { + it(`ember ${command} --environment ${env}`, function () { let server = stubRun('serve'); process.env.EMBER_ENV = 'production'; - return ember([command, '--environment', env]).then(function() { + return ember([command, '--environment', env]).then(function () { td.verify(server(), { ignoreExtraArgs: true, times: 1 }); expect(process.env.EMBER_ENV).to.equal('production', 'uses EMBER_ENV over environment'); @@ -427,13 +461,13 @@ describe('Unit: CLI', function() { }); }); - ['production', 'development', 'foo'].forEach(function(env) { - it(`EMBER_ENV=${env} ember ${command}`, function() { + ['production', 'development', 'foo'].forEach(function (env) { + it(`EMBER_ENV=${env} ember ${command}`, function () { let server = stubRun('serve'); process.env.EMBER_ENV = env; - return ember([command]).then(function() { + return ember([command]).then(function () { td.verify(server(), { ignoreExtraArgs: true, times: 1 }); expect(process.env.EMBER_ENV).to.equal(env, 'correct environment'); @@ -443,12 +477,12 @@ describe('Unit: CLI', function() { }); }); - describe('generate', function() { - ['generate', 'g'].forEach(function(command) { - it(`ember ${command} foo bar baz`, function() { + describe('generate', function () { + ['generate', 'g'].forEach(function (command) { + it(`ember ${command} foo bar baz`, function () { let generate = stubRun('generate'); - return ember([command, 'foo', 'bar', 'baz']).then(function() { + return ember([command, 'foo', 'bar', 'baz']).then(function () { let captor = td.matchers.captor(); td.verify(generate(captor.capture(), ['foo', 'bar', 'baz']), { times: 1 }); @@ -460,20 +494,20 @@ describe('Unit: CLI', function() { }); }); - describe('init', function() { - ['init'].forEach(function(command) { - it(`ember ${command}`, function() { + describe('init', function () { + ['init'].forEach(function (command) { + it(`ember ${command}`, function () { let init = stubValidateAndRun('init'); - return ember([command]).then(function() { + return ember([command]).then(function () { td.verify(init(), { ignoreExtraArgs: true, times: 1 }); }); }); - it(`ember ${command} `, function() { + it(`ember ${command} `, function () { let init = stubRun('init'); - return ember([command, 'my-blog']).then(function() { + return ember([command, 'my-blog']).then(function () { let captor = td.matchers.captor(); td.verify(init(captor.capture(), ['my-blog']), { times: 1 }); @@ -485,34 +519,34 @@ describe('Unit: CLI', function() { }); }); - describe('new', function() { - it('ember new', function() { + describe('new', function () { + it('ember new', function () { isWithinProject = false; let newCommand = stubRun('new'); - return ember(['new']).then(function() { + return ember(['new']).then(function () { td.verify(newCommand(), { ignoreExtraArgs: true, times: 1 }); }); }); - it('ember new MyApp', function() { + it('ember new MyApp', function () { isWithinProject = false; let newCommand = stubRun('new'); - return ember(['new', 'MyApp']).then(function() { + return ember(['new', 'MyApp']).then(function () { td.verify(newCommand(td.matchers.anything(), ['MyApp']), { times: 1 }); }); }); }); - describe('build', function() { - ['build', 'b'].forEach(function(command) { - it(`ember ${command}`, function() { + describe('build', function () { + ['build', 'b'].forEach(function (command) { + it(`ember ${command}`, function () { let build = stubRun('build'); - return ember([command]).then(function() { + return ember([command]).then(function () { let captor = td.matchers.captor(); td.verify(build(captor.capture()), { ignoreExtraArgs: true, times: 1 }); @@ -522,10 +556,10 @@ describe('Unit: CLI', function() { }); }); - it(`ember ${command} --disable-analytics`, function() { + it(`ember ${command} --disable-analytics`, function () { let build = stubRun('build'); - return ember([command, '--disable-analytics']).then(function() { + return ember([command, '--disable-analytics']).then(function () { let captor = td.matchers.captor(); td.verify(build(captor.capture()), { ignoreExtraArgs: true, times: 1 }); @@ -534,10 +568,10 @@ describe('Unit: CLI', function() { }); }); - it(`ember ${command} --watch`, function() { + it(`ember ${command} --watch`, function () { let build = stubRun('build'); - return ember([command, '--watch']).then(function() { + return ember([command, '--watch']).then(function () { let captor = td.matchers.captor(); td.verify(build(captor.capture()), { ignoreExtraArgs: true, times: 1 }); @@ -546,10 +580,10 @@ describe('Unit: CLI', function() { }); }); - it(`ember ${command} --suppress-sizes`, function() { + it(`ember ${command} --suppress-sizes`, function () { let build = stubRun('build'); - return ember([command, '--suppress-sizes']).then(function() { + return ember([command, '--suppress-sizes']).then(function () { let captor = td.matchers.captor(); td.verify(build(captor.capture()), { ignoreExtraArgs: true, times: 1 }); @@ -558,11 +592,11 @@ describe('Unit: CLI', function() { }); }); - ['production', 'development', 'baz'].forEach(function(env) { - it(`ember ${command} --environment ${env}`, function() { + ['production', 'development', 'baz'].forEach(function (env) { + it(`ember ${command} --environment ${env}`, function () { let build = stubRun('build'); - return ember([command, '--environment', env]).then(function() { + return ember([command, '--environment', env]).then(function () { let captor = td.matchers.captor(); td.verify(build(captor.capture()), { ignoreExtraArgs: true, times: 1 }); @@ -572,13 +606,13 @@ describe('Unit: CLI', function() { }); }); - ['development', 'baz'].forEach(function(env) { - it(`EMBER_ENV=production ember ${command} --environment ${env}`, function() { + ['development', 'baz'].forEach(function (env) { + it(`EMBER_ENV=production ember ${command} --environment ${env}`, function () { let build = stubRun('build'); process.env.EMBER_ENV = 'production'; - return ember([command, '--environment', env]).then(function() { + return ember([command, '--environment', env]).then(function () { td.verify(build(), { ignoreExtraArgs: true, times: 1 }); expect(process.env.EMBER_ENV).to.equal('production', 'uses EMBER_ENV over environment'); @@ -586,13 +620,13 @@ describe('Unit: CLI', function() { }); }); - ['production', 'development', 'baz'].forEach(function(env) { - it(`EMBER_ENV=${env} ember ${command} `, function() { + ['production', 'development', 'baz'].forEach(function (env) { + it(`EMBER_ENV=${env} ember ${command} `, function () { let build = stubRun('build'); process.env.EMBER_ENV = env; - return ember([command]).then(function() { + return ember([command]).then(function () { td.verify(build(), { ignoreExtraArgs: true, times: 1 }); expect(process.env.EMBER_ENV).to.equal(env, 'correct environment'); @@ -602,11 +636,11 @@ describe('Unit: CLI', function() { }); }); - it('ember ', function() { + it('ember ', function () { let help = stubValidateAndRun('help'); let serve = stubValidateAndRun('serve'); - return ember(['serve']).then(function() { + return ember(['serve']).then(function () { td.verify(help(), { ignoreExtraArgs: true, times: 0 }); td.verify(serve(), { ignoreExtraArgs: true, times: 1 }); @@ -615,11 +649,11 @@ describe('Unit: CLI', function() { }); }); - it.skip('ember ', function() { + it.skip('ember ', function () { let help = stubValidateAndRun('help'); let serve = stubValidateAndRun('serve'); - return ember(['serve', 'lorem', 'ipsum', 'dolor', '--flag1=one']).then(function() { + return ember(['serve', 'lorem', 'ipsum', 'dolor', '--flag1=one']).then(function () { let args = serve.calledWith[0][0].cliArgs; expect(help.called).to.equal(0, 'expected the help command NOT to be run'); @@ -633,10 +667,10 @@ describe('Unit: CLI', function() { }); }); - it('ember ', function() { + it('ember ', function () { let help = stubValidateAndRun('help'); - return expect(ember(['unknownCommand'])).to.be.rejected.then(error => { + return expect(ember(['unknownCommand'])).to.be.rejected.then((error) => { expect(help.called, 'help command was executed').to.not.be.ok; expect(error.name).to.equal('SilentError'); expect(error.message).to.equal( @@ -645,12 +679,12 @@ describe('Unit: CLI', function() { }); }); - describe.skip('default options config file', function() { - it('reads default options from .ember-cli file', function() { + describe.skip('default options config file', function () { + it('reads default options from .ember-cli file', function () { let defaults = ['--output', process.cwd()]; let build = stubValidateAndRun('build'); - return ember(['build'], defaults).then(function() { + return ember(['build'], defaults).then(function () { let options = build.calledWith[0][1].cliOptions; expect(options.output).to.equal(process.cwd()); @@ -658,8 +692,8 @@ describe('Unit: CLI', function() { }); }); - describe('logError', function() { - it('returns error status code in production', function() { + describe('logError', function () { + it('returns error status code in production', function () { let cli = new CLI({ ui: new MockUI(), testing: false, @@ -668,7 +702,7 @@ describe('Unit: CLI', function() { expect(cli.logError('foo')).to.equal(1); }); - it('does not throw an error in production', function() { + it('does not throw an error in production', function () { let cli = new CLI({ ui: new MockUI(), testing: false, @@ -679,7 +713,7 @@ describe('Unit: CLI', function() { expect(invokeError).to.not.throw(); }); - it('throws error in testing', function() { + it('throws error in testing', function () { let cli = new CLI({ ui: new MockUI(), testing: true, @@ -691,21 +725,21 @@ describe('Unit: CLI', function() { }); }); - describe('Global command options', function() { - let verboseCommand = function(args) { + describe('Global command options', function () { + let verboseCommand = function (args) { return ember(['fake-command', '--verbose'].concat(args)); }; - describe('--verbose', function() { - describe('option parsing', function() { - afterEach(function() { + describe('--verbose', function () { + describe('option parsing', function () { + afterEach(function () { delete process.env.EMBER_VERBOSE_FAKE_OPTION_1; delete process.env.EMBER_VERBOSE_FAKE_OPTION_2; }); // eslint-disable-next-line no-template-curly-in-string - it('sets process.env.EMBER_VERBOSE_${NAME} for each space delimited option', function() { - return expect(verboseCommand(['fake_option_1', 'fake_option_2'])).to.be.rejected.then(error => { + it('sets process.env.EMBER_VERBOSE_${NAME} for each space delimited option', function () { + return expect(verboseCommand(['fake_option_1', 'fake_option_2'])).to.be.rejected.then((error) => { expect(process.env.EMBER_VERBOSE_FAKE_OPTION_1).to.be.ok; expect(process.env.EMBER_VERBOSE_FAKE_OPTION_2).to.be.ok; expect(error.name).to.equal('SilentError'); @@ -715,9 +749,9 @@ describe('Unit: CLI', function() { }); }); - it('ignores verbose options after --', function() { + it('ignores verbose options after --', function () { return expect(verboseCommand(['fake_option_1', '--fake-option', 'fake_option_2'])).to.be.rejected.then( - error => { + (error) => { expect(process.env.EMBER_VERBOSE_FAKE_OPTION_1).to.be.ok; expect(process.env.EMBER_VERBOSE_FAKE_OPTION_2).to.not.be.ok; expect(error.name).to.equal('SilentError'); @@ -728,8 +762,8 @@ describe('Unit: CLI', function() { ); }); - it('ignores verbose options after -', function() { - return expect(verboseCommand(['fake_option_1', '-f', 'fake_option_2'])).to.be.rejected.then(error => { + it('ignores verbose options after -', function () { + return expect(verboseCommand(['fake_option_1', '-f', 'fake_option_2'])).to.be.rejected.then((error) => { expect(process.env.EMBER_VERBOSE_FAKE_OPTION_1).to.be.ok; expect(process.env.EMBER_VERBOSE_FAKE_OPTION_2).to.not.be.ok; expect(error.name).to.equal('SilentError'); diff --git a/tests/unit/cli/lookup-command-test.js b/tests/unit/cli/lookup-command-test.js index c2d0e1d708..d256ed4c98 100644 --- a/tests/unit/cli/lookup-command-test.js +++ b/tests/unit/cli/lookup-command-test.js @@ -23,7 +23,7 @@ let commands = { function AddonServeCommand() { return this; } -AddonServeCommand.prototype.includedCommands = function() { +AddonServeCommand.prototype.includedCommands = function () { return { Serve: { name: 'serve', @@ -32,7 +32,7 @@ AddonServeCommand.prototype.includedCommands = function() { }; }; -describe('cli/lookup-command.js', function() { +describe('cli/lookup-command.js', function () { let ui; let project = { isEmberCLIProject() { @@ -45,18 +45,18 @@ describe('cli/lookup-command.js', function() { eachAddonCommand: Project.prototype.eachAddonCommand, }; - before(function() { + before(function () { ui = new MockUI(); }); - it('lookupCommand() should find commands by name and aliases.', function() { + it('lookupCommand() should find commands by name and aliases.', function () { // Valid commands expect(lookupCommand(commands, 'serve')).to.exist; expect(lookupCommand(commands, 's')).to.exist; }); - it('lookupCommand() should find commands that addons add by name and aliases.', function() { + it('lookupCommand() should find commands that addons add by name and aliases.', function () { let command, Command; Command = lookupCommand(commands, 'addon-command', [], { @@ -119,7 +119,7 @@ describe('cli/lookup-command.js', function() { expect(command.name).to.equal('class-addon-command'); }); - it('lookupCommand() should write out a warning when overriding a core command', function() { + it('lookupCommand() should write out a warning when overriding a core command', function () { project = { isEmberCLIProject() { return true; @@ -141,7 +141,7 @@ describe('cli/lookup-command.js', function() { ); }); - it('lookupCommand() should write out a warning when overriding a core command and allow it if intentional', function() { + it('lookupCommand() should write out a warning when overriding a core command and allow it if intentional', function () { project = { isEmberCLIProject() { return true; @@ -163,7 +163,7 @@ describe('cli/lookup-command.js', function() { ); }); - it('lookupCommand() should return UnknownCommand object when command name is not present.', function() { + it('lookupCommand() should return UnknownCommand object when command name is not present.', function () { let Command = lookupCommand(commands, 'something-else', [], { project, ui, diff --git a/tests/unit/commands/addon-test.js b/tests/unit/commands/addon-test.js index 4cf4f69aac..28ec08aee2 100644 --- a/tests/unit/commands/addon-test.js +++ b/tests/unit/commands/addon-test.js @@ -5,13 +5,12 @@ const commandOptions = require('../../factories/command-options'); const map = require('ember-cli-lodash-subset').map; const AddonCommand = require('../../../lib/commands/addon'); const Blueprint = require('../../../lib/models/blueprint'); -const { isExperimentEnabled } = require('../../../lib/experiments'); const td = require('testdouble'); -describe('addon command', function() { +describe('addon command', function () { let command; - beforeEach(function() { + beforeEach(function () { let options = commandOptions({ project: { isEmberCLIProject() { @@ -26,62 +25,61 @@ describe('addon command', function() { command = new AddonCommand(options); }); - afterEach(function() { + afterEach(function () { td.reset(); }); - it("doesn't allow to create an addon named `test`", function() { - return expect(command.validateAndRun(['test'])).to.be.rejected.then(error => { + it("doesn't allow to create an addon named `test`", function () { + return expect(command.validateAndRun(['test'])).to.be.rejected.then((error) => { expect(error.message).to.equal('We currently do not support a name of `test`.'); }); }); - it("doesn't allow to create an addon named `ember`", function() { - return expect(command.validateAndRun(['ember'])).to.be.rejected.then(error => { + it("doesn't allow to create an addon named `ember`", function () { + return expect(command.validateAndRun(['ember'])).to.be.rejected.then((error) => { expect(error.message).to.equal('We currently do not support a name of `ember`.'); }); }); - it("doesn't allow to create an addon named `Ember`", function() { - return expect(command.validateAndRun(['Ember'])).to.be.rejected.then(error => { + it("doesn't allow to create an addon named `Ember`", function () { + return expect(command.validateAndRun(['Ember'])).to.be.rejected.then((error) => { expect(error.message).to.equal('We currently do not support a name of `Ember`.'); }); }); - it("doesn't allow to create an addon named `ember-cli`", function() { - return expect(command.validateAndRun(['ember-cli'])).to.be.rejected.then(error => { + it("doesn't allow to create an addon named `ember-cli`", function () { + return expect(command.validateAndRun(['ember-cli'])).to.be.rejected.then((error) => { expect(error.message).to.equal('We currently do not support a name of `ember-cli`.'); }); }); - it("doesn't allow to create an addon named `vendor`", function() { - return expect(command.validateAndRun(['vendor'])).to.be.rejected.then(error => { + it("doesn't allow to create an addon named `vendor`", function () { + return expect(command.validateAndRun(['vendor'])).to.be.rejected.then((error) => { expect(error.message).to.equal('We currently do not support a name of `vendor`.'); }); }); - it("doesn't allow to create an addon with a period in the name", function() { - return expect(command.validateAndRun(['zomg.awesome'])).to.be.rejected.then(error => { + it("doesn't allow to create an addon with a period in the name", function () { + return expect(command.validateAndRun(['zomg.awesome'])).to.be.rejected.then((error) => { expect(error.message).to.equal('We currently do not support a name of `zomg.awesome`.'); }); }); - it("doesn't allow to create an addon with a name beginning with a number", function() { - return expect(command.validateAndRun(['123-my-bagel'])).to.be.rejected.then(error => { + it("doesn't allow to create an addon with a name beginning with a number", function () { + return expect(command.validateAndRun(['123-my-bagel'])).to.be.rejected.then((error) => { expect(error.message).to.equal('We currently do not support a name of `123-my-bagel`.'); }); }); - it("doesn't allow to create an addon when the name is a period", function() { - let blueprintName = isExperimentEnabled('MODULE_UNIFICATION') ? 'module-unification-addon' : 'addon'; - return expect(command.validateAndRun(['.'])).to.be.rejected.then(error => { + it("doesn't allow to create an addon when the name is a period", function () { + return expect(command.validateAndRun(['.'])).to.be.rejected.then((error) => { expect(error.message).to.equal( - `Trying to generate an ${blueprintName} structure in this directory? Use \`ember init\` instead.` + `Trying to generate an addon structure in this directory? Use \`ember init\` instead.` ); }); }); - it('registers blueprint options in beforeRun', function() { + it('registers blueprint options in beforeRun', function () { td.replace(Blueprint, 'lookup', td.function()); td.when(Blueprint.lookup('addon'), { ignoreExtraArgs: true }).thenReturn({ diff --git a/tests/unit/commands/build-test.js b/tests/unit/commands/build-test.js index da53db5832..851c7d8413 100644 --- a/tests/unit/commands/build-test.js +++ b/tests/unit/commands/build-test.js @@ -6,11 +6,11 @@ const Task = require('../../../lib/models/task'); const BuildCommand = require('../../../lib/commands/build'); const td = require('testdouble'); -describe('build command', function() { +describe('build command', function () { let tasks, options, command; let buildTaskInstance, buildWatchTaskInstance; - beforeEach(function() { + beforeEach(function () { tasks = { Build: Task.extend({ init() { @@ -47,24 +47,24 @@ describe('build command', function() { td.when(tasks.BuildWatch.prototype.run(), { ignoreExtraArgs: true, times: 1 }).thenResolve(); }); - afterEach(function() { + afterEach(function () { td.reset(); }); - it('Build task is provided with the project instance', function() { - return command.validateAndRun([]).then(function() { + it('Build task is provided with the project instance', function () { + return command.validateAndRun([]).then(function () { expect(buildTaskInstance.project).to.equal(options.project, 'has correct project instance'); }); }); - it('BuildWatch task is provided with the project instance', function() { - return command.validateAndRun(['--watch']).then(function() { + it('BuildWatch task is provided with the project instance', function () { + return command.validateAndRun(['--watch']).then(function () { expect(buildWatchTaskInstance.project).to.equal(options.project, 'has correct project instance'); }); }); - it('BuildWatch task is provided with a watcher option', function() { - return command.validateAndRun(['--watch', '--watcher poller']).then(function() { + it('BuildWatch task is provided with a watcher option', function () { + return command.validateAndRun(['--watch', '--watcher poller']).then(function () { let buildWatchRun = tasks.BuildWatch.prototype.run; let captor = td.matchers.captor(); @@ -73,24 +73,24 @@ describe('build command', function() { }); }); - it('Asset Size Printer task is not run after Build task in non-production environment', function() { - return new BuildCommand(options).validateAndRun([]).then(function() { + it('Asset Size Printer task is not run after Build task in non-production environment', function () { + return new BuildCommand(options).validateAndRun([]).then(function () { let showSizesRun = tasks.ShowAssetSizes.prototype.run; td.verify(showSizesRun(), { ignoreExtraArgs: true, times: 0 }); }); }); - it('Asset Size Printer task is run after Build task in production environment', function() { - return new BuildCommand(options).validateAndRun(['--environment=production']).then(function() { + it('Asset Size Printer task is run after Build task in production environment', function () { + return new BuildCommand(options).validateAndRun(['--environment=production']).then(function () { let showSizesRun = tasks.ShowAssetSizes.prototype.run; td.verify(showSizesRun(), { ignoreExtraArgs: true, times: 1 }); }); }); - it('Asset Size Printer task is not run if suppress sizes option is provided', function() { - return new BuildCommand(options).validateAndRun(['--suppress-sizes']).then(function() { + it('Asset Size Printer task is not run if suppress sizes option is provided', function () { + return new BuildCommand(options).validateAndRun(['--suppress-sizes']).then(function () { let showSizesRun = tasks.ShowAssetSizes.prototype.run; td.verify(showSizesRun(), { ignoreExtraArgs: true, times: 0 }); diff --git a/tests/unit/commands/destroy-test.js b/tests/unit/commands/destroy-test.js index 617abd6240..ff2e2f05c7 100644 --- a/tests/unit/commands/destroy-test.js +++ b/tests/unit/commands/destroy-test.js @@ -5,17 +5,16 @@ const EOL = require('os').EOL; const MockProject = require('../../helpers/mock-project'); const processHelpString = require('../../helpers/process-help-string'); const commandOptions = require('../../factories/command-options'); -const Promise = require('rsvp').Promise; const Task = require('../../../lib/models/task'); const DestroyCommand = require('../../../lib/commands/destroy'); -describe('destroy command', function() { +describe('destroy command', function () { let options, command, project; - beforeEach(function() { + beforeEach(function () { project = new MockProject(); - project.isEmberCLIProject = function() { + project.isEmberCLIProject = function () { return true; }; @@ -34,16 +33,16 @@ describe('destroy command', function() { command = new DestroyCommand(options); }); - it('runs DestroyFromBlueprint with expected options', function() { - return command.validateAndRun(['controller', 'foo']).then(function(options) { + it('runs DestroyFromBlueprint with expected options', function () { + return command.validateAndRun(['controller', 'foo']).then(function (options) { expect(options.dryRun).to.be.false; expect(options.verbose).to.be.false; expect(options.args).to.deep.equal(['controller', 'foo']); }); }); - it('complains if no entity name is given', function() { - return expect(command.validateAndRun(['controller'])).to.be.rejected.then(error => { + it('complains if no entity name is given', function () { + return expect(command.validateAndRun(['controller'])).to.be.rejected.then((error) => { expect(error.message).to.equal( 'The `ember destroy` command requires an ' + 'entity name to be specified. ' + @@ -52,8 +51,8 @@ describe('destroy command', function() { }); }); - it('complains if no blueprint name is given', function() { - return expect(command.validateAndRun([])).to.be.rejected.then(error => { + it('complains if no blueprint name is given', function () { + return expect(command.validateAndRun([])).to.be.rejected.then((error) => { expect(error.message).to.equal( 'The `ember destroy` command requires a ' + 'blueprint name to be specified. ' + @@ -62,13 +61,13 @@ describe('destroy command', function() { }); }); - it('does not throw errors when beforeRun is invoked without the blueprint name', function() { + it('does not throw errors when beforeRun is invoked without the blueprint name', function () { expect(() => { command.beforeRun([]); }).to.not.throw(); }); - it('rethrows errors from beforeRun', function() { + it('rethrows errors from beforeRun', function () { project.blueprintLookupPaths = undefined; expect(() => { @@ -76,8 +75,8 @@ describe('destroy command', function() { }).to.throw(/(is not a function)|(has no method)/); }); - describe('help', function() { - it('prints extra info', function() { + describe('help', function () { + it('prints extra info', function () { command.printDetailedHelp(); let output = options.ui.output; diff --git a/tests/unit/commands/generate-test.js b/tests/unit/commands/generate-test.js index c8d3dfeaae..6a978151c8 100644 --- a/tests/unit/commands/generate-test.js +++ b/tests/unit/commands/generate-test.js @@ -5,26 +5,27 @@ const EOL = require('os').EOL; const commandOptions = require('../../factories/command-options'); const processHelpString = require('../../helpers/process-help-string'); const MockProject = require('../../helpers/mock-project'); -const Promise = require('rsvp').Promise; const Task = require('../../../lib/models/task'); const Blueprint = require('../../../lib/models/blueprint'); const GenerateCommand = require('../../../lib/commands/generate'); const td = require('testdouble'); -const fs = require('fs-extra'); -const path = require('path'); -const ci = require('ci-info'); +const ROOT = process.cwd(); +const { createTempDir } = require('broccoli-test-helper'); -describe('generate command', function() { - let options, command; +describe('generate command', function () { + let input, options, command; - beforeEach(function() { - let project = new MockProject(); + beforeEach(async function () { + input = await createTempDir(); + process.chdir(input.path()); - project.isEmberCLIProject = function() { + let project = new MockProject({ root: input.path() }); + + project.isEmberCLIProject = function () { return true; }; - project.blueprintLookupPaths = function() { + project.blueprintLookupPaths = function () { return []; }; @@ -44,46 +45,44 @@ describe('generate command', function() { command = new GenerateCommand(options); }); - afterEach(function() { + afterEach(async function () { td.reset(); - }); - describe('without yarn.lock file', function() { - let originalYarnLockPath, dummyYarnLockPath; + process.chdir(ROOT); + await input.dispose(); + }); - beforeEach(function() { - originalYarnLockPath = path.join(command.project.root, 'yarn.lock'); - dummyYarnLockPath = path.join(command.project.root, 'foo.bar'); - fs.renameSync(originalYarnLockPath, dummyYarnLockPath); - }); + it('runs GenerateFromBlueprint but with null nodeModulesPath with npm', function () { + command.project.hasDependencies = function () { + return false; + }; - afterEach(function() { - fs.renameSync(dummyYarnLockPath, originalYarnLockPath); + return expect(command.validateAndRun(['controller', 'foo'])).to.be.rejected.then((reason) => { + expect(reason.message).to.eql( + 'Required packages are missing, run `npm install` from this directory to install them.' + ); }); + }); - (ci.APPVEYOR ? it.skip : it)('runs GenerateFromBlueprint but with null nodeModulesPath with npm', function() { - command.project.hasDependencies = function() { - return false; - }; - - return expect(command.validateAndRun(['controller', 'foo'])).to.be.rejected.then(reason => { - expect(reason.message).to.eql('node_modules appears empty, you may need to run `npm install`'); - }); + it('runs GenerateFromBlueprint but with null nodeModulesPath with yarn', function () { + // force usage of `yarn` by adding yarn.lock file + input.write({ + 'yarn.lock': '', }); - }); - (ci.APPVEYOR ? it.skip : it)('runs GenerateFromBlueprint but with null nodeModulesPath with yarn', function() { - command.project.hasDependencies = function() { + command.project.hasDependencies = function () { return false; }; - return expect(command.validateAndRun(['controller', 'foo'])).to.be.rejected.then(reason => { - expect(reason.message).to.eql('node_modules appears empty, you may need to run `yarn install`'); + return expect(command.validateAndRun(['controller', 'foo'])).to.be.rejected.then((reason) => { + expect(reason.message).to.eql( + 'Required packages are missing, run `yarn install` from this directory to install them.' + ); }); }); - it('runs GenerateFromBlueprint with expected options', function() { - return command.validateAndRun(['controller', 'foo']).then(function(options) { + it('runs GenerateFromBlueprint with expected options', function () { + return command.validateAndRun(['controller', 'foo']).then(function (options) { expect(options.pod).to.be.false; expect(options.dryRun).to.be.false; expect(options.verbose).to.be.false; @@ -91,14 +90,14 @@ describe('generate command', function() { }); }); - it('does not throw errors when beforeRun is invoked without the blueprint name', function() { + it('does not throw errors when beforeRun is invoked without the blueprint name', function () { expect(() => { command.beforeRun([]); }).to.not.throw(); }); - it('complains if no blueprint name is given', function() { - return expect(command.validateAndRun([])).to.be.rejected.then(error => { + it('complains if no blueprint name is given', function () { + return expect(command.validateAndRun([])).to.be.rejected.then((error) => { expect(error.message).to.equal( 'The `ember generate` command requires a ' + 'blueprint name to be specified. ' + @@ -107,12 +106,12 @@ describe('generate command', function() { }); }); - describe('help', function() { - beforeEach(function() { + describe('help', function () { + beforeEach(function () { td.replace(Blueprint, 'list', td.function()); }); - it('lists available blueprints', function() { + it('lists available blueprints', function () { td.when(Blueprint.list(), { ignoreExtraArgs: true }).thenReturn([ { source: 'my-app', @@ -149,7 +148,7 @@ ${EOL}`); expect(output).to.equal(testString); }); - it('lists available blueprints json', function() { + it('lists available blueprints json', function () { td.when(Blueprint.list(), { ignoreExtraArgs: true }).thenReturn([ { source: 'my-app', @@ -196,7 +195,7 @@ ${EOL}`); ]); }); - it('works with single blueprint', function() { + it('works with single blueprint', function () { td.when(Blueprint.list(), { ignoreExtraArgs: true }).thenReturn([ { source: 'my-app', @@ -228,7 +227,7 @@ ${EOL}`); expect(output).to.equal(testString); }); - it('works with single blueprint json', function() { + it('works with single blueprint json', function () { td.when(Blueprint.list(), { ignoreExtraArgs: true }).thenReturn([ { source: 'my-app', @@ -267,7 +266,7 @@ ${EOL}`); ]); }); - it('handles missing blueprint', function() { + it('handles missing blueprint', function () { td.when(Blueprint.list(), { ignoreExtraArgs: true }).thenReturn([ { source: 'my-app', @@ -292,7 +291,7 @@ ${EOL}`); expect(output).to.equal(testString); }); - it('handles missing blueprint json', function() { + it('handles missing blueprint json', function () { td.when(Blueprint.list(), { ignoreExtraArgs: true }).thenReturn([ { source: 'my-app', @@ -318,7 +317,7 @@ ${EOL}`); ]); }); - it('ignores overridden blueprints when verbose false', function() { + it('ignores overridden blueprints when verbose false', function () { td.when(Blueprint.list(), { ignoreExtraArgs: true }).thenReturn([ { source: 'my-app', @@ -346,7 +345,7 @@ ${EOL}`); expect(output).to.equal(testString); }); - it('shows overridden blueprints when verbose true', function() { + it('shows overridden blueprints when verbose true', function () { td.when(Blueprint.list(), { ignoreExtraArgs: true }).thenReturn([ { source: 'my-app', diff --git a/tests/unit/commands/help-test.js b/tests/unit/commands/help-test.js index c315e0459d..b271495d99 100644 --- a/tests/unit/commands/help-test.js +++ b/tests/unit/commands/help-test.js @@ -9,16 +9,16 @@ const td = require('testdouble'); let HelpCommand = require('../../../lib/commands/help'); -describe('help command', function() { +describe('help command', function () { let options; - beforeEach(function() { + beforeEach(function () { options = commandOptions(); }); - describe('common to both', function() { - it('finds command on disk', function() { - let Command1 = function() {}; + describe('common to both', function () { + it('finds command on disk', function () { + let Command1 = function () {}; Command1.prototype.printBasicHelp = td.function(); Command1.prototype.printDetailedHelp = td.function(); @@ -29,7 +29,7 @@ describe('help command', function() { let command = new HelpCommand(options); let wasCalled; - command._lookupCommand = function() { + command._lookupCommand = function () { expect(arguments[0]).to.equal(options.commands); expect(arguments[1]).to.equal('command-2'); wasCalled = true; @@ -42,10 +42,10 @@ describe('help command', function() { expect(wasCalled).to.be.true; }); - it('looks up multiple commands', function() { - let Command1 = function() {}; - let Command2 = function() {}; - let Command3 = function() {}; + it('looks up multiple commands', function () { + let Command1 = function () {}; + let Command2 = function () {}; + let Command3 = function () {}; Command1.prototype.printBasicHelp = td.function(); Command2.prototype.printBasicHelp = td.function(); Command3.prototype.printBasicHelp = td.function(); @@ -72,10 +72,10 @@ describe('help command', function() { }); }); - describe('unique to text printing', function() { - it('lists commands', function() { - let Command1 = function() {}; - let Command2 = function() {}; + describe('unique to text printing', function () { + it('lists commands', function () { + let Command1 = function () {}; + let Command2 = function () {}; Command1.prototype.printBasicHelp = td.function(); Command2.prototype.printBasicHelp = td.function(); Command1.prototype.printDetailedHelp = td.function(); @@ -96,9 +96,9 @@ describe('help command', function() { td.verify(Command2.prototype.printDetailedHelp(), { ignoreExtraArgs: true, times: 0 }); }); - it('works with single command', function() { - let Command1 = function() {}; - let Command2 = function() {}; + it('works with single command', function () { + let Command1 = function () {}; + let Command2 = function () {}; Command1.prototype.printBasicHelp = td.function(); Command2.prototype.printBasicHelp = td.function(); Command1.prototype.printDetailedHelp = td.function(); @@ -119,8 +119,8 @@ describe('help command', function() { td.verify(Command2.prototype.printDetailedHelp(), { ignoreExtraArgs: true, times: 0 }); }); - it('works with single command alias', function() { - let Command1 = function() {}; + it('works with single command alias', function () { + let Command1 = function () {}; Command1.prototype.aliases = ['my-alias']; Command1.prototype.printBasicHelp = td.function(); Command1.prototype.printDetailedHelp = td.function(); @@ -136,8 +136,8 @@ describe('help command', function() { td.verify(Command1.prototype.printBasicHelp(), { ignoreExtraArgs: true, times: 1 }); }); - it('passes extra commands to `generate`', function() { - let Generate = function() {}; + it('passes extra commands to `generate`', function () { + let Generate = function () {}; Generate.prototype.printBasicHelp = td.function(); Generate.prototype.printDetailedHelp = td.function(); @@ -158,8 +158,8 @@ describe('help command', function() { expect(captor.value.rawArgs).to.deep.equal(['something', 'else']); }); - it('handles no extra commands to `generate`', function() { - let Generate = function() {}; + it('handles no extra commands to `generate`', function () { + let Generate = function () {}; Generate.prototype.printBasicHelp = td.function(); Generate.prototype.printDetailedHelp = td.function(); @@ -180,8 +180,8 @@ describe('help command', function() { expect(captor.value.rawArgs).to.be.undefined; }); - it('passes extra commands to `generate` alias', function() { - let Generate = function() {}; + it('passes extra commands to `generate` alias', function () { + let Generate = function () {}; Generate.prototype.aliases = ['g']; Generate.prototype.printBasicHelp = td.function(); Generate.prototype.printDetailedHelp = td.function(); @@ -203,8 +203,8 @@ describe('help command', function() { expect(captor.value.rawArgs).to.deep.equal(['something', 'else']); }); - it('handles missing command', function() { - let Command1 = function() {}; + it('handles missing command', function () { + let Command1 = function () {}; options.commands = { Command1, @@ -224,11 +224,11 @@ ${EOL}\ expect(output).to.include(testString); }); - it('respects skipHelp when listing', function() { - let Command1 = function() { + it('respects skipHelp when listing', function () { + let Command1 = function () { this.skipHelp = true; }; - let Command2 = function() {}; + let Command2 = function () {}; Command1.prototype.printBasicHelp = td.function(); Command2.prototype.printBasicHelp = td.function(); @@ -245,8 +245,8 @@ ${EOL}\ td.verify(Command2.prototype.printBasicHelp(), { ignoreExtraArgs: true, times: 1 }); }); - it('ignores skipHelp when single', function() { - let Command1 = function() { + it('ignores skipHelp when single', function () { + let Command1 = function () { this.skipHelp = true; }; Command1.prototype.printBasicHelp = td.function(); @@ -263,13 +263,13 @@ ${EOL}\ td.verify(Command1.prototype.printBasicHelp(), { ignoreExtraArgs: true, times: 1 }); }); - it('lists addons', function() { - let Command1 = function() {}; - let Command2 = function() {}; + it('lists addons', function () { + let Command1 = function () {}; + let Command2 = function () {}; Command1.prototype.printBasicHelp = td.function(); Command2.prototype.printBasicHelp = td.function(); - options.project.eachAddonCommand = function(callback) { + options.project.eachAddonCommand = function (callback) { callback('my-addon', { Command1, Command2, @@ -291,13 +291,13 @@ Available commands from my-addon:${EOL}`); td.verify(Command2.prototype.printBasicHelp(), { ignoreExtraArgs: true, times: 1 }); }); - it('finds single addon command', function() { - let Command1 = function() {}; - let Command2 = function() {}; + it('finds single addon command', function () { + let Command1 = function () {}; + let Command2 = function () {}; Command1.prototype.printBasicHelp = td.function(); Command1.prototype.printDetailedHelp = td.function(); - options.project.eachAddonCommand = function(callback) { + options.project.eachAddonCommand = function (callback) { callback('my-addon', { Command1, Command2, @@ -312,13 +312,13 @@ Available commands from my-addon:${EOL}`); }); }); - describe('unique to json printing', function() { - beforeEach(function() { + describe('unique to json printing', function () { + beforeEach(function () { options.json = true; }); - it('lists commands', function() { - let Command1 = function() { + it('lists commands', function () { + let Command1 = function () { return { getJson() { return { @@ -328,7 +328,7 @@ Available commands from my-addon:${EOL}`); }; }; - let Command2 = function() { + let Command2 = function () { return { getJson() { return { @@ -356,8 +356,8 @@ Available commands from my-addon:${EOL}`); ]); }); - it('handles special option `Path`', function() { - let Command1 = function() { + it('handles special option `Path`', function () { + let Command1 = function () { return { getJson() { return { @@ -382,14 +382,14 @@ Available commands from my-addon:${EOL}`); ]); }); - it('respects skipHelp when listing', function() { - let Command1 = function() { + it('respects skipHelp when listing', function () { + let Command1 = function () { return { skipHelp: true, }; }; - let Command2 = function() { + let Command2 = function () { return { getJson() { return { @@ -414,8 +414,8 @@ Available commands from my-addon:${EOL}`); ]); }); - it('lists addons', function() { - let Command1 = function() { + it('lists addons', function () { + let Command1 = function () { return { getJson() { return { @@ -425,7 +425,7 @@ Available commands from my-addon:${EOL}`); }; }; - let Command2 = function() { + let Command2 = function () { return { getJson() { return { @@ -435,7 +435,7 @@ Available commands from my-addon:${EOL}`); }; }; - options.project.eachAddonCommand = function(callback) { + options.project.eachAddonCommand = function (callback) { callback('my-addon', { Command1, Command2 }); }; diff --git a/tests/unit/commands/init-test.js b/tests/unit/commands/init-test.js index 412c6d91be..7b5cfeac01 100644 --- a/tests/unit/commands/init-test.js +++ b/tests/unit/commands/init-test.js @@ -7,7 +7,6 @@ const expect = require('../../chai').expect; const map = require('ember-cli-lodash-subset').map; const MockUI = require('console-ui/mock'); const MockAnalytics = require('../../helpers/mock-analytics'); -const Promise = require('rsvp').Promise; const Blueprint = require('../../../lib/models/blueprint'); const Project = require('../../../lib/models/project'); const Task = require('../../../lib/models/task'); @@ -15,10 +14,10 @@ const InitCommand = require('../../../lib/commands/init'); const MockCLI = require('../../helpers/mock-cli'); const td = require('testdouble'); -describe('init command', function() { +describe('init command', function () { let ui, analytics, tasks, command, workingDir; - beforeEach(function() { + beforeEach(function () { ui = new MockUI(); analytics = new MockAnalytics(); tasks = { @@ -33,7 +32,7 @@ describe('init command', function() { fs.mkdirSync(workingDir); }); - afterEach(function() { + afterEach(function () { fs.removeSync(workingDir); td.reset(); }); @@ -51,25 +50,25 @@ describe('init command', function() { command = new InitCommand(options); } - it("doesn't allow to create an application named `test`", function() { + it("doesn't allow to create an application named `test`", function () { buildCommand({ name: 'test' }); - return expect(command.validateAndRun([])).to.be.rejected.then(error => { + return expect(command.validateAndRun([])).to.be.rejected.then((error) => { expect(error.message).to.equal('We currently do not support a name of `test`.'); }); }); - it("doesn't allow to create an application without project name", function() { + it("doesn't allow to create an application without project name", function () { buildCommand({ name: undefined }); - return expect(command.validateAndRun([])).to.be.rejected.then(error => { + return expect(command.validateAndRun([])).to.be.rejected.then((error) => { expect(error.message).to.equal( 'The `ember init` command requires a package.json in current folder with name attribute or a specified name via arguments. For more details, use `ember help`.' ); }); }); - it('Uses the name of the closest project to when calling installBlueprint', function() { + it('Uses the name of the closest project to when calling installBlueprint', function () { tasks.InstallBlueprint = Task.extend({ run(blueprintOpts) { expect(blueprintOpts.rawName).to.equal('some-random-name'); @@ -79,12 +78,12 @@ describe('init command', function() { buildCommand(); - return command.validateAndRun([]).catch(function(reason) { + return command.validateAndRun([]).catch(function (reason) { expect(reason).to.equal('Called run'); }); }); - it('Uses the provided app name over the closest found project', function() { + it('Uses the provided app name over the closest found project', function () { tasks.InstallBlueprint = Task.extend({ run(blueprintOpts) { expect(blueprintOpts.rawName).to.equal('provided-name'); @@ -94,12 +93,12 @@ describe('init command', function() { buildCommand(); - return command.validateAndRun(['--name=provided-name']).catch(function(reason) { + return command.validateAndRun(['--name=provided-name']).catch(function (reason) { expect(reason).to.equal('Called run'); }); }); - it('Uses process.cwd if no package is found when calling installBlueprint', function() { + it('Uses process.cwd if no package is found when calling installBlueprint', function () { // change the working dir so `process.cwd` can't be an invalid path for base directories // named `ember-cli`. @@ -118,15 +117,15 @@ describe('init command', function() { return command .validateAndRun([]) - .catch(function(reason) { + .catch(function (reason) { expect(reason).to.equal('Called run'); }) - .then(function() { + .then(function () { process.chdir(currentWorkingDir); }); }); - it("doesn't use --dry-run or any other command option as the name", function() { + it("doesn't use --dry-run or any other command option as the name", function () { tasks.InstallBlueprint = Task.extend({ run(blueprintOpts) { expect(blueprintOpts.rawName).to.equal('some-random-name'); @@ -136,12 +135,12 @@ describe('init command', function() { buildCommand(); - return command.validateAndRun(['--dry-run']).catch(function(reason) { + return command.validateAndRun(['--dry-run']).catch(function (reason) { expect(reason).to.equal('Called run'); }); }); - it("doesn't use . as the name", function() { + it("doesn't use . as the name", function () { tasks.InstallBlueprint = Task.extend({ run(blueprintOpts) { expect(blueprintOpts.rawName).to.equal('some-random-name'); @@ -151,12 +150,12 @@ describe('init command', function() { buildCommand(); - return command.validateAndRun(['.']).catch(function(reason) { + return command.validateAndRun(['.']).catch(function (reason) { expect(reason).to.equal('Called run'); }); }); - it('Uses the "app" blueprint by default', function() { + it('Uses the "app" blueprint by default', function () { tasks.InstallBlueprint = Task.extend({ run(blueprintOpts) { expect(blueprintOpts.blueprint).to.equal('app'); @@ -166,12 +165,12 @@ describe('init command', function() { buildCommand(); - return command.validateAndRun(['--name=provided-name']).catch(function(reason) { + return command.validateAndRun(['--name=provided-name']).catch(function (reason) { expect(reason).to.equal('Called run'); }); }); - it('Uses arguments to select files to init', function() { + it('Uses arguments to select files to init', function () { tasks.InstallBlueprint = Task.extend({ run(blueprintOpts) { expect(blueprintOpts.blueprint).to.equal('app'); @@ -181,12 +180,12 @@ describe('init command', function() { buildCommand(); - return command.validateAndRun(['package.json', '--name=provided-name']).catch(function(reason) { + return command.validateAndRun(['package.json', '--name=provided-name']).catch(function (reason) { expect(reason).to.equal('Called run'); }); }); - it('Uses the "addon" blueprint for addons', function() { + it('Uses the "addon" blueprint for addons', function () { tasks.InstallBlueprint = Task.extend({ run(blueprintOpts) { expect(blueprintOpts.blueprint).to.equal('addon'); @@ -196,12 +195,12 @@ describe('init command', function() { buildCommand({ keywords: ['ember-addon'], name: 'some-random-name' }); - return command.validateAndRun(['--name=provided-name']).catch(function(reason) { + return command.validateAndRun(['--name=provided-name']).catch(function (reason) { expect(reason).to.equal('Called run'); }); }); - it('Registers blueprint options in beforeRun', function() { + it('Registers blueprint options in beforeRun', function () { td.replace(Blueprint, 'lookup', td.function()); td.when(Blueprint.lookup('app'), { ignoreExtraArgs: true }).thenReturn({ availableOptions: [{ name: 'custom-blueprint-option', type: String }], @@ -213,7 +212,7 @@ describe('init command', function() { expect(map(command.availableOptions, 'name')).to.contain('custom-blueprint-option'); }); - it('Passes command options through to the install blueprint task', function() { + it('Passes command options through to the install blueprint task', function () { tasks.InstallBlueprint = Task.extend({ run(blueprintOpts) { expect(blueprintOpts).to.contain.keys('customOption'); @@ -224,7 +223,7 @@ describe('init command', function() { buildCommand(); - return expect(command.validateAndRun(['--custom-option=customValue'])).to.be.rejected.then(reason => { + return expect(command.validateAndRun(['--custom-option=customValue'])).to.be.rejected.then((reason) => { expect(reason).to.equal('Called run'); }); }); diff --git a/tests/unit/commands/install-test.js b/tests/unit/commands/install-test.js index 3d5fd820f1..9f606575c4 100644 --- a/tests/unit/commands/install-test.js +++ b/tests/unit/commands/install-test.js @@ -4,24 +4,23 @@ const expect = require('../../chai').expect; const MockProject = require('../../helpers/mock-project'); const commandOptions = require('../../factories/command-options'); const Task = require('../../../lib/models/task'); -const Promise = require('rsvp').Promise; const AddonInstall = require('../../../lib/tasks/addon-install'); const InstallCommand = require('../../../lib/commands/install'); const td = require('testdouble'); -describe('install command', function() { +describe('install command', function () { let generateBlueprintInstance, npmInstance; let command, tasks; - beforeEach(function() { + beforeEach(function () { let project = new MockProject(); - project.isEmberCLIProject = function() { + project.isEmberCLIProject = function () { return true; }; - project.initializeAddons = function() {}; - project.reloadAddons = function() { + project.initializeAddons = function () {}; + project.reloadAddons = function () { this.addons = [ { pkg: { @@ -80,12 +79,12 @@ describe('install command', function() { command = new InstallCommand(options); }); - afterEach(function() { + afterEach(function () { td.reset(); }); - it('initializes npm install and generate blueprint task with ui, project and analytics', function() { - return command.validateAndRun(['ember-data']).then(function() { + it('initializes npm install and generate blueprint task with ui, project and analytics', function () { + return command.validateAndRun(['ember-data']).then(function () { expect(npmInstance.ui, 'ui was set').to.be.ok; expect(npmInstance.project, 'project was set').to.be.ok; expect(npmInstance.analytics, 'analytics was set').to.be.ok; @@ -96,9 +95,9 @@ describe('install command', function() { }); }); - describe('with args', function() { - it('runs the npm install task with given name and save-dev true', function() { - return command.validateAndRun(['ember-data']).then(function() { + describe('with args', function () { + it('runs the npm install task with given name and save-dev true', function () { + return command.validateAndRun(['ember-data']).then(function () { let npmRun = tasks.NpmInstall.prototype.run; td.verify( @@ -114,14 +113,14 @@ describe('install command', function() { }); }); - it('runs the npm install task with given name and save-dev true in an addon', function() { - command.project.isEmberCLIProject = function() { + it('runs the npm install task with given name and save-dev true in an addon', function () { + command.project.isEmberCLIProject = function () { return false; }; - command.project.isEmberCLIAddon = function() { + command.project.isEmberCLIAddon = function () { return true; }; - return command.validateAndRun(['ember-data']).then(function() { + return command.validateAndRun(['ember-data']).then(function () { let npmRun = tasks.NpmInstall.prototype.run; td.verify( @@ -137,8 +136,8 @@ describe('install command', function() { }); }); - it('runs the npm install task with given name and save true with the --save option', function() { - return command.validateAndRun(['ember-data', '--save']).then(function() { + it('runs the npm install task with given name and save true with the --save option', function () { + return command.validateAndRun(['ember-data', '--save']).then(function () { let npmRun = tasks.NpmInstall.prototype.run; td.verify( @@ -154,14 +153,14 @@ describe('install command', function() { }); }); - it('runs the npm install task with given name and save true in an addon with the --save option', function() { - command.project.isEmberCLIProject = function() { + it('runs the npm install task with given name and save true in an addon with the --save option', function () { + command.project.isEmberCLIProject = function () { return false; }; - command.project.isEmberCLIAddon = function() { + command.project.isEmberCLIAddon = function () { return true; }; - return command.validateAndRun(['ember-data', '--save']).then(function() { + return command.validateAndRun(['ember-data', '--save']).then(function () { let npmRun = tasks.NpmInstall.prototype.run; td.verify( @@ -177,8 +176,8 @@ describe('install command', function() { }); }); - it('runs the package name blueprint task with given name and args', function() { - return command.validateAndRun(['ember-data']).then(function() { + it('runs the package name blueprint task with given name and args', function () { + return command.validateAndRun(['ember-data']).then(function () { let generateRun = tasks.GenerateFromBlueprint.prototype.run; let captor = td.matchers.captor(); td.verify(generateRun(captor.capture())); @@ -187,8 +186,8 @@ describe('install command', function() { }); }); - it('fails to install second argument for unknown addon', function() { - return expect(command.validateAndRun(['ember-cli-cordova', 'com.ember.test'])).to.be.rejected.then(error => { + it('fails to install second argument for unknown addon', function () { + return expect(command.validateAndRun(['ember-cli-cordova', 'com.ember.test'])).to.be.rejected.then((error) => { let generateRun = tasks.GenerateFromBlueprint.prototype.run; let captor = td.matchers.captor(); td.verify(generateRun(captor.capture())); @@ -204,8 +203,8 @@ describe('install command', function() { }); }); - it('runs npmInstall once and installs three addons', function() { - return command.validateAndRun(['ember-data', 'ember-cli-cordova', 'ember-cli-qunit']).then(function() { + it('runs npmInstall once and installs three addons', function () { + return command.validateAndRun(['ember-data', 'ember-cli-cordova', 'ember-cli-qunit']).then(function () { let npmRun = tasks.NpmInstall.prototype.run; td.verify( @@ -220,15 +219,15 @@ describe('install command', function() { ); let generateRun = tasks.GenerateFromBlueprint.prototype.run; - let generateRunArgs = td.explain(generateRun).calls.map(function(call) { + let generateRunArgs = td.explain(generateRun).calls.map(function (call) { return call.args[0].args[0]; }); expect(generateRunArgs).to.deep.equal(['ember-data', 'cordova-starter-kit', 'ember-cli-qunit']); }); }); - it('ember-cli/ember-cli-qunit: runs npmInstall but does not install the addon blueprint', function() { - return command.validateAndRun(['ember-cli/ember-cli-qunit']).then(function() { + it('ember-cli/ember-cli-qunit: runs npmInstall but does not install the addon blueprint', function () { + return command.validateAndRun(['ember-cli/ember-cli-qunit']).then(function () { let npmRun = tasks.NpmInstall.prototype.run; td.verify( @@ -247,8 +246,8 @@ describe('install command', function() { }); }); - it('ember-cli-qunit@1.2.0: runs npmInstall and installs the addon blueprint', function() { - return command.validateAndRun(['ember-cli-qunit@1.2.0']).then(function() { + it('ember-cli-qunit@1.2.0: runs npmInstall and installs the addon blueprint', function () { + return command.validateAndRun(['ember-cli-qunit@1.2.0']).then(function () { let npmRun = tasks.NpmInstall.prototype.run; td.verify( @@ -263,15 +262,15 @@ describe('install command', function() { ); let generateRun = tasks.GenerateFromBlueprint.prototype.run; - let generateRunArgs = td.explain(generateRun).calls.map(function(call) { + let generateRunArgs = td.explain(generateRun).calls.map(function (call) { return call.args[0].args[0]; }); expect(generateRunArgs).to.deep.equal(['ember-cli-qunit']); }); }); - it('@ember-cli/ember-cli-qunit: runs npmInstall and installs the addon blueprint', function() { - return command.validateAndRun(['@ember-cli/ember-cli-qunit']).then(function() { + it('@ember-cli/ember-cli-qunit: runs npmInstall and installs the addon blueprint', function () { + return command.validateAndRun(['@ember-cli/ember-cli-qunit']).then(function () { let npmRun = tasks.NpmInstall.prototype.run; td.verify( @@ -286,15 +285,15 @@ describe('install command', function() { ); let generateRun = tasks.GenerateFromBlueprint.prototype.run; - let generateRunArgs = td.explain(generateRun).calls.map(function(call) { + let generateRunArgs = td.explain(generateRun).calls.map(function (call) { return call.args[0].args[0]; }); expect(generateRunArgs).to.deep.equal(['@ember-cli/ember-cli-qunit']); }); }); - it("gives helpful message if it can't find the addon", function() { - return expect(command.validateAndRun(['unknown-addon'])).to.be.rejected.then(error => { + it("gives helpful message if it can't find the addon", function () { + return expect(command.validateAndRun(['unknown-addon'])).to.be.rejected.then((error) => { expect(error.message).to.equal( 'Install failed. Could not find addon with name: unknown-addon', 'expected error to have helpful message' @@ -303,9 +302,9 @@ describe('install command', function() { }); }); - describe('without args', function() { - it('gives a helpful message if no arguments are passed', function() { - return expect(command.validateAndRun([])).to.be.rejected.then(error => { + describe('without args', function () { + it('gives a helpful message if no arguments are passed', function () { + return expect(command.validateAndRun([])).to.be.rejected.then((error) => { expect(error.message).to.equal( 'The `install` command must take an argument with the name ' + 'of an ember-cli addon. For installing all npm and bower ' + diff --git a/tests/unit/commands/new-test.js b/tests/unit/commands/new-test.js index 3a60b65ff8..aa703248c0 100644 --- a/tests/unit/commands/new-test.js +++ b/tests/unit/commands/new-test.js @@ -4,17 +4,15 @@ const expect = require('../../chai').expect; const map = require('ember-cli-lodash-subset').map; const commandOptions = require('../../factories/command-options'); const NewCommand = require('../../../lib/commands/new'); -const Promise = require('rsvp').Promise; const Blueprint = require('../../../lib/models/blueprint'); const Command = require('../../../lib/models/command'); const Task = require('../../../lib/models/task'); -const { isExperimentEnabled } = require('../../../lib/experiments'); const td = require('testdouble'); -describe('new command', function() { +describe('new command', function () { let command; - beforeEach(function() { + beforeEach(function () { let options = commandOptions({ project: { isEmberCLIProject() { @@ -31,62 +29,53 @@ describe('new command', function() { td.replace(Blueprint, 'lookup', td.function()); }); - afterEach(function() { + afterEach(function () { td.reset(); }); - it("doesn't allow to create an application named `test`", function() { - return expect(command.validateAndRun(['test'])).to.be.rejected.then(error => { - expect(error.message).to.equal('We currently do not support a name of `test`.'); - }); + it("doesn't allow to create an application named `test`", async function () { + let { message } = await expect(command.validateAndRun(['test'])).to.be.rejected; + expect(message).to.equal('We currently do not support a name of `test`.'); }); - it("doesn't allow to create an application named `ember`", function() { - return expect(command.validateAndRun(['ember'])).to.be.rejected.then(error => { - expect(error.message).to.equal('We currently do not support a name of `ember`.'); - }); + it("doesn't allow to create an application named `ember`", async function () { + let { message } = await expect(command.validateAndRun(['ember'])).to.be.rejected; + expect(message).to.equal('We currently do not support a name of `ember`.'); }); - it("doesn't allow to create an application named `Ember`", function() { - return expect(command.validateAndRun(['Ember'])).to.be.rejected.then(error => { - expect(error.message).to.equal('We currently do not support a name of `Ember`.'); - }); + it("doesn't allow to create an application named `Ember`", async function () { + let { message } = await expect(command.validateAndRun(['Ember'])).to.be.rejected; + expect(message).to.equal('We currently do not support a name of `Ember`.'); }); - it("doesn't allow to create an application named `ember-cli`", function() { - return expect(command.validateAndRun(['ember-cli'])).to.be.rejected.then(error => { - expect(error.message).to.equal('We currently do not support a name of `ember-cli`.'); - }); + it("doesn't allow to create an application named `ember-cli`", async function () { + let { message } = await expect(command.validateAndRun(['ember-cli'])).to.be.rejected; + expect(message).to.equal('We currently do not support a name of `ember-cli`.'); }); - it("doesn't allow to create an application named `vendor`", function() { - return expect(command.validateAndRun(['vendor'])).to.be.rejected.then(error => { - expect(error.message).to.equal('We currently do not support a name of `vendor`.'); - }); + it("doesn't allow to create an application named `vendor`", async function () { + let { message } = await expect(command.validateAndRun(['vendor'])).to.be.rejected; + expect(message).to.equal('We currently do not support a name of `vendor`.'); }); - it("doesn't allow to create an application with a period in the name", function() { - return expect(command.validateAndRun(['zomg.awesome'])).to.be.rejected.then(error => { - expect(error.message).to.equal('We currently do not support a name of `zomg.awesome`.'); - }); + it("doesn't allow to create an application with a period in the name", async function () { + let { message } = await expect(command.validateAndRun(['zomg.awesome'])).to.be.rejected; + expect(message).to.equal('We currently do not support a name of `zomg.awesome`.'); }); - it("doesn't allow to create an application with a name beginning with a number", function() { - return expect(command.validateAndRun(['123-my-bagel'])).to.be.rejected.then(error => { - expect(error.message).to.equal('We currently do not support a name of `123-my-bagel`.'); - }); + it("doesn't allow to create an application with a name beginning with a number", async function () { + let { message } = await expect(command.validateAndRun(['123-my-bagel'])).to.be.rejected; + expect(message).to.equal('We currently do not support a name of `123-my-bagel`.'); }); - it('shows a suggestion messages when the application name is a period', function() { - let blueprintName = isExperimentEnabled('MODULE_UNIFICATION') ? 'module-unification-app' : 'application'; - return expect(command.validateAndRun(['.'])).to.be.rejected.then(error => { - expect(error.message).to.equal( - `Trying to generate an ${blueprintName} structure in this directory? Use \`ember init\` instead.` - ); - }); + it('shows a suggestion messages when the application name is a period', async function () { + let { message } = await expect(command.validateAndRun(['.'])).to.be.rejected; + expect(message).to.equal( + `Trying to generate an application structure in this directory? Use \`ember init\` instead.` + ); }); - it('registers blueprint options in beforeRun', function() { + it('registers blueprint options in beforeRun', function () { td.when(Blueprint.lookup('app'), { ignoreExtraArgs: true }).thenReturn({ availableOptions: [{ name: 'custom-blueprint-option', type: String }], }); @@ -95,7 +84,7 @@ describe('new command', function() { expect(map(command.availableOptions, 'name')).to.contain('custom-blueprint-option'); }); - it('passes command options through to init command', function() { + it('passes command options through to init command', async function () { command.tasks.CreateAndStepIntoDirectory = Task.extend({ run() { return Promise.resolve(); @@ -114,8 +103,7 @@ describe('new command', function() { availableOptions: [{ name: 'custom-blueprint-option', type: String }], }); - return command.validateAndRun(['foo', '--custom-option=customValue']).then(function(reason) { - expect(reason).to.equal('Called run'); - }); + let reason = await command.validateAndRun(['foo', '--custom-option=customValue']); + expect(reason).to.equal('Called run'); }); }); diff --git a/tests/unit/commands/serve-test.js b/tests/unit/commands/serve-test.js index f6ede2b3fe..d45000632f 100644 --- a/tests/unit/commands/serve-test.js +++ b/tests/unit/commands/serve-test.js @@ -4,19 +4,18 @@ const expect = require('../../chai').expect; const EOL = require('os').EOL; const commandOptions = require('../../factories/command-options'); const Task = require('../../../lib/models/task'); -const RSVP = require('rsvp'); +const util = require('util'); const td = require('testdouble'); const PortFinder = require('portfinder'); -const Promise = RSVP.Promise; -const getPort = RSVP.denodeify(PortFinder.getPort); +const getPort = util.promisify(PortFinder.getPort); const ServeCommand = require('../../../lib/commands/serve'); -describe('serve command', function() { +describe('serve command', function () { let tasks, options, command; - beforeEach(function() { + beforeEach(function () { tasks = { Serve: Task.extend(), }; @@ -30,12 +29,12 @@ describe('serve command', function() { command = new ServeCommand(options); }); - afterEach(function() { + afterEach(function () { td.reset(); }); - it('has correct default options', function() { - return command.validateAndRun(['--port', '0']).then(function() { + it('has correct default options', function () { + return command.validateAndRun(['--port', '0']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Serve.prototype.run(captor.capture()), { times: 1 }); expect(captor.value.port).to.be.gte(4200, 'has correct port'); @@ -43,9 +42,9 @@ describe('serve command', function() { }); }); - it('setting --port without --live-reload-port', function() { - return getPort().then(function(port) { - return command.validateAndRun(['--port', `${port}`]).then(function() { + it('setting --port without --live-reload-port', function () { + return getPort().then(function (port) { + return command.validateAndRun(['--port', `${port}`]).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Serve.prototype.run(captor.capture()), { times: 1 }); expect(captor.value.port).to.equal(port, 'has correct port'); @@ -54,9 +53,9 @@ describe('serve command', function() { }); }); - it('setting both --port and --live-reload-port', function() { - return getPort().then(function(port) { - return command.validateAndRun(['--port', `${port}`, '--live-reload-port', '8005']).then(function() { + it('setting both --port and --live-reload-port', function () { + return getPort().then(function (port) { + return command.validateAndRun(['--port', `${port}`, '--live-reload-port', '8005']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Serve.prototype.run(captor.capture()), { times: 1 }); expect(captor.value.port).to.equal(port, 'has correct port'); @@ -72,24 +71,24 @@ describe('serve command', function() { // Works correctly on Travis and has been left for context as it does test // a valid code path. - let testServer = function(opts, test) { - let server = require('http').createServer(function() {}); - return new Promise(function(resolve) { - server.listen(opts.port, opts.host, function() { + let testServer = function (opts, test) { + let server = require('http').createServer(function () {}); + return new Promise(function (resolve) { + server.listen(opts.port, opts.host, function () { resolve(test(opts, server)); }); - }).finally(function() { - return new Promise(function(resolve) { - server.close(function() { + }).finally(function () { + return new Promise(function (resolve) { + server.close(function () { resolve(); }); }); }); }; - it('should throw error when -p PORT is taken', function() { - return testServer({ port: '32773' }, function() { - return expect(command.validateAndRun(['--port', '32773'])).to.be.rejected.then(err => { + it('should throw error when -p PORT is taken', function () { + return testServer({ port: '32773' }, function () { + return expect(command.validateAndRun(['--port', '32773'])).to.be.rejected.then((err) => { td.verify(tasks.Serve.prototype.run(), { ignoreExtraArgs: true, times: 0 }); expect(err.message).to.contain('is already in use.'); }); @@ -97,8 +96,8 @@ describe('serve command', function() { }); } - it('allows OS to choose port', function() { - return command.validateAndRun(['--port', '0']).then(function() { + it('allows OS to choose port', function () { + return command.validateAndRun(['--port', '0']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Serve.prototype.run(captor.capture()), { times: 1 }); expect(captor.value.port).to.be.within(7020, 65535, 'has correct port'); @@ -106,123 +105,144 @@ describe('serve command', function() { }); }); - it('has correct liveLoadPort', function() { - return command.validateAndRun(['--port', '0', '--live-reload-port', '4001']).then(function() { + it('has correct liveLoadPort', function () { + return command.validateAndRun(['--port', '0', '--live-reload-port', '4001']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Serve.prototype.run(captor.capture()), { times: 1 }); expect(captor.value.liveReloadPort).to.be.gte(4001, 'has correct liveReload port'); }); }); - it('has correct liveReloadLoadHost', function() { - return command.validateAndRun(['--port', '0', '--live-reload-host', '127.0.0.1']).then(function() { + it('has correct liveReloadLoadHost', function () { + return command.validateAndRun(['--port', '0', '--live-reload-host', '127.0.0.1']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Serve.prototype.run(captor.capture()), { times: 1 }); expect(captor.value.liveReloadHost).to.equal('127.0.0.1', 'has correct liveReload host'); }); }); - it('has correct liveLoadBaseUrl', function() { - return command.validateAndRun(['--port', '0', '--live-reload-base-url', 'http://127.0.0.1:4200/']).then(function() { - let captor = td.matchers.captor(); - td.verify(tasks.Serve.prototype.run(captor.capture()), { times: 1 }); - expect(captor.value.liveReloadBaseUrl).to.equal('http://127.0.0.1:4200/', 'has correct liveReload baseUrl'); - }); + it('has correct liveLoadBaseUrl', function () { + return command + .validateAndRun(['--port', '0', '--live-reload-base-url', 'http://127.0.0.1:4200/']) + .then(function () { + let captor = td.matchers.captor(); + td.verify(tasks.Serve.prototype.run(captor.capture()), { times: 1 }); + expect(captor.value.liveReloadBaseUrl).to.equal('http://127.0.0.1:4200/', 'has correct liveReload baseUrl'); + }); }); - it('has correct proxy', function() { - return command.validateAndRun(['--port', '0', '--proxy', 'http://localhost:3000/']).then(function() { + it('has correct proxy', function () { + return command.validateAndRun(['--port', '0', '--proxy', 'http://localhost:3000/']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Serve.prototype.run(captor.capture()), { times: 1 }); expect(captor.value.proxy).to.equal('http://localhost:3000/', 'has correct port'); }); }); - it('has correct secure proxy option', function() { - return command.validateAndRun(['--port', '0', '--secure-proxy', 'false']).then(function() { + it('has correct secure proxy option', function () { + return command.validateAndRun(['--port', '0', '--secure-proxy', 'false']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Serve.prototype.run(captor.capture()), { times: 1 }); expect(captor.value.secureProxy).to.equal(false, 'has correct insecure proxy option'); }); }); - it('has correct default value for secure proxy', function() { - return command.validateAndRun(['--port', '0']).then(function() { + it('has correct default value for secure proxy', function () { + return command.validateAndRun(['--port', '0']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Serve.prototype.run(captor.capture()), { times: 1 }); expect(captor.value.secureProxy).to.equal(true, 'has correct secure proxy option when not set'); }); }); - it('requires proxy URL to include protocol', function() { - return expect(command.validateAndRun(['--port', '0', '--proxy', 'localhost:3000'])).to.be.rejected.then(error => { + it('requires proxy URL to include protocol', function () { + return expect(command.validateAndRun(['--port', '0', '--proxy', 'localhost:3000'])).to.be.rejected.then((error) => { expect(error.message).to.equal( `You need to include a protocol with the proxy URL.${EOL}Try --proxy http://localhost:3000` ); }); }); - it('has correct default value for transparent proxy', function() { - return command.validateAndRun(['--port', '0']).then(function() { + it('has correct default value for transparent proxy', function () { + return command.validateAndRun(['--port', '0']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Serve.prototype.run(captor.capture()), { times: 1 }); expect(captor.value.transparentProxy).to.equal(true, 'has correct transparent proxy option when not set'); }); }); - it('host alias does not conflict with help alias', function() { - return command.validateAndRun(['--port', '0', '-H', 'localhost']).then(function() { + it('host alias does not conflict with help alias', function () { + return command.validateAndRun(['--port', '0', '-H', 'localhost']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Serve.prototype.run(captor.capture()), { times: 1 }); expect(captor.value.host).to.equal('localhost', 'has correct hostname'); }); }); - it('has correct value for proxy-in-timeout', function() { - return command.validateAndRun(['--port', '0', '--proxy-in-timeout', '300000']).then(function() { + it('has correct value for proxy-in-timeout', function () { + return command.validateAndRun(['--port', '0', '--proxy-in-timeout', '300000']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Serve.prototype.run(captor.capture()), { times: 1 }); expect(captor.value.proxyInTimeout).to.equal(300000, 'has correct incoming proxy timeout option'); }); }); - it('has correct default value for proxy-in-timeout', function() { - return command.validateAndRun(['--port', '0']).then(function() { + it('has correct default value for proxy-in-timeout', function () { + return command.validateAndRun(['--port', '0']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Serve.prototype.run(captor.capture()), { times: 1 }); expect(captor.value.proxyInTimeout).to.equal(120000, 'has correct incoming proxy timeout option when not set'); }); }); - it('has correct value for proxy-out-timeout', function() { - return command.validateAndRun(['--port', '0', '--proxy-out-timeout', '30000']).then(function() { + it('has correct value for proxy-out-timeout', function () { + return command.validateAndRun(['--port', '0', '--proxy-out-timeout', '30000']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Serve.prototype.run(captor.capture()), { times: 1 }); expect(captor.value.proxyOutTimeout).to.equal(30000, 'has correct outgoing proxy timeout option'); }); }); - it('has correct default value for proxy-out-timeout', function() { - return command.validateAndRun(['--port', '0']).then(function() { + it('has correct default value for proxy-out-timeout', function () { + return command.validateAndRun(['--port', '0']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Serve.prototype.run(captor.capture()), { times: 1 }); expect(captor.value.proxyOutTimeout).to.equal(0, 'has correct outgoing proxy timeout option when not set'); }); }); - it('throws a meaningful error when run outside the project', function() { - command.project.hasDependencies = function() { + it('throws a meaningful error when run outside the project', function () { + command.project.hasDependencies = function () { return false; }; - command.project.isEmberCLIProject = function() { + command.project.isEmberCLIProject = function () { return false; }; command.isWithinProject = false; - return expect(command.validateAndRun(['--port', '0'])).to.be.rejected.then(error => { + return expect(command.validateAndRun(['--port', '0'])).to.be.rejected.then((error) => { expect(error.message).to.match(/You have to be inside an ember-cli project/); }); }); + + it('waits on the serve tasks promise', async function () { + let serveTaskResolved = false; + + tasks.Serve = class extends Task { + run() { + return new Promise((resolve) => { + setTimeout(() => { + serveTaskResolved = true; + resolve(); + }, 10); + }); + } + }; + + await command.validateAndRun(['--port', '0']); + + expect(serveTaskResolved).to.be.ok; + }); }); diff --git a/tests/unit/commands/show-asset-sizes-test.js b/tests/unit/commands/show-asset-sizes-test.js index 47751e63cc..31c32b518d 100644 --- a/tests/unit/commands/show-asset-sizes-test.js +++ b/tests/unit/commands/show-asset-sizes-test.js @@ -6,16 +6,16 @@ const Task = require('../../../lib/models/task'); const path = require('path'); const td = require('testdouble'); -describe('asset-sizes command', function() { +describe('asset-sizes command', function () { let ShowCommand; let tasks; let options; - before(function() { + before(function () { ShowCommand = require('../../../lib/commands/asset-sizes'); }); - beforeEach(function() { + beforeEach(function () { tasks = { ShowAssetSizes: Task.extend({}), }; @@ -28,24 +28,24 @@ describe('asset-sizes command', function() { td.replace(tasks.ShowAssetSizes.prototype, 'run', td.function()); }); - after(function() { + after(function () { ShowCommand = null; }); - afterEach(function() { + afterEach(function () { td.reset(); }); - it('has correct default value for output path', function() { - return new ShowCommand(options).validateAndRun().then(function() { + it('has correct default value for output path', function () { + return new ShowCommand(options).validateAndRun().then(function () { let captor = td.matchers.captor(); td.verify(tasks.ShowAssetSizes.prototype.run(captor.capture()), { times: 1 }); expect(captor.value.outputPath).to.equal('dist/', 'has correct output path option when not set'); }); }); - it('has correct options', function() { - return new ShowCommand(options).validateAndRun(['--output-path', path.join('some', 'path')]).then(function() { + it('has correct options', function () { + return new ShowCommand(options).validateAndRun(['--output-path', path.join('some', 'path')]).then(function () { let captor = td.matchers.captor(); td.verify(tasks.ShowAssetSizes.prototype.run(captor.capture()), { times: 1 }); expect(captor.value.outputPath).to.equal(path.join(process.cwd(), 'some', 'path'), 'has correct asset path'); @@ -53,6 +53,6 @@ describe('asset-sizes command', function() { }); }); -describe.skip('default options config file', function() { - it('reads default options from .ember-cli file', function() {}); +describe.skip('default options config file', function () { + it('reads default options from .ember-cli file', function () {}); }); diff --git a/tests/unit/commands/test-test.js b/tests/unit/commands/test-test.js index 48a2a8b284..ebf2ff9f63 100644 --- a/tests/unit/commands/test-test.js +++ b/tests/unit/commands/test-test.js @@ -5,18 +5,16 @@ const CoreObject = require('core-object'); const expect = require('../../chai').expect; const MockProject = require('../../helpers/mock-project'); const commandOptions = require('../../factories/command-options'); -const Promise = require('rsvp').Promise; const Task = require('../../../lib/models/task'); const TestCommand = require('../../../lib/commands/test'); const td = require('testdouble'); -const ci = require('ci-info'); -describe('test command', function() { +describe('test command', function () { this.timeout(30000); let tasks, options, command; - beforeEach(function() { + beforeEach(function () { tasks = { Build: Task.extend(), Test: Task.extend(), @@ -25,7 +23,7 @@ describe('test command', function() { let project = new MockProject(); - project.isEmberCLIProject = function() { + project.isEmberCLIProject = function () { return true; }; @@ -43,7 +41,7 @@ describe('test command', function() { td.when(tasks.TestServer.prototype.run(), { ignoreExtraArgs: true }).thenReturn(Promise.resolve()); }); - afterEach(function() { + afterEach(function () { td.reset(); }); @@ -51,17 +49,17 @@ describe('test command', function() { command = new TestCommand(options); } - describe('default', function() { - beforeEach(function() { + describe('default', function () { + beforeEach(function () { buildCommand(); }); - it('builds and runs test', function() { + it('builds and runs test', function () { return command.validateAndRun([]); }); - it('has the correct options', function() { - return command.validateAndRun([]).then(function() { + it('has the correct options', function () { + return command.validateAndRun([]).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Build.prototype.run(captor.capture())); @@ -74,8 +72,8 @@ describe('test command', function() { }); }); - it('passes through custom configFile option', function() { - return command.validateAndRun(['--config-file=some-random/path.json']).then(function() { + it('passes through custom configFile option', function () { + return command.validateAndRun(['--config-file=some-random/path.json']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Test.prototype.run(captor.capture())); @@ -83,8 +81,8 @@ describe('test command', function() { }); }); - it('does not pass any port options', function() { - return command.validateAndRun([]).then(function() { + it('does not pass any port options', function () { + return command.validateAndRun([]).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Test.prototype.run(captor.capture())); @@ -92,8 +90,8 @@ describe('test command', function() { }); }); - it('passes through a custom test port option', function() { - return command.validateAndRun(['--test-port=5679']).then(function() { + it('passes through a custom test port option', function () { + return command.validateAndRun(['--test-port=5679']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Test.prototype.run(captor.capture())); @@ -101,8 +99,8 @@ describe('test command', function() { }); }); - it('passes through a custom test port option of 0 to allow OS to choose open system port', function() { - return command.validateAndRun(['--test-port=0']).then(function() { + it('passes through a custom test port option of 0 to allow OS to choose open system port', function () { + return command.validateAndRun(['--test-port=0']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Test.prototype.run(captor.capture())); @@ -110,8 +108,8 @@ describe('test command', function() { }); }); - it('only passes through the port option', function() { - return command.validateAndRun(['--port=5678']).then(function() { + it('only passes through the port option', function () { + return command.validateAndRun(['--port=5678']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Test.prototype.run(captor.capture())); @@ -119,8 +117,8 @@ describe('test command', function() { }); }); - it('passes both the port and the test port options', function() { - return command.validateAndRun(['--port=5678', '--test-port=5900']).then(function() { + it('passes both the port and the test port options', function () { + return command.validateAndRun(['--port=5678', '--test-port=5900']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Test.prototype.run(captor.capture())); @@ -128,8 +126,8 @@ describe('test command', function() { }); }); - it('passes through custom host option', function() { - return command.validateAndRun(['--host=greatwebsite.com']).then(function() { + it('passes through custom host option', function () { + return command.validateAndRun(['--host=greatwebsite.com']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Test.prototype.run(captor.capture())); @@ -137,8 +135,8 @@ describe('test command', function() { }); }); - it('passes through output path option', function() { - return command.validateAndRun(['--output-path=some/path']).then(function() { + it('passes through output path option', function () { + return command.validateAndRun(['--output-path=some/path']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Test.prototype.run(captor.capture())); @@ -146,8 +144,8 @@ describe('test command', function() { }); }); - it('passes through custom reporter option', function() { - return command.validateAndRun(['--reporter=xunit']).then(function() { + it('passes through custom reporter option', function () { + return command.validateAndRun(['--reporter=xunit']).then(function () { let captor = td.matchers.captor(); td.verify(tasks.Test.prototype.run(captor.capture())); @@ -155,27 +153,24 @@ describe('test command', function() { }); }); - (ci.APPVEYOR ? it.skip : it)( - 'has the correct options when called with a build path and does not run a build task', - function() { - return command.validateAndRun(['--path=tests']).then(function() { - let captor = td.matchers.captor(); + it('has the correct options when called with a build path and does not run a build task', function () { + return command.validateAndRun(['--path=tests']).then(function () { + let captor = td.matchers.captor(); - td.verify(tasks.Build.prototype.run(td.matchers.anything()), { times: 0 }); - td.verify(tasks.Test.prototype.run(captor.capture())); + td.verify(tasks.Build.prototype.run(td.matchers.anything()), { times: 0 }); + td.verify(tasks.Test.prototype.run(captor.capture())); - expect(captor.value.outputPath).to.equal(path.resolve('tests'), 'has outputPath'); - expect(captor.value.configFile).to.equal( - undefined, - 'does not include configFile when not specified in options' - ); - expect(captor.value.port).to.equal(7357, 'has port'); - }); - } - ); + expect(captor.value.outputPath).to.equal(path.resolve('tests'), 'has outputPath'); + expect(captor.value.configFile).to.equal( + undefined, + 'does not include configFile when not specified in options' + ); + expect(captor.value.port).to.equal(7357, 'has port'); + }); + }); - it('throws an error if the build path does not exist', function() { - return expect(command.validateAndRun(['--path=bad/path/to/build'])).to.be.rejected.then(error => { + it('throws an error if the build path does not exist', function () { + return expect(command.validateAndRun(['--path=bad/path/to/build'])).to.be.rejected.then((error) => { let expectedPath = path.resolve('bad/path/to/build'); expect(error.message).to.equal( `The path ${expectedPath} does not exist. Please specify a valid build directory to test.` @@ -184,9 +179,9 @@ describe('test command', function() { }); }); - describe('--server option', function() { + describe('--server option', function () { let buildCleanupWasCalled; - beforeEach(function() { + beforeEach(function () { buildCleanupWasCalled = false; options.Builder = CoreObject.extend({ cleanup() { @@ -198,74 +193,74 @@ describe('test command', function() { buildCommand(); }); - it('builds a watcher with verbose set to false', function() { + it('builds a watcher with verbose set to false', function () { return command .validateAndRun(['--server']) - .then(function() { + .then(function () { let captor = td.matchers.captor(); td.verify(tasks.TestServer.prototype.run(captor.capture())); expect(captor.value.watcher.verbose).to.be.false; }) - .finally(function() { + .finally(function () { expect(buildCleanupWasCalled).to.be.true; }); }); - it('builds a watcher with options.watcher set to value provided', function() { + it('builds a watcher with options.watcher set to value provided', function () { return command .validateAndRun(['--server', '--watcher=polling']) - .then(function() { + .then(function () { let captor = td.matchers.captor(); td.verify(tasks.TestServer.prototype.run(captor.capture())); expect(captor.value.watcher.options.watcher).to.equal('polling'); }) - .finally(function() { + .finally(function () { expect(buildCleanupWasCalled).to.be.true; }); }); - it('DOES NOT throw an error if using a build path', function() { + it('DOES NOT throw an error if using a build path', function () { expect(command.validateAndRun(['--server', '--path=tests'])).to.be.ok; }); }); - describe('_generateCustomConfigs', function() { + describe('_generateCustomConfigs', function () { let runOptions; - beforeEach(function() { + beforeEach(function () { buildCommand(); runOptions = {}; }); - it('should return an object even if passed param is empty object', function() { + it('should return an object even if passed param is empty object', function () { let result = command._generateCustomConfigs(runOptions); expect(result).to.be.an('object'); }); - it('when launch option is present, should be reflected in returned config', function() { + it('when launch option is present, should be reflected in returned config', function () { runOptions.launch = 'fooLauncher'; let result = command._generateCustomConfigs(runOptions); expect(result.launch).to.equal('fooLauncher'); }); - it('when query option is present, should be reflected in returned config', function() { + it('when query option is present, should be reflected in returned config', function () { runOptions.query = 'someQuery=test'; let result = command._generateCustomConfigs(runOptions); expect(result.queryString).to.equal(runOptions.query); }); - it('when provided test-page the new file returned contains the value in test_page', function() { + it('when provided test-page the new file returned contains the value in test_page', function () { runOptions['test-page'] = 'foo/test.html?foo'; let result = command._generateCustomConfigs(runOptions); expect(result.testPage).to.be.equal('foo/test.html?foo&'); }); - it('when provided test-page with filter, module, and query the new file returned contains those values in test_page', function() { + it('when provided test-page with filter, module, and query the new file returned contains those values in test_page', function () { runOptions.module = 'fooModule'; runOptions.filter = 'bar'; runOptions.query = 'someQuery=test'; @@ -275,7 +270,7 @@ describe('test command', function() { expect(contents.testPage).to.be.equal('foo/test.html?foo&module=fooModule&filter=bar&someQuery=test'); }); - it('when provided test-page with filter and module the new file returned contains both option values in test_page', function() { + it('when provided test-page with filter and module the new file returned contains both option values in test_page', function () { runOptions.module = 'fooModule'; runOptions.filter = 'bar'; runOptions['test-page'] = 'foo/test.html?foo'; @@ -284,7 +279,7 @@ describe('test command', function() { expect(contents.testPage).to.be.equal('foo/test.html?foo&module=fooModule&filter=bar'); }); - it('when provided test-page with filter and query the new file returned contains both option values in test_page', function() { + it('when provided test-page with filter and query the new file returned contains both option values in test_page', function () { runOptions.query = 'someQuery=test'; runOptions.filter = 'bar'; runOptions['test-page'] = 'foo/test.html?foo'; @@ -293,7 +288,7 @@ describe('test command', function() { expect(contents.testPage).to.be.equal('foo/test.html?foo&filter=bar&someQuery=test'); }); - it('when provided test-page with module and query the new file returned contains both option values in test_page', function() { + it('when provided test-page with module and query the new file returned contains both option values in test_page', function () { runOptions.module = 'fooModule'; runOptions.query = 'someQuery=test'; runOptions['test-page'] = 'foo/test.html?foo'; @@ -302,14 +297,14 @@ describe('test command', function() { expect(contents.testPage).to.be.equal('foo/test.html?foo&module=fooModule&someQuery=test'); }); - it('when provided launch the new file returned contains the value in launch', function() { + it('when provided launch the new file returned contains the value in launch', function () { runOptions.launch = 'fooLauncher'; let contents = command._generateCustomConfigs(runOptions); expect(contents['launch']).to.be.equal('fooLauncher'); }); - it('when provided filter is all lowercase to match the test name', function() { + it('when provided filter is all lowercase to match the test name', function () { runOptions['test-page'] = 'tests/index.html'; runOptions.filter = 'BAR'; let contents = command._generateCustomConfigs(runOptions); @@ -317,10 +312,10 @@ describe('test command', function() { expect(contents.testPage).to.be.equal('tests/index.html?filter=bar'); }); - it('when module and filter option is present uses buildTestPageQueryString for test_page queryString', function() { + it('when module and filter option is present uses buildTestPageQueryString for test_page queryString', function () { runOptions.filter = 'bar'; runOptions['test-page'] = 'tests/index.html'; - command.buildTestPageQueryString = function(options) { + command.buildTestPageQueryString = function (options) { expect(options).to.deep.equal(runOptions); return 'blah=zorz'; @@ -331,7 +326,7 @@ describe('test command', function() { expect(contents.testPage).to.be.equal('tests/index.html?blah=zorz'); }); - it('new file returned contains the filter option value in test_page', function() { + it('new file returned contains the filter option value in test_page', function () { runOptions.filter = 'foo'; runOptions['test-page'] = 'tests/index.html'; let contents = command._generateCustomConfigs(runOptions); @@ -339,7 +334,7 @@ describe('test command', function() { expect(contents.testPage).to.be.equal('tests/index.html?filter=foo'); }); - it('adds with a `&` if query string contains `?` already', function() { + it('adds with a `&` if query string contains `?` already', function () { runOptions.filter = 'foo'; runOptions['test-page'] = 'tests/index.html?hidepassed'; let contents = command._generateCustomConfigs(runOptions); @@ -347,7 +342,7 @@ describe('test command', function() { expect(contents.testPage).to.be.equal('tests/index.html?hidepassed&filter=foo'); }); - it('new file returned contains the module option value in test_page', function() { + it('new file returned contains the module option value in test_page', function () { runOptions.module = 'fooModule'; runOptions['test-page'] = 'tests/index.html'; let contents = command._generateCustomConfigs(runOptions); @@ -355,7 +350,7 @@ describe('test command', function() { expect(contents.testPage).to.be.equal('tests/index.html?module=fooModule'); }); - it('new file returned contains the query option value in test_page', function() { + it('new file returned contains the query option value in test_page', function () { runOptions.query = 'someQuery=test'; runOptions['test-page'] = 'tests/index.html'; let contents = command._generateCustomConfigs(runOptions); @@ -363,7 +358,7 @@ describe('test command', function() { expect(contents.testPage).to.be.equal('tests/index.html?someQuery=test'); }); - it('new file returned contains the query option value with multiple queries in test_page', function() { + it('new file returned contains the query option value with multiple queries in test_page', function () { runOptions.query = 'someQuery=test&something&else=false'; runOptions['test-page'] = 'tests/index.html'; let contents = command._generateCustomConfigs(runOptions); diff --git a/tests/unit/commands/uninstall-npm-test.js b/tests/unit/commands/uninstall-npm-test.js index 0b295f933c..31333c3411 100644 --- a/tests/unit/commands/uninstall-npm-test.js +++ b/tests/unit/commands/uninstall-npm-test.js @@ -5,15 +5,15 @@ const MockProject = require('../../helpers/mock-project'); const commandOptions = require('../../factories/command-options'); const UninstallNpmCommand = require('../../../lib/commands/uninstall-npm'); -describe('uninstall:npm command', function() { +describe('uninstall:npm command', function () { let command; let msg = 'This command has been removed. Please use `npm uninstall ' + ' --save-dev` instead.'; - beforeEach(function() { + beforeEach(function () { let project = new MockProject(); - project.isEmberCLIProject = function() { + project.isEmberCLIProject = function () { return true; }; @@ -24,14 +24,14 @@ describe('uninstall:npm command', function() { command = new UninstallNpmCommand(options); }); - it('throws a friendly silent error with no args', function() { - return expect(command.validateAndRun([])).to.be.rejected.then(error => { + it('throws a friendly silent error with no args', function () { + return expect(command.validateAndRun([])).to.be.rejected.then((error) => { expect(error.message).to.equal(msg, 'expect error to have a helpful message'); }); }); - it('throws a friendly silent error with args', function() { - return expect(command.validateAndRun(['moment', 'lodash'])).to.be.rejected.then(error => { + it('throws a friendly silent error with args', function () { + return expect(command.validateAndRun(['moment', 'lodash'])).to.be.rejected.then((error) => { expect(error.message).to.equal(msg, 'expect error to have a helpful message'); }); }); diff --git a/tests/unit/commands/version-test.js b/tests/unit/commands/version-test.js index 8cccdbd58e..7e768ca20a 100644 --- a/tests/unit/commands/version-test.js +++ b/tests/unit/commands/version-test.js @@ -5,10 +5,10 @@ const EOL = require('os').EOL; const commandOptions = require('../../factories/command-options'); const VersionCommand = require('../../../lib/commands/version'); -describe('version command', function() { +describe('version command', function () { let options, command; - beforeEach(function() { + beforeEach(function () { options = commandOptions({ project: { isEmberCLIProject() { @@ -20,8 +20,8 @@ describe('version command', function() { command = new VersionCommand(options); }); - it('reports node, npm, and os versions', function() { - return command.validateAndRun().then(function() { + it('reports node, npm, and os versions', function () { + return command.validateAndRun().then(function () { let lines = options.ui.output.split(EOL); expect(someLineStartsWith(lines, 'ember-cli:'), 'contains the version of ember-cli').to.be.ok; expect(someLineStartsWith(lines, 'node:'), 'contains the version of node').to.be.ok; @@ -29,8 +29,8 @@ describe('version command', function() { }); }); - it('supports a --verbose flag', function() { - return command.validateAndRun(['--verbose']).then(function() { + it('supports a --verbose flag', function () { + return command.validateAndRun(['--verbose']).then(function () { let lines = options.ui.output.split(EOL); expect(someLineStartsWith(lines, 'node:'), 'contains the version of node').to.be.ok; expect(someLineStartsWith(lines, 'os:'), 'contains the version of os').to.be.ok; @@ -40,7 +40,7 @@ describe('version command', function() { }); function someLineStartsWith(lines, search) { - return lines.some(function(line) { + return lines.some(function (line) { return line.indexOf(search) === 0; }); } diff --git a/tests/unit/docs-lint-test.js b/tests/unit/docs-lint-test.js index 4a600c5e25..e35c48ee83 100644 --- a/tests/unit/docs-lint-test.js +++ b/tests/unit/docs-lint-test.js @@ -1,45 +1,62 @@ 'use strict'; +/* + * Turns out, YUIDoc attached a process.on('unhandledRejection' handler which + * incorrectly reports any unhandledRejection as a YUIDoc error. This makes + * debugging of unhandledRejections in the ember-cli test suite quite painful + * + * To address YUIDocs behavior, we run YUIDoc isolated in it's own process; + */ + +if (!process.env['IS_CHILD']) { + const execa = require('execa'); + + describe('YUIDoc', function () { + it('parses without warnings', async function () { + await execa('node', [`--unhandled-rejections=strict`, __filename], { + env: { + IS_CHILD: true, + }, + }); + }); + }); + return; +} + const Y = require('yuidocjs'); const EOL = require('os').EOL; -describe('YUIDoc', function() { - let options = Y.Project.init({ - quiet: true, - }); - let yuiDoc = new Y.YUIDoc(options); - - it('parses without warnings', function() { - let json = yuiDoc.run(); +let options = Y.Project.init({ + quiet: true, +}); +let yuiDoc = new Y.YUIDoc(options); - let warnings = {}; - json.warnings.forEach(function(warning) { - let tmp = warning.line.split(':'); - let file = tmp[0].trim(); - let line = tmp[1]; +let json = yuiDoc.run(); - if (!warnings[file]) { - warnings[file] = []; - } +let warnings = {}; +json.warnings.forEach(function (warning) { + let tmp = warning.line.split(':'); + let file = tmp[0].trim(); + let line = tmp[1]; - warnings[file].push({ - line, - message: warning.message, - }); - }); + if (!warnings[file]) { + warnings[file] = []; + } - let message = ''; - Object.keys(warnings).forEach(function(file) { - message += `\t${file} – YUIDoc issues found:${EOL}${EOL}`; - warnings[file].forEach(function(warning) { - message += `\t\tline ${warning.line}: ${warning.message}${EOL}`; - }); - }); + warnings[file].push({ + line, + message: warning.message, + }); +}); - if (message.length) { - let error = new Error(message); - delete error.stack; - throw error; - } +let message = ''; +Object.keys(warnings).forEach(function (file) { + message += `\t${file} – YUIDoc issues found:${EOL}${EOL}`; + warnings[file].forEach(function (warning) { + message += `\t\tline ${warning.line}: ${warning.message}${EOL}`; }); }); + +if (message.length) { + throw new Error(message); +} diff --git a/tests/unit/errors/silent-test.js b/tests/unit/errors/silent-test.js index e87e6b5eec..7b83d7e946 100644 --- a/tests/unit/errors/silent-test.js +++ b/tests/unit/errors/silent-test.js @@ -3,8 +3,8 @@ const SilentError = require('silent-error'); const expect = require('chai').expect; -describe('SilentError', function() { - it('return silent-error and print a deprecation', function() { +describe('SilentError', function () { + it('return silent-error and print a deprecation', function () { const SilentErrorLib = require('../../../lib/errors/silent'); expect(SilentErrorLib, 'returns silent-error').to.equal(SilentError); }); diff --git a/tests/unit/experiments-test.js b/tests/unit/experiments-test.js index 086e5751be..676d83fae4 100644 --- a/tests/unit/experiments-test.js +++ b/tests/unit/experiments-test.js @@ -1,7 +1,7 @@ 'use strict'; const expect = require('chai').expect; -const { isExperimentEnabled } = require('../../lib/experiments'); +const { isExperimentEnabled, _deprecatedExperimentsDeprecationsIssued } = require('../../lib/experiments'); function resetProcessEnv(originalProcessEnv) { for (let key in process.env) { @@ -19,51 +19,51 @@ function resetProcessEnv(originalProcessEnv) { } } -describe('experiments', function() { - describe('isExperimentEnabled', function() { - let originalProcessEnv; +const ORIGINAL_CONSOLE = Object.assign({}, console); - beforeEach(function() { +describe('experiments', function () { + describe('isExperimentEnabled', function () { + let originalProcessEnv, warnings; + + beforeEach(function () { originalProcessEnv = Object.assign({}, process.env); // reset all experiment flags for these tests, they will be restored in // afterEach delete process.env.EMBER_CLI_ENABLE_ALL_EXPERIMENTS; - delete process.env.EMBER_CLI_MODULE_UNIFICATION; - delete process.env.EMBER_CLI_PACKAGER; - delete process.env.EMBER_CLI_DELAYED_TRANSPILATION; - delete process.env.EMBER_CLI_SYSTEM_TEMP; + delete process.env.EMBER_CLI_EMBROIDER; + delete process.env.EMBER_CLI_CLASSIC; + + warnings = []; + console.warn = (warning) => warnings.push(warning); }); - afterEach(function() { + afterEach(function () { resetProcessEnv(originalProcessEnv); + Object.assign(console, ORIGINAL_CONSOLE); + _deprecatedExperimentsDeprecationsIssued.length = 0; }); - it('should return true for all experiments when `EMBER_CLI_ENABLE_ALL_EXPERIMENTS` is set', function() { + it('should return true for all experiments when `EMBER_CLI_ENABLE_ALL_EXPERIMENTS` is set', function () { process.env.EMBER_CLI_ENABLE_ALL_EXPERIMENTS = true; - expect(isExperimentEnabled('PACKAGER')).to.be.true; - expect(isExperimentEnabled('SYSTEM_TEMP')).to.be.true; - expect(isExperimentEnabled('DELAYED_TRANSPILATION')).to.be.true; - }); + expect(isExperimentEnabled('EMBROIDER')).to.be.true; - it('should have SYSTEM_TEMP disabled when environment flag is present', function() { - process.env.EMBER_CLI_SYSTEM_TEMP = 'false'; - expect(isExperimentEnabled('SYSTEM_TEMP')).to.be.false; + expect(warnings).to.deep.equal([]); }); - it('setting an already disabled feature to false does not enable it', function() { - process.env.EMBER_CLI_PACKAGER = 'false'; - expect(isExperimentEnabled('PACKAGER')).to.be.false; - }); + it('should return true when an experiment is enabled via environment variable', function () { + process.env.EMBER_CLI_EMBROIDER = 'true'; + process.env.EMBER_CLI_CLASSIC = 'true'; - it('should have MODULE_UNIFICATION disabled by default', function() { - expect(isExperimentEnabled('MODULE_UNIFICATION')).to.be.false; - }); + // classic experiment will disable embroider + expect(isExperimentEnabled('EMBROIDER')).to.be.false; + + delete process.env.EMBER_CLI_CLASSIC; + + expect(isExperimentEnabled('EMBROIDER')).to.be.true; - it('should have MODULE_UNIFICATION enabled when environment variable is set', function() { - process.env.EMBER_CLI_MODULE_UNIFICATION = 'true'; - expect(isExperimentEnabled('MODULE_UNIFICATION')).to.be.true; + expect(warnings).to.deep.equal([]); }); }); }); diff --git a/tests/unit/models/addon-test.js b/tests/unit/models/addon-test.js index 34bd73f023..b77ec380f9 100644 --- a/tests/unit/models/addon-test.js +++ b/tests/unit/models/addon-test.js @@ -4,9 +4,7 @@ const fs = require('fs-extra'); const path = require('path'); const Project = require('../../../lib/models/project'); const Addon = require('../../../lib/models/addon'); -const RSVP = require('rsvp'); const expect = require('chai').expect; -let remove = RSVP.denodeify(fs.remove); const findWhere = require('ember-cli-lodash-subset').find; const MockUI = require('console-ui/mock'); const MockCLI = require('../../helpers/mock-cli'); @@ -21,11 +19,11 @@ let tmproot = path.join(root, 'tmp'); let fixturePath = path.resolve(__dirname, '../../fixtures/addon'); -describe('models/addon.js', function() { +describe('models/addon.js', function () { let addon, project, projectPath; - describe('root property', function() { - it('is required', function() { + describe('root property', function () { + it('is required', function () { expect(() => { let TheAddon = Addon.extend({ root: undefined }); new TheAddon(); @@ -33,12 +31,13 @@ describe('models/addon.js', function() { }); }); - describe('old core object compat', function() { - it('treeGenerator works without .project', function() { + describe('old core object compat', function () { + it('treeGenerator works without .project', function () { let warning; let TheAddon = Addon.extend({ name: 'such name', root: path.resolve(fixturePath, 'simple'), + packageRoot: path.resolve(fixturePath, 'simple'), _warn(message) { warning = `${message}`; }, @@ -51,10 +50,10 @@ describe('models/addon.js', function() { }); }); - describe('treePaths and treeForMethods', function() { + describe('treePaths and treeForMethods', function () { let FirstAddon, SecondAddon; - beforeEach(function() { + beforeEach(function () { projectPath = path.resolve(fixturePath, 'simple'); const packageContents = require(path.join(projectPath, 'package.json')); let cli = new MockCLI(); @@ -64,6 +63,7 @@ describe('models/addon.js', function() { FirstAddon = Addon.extend({ name: 'first', root: projectPath, + packageRoot: projectPath, init() { this._super.apply(this, arguments); @@ -75,6 +75,7 @@ describe('models/addon.js', function() { SecondAddon = Addon.extend({ name: 'first', root: projectPath, + packageRoot: projectPath, init() { this._super.apply(this, arguments); @@ -84,10 +85,10 @@ describe('models/addon.js', function() { }); }); - describe('.jshintAddonTree', function() { + describe('.jshintAddonTree', function () { let addon; - beforeEach(function() { + beforeEach(function () { addon = new FirstAddon(project, project); // TODO: fix config story... @@ -98,11 +99,11 @@ describe('models/addon.js', function() { }, }; - addon.jshintTrees = function() {}; + addon.jshintTrees = function () {}; }); - it('lints the files before preprocessing', function() { - addon.preprocessJs = function() { + it('lints the files before preprocessing', function () { + addon.preprocessJs = function () { throw new Error('should not preprocess files'); }; @@ -113,7 +114,7 @@ describe('models/addon.js', function() { }); }); - it('modifying a treePath does not affect other addons', function() { + it('modifying a treePath does not affect other addons', function () { let first = new FirstAddon(project); let second = new SecondAddon(project); @@ -121,7 +122,7 @@ describe('models/addon.js', function() { expect(second.treePaths.vendor).to.equal('blammo'); }); - it('modifying a treeForMethod does not affect other addons', function() { + it('modifying a treeForMethod does not affect other addons', function () { let first = new FirstAddon(project); let second = new SecondAddon(project); @@ -130,9 +131,9 @@ describe('models/addon.js', function() { }); }); - describe('initialized addon', function() { + describe('initialized addon', function () { this.timeout(40000); - beforeEach(function() { + beforeEach(function () { projectPath = path.resolve(fixturePath, 'simple'); const packageContents = require(path.join(projectPath, 'package.json')); let ui = new MockUI(); @@ -141,57 +142,57 @@ describe('models/addon.js', function() { project.initializeAddons(); }); - describe('generated addon', function() { - beforeEach(function() { + describe('generated addon', function () { + beforeEach(function () { addon = findWhere(project.addons, { name: 'ember-generated-with-export-addon' }); // Clear the caches delete addon._moduleName; }); - it('sets its project', function() { + it('sets its project', function () { expect(addon.project.name).to.equal(project.name); }); - it('sets its parent', function() { + it('sets its parent', function () { expect(addon.parent.name).to.equal(project.name); }); - it('sets the root', function() { + it('sets the root', function () { expect(addon.root).to.not.equal(undefined); }); - it('sets the pkg', function() { + it('sets the pkg', function () { expect(addon.pkg).to.not.equal(undefined); }); - describe('trees for its treePaths', function() { - it('app', function() { + describe('trees for its treePaths', function () { + it('app', function () { let tree = addon.treeFor('app'); expect(typeof (tree.read || tree.rebuild)).to.equal('function'); }); - it('styles', function() { + it('styles', function () { let tree = addon.treeFor('styles'); expect(typeof (tree.read || tree.rebuild)).to.equal('function'); }); - it('templates', function() { + it('templates', function () { let tree = addon.treeFor('templates'); expect(typeof (tree.read || tree.rebuild)).to.equal('function'); }); - it('addon-templates', function() { + it('addon-templates', function () { let tree = addon.treeFor('addon-templates'); expect(typeof (tree.read || tree.rebuild)).to.equal('function'); }); - it('vendor', function() { + it('vendor', function () { let tree = addon.treeFor('vendor'); expect(typeof (tree.read || tree.rebuild)).to.equal('function'); }); - it('addon', function() { + it('addon', function () { let app = { importWhitelist: {}, options: {}, @@ -218,38 +219,38 @@ describe('models/addon.js', function() { }); }); - describe('custom treeFor methods', function() { - it('can define treeForApp', function() { + describe('custom treeFor methods', function () { + it('can define treeForApp', function () { addon.treeForApp = td.function(); addon.treeFor('app'); td.verify(addon.treeForApp(), { ignoreExtraArgs: true }); }); - it('can define treeForStyles', function() { + it('can define treeForStyles', function () { addon.treeForStyles = td.function(); addon.treeFor('styles'); td.verify(addon.treeForStyles(), { ignoreExtraArgs: true }); }); - it('can define treeForVendor', function() { + it('can define treeForVendor', function () { addon.treeForVendor = td.function(); addon.treeFor('vendor'); td.verify(addon.treeForVendor(), { ignoreExtraArgs: true }); }); - it('can define treeForTemplates', function() { + it('can define treeForTemplates', function () { addon.treeForTemplates = td.function(); addon.treeFor('templates'); td.verify(addon.treeForTemplates(), { ignoreExtraArgs: true }); }); - it('can define treeForAddonTemplates', function() { + it('can define treeForAddonTemplates', function () { addon.treeForAddonTemplates = td.function(); addon.treeFor('addon-templates'); td.verify(addon.treeForAddonTemplates(), { ignoreExtraArgs: true }); }); - it('can define treeForPublic', function() { + it('can define treeForPublic', function () { addon.treeForPublic = td.function(); addon.treeFor('public'); td.verify(addon.treeForPublic(), { ignoreExtraArgs: true }); @@ -257,12 +258,12 @@ describe('models/addon.js', function() { }); }); - describe('addon with dependencies', function() { - beforeEach(function() { + describe('addon with dependencies', function () { + beforeEach(function () { addon = findWhere(project.addons, { name: 'ember-addon-with-dependencies' }); }); - it("returns a listing of all dependencies in the addon's package.json", function() { + it("returns a listing of all dependencies in the addon's package.json", function () { let expected = { 'ember-cli': 'latest', 'something-else': 'latest', @@ -272,21 +273,22 @@ describe('models/addon.js', function() { }); }); - it('must define a `name` property', function() { - let Foo = Addon.extend({ root: 'foo' }); + it('must define a `name` property', function () { + let Foo = Addon.extend({ root: 'foo', packageRoot: 'foo' }); expect(() => { new Foo(project); }).to.throw(/An addon must define a `name` property./); }); - describe('isDevelopingAddon', function() { + describe('isDevelopingAddon', function () { let originalEnvValue, addon, project; - beforeEach(function() { + beforeEach(function () { let MyAddon = Addon.extend({ name: 'test-project', root: 'foo', + packageRoot: 'foo', }); let projectPath = path.resolve(fixturePath, 'simple'); @@ -300,7 +302,7 @@ describe('models/addon.js', function() { originalEnvValue = process.env.EMBER_ADDON_ENV; }); - afterEach(function() { + afterEach(function () { if (originalEnvValue === undefined) { delete process.env.EMBER_ADDON_ENV; } else { @@ -310,13 +312,13 @@ describe('models/addon.js', function() { delete process.env.EMBER_CLI_IGNORE_ADDON_NAME_MISMATCH; }); - it('returns true when `EMBER_ADDON_ENV` is set to development', function() { + it('returns true when `EMBER_ADDON_ENV` is set to development', function () { process.env.EMBER_ADDON_ENV = 'development'; expect(addon.isDevelopingAddon(), 'addon is being developed').to.eql(true); }); - it('throws when the addon name is prefixed in package.json and not in index.js', function() { + it('throws when the addon name is prefixed in package.json and not in index.js', function () { process.env.EMBER_ADDON_ENV = 'development'; project.root = 'foo'; project.name = () => '@foo/my-addon'; @@ -324,7 +326,7 @@ describe('models/addon.js', function() { expect(() => addon.isDevelopingAddon()).to.throw(/Your names in package.json and index.js should match*/); }); - it('does not throw for a mismatched addon name when process.env.EMBER_CLI_IGNORE_ADDON_NAME_MISMATCH is set', function() { + it('does not throw for a mismatched addon name when process.env.EMBER_CLI_IGNORE_ADDON_NAME_MISMATCH is set', function () { process.env.EMBER_CLI_IGNORE_ADDON_NAME_MISMATCH = 'true'; process.env.EMBER_ADDON_ENV = 'development'; project.root = 'foo'; @@ -333,7 +335,7 @@ describe('models/addon.js', function() { expect(addon.isDevelopingAddon()).to.eql(true); }); - it('throws an error if addon name is different in package.json and index.js ', function() { + it('throws an error if addon name is different in package.json and index.js ', function () { process.env.EMBER_ADDON_ENV = 'development'; project.root = 'foo'; project.name = () => 'foo-my-addon'; @@ -341,19 +343,19 @@ describe('models/addon.js', function() { expect(() => addon.isDevelopingAddon()).to.throw(/Your names in package.json and index.js should match*/); }); - it('returns false when `EMBER_ADDON_ENV` is not set', function() { + it('returns false when `EMBER_ADDON_ENV` is not set', function () { delete process.env.EMBER_ADDON_ENV; expect(addon.isDevelopingAddon()).to.eql(false); }); - it('returns false when `EMBER_ADDON_ENV` is something other than `development`', function() { + it('returns false when `EMBER_ADDON_ENV` is something other than `development`', function () { process.env.EMBER_ADDON_ENV = 'production'; expect(addon.isDevelopingAddon()).to.equal(false); }); - it('returns false when the addon is not the one being developed', function() { + it('returns false when the addon is not the one being developed', function () { process.env.EMBER_ADDON_ENV = 'development'; addon.name = 'my-addon'; @@ -361,20 +363,21 @@ describe('models/addon.js', function() { }); }); - describe('findOwnAddonByName', function() { + describe('findOwnAddonByName', function () { let ThisAddon = Addon.extend({ root: 'foo', + packageRoot: 'foo', name: 'this-addon', }); - it('it has the given addon', function() { + it('it has the given addon', function () { let addon = new ThisAddon(); let ownAddon = { name: 'my-cool-addon' }; addon.addons = [ownAddon]; expect(addon.findOwnAddonByName('my-cool-addon')).to.eql(ownAddon); }); - it('it does not have the given addon', function() { + it('it does not have the given addon', function () { let addon = new ThisAddon(); let ownAddon = { name: 'my-cool-addon' }; addon.addons = [ownAddon]; @@ -382,7 +385,7 @@ describe('models/addon.js', function() { }); }); - describe('hintingEnabled', function() { + describe('hintingEnabled', function () { /** Tests the various configuration options that affect the hintingEnabled method. @@ -398,10 +401,11 @@ describe('models/addon.js', function() { let originalEnvValue, originalEmberEnvValue, originalTestCommand, addon, project; - beforeEach(function() { + beforeEach(function () { let MyAddon = Addon.extend({ name: 'test-project', root: 'foo', + packageRoot: 'foo', }); let projectPath = path.resolve(fixturePath, 'simple'); @@ -417,7 +421,7 @@ describe('models/addon.js', function() { originalTestCommand = process.env.EMBER_CLI_TEST_COMMAND; }); - afterEach(function() { + afterEach(function () { addon.app = { options: {}, }; @@ -441,7 +445,7 @@ describe('models/addon.js', function() { } }); - it('returns true when `EMBER_ENV` is not set to production and options.hinting is true', function() { + it('returns true when `EMBER_ENV` is not set to production and options.hinting is true', function () { process.env.EMBER_ENV = 'development'; addon.app = { @@ -451,7 +455,7 @@ describe('models/addon.js', function() { expect(addon.hintingEnabled()).to.be.true; }); - it('returns true when `EMBER_CLI_TEST_COMMAND` is set and options.hinting is true', function() { + it('returns true when `EMBER_CLI_TEST_COMMAND` is set and options.hinting is true', function () { addon.app = { options: { hinting: true }, }; @@ -459,7 +463,7 @@ describe('models/addon.js', function() { expect(addon.hintingEnabled()).to.be.true; }); - it('returns false when `EMBER_ENV` is set to production, `EMBER_CLI_TEST_COMMAND` is unset and options.hinting is true', function() { + it('returns false when `EMBER_ENV` is set to production, `EMBER_CLI_TEST_COMMAND` is unset and options.hinting is true', function () { process.env.EMBER_ENV = 'production'; delete process.env.EMBER_CLI_TEST_COMMAND; @@ -470,7 +474,7 @@ describe('models/addon.js', function() { expect(addon.hintingEnabled()).to.be.false; }); - it('returns false when options.hinting is set to false', function() { + it('returns false when options.hinting is set to false', function () { addon.app = { options: { hinting: false }, }; @@ -478,14 +482,14 @@ describe('models/addon.js', function() { expect(addon.hintingEnabled()).to.be.false; }); - it('returns true when options.hinting is not set', function() { + it('returns true when options.hinting is not set', function () { expect(addon.hintingEnabled()).to.be.ok; }); }); - describe('treeGenerator', function() { - it('watch tree when developing the addon itself', function() { - addon.isDevelopingAddon = function() { + describe('treeGenerator', function () { + it('watch tree when developing the addon itself', function () { + addon.isDevelopingAddon = function () { return true; }; @@ -494,8 +498,8 @@ describe('models/addon.js', function() { expect(tree.__broccoliGetInfo__()).to.have.property('watched', true); }); - it('uses UnwatchedDir when not developing the addon itself', function() { - addon.isDevelopingAddon = function() { + it('uses UnwatchedDir when not developing the addon itself', function () { + addon.isDevelopingAddon = function () { return false; }; @@ -505,25 +509,25 @@ describe('models/addon.js', function() { }); }); - describe('blueprintsPath', function() { + describe('blueprintsPath', function () { let tmpdir; - beforeEach(async function() { + beforeEach(async function () { tmpdir = await mkTmpDirIn(tmproot); addon.root = tmpdir; }); - afterEach(function() { - return remove(tmproot); + afterEach(function () { + return fs.remove(tmproot); }); - it('returns undefined if the `blueprint` folder does not exist', function() { + it('returns undefined if the `blueprint` folder does not exist', function () { let returnedPath = addon.blueprintsPath(); expect(returnedPath).to.equal(undefined); }); - it('returns blueprint path if the folder exists', function() { + it('returns blueprint path if the folder exists', function () { let blueprintsDir = path.join(tmpdir, 'blueprints'); fs.mkdirSync(blueprintsDir); @@ -533,15 +537,15 @@ describe('models/addon.js', function() { }); }); - describe('config', function() { - it('returns undefined if `config/environment.js` does not exist', function() { + describe('config', function () { + it('returns undefined if `config/environment.js` does not exist', function () { addon.root = path.join(fixturePath, 'no-config'); let result = addon.config(); expect(result).to.equal(undefined); }); - it('returns blueprint path if the folder exists', function() { + it('returns blueprint path if the folder exists', function () { addon.root = path.join(fixturePath, 'with-config'); let appConfig = {}; @@ -552,8 +556,8 @@ describe('models/addon.js', function() { }); }); - describe('compileTemplates', function() { - beforeEach(function() { + describe('compileTemplates', function () { + beforeEach(function () { projectPath = path.resolve(fixturePath, 'simple'); const packageContents = require(path.join(projectPath, 'package.json')); let cli = new MockCLI(); @@ -565,7 +569,7 @@ describe('models/addon.js', function() { addon = findWhere(project.addons, { name: 'ember-generated-with-export-addon' }); }); - it('should not throw an error if addon/templates is present but empty', function() { + it('should not throw an error if addon/templates is present but empty', function () { addon.root = path.join(fixturePath, 'with-empty-addon-templates'); expect(() => { @@ -574,8 +578,8 @@ describe('models/addon.js', function() { }); }); - describe('_fileSystemInfo', function() { - beforeEach(function() { + describe('_fileSystemInfo', function () { + beforeEach(function () { projectPath = path.resolve(fixturePath, 'simple'); const packageContents = require(path.join(projectPath, 'package.json')); let cli = new MockCLI(); @@ -587,9 +591,9 @@ describe('models/addon.js', function() { addon = findWhere(project.addons, { name: 'ember-generated-with-export-addon' }); }); - it('should not call _getAddonTemplatesTreeFiles when default treePath is used', function() { + it('should not call _getAddonTemplatesTreeFiles when default treePath is used', function () { let wasCalled = false; - addon._getAddonTemplatesTreeFiles = function() { + addon._getAddonTemplatesTreeFiles = function () { wasCalled = true; return []; }; @@ -599,10 +603,10 @@ describe('models/addon.js', function() { expect(wasCalled).to.not.be.ok; }); - it("should call _getAddonTemplatesTreeFiles when custom treePaths['addon-templates'] is used", function() { + it("should call _getAddonTemplatesTreeFiles when custom treePaths['addon-templates'] is used", function () { addon.treePaths['addon-templates'] = 'foo'; let wasCalled = false; - addon._getAddonTemplatesTreeFiles = function() { + addon._getAddonTemplatesTreeFiles = function () { wasCalled = true; return []; }; @@ -612,8 +616,8 @@ describe('models/addon.js', function() { expect(wasCalled).to.be.ok; }); - it('hasPodTemplates when pod templates found', function() { - addon._getAddonTreeFiles = function() { + it('hasPodTemplates when pod templates found', function () { + addon._getAddonTreeFiles = function () { return ['foo-bar/', 'foo-bar/component.js', 'foo-bar/template.hbs']; }; @@ -624,8 +628,8 @@ describe('models/addon.js', function() { }); }); - it('does not hasPodTemplates when no pod templates found', function() { - addon._getAddonTreeFiles = function() { + it('does not hasPodTemplates when no pod templates found', function () { + addon._getAddonTreeFiles = function () { return ['templates/', 'templates/components/', 'templates/components/foo-bar.hbs']; }; @@ -636,8 +640,8 @@ describe('models/addon.js', function() { }); }); - it('does not hasPodTemplates when no pod templates found (pod-like structure in `addon/templates/`)', function() { - addon._getAddonTreeFiles = function() { + it('does not hasPodTemplates when no pod templates found (pod-like structure in `addon/templates/`)', function () { + addon._getAddonTreeFiles = function () { return [ 'templates/', // this doesn't need "pod template handling" because @@ -653,8 +657,8 @@ describe('models/addon.js', function() { }); }); - it('does not hasTemplates when no templates found', function() { - addon._getAddonTreeFiles = function() { + it('does not hasTemplates when no templates found', function () { + addon._getAddonTreeFiles = function () { return ['components/', 'components/foo-bar.js', 'templates/', 'templates/components/']; }; @@ -665,8 +669,8 @@ describe('models/addon.js', function() { }); }); - it('does not hasJSFiles when none found', function() { - addon._getAddonTreeFiles = function() { + it('does not hasJSFiles when none found', function () { + addon._getAddonTreeFiles = function () { return ['components/', 'templates/', 'templates/components/', 'styles/foo.css']; }; @@ -678,10 +682,10 @@ describe('models/addon.js', function() { }); }); - describe('packageInfoCache', function() { + describe('packageInfoCache', function () { let packageInfoCache, addon, ui; - beforeEach(function() { + beforeEach(function () { projectPath = path.resolve(fixturePath, 'simple'); const packageContents = require(path.join(projectPath, 'package.json')); @@ -692,21 +696,22 @@ describe('models/addon.js', function() { let AddonTemp = Addon.extend({ name: 'temp', root: 'foo', + packageRoot: 'foo', }); addon = new AddonTemp(project, project); packageInfoCache = addon.packageInfoCache; }); - it("is provided with the parent's `packageInfoCache` object", function() { + it("is provided with the parent's `packageInfoCache` object", function () { expect(packageInfoCache).to.equal(project.packageInfoCache); }); }); - describe('treeForStyles', function() { + describe('treeForStyles', function () { let builder, addon; - beforeEach(function() { + beforeEach(function () { projectPath = path.resolve(fixturePath, 'with-app-styles'); const packageContents = require(path.join(projectPath, 'package.json')); let cli = new MockCLI(); @@ -716,18 +721,19 @@ describe('models/addon.js', function() { let BaseAddon = Addon.extend({ name: 'test-project', root: projectPath, + packageRoot: projectPath, }); addon = new BaseAddon(project, project); }); - afterEach(function() { + afterEach(function () { if (builder) { return builder.cleanup(); } }); - it('should move files in the root of the addons app/styles tree into the app/styles path', async function() { + it('should move files in the root of the addons app/styles tree into the app/styles path', async function () { builder = new broccoli.Builder(addon.treeFor('styles')); let results = await builder.build(); @@ -739,11 +745,12 @@ describe('models/addon.js', function() { }); }); - describe('._eachProjectAddonInvoke', function() { - beforeEach(function() { + describe('._eachProjectAddonInvoke', function () { + beforeEach(function () { let MyAddon = Addon.extend({ name: 'test-project', root: 'foo', + packageRoot: 'foo', }); let projectPath = path.resolve(fixturePath, 'simple'); @@ -754,7 +761,7 @@ describe('models/addon.js', function() { addon = new MyAddon(project, project); }); - it('should invoke the method on each of the project addons', function() { + it('should invoke the method on each of the project addons', function () { let counter = 0; project.addons = [ { @@ -773,7 +780,7 @@ describe('models/addon.js', function() { expect(counter).to.eql(2); }); - it('should provide default arguments if none are specified', function() { + it('should provide default arguments if none are specified', function () { let counter = 0; project.addons = [ { @@ -793,7 +800,7 @@ describe('models/addon.js', function() { }); }); - describe('addon tree caching', function() { + describe('addon tree caching', function () { let projectPath = path.resolve(fixturePath, 'simple'); const packageContents = require(path.join(projectPath, 'package.json')); @@ -803,12 +810,13 @@ describe('models/addon.js', function() { return new Addon(project, project); } - describe('cacheKeyForTree', function() { - it('returns null if `treeForApp` methods are implemented for the app tree', function() { + describe('cacheKeyForTree', function () { + it('returns null if `treeForApp` methods are implemented for the app tree', function () { let addon = createAddon( Addon.extend({ name: 'test-project', root: 'foo', + packageRoot: 'foo', treeForApp() {}, }) ); @@ -816,11 +824,12 @@ describe('models/addon.js', function() { expect(addon.cacheKeyForTree('app')).to.equal(null); }); - it('returns null if `compileAddon` methods are implemented for the addon tree', function() { + it('returns null if `compileAddon` methods are implemented for the addon tree', function () { let addon = createAddon( Addon.extend({ name: 'test-project', root: 'foo', + packageRoot: 'foo', compileAddon() {}, }) ); @@ -828,11 +837,12 @@ describe('models/addon.js', function() { expect(addon.cacheKeyForTree('addon')).to.equal(null); }); - it('returns null if `treeForMethods` is modified', function() { + it('returns null if `treeForMethods` is modified', function () { let addon = createAddon( Addon.extend({ name: 'test-project', root: 'foo', + packageRoot: 'foo', init() { this._super && this._super.init.apply(this, arguments); @@ -844,11 +854,12 @@ describe('models/addon.js', function() { expect(addon.cacheKeyForTree('app')).to.equal(null); }); - it('returns stable value for repeated invocations', function() { + it('returns stable value for repeated invocations', function () { let addon = createAddon( Addon.extend({ name: 'test-project', root: 'foo', + packageRoot: 'foo', }) ); @@ -859,12 +870,13 @@ describe('models/addon.js', function() { }); }); - describe('treeFor caching', function() { - it('defining custom treeForAddon without modifying cacheKeyForTree does not cache', function() { + describe('treeFor caching', function () { + it('defining custom treeForAddon without modifying cacheKeyForTree does not cache', function () { let addon = createAddon( Addon.extend({ name: 'test-project', root: path.join(projectPath, 'node_modules', 'ember-generated-with-export-addon'), + packageRoot: path.join(projectPath, 'node_modules', 'ember-generated-with-export-addon'), treeForAddon(tree) { return tree; }, @@ -877,15 +889,16 @@ describe('models/addon.js', function() { expect(firstTree).not.to.equal(secondTree); }); - it('defining custom cacheKeyForTree allows addon control of cache', function() { + it('defining custom cacheKeyForTree allows addon control of cache', function () { let addonProto = { name: 'test-project', root: path.join(projectPath, 'node_modules', 'ember-generated-with-export-addon'), + packageRoot: path.join(projectPath, 'node_modules', 'ember-generated-with-export-addon'), treeForAddon(tree) { return tree; }, }; - addonProto.cacheKeyForTree = function(type) { + addonProto.cacheKeyForTree = function (type) { return type; }; diff --git a/tests/unit/models/asset-size-printer-test.js b/tests/unit/models/asset-size-printer-test.js index c447a6a421..c5250a9b66 100644 --- a/tests/unit/models/asset-size-printer-test.js +++ b/tests/unit/models/asset-size-printer-test.js @@ -3,22 +3,18 @@ const expect = require('chai').expect; const MockUi = require('console-ui/mock'); const AssetSizePrinter = require('../../../lib/models/asset-size-printer'); -const RSVP = require('rsvp'); const path = require('path'); const fs = require('fs-extra'); let root = process.cwd(); const mkTmpDirIn = require('../../../lib/utilities/mk-tmp-dir-in'); let tmpRoot = path.join(root, 'tmp'); -const Promise = RSVP.Promise; -const remove = RSVP.denodeify(fs.remove); - const chai = require('chai'); const chaiAsPromised = require('chai-as-promised'); chai.use(chaiAsPromised); -describe('models/asset-size-printer', function() { +describe('models/asset-size-printer', function () { let storedTmpDir, assetDir, assetChildDir; function writeFiles() { @@ -40,116 +36,113 @@ describe('models/asset-size-printer', function() { }); } - beforeEach(function() { - return mkTmpDirIn(tmpRoot).then(function(tmpdir) { - storedTmpDir = tmpdir; - assetDir = path.join(storedTmpDir, 'assets'); - assetChildDir = path.join(assetDir, 'childDir'); + beforeEach(async function () { + storedTmpDir = await mkTmpDirIn(tmpRoot); + assetDir = path.join(storedTmpDir, 'assets'); + assetChildDir = path.join(assetDir, 'childDir'); - fs.mkdirsSync(assetDir); - fs.mkdirsSync(assetChildDir); + fs.mkdirsSync(assetDir); + fs.mkdirsSync(assetChildDir); - writeFiles(); - }); + writeFiles(); }); - afterEach(function() { - return Promise.all([remove(storedTmpDir)]); + afterEach(function () { + return fs.remove(storedTmpDir); }); - it('prints human-readable file sizes (including gzipped sizes) of css and js files in the output path', function() { + it('prints human-readable file sizes (including gzipped sizes) of css and js files in the output path', async function () { let sizePrinter = new AssetSizePrinter({ ui: new MockUi(), outputPath: storedTmpDir, }); - return sizePrinter.print().then(function() { - expect(sizePrinter.ui.output).to.include('File sizes:'); - expect(sizePrinter.ui.output).to.include('some-project.css: '); - expect(sizePrinter.ui.output).to.include('some-project.js: '); - expect(sizePrinter.ui.output).to.include('24 B'); - expect(sizePrinter.ui.output).to.include('32 B'); - expect(sizePrinter.ui.output).to.include('(44 B gzipped)'); - expect(sizePrinter.ui.output).to.include('(52 B gzipped)'); - }); + await sizePrinter.print(); + + expect(sizePrinter.ui.output).to.include('File sizes:'); + expect(sizePrinter.ui.output).to.include('some-project.css: '); + expect(sizePrinter.ui.output).to.include('some-project.js: '); + expect(sizePrinter.ui.output).to.include('24 B'); + expect(sizePrinter.ui.output).to.include('32 B'); + expect(sizePrinter.ui.output).to.include('(44 B gzipped)'); + expect(sizePrinter.ui.output).to.include('(52 B gzipped)'); }); - it('does not print gzipped file sizes of empty files', function() { + it('does not print gzipped file sizes of empty files', async function () { let sizePrinter = new AssetSizePrinter({ ui: new MockUi(), outputPath: storedTmpDir, }); - return sizePrinter.print().then(function() { - expect(sizePrinter.ui.output).to.not.include('0 B gzipped)'); - }); + await sizePrinter.print(); + expect(sizePrinter.ui.output).to.not.include('0 B gzipped)'); }); - it('does not print project test helper file sizes', function() { + it('does not print project test helper file sizes', async function () { let sizePrinter = new AssetSizePrinter({ ui: new MockUi(), outputPath: storedTmpDir, }); - return sizePrinter.print().then(function() { - expect(sizePrinter.ui.output).to.not.include('test-loader'); - expect(sizePrinter.ui.output).to.not.include('test-support'); - expect(sizePrinter.ui.output).to.not.include('testem'); - expect(sizePrinter.ui.output).to.include('test.js'); - }); + await sizePrinter.print(); + + expect(sizePrinter.ui.output).to.not.include('test-loader'); + expect(sizePrinter.ui.output).to.not.include('test-support'); + expect(sizePrinter.ui.output).to.not.include('testem'); + expect(sizePrinter.ui.output).to.include('test.js'); }); - it('does not print non-css or js file sizes', function() { + it('does not print non-css or js file sizes', async function () { let sizePrinter = new AssetSizePrinter({ ui: new MockUi(), outputPath: storedTmpDir, }); - return sizePrinter.print().then(function() { - expect(sizePrinter.ui.output).to.not.include('some-project.scss'); - expect(sizePrinter.ui.output).to.not.include('some-project.css4'); - expect(sizePrinter.ui.output).to.not.include('some-project.json'); - }); + await sizePrinter.print(); + + expect(sizePrinter.ui.output).to.not.include('some-project.scss'); + expect(sizePrinter.ui.output).to.not.include('some-project.css4'); + expect(sizePrinter.ui.output).to.not.include('some-project.json'); }); - it('can print out to JSON', function() { + it('can print out to JSON', async function () { let sizePrinter = new AssetSizePrinter({ ui: new MockUi(), outputPath: storedTmpDir, }); - return sizePrinter.printJSON().then(function() { - let output = JSON.parse(sizePrinter.ui.output); + await sizePrinter.printJSON(); - expect(output.files[0].name).to.include('nested-asset.css'); - expect(output.files[1].name).to.include('nested-asset.js'); - expect(output.files[1].size).to.equal(32); - expect(output.files[1].gzipSize).to.equal(52); - expect(output.files[0]).to.not.have.property('showGzipped'); - }); + let output = JSON.parse(sizePrinter.ui.output); + + expect(output.files[0].name).to.include('nested-asset.css'); + expect(output.files[1].name).to.include('nested-asset.js'); + expect(output.files[1].size).to.equal(32); + expect(output.files[1].gzipSize).to.equal(52); + expect(output.files[0]).to.not.have.property('showGzipped'); }); - it('creates an array of asset objects', function() { + it('creates an array of asset objects', async function () { let assetObjectKeys; let sizePrinter = new AssetSizePrinter({ ui: new MockUi(), outputPath: storedTmpDir, }); - return sizePrinter.makeAssetSizesObject().then(function(assetObject) { - assetObjectKeys = Object.keys(assetObject[0]); + let assetObject = await sizePrinter.makeAssetSizesObject(); - expect(assetObjectKeys).to.deep.equal(['name', 'size', 'gzipSize', 'showGzipped']); - expect(assetObject[0].name).to.include('nested-asset.css'); - expect(assetObject[1].name).to.include('nested-asset.js'); - expect(assetObject[2].name).to.include('empty.js'); - expect(assetObject[3].name).to.include('some-project.css'); - expect(assetObject[4].name).to.include('some-project.js'); - expect(assetObject[5].name).to.include('test.js'); - }); + assetObjectKeys = Object.keys(assetObject[0]); + + expect(assetObjectKeys).to.deep.equal(['name', 'size', 'gzipSize', 'showGzipped']); + expect(assetObject[0].name).to.include('nested-asset.css'); + expect(assetObject[1].name).to.include('nested-asset.js'); + expect(assetObject[2].name).to.include('empty.js'); + expect(assetObject[3].name).to.include('some-project.css'); + expect(assetObject[4].name).to.include('some-project.js'); + expect(assetObject[5].name).to.include('test.js'); }); - it('prints an error when no files are found', function() { + it('prints an error when no files are found', function () { let outputPath = path.join('path', 'that', 'does', 'not', 'exist'); let sizePrinter = new AssetSizePrinter({ ui: new MockUi(), diff --git a/tests/unit/models/blueprint-test.js b/tests/unit/models/blueprint-test.js index 0c4a075352..4593a9d275 100644 --- a/tests/unit/models/blueprint-test.js +++ b/tests/unit/models/blueprint-test.js @@ -9,13 +9,13 @@ const td = require('testdouble'); let Blueprint = require('../../../lib/models/blueprint'); -describe('Blueprint', function() { - afterEach(function() { +describe('Blueprint', function () { + afterEach(function () { td.reset(); }); - describe('.mapFile', function() { - it('replaces all occurrences of __name__ with module name', function() { + describe('.mapFile', function () { + it('replaces all occurrences of __name__ with module name', function () { let path = Blueprint.prototype.mapFile('__name__/__name__-controller.js', { dasherizedModuleName: 'my-blueprint', }); @@ -28,7 +28,7 @@ describe('Blueprint', function() { expect(path).to.equal('my-blueprint/my-blueprint.js'); }); - it('accepts locals.fileMap with multiple mappings', function() { + it('accepts locals.fileMap with multiple mappings', function () { let locals = {}; locals.fileMap = { __name__: 'user', @@ -45,24 +45,24 @@ describe('Blueprint', function() { }); }); - describe('.list', function() { - beforeEach(function() { - td.replace(Blueprint, '_existsSync', function(path) { + describe('.list', function () { + beforeEach(function () { + td.replace(Blueprint, '_existsSync', function (path) { return path.indexOf('package.json') === -1; }); td.replace(Blueprint, 'defaultLookupPaths'); td.when(Blueprint.defaultLookupPaths()).thenReturn([]); - td.replace(Blueprint, 'load', function(blueprintPath) { + td.replace(Blueprint, 'load', function (blueprintPath) { return { name: path.basename(blueprintPath), }; }); }); - it('returns a list of blueprints grouped by lookup path', function() { - td.replace(Blueprint, '_readdirSync', function() { + it('returns a list of blueprints grouped by lookup path', function () { + td.replace(Blueprint, '_readdirSync', function () { return ['test1', 'test2']; }); @@ -83,8 +83,8 @@ describe('Blueprint', function() { }); }); - it('overrides a blueprint of the same name from another package', function() { - td.replace(Blueprint, '_readdirSync', function() { + it('overrides a blueprint of the same name from another package', function () { + td.replace(Blueprint, '_readdirSync', function () { return ['test2']; }); @@ -113,21 +113,21 @@ describe('Blueprint', function() { }); }); - describe('help', function() { + describe('help', function () { let blueprint; - beforeEach(function() { + beforeEach(function () { blueprint = new Blueprint('path/to/my-blueprint'); }); - describe('printBasicHelp', function() { - beforeEach(function() { + describe('printBasicHelp', function () { + beforeEach(function () { td.replace(blueprint, '_printCommand', td.function()); td.replace(blueprint, 'printDetailedHelp', td.function()); td.when(blueprint.printDetailedHelp(), { ignoreExtraArgs: true }).thenReturn('help in detail'); }); - it('handles overridden', function() { + it('handles overridden', function () { Object.assign(blueprint, { overridden: true, }); @@ -142,7 +142,7 @@ describe('Blueprint', function() { td.verify(blueprint._printCommand(), { times: 0 }); }); - it('calls printCommand', function() { + it('calls printCommand', function () { td.when(blueprint._printCommand(), { ignoreExtraArgs: true }).thenReturn(' command printed'); let output = blueprint.printBasicHelp(); @@ -153,7 +153,7 @@ describe('Blueprint', function() { expect(output).to.equal(testString); }); - it('prints detailed help if verbose', function() { + it('prints detailed help if verbose', function () { td.when(blueprint._printCommand(), { ignoreExtraArgs: true }).thenReturn(' command printed'); let availableOptions = []; @@ -171,9 +171,9 @@ help in detail`); }); }); - describe('printDetailedHelp', function() { - it('did not find the file', function() { - td.replace(Blueprint, '_existsSync', function() { + describe('printDetailedHelp', function () { + it('did not find the file', function () { + td.replace(Blueprint, '_existsSync', function () { return false; }); @@ -185,12 +185,12 @@ help in detail`); td.verify(MarkdownColor.prototype.renderFile(), { ignoreExtraArgs: true, times: 0 }); }); - it('found the file', function() { - td.replace(Blueprint, '_existsSync', function() { + it('found the file', function () { + td.replace(Blueprint, '_existsSync', function () { return true; }); - td.replace(MarkdownColor.prototype, 'renderFile', function() { + td.replace(MarkdownColor.prototype, 'renderFile', function () { expect(arguments[1].indent).to.equal(' '); return 'test-file'; }); @@ -201,12 +201,12 @@ help in detail`); }); }); - describe('getJson', function() { - beforeEach(function() { + describe('getJson', function () { + beforeEach(function () { blueprint._printableProperties = ['test1', 'availableOptions']; }); - it('iterates options', function() { + it('iterates options', function () { let availableOptions = [ { type: 'my-string-type', @@ -238,7 +238,7 @@ help in detail`); }); }); - it('does not print detailed if not verbose', function() { + it('does not print detailed if not verbose', function () { td.replace(blueprint, 'printDetailedHelp', td.function()); blueprint.getJson(); @@ -246,7 +246,7 @@ help in detail`); td.verify(blueprint.printDetailedHelp(), { ignoreExtraArgs: true, times: 0 }); }); - it('is calling printDetailedHelp with availableOptions', function() { + it('is calling printDetailedHelp with availableOptions', function () { td.replace(blueprint, 'printDetailedHelp', td.function()); let availableOptions = []; @@ -259,7 +259,7 @@ help in detail`); td.verify(blueprint.printDetailedHelp(availableOptions)); }); - it("if printDetailedHelp returns falsy, don't attach property detailedHelp", function() { + it("if printDetailedHelp returns falsy, don't attach property detailedHelp", function () { td.replace(blueprint, 'printDetailedHelp', td.function()); let json = blueprint.getJson(true); @@ -268,7 +268,7 @@ help in detail`); expect(json).to.not.have.property('detailedHelp'); }); - it('sets detailedHelp properly', function() { + it('sets detailedHelp properly', function () { td.replace(blueprint, 'printDetailedHelp', td.function()); td.when(blueprint.printDetailedHelp(), { ignoreExtraArgs: true }).thenReturn('some details'); diff --git a/tests/unit/models/builder-test.js b/tests/unit/models/builder-test.js index 028ce5fc8c..d92af85661 100644 --- a/tests/unit/models/builder-test.js +++ b/tests/unit/models/builder-test.js @@ -4,27 +4,21 @@ const fs = require('fs-extra'); const path = require('path'); const BuildCommand = require('../../../lib/commands/build'); const commandOptions = require('../../factories/command-options'); -const RSVP = require('rsvp'); const rimraf = require('rimraf'); const fixturify = require('fixturify'); const MockProject = require('../../helpers/mock-project'); const mkTmpDirIn = require('../../../lib/utilities/mk-tmp-dir-in'); -const { isExperimentEnabled } = require('../../../lib/experiments'); const td = require('testdouble'); -const ci = require('ci-info'); const chai = require('../../chai'); let expect = chai.expect; let file = chai.file; let root = process.cwd(); -let tmproot = path.join(root, 'tmp'); +let tmpRoot = path.join(root, 'tmp'); let Builder; -const Promise = RSVP.Promise; -const remove = RSVP.denodeify(fs.remove); - -describe('models/builder.js', function() { +describe('models/builder.js', function () { let addon, builder, buildResults, tmpdir; function setupBroccoliBuilder() { @@ -46,7 +40,7 @@ describe('models/builder.js', function() { }; } - before(function() { + before(function () { let willInterruptProcess = require('../../../lib/utilities/will-interrupt-process'); td.replace(willInterruptProcess, 'addHandler', td.function()); td.replace(willInterruptProcess, 'removeHandler', td.function()); @@ -54,15 +48,15 @@ describe('models/builder.js', function() { Builder = require('../../../lib/models/builder'); }); - afterEach(function() { + afterEach(function () { if (builder) { return builder.cleanup(); } }); - describe('copyToOutputPath', function() { - beforeEach(async function() { - tmpdir = await mkTmpDirIn(tmproot); + describe('copyToOutputPath', function () { + beforeEach(async function () { + tmpdir = await mkTmpDirIn(tmpRoot); let project = new MockProject(); builder = new Builder({ project, @@ -71,22 +65,22 @@ describe('models/builder.js', function() { }); }); - afterEach(function() { - return remove(tmproot); + afterEach(function () { + return fs.remove(tmpRoot); }); - (ci.APPVEYOR ? it.skip : it)('allows for non-existent output-paths at arbitrary depth', function() { + it('allows for non-existent output-paths at arbitrary depth', function () { builder.outputPath = path.join(tmpdir, 'some', 'path', 'that', 'does', 'not', 'exist'); builder.copyToOutputPath('tests/fixtures/blueprints/basic_2'); expect(file(path.join(builder.outputPath, 'files', 'foo.txt'))).to.exist; }); - describe('build command', function() { + describe('build command', function () { let command; let parentPath = `..${path.sep}..${path.sep}`; - beforeEach(function() { + beforeEach(function () { command = new BuildCommand(commandOptions()); let project = new MockProject(); @@ -97,7 +91,7 @@ describe('models/builder.js', function() { }); }); - it('when outputPath is root directory ie., `--output-path=/` or `--output-path=C:`', function() { + it('when outputPath is root directory ie., `--output-path=/` or `--output-path=C:`', function () { let outputPathArg = '--output-path=.'; let outputPath = command.parseArgs([outputPathArg]).options.outputPath; outputPath = outputPath.split(path.sep)[0] + path.sep; @@ -106,7 +100,7 @@ describe('models/builder.js', function() { expect(builder.canDeleteOutputPath(outputPath)).to.equal(false); }); - it('when outputPath is project root ie., `--output-path=.`', function() { + it('when outputPath is project root ie., `--output-path=.`', function () { let outputPathArg = '--output-path=.'; let outputPath = command.parseArgs([outputPathArg]).options.outputPath; builder.outputPath = outputPath; @@ -114,7 +108,7 @@ describe('models/builder.js', function() { expect(builder.canDeleteOutputPath(outputPath)).to.equal(false); }); - it(`when outputPath is a parent directory ie., \`--output-path=${parentPath}\``, function() { + it(`when outputPath is a parent directory ie., \`--output-path=${parentPath}\``, function () { let outputPathArg = `--output-path=${parentPath}`; let outputPath = command.parseArgs([outputPathArg]).options.outputPath; builder.outputPath = outputPath; @@ -122,7 +116,7 @@ describe('models/builder.js', function() { expect(builder.canDeleteOutputPath(outputPath)).to.equal(false); }); - it('allow outputPath to contain the root path as a substring, as long as it is not a parent', function() { + it('allow outputPath to contain the root path as a substring, as long as it is not a parent', function () { let outputPathArg = '--output-path=.'; let outputPath = command.parseArgs([outputPathArg]).options.outputPath; outputPath = outputPath.substr(0, outputPath.length - 1); @@ -133,12 +127,12 @@ describe('models/builder.js', function() { }); }); - describe('build', function() { + describe('build', function () { let instrumentationStart; let instrumentationStop; let cwd, project; - beforeEach(function() { + beforeEach(function () { // Cache cwd to reset after test cwd = process.cwd(); project = new MockProject(); @@ -146,8 +140,8 @@ describe('models/builder.js', function() { project, ui: project.ui, setupBroccoliBuilder, - processBuildResult(buildResults) { - return Promise.resolve(buildResults); + copyToOutputPath() { + return []; }, }); @@ -155,7 +149,7 @@ describe('models/builder.js', function() { instrumentationStop = td.replace(builder.project._instrumentation, 'stopAndReport'); }); - afterEach(function() { + afterEach(function () { process.chdir(cwd); delete process._heimdall; delete process.env.BROCCOLI_VIZ; @@ -165,13 +159,13 @@ describe('models/builder.js', function() { } }); - it('calls instrumentation.start', async function() { + it('calls instrumentation.start', async function () { let mockAnnotation = 'MockAnnotation'; await builder.build(null, mockAnnotation); td.verify(instrumentationStart('build'), { times: 1 }); }); - it('calls instrumentation.stop(build, result, resultAnnotation)', async function() { + it('calls instrumentation.stop(build, result, resultAnnotation)', async function () { let mockAnnotation = 'MockAnnotation'; await builder.build(null, mockAnnotation); @@ -182,7 +176,7 @@ describe('models/builder.js', function() { ); }); - it('prints a deprecation warning if it discovers a < v0.1.4 version of heimdalljs', async function() { + it('prints a deprecation warning if it discovers a < v0.1.4 version of heimdalljs', async function () { process._heimdall = {}; await builder.build(); @@ -191,7 +185,7 @@ describe('models/builder.js', function() { expect(output).to.include('Heimdalljs < 0.1.4 found. Please remove old versions'); }); - it('does not print a deprecation warning if it does not discover a < v0.1.4 version of heimdalljs', async function() { + it('does not print a deprecation warning if it does not discover a < v0.1.4 version of heimdalljs', async function () { expect(process._heimdall).to.equal(undefined); await builder.build(); @@ -200,55 +194,35 @@ describe('models/builder.js', function() { expect(output).to.not.include('Heimdalljs < 0.1.4 found. Please remove old versions'); }); - if (!isExperimentEnabled('SYSTEM_TEMP')) { - it('writes temp files to project root by default', async function() { - const project = new MockProject(); - project.root += '/tests/fixtures/build/simple'; - - builder = new Builder({ - project, - ui: project.ui, - processBuildResult(buildResults) { - return Promise.resolve(buildResults); - }, - }); - - await builder.build(); - expect(fs.existsSync(`${builder.project.root}/tmp`)).to.be.true; + it('writes temp files to Broccoli temp dir', async function () { + const project = new MockProject(); + project.root += '/tests/fixtures/build/simple'; + expect(fs.existsSync(`${builder.project.root}/tmp`)).to.be.false; + builder = new Builder({ + project, + ui: project.ui, + copyToOutputPath() { + return []; + }, }); - } - if (isExperimentEnabled('SYSTEM_TEMP')) { - it('writes temp files to Broccoli temp dir when EMBER_CLI_SYSTEM_TEMP=1', async function() { - const project = new MockProject(); - project.root += '/tests/fixtures/build/simple'; - expect(fs.existsSync(`${builder.project.root}/tmp`)).to.be.false; - builder = new Builder({ - project, - ui: project.ui, - processBuildResult(buildResults) { - return Promise.resolve(buildResults); - }, - }); + expect(fs.existsSync(`${builder.project.root}/tmp`)).to.be.false; - expect(fs.existsSync(`${builder.project.root}/tmp`)).to.be.false; - - let result = await builder.build(); - expect(fs.existsSync(result.directory)).to.be.true; - expect(fs.existsSync(`${builder.project.root}/tmp`)).to.be.false; - rimraf.sync(result.directory); - }); - } + let result = await builder.build(); + expect(fs.existsSync(result.directory)).to.be.true; + expect(fs.existsSync(`${builder.project.root}/tmp`)).to.be.false; + rimraf.sync(result.directory); + }); - (ci.APPVEYOR ? it.skip : it)('produces the correct output', async function() { + it('produces the correct output', async function () { const project = new MockProject(); project.root += '/tests/fixtures/build/simple'; const setup = () => new Builder({ project, ui: project.ui, - processBuildResult(buildResults) { - return Promise.resolve(buildResults); + copyToOutputPath() { + return []; }, }); @@ -257,15 +231,15 @@ describe('models/builder.js', function() { expect(fixturify.readSync(result.directory)).to.deep.equal(fixturify.readSync(`${project.root}/dist`)); }); - it('returns {directory, graph} as the result object', async function() { + it('returns {directory, graph} as the result object', async function () { const project = new MockProject(); project.root += '/tests/fixtures/build/simple'; builder = new Builder({ project, ui: project.ui, - processBuildResult(buildResults) { - return Promise.resolve(buildResults); + copyToOutputPath() { + return []; }, }); @@ -277,31 +251,34 @@ describe('models/builder.js', function() { }); }); - describe('cleanup', function() { - beforeEach(function() { + describe('cleanup', function () { + beforeEach(function () { let project = new MockProject(); builder = new Builder({ project, ui: project.ui, setupBroccoliBuilder, - processBuildResult(buildResults) { - return Promise.resolve(buildResults); - }, }); }); - it('is idempotent', function() { - let firstCleanupPromise = builder.cleanup(); - expect(builder.cleanup()).to.equal(firstCleanupPromise); + it('is idempotent', async function () { + let cleanupCount = 0; + builder.builder.cleanup = function () { + cleanupCount++; + }; + + let cleanupPromises = [builder.cleanup(), builder.cleanup(), builder.cleanup(), builder.cleanup()]; + + await Promise.all(cleanupPromises); - return firstCleanupPromise; + expect(cleanupCount).to.equal(1); }); }); - describe('addons', function() { + describe('addons', function () { let hooksCalled; - beforeEach(function() { + beforeEach(function () { hooksCalled = []; addon = { name: 'TestAddon', @@ -339,8 +316,8 @@ describe('models/builder.js', function() { return originalBuild.call(this); }; }, - processBuildResult(buildResults) { - return Promise.resolve(buildResults); + copyToOutputPath() { + return []; }, project, ui: project.ui, @@ -354,39 +331,42 @@ describe('models/builder.js', function() { }; }); - afterEach(function() { + afterEach(function () { delete process.env.BROCCOLI_VIZ; delete process.env.EMBER_CLI_INSTRUMENTATION; }); - it('allows addons to add promises preBuild', function() { + it('allows addons to add promises preBuild', function () { let preBuild = td.replace(addon, 'preBuild', td.function()); td.when(preBuild(), { ignoreExtraArgs: true, times: 1 }).thenReturn(Promise.resolve()); return builder.build(); }); - it('allows addons to add promises postBuild', async function() { + it('allows addons to add promises postBuild', async function () { let postBuild = td.replace(addon, 'postBuild', td.function()); await builder.build(); td.verify(postBuild(buildResults), { times: 1 }); }); - it('allows addons to add promises outputReady', async function() { + it('allows addons to add promises outputReady', async function () { let outputReady = td.replace(addon, 'outputReady', td.function()); + builder.outputPath = 'dist/'; await builder.build(); - td.verify(outputReady(buildResults), { times: 1 }); + + let expected = Object.assign({}, buildResults, { outputChanges: [], directory: 'dist/' }); + td.verify(outputReady(expected), { times: 1 }); }); - describe('instrumentation hooks', function() { - beforeEach(function() { + describe('instrumentation hooks', function () { + beforeEach(function () { process.env.EMBER_CLI_INSTRUMENTATION = '1'; }); - it('invokes the instrumentation hook if it is preset', async function() { - addon.instrumentation = function() { + it('invokes the instrumentation hook if it is preset', async function () { + addon.instrumentation = function () { hooksCalled.push('instrumentation'); }; @@ -395,50 +375,57 @@ describe('models/builder.js', function() { }); }); - it('hooks are called in the right order without visualization', async function() { + it('hooks are called in the right order without visualization', async function () { await builder.build(); expect(hooksCalled).to.deep.equal(['preBuild', 'build', 'postBuild', 'outputReady']); }); - it('should call postBuild before processBuildResult', async function() { + it('should call postBuild before copying to dist', async function () { let called = []; - addon.postBuild = function() { + addon.postBuild = function () { called.push('postBuild'); }; - builder.processBuildResult = function() { - called.push('processBuildResult'); + builder.copyToOutputPath = function () { + called.push('copyToOutputPath'); }; await builder.build(); - expect(called).to.deep.equal(['postBuild', 'processBuildResult']); + expect(called).to.deep.equal(['postBuild', 'copyToOutputPath']); }); - it('should call outputReady after processBuildResult', async function() { + it('should call outputReady after copying to output path', async function () { let called = []; - builder.processBuildResult = function() { - called.push('processBuildResult'); + builder.copyToOutputPath = function (directory) { + called.push(['copyToOutputPath', directory]); + return []; }; - addon.outputReady = function() { - called.push('outputReady'); + addon.outputReady = function (result) { + called.push(['outputReady', result]); }; + builder.outputPath = 'dist/'; + await builder.build(); - expect(called).to.deep.equal(['processBuildResult', 'outputReady']); + + expect(called).to.deep.equal([ + ['copyToOutputPath', buildResults.directory], + ['outputReady', Object.assign({}, buildResults, { outputChanges: [], directory: 'dist/' })], + ]); }); - it('buildError receives the error object from the errored step', async function() { + it('buildError receives the error object from the errored step', async function () { let thrownBuildError = new Error('buildError'); let receivedBuildError; - addon.buildError = function(errorThrown) { + addon.buildError = function (errorThrown) { receivedBuildError = errorThrown; }; - builder.builder.build = function() { + builder.builder.build = function () { hooksCalled.push('build'); return Promise.reject(thrownBuildError); @@ -448,8 +435,8 @@ describe('models/builder.js', function() { expect(receivedBuildError).to.equal(thrownBuildError); }); - it('calls buildError and does not call build, postBuild or outputReady when preBuild fails', async function() { - addon.preBuild = function() { + it('calls buildError and does not call build, postBuild or outputReady when preBuild fails', async function () { + addon.preBuild = function () { hooksCalled.push('preBuild'); return Promise.reject(new Error('preBuild Error')); @@ -459,8 +446,8 @@ describe('models/builder.js', function() { expect(hooksCalled).to.deep.equal(['preBuild', 'buildError']); }); - it('calls buildError and does not call postBuild or outputReady when build fails', async function() { - builder.builder.build = function() { + it('calls buildError and does not call postBuild or outputReady when build fails', async function () { + builder.builder.build = function () { hooksCalled.push('build'); return Promise.reject(new Error('build Error')); @@ -470,8 +457,8 @@ describe('models/builder.js', function() { expect(hooksCalled).to.deep.equal(['preBuild', 'build', 'buildError']); }); - it('calls buildError when postBuild fails', async function() { - addon.postBuild = function() { + it('calls buildError when postBuild fails', async function () { + addon.postBuild = function () { hooksCalled.push('postBuild'); return Promise.reject(new Error('preBuild Error')); @@ -481,8 +468,8 @@ describe('models/builder.js', function() { expect(hooksCalled).to.deep.equal(['preBuild', 'build', 'postBuild', 'buildError']); }); - it('calls buildError when outputReady fails', async function() { - addon.outputReady = function() { + it('calls buildError when outputReady fails', async function () { + addon.outputReady = function () { hooksCalled.push('outputReady'); return Promise.reject(new Error('outputReady Error')); @@ -491,10 +478,24 @@ describe('models/builder.js', function() { await expect(builder.build()).to.be.rejected; expect(hooksCalled).to.deep.equal(['preBuild', 'build', 'postBuild', 'outputReady', 'buildError']); }); + + it('sets `isBuilderError` on handled addon errors', async function () { + addon.postBuild = function () { + return Promise.reject(new Error('preBuild Error')); + }; + + let error; + try { + await builder.build(); + } catch (e) { + error = e; + } + expect(error).to.haveOwnProperty('isBuilderError', true); + }); }); - describe('fallback from broccoli 2 to broccoli-builder', function() { - it('falls back to broccoli-builder if an InvalidNode error is thrown for read/rebuild api', function() { + describe('fallback from broccoli 2 to broccoli-builder', function () { + it('falls back to broccoli-builder if an InvalidNode error is thrown for read/rebuild api', function () { let project = new MockProject(); const builder = new Builder({ project, @@ -517,7 +518,7 @@ describe('models/builder.js', function() { ); }); - it('errors for an invalid node', function() { + it('errors for an invalid node', function () { let project = new MockProject(); expect( () => diff --git a/tests/unit/models/command-test.js b/tests/unit/models/command-test.js index 30f566c030..c3eaf645e5 100644 --- a/tests/unit/models/command-test.js +++ b/tests/unit/models/command-test.js @@ -6,9 +6,6 @@ const processHelpString = require('../../helpers/process-help-string'); const Yam = require('yam'); const EOL = require('os').EOL; const td = require('testdouble'); -const ci = require('ci-info'); -const RSVP = require('rsvp'); -const Promise = RSVP.Promise; let Task = require('../../../lib/models/task'); let Command = require('../../../lib/models/command'); @@ -80,59 +77,56 @@ let OptionsAliasCommand = Command.extend({ }, }); -describe('models/command.js', function() { +describe('models/command.js', function () { let ui; let config; let options; - before(function() { + before(function () { config = new Yam('ember-cli', { secondary: `${process.cwd()}/tests/fixtures/home`, primary: `${process.cwd()}/tests/fixtures/project`, }); }); - beforeEach(function() { + beforeEach(function () { options = commandOptions(); ui = options.ui; }); - afterEach(function() { + afterEach(function () { td.reset(); }); - it('parseArgs() should parse the command options.', function() { + it('parseArgs() should parse the command options.', function () { expect(new ServeCommand(options).parseArgs(['--port', '80'])).to.have.nested.property('options.port', 80); }); - (ci.APPVEYOR ? it.skip : it)( - 'parseArgs() should get command options from the config file and command line', - function() { - expect( - new ServeCommand( - Object.assign(options, { - settings: config.getAll(), - }) - ).parseArgs(['--port', '789']) - ).to.deep.equal({ - options: { - port: 789, - environment: 'mock-development', - host: '0.1.0.1', - proxy: 'http://iamstef.net/ember-cli', - liveReload: false, - checkForUpdates: true, - }, - args: [], - }); - } - ); + it('parseArgs() should get command options from the config file and command line', function () { + expect( + new ServeCommand( + Object.assign(options, { + settings: config.getAll(), + }) + ).parseArgs(['--port', '789']) + ).to.deep.equal({ + options: { + port: 789, + environment: 'mock-development', + host: '0.1.0.1', + proxy: 'http://iamstef.net/ember-cli', + liveReload: false, + checkForUpdates: true, + }, + args: [], + }); + }); - it('parseArgs() should set default option values.', function() { + it('parseArgs() should set default option values.', function () { expect(new ServeCommand(options).parseArgs([])).to.have.nested.property('options.port', 4200); }); - (ci.APPVEYOR ? it.skip : it)('parseArgs() should return args too.', function() { + it('parseArgs() should return args too.', function () { expect( new ServeCommand( Object.assign(options, { @@ -152,7 +146,7 @@ describe('models/command.js', function() { }); }); - it('parseArgs() should warn if an option is invalid.', function() { + it('parseArgs() should warn if an option is invalid.', function () { new ServeCommand( Object.assign(options, { settings: config.getAll(), @@ -163,18 +157,18 @@ describe('models/command.js', function() { ); }); - it('parseArgs() should parse shorthand options.', function() { + it('parseArgs() should parse shorthand options.', function () { expect(new ServeCommand(options).parseArgs(['-e', 'tacotown'])).to.have.nested.property( 'options.environment', 'tacotown' ); }); - it('parseArgs() should parse shorthand dasherized options.', function() { + it('parseArgs() should parse shorthand dasherized options.', function () { expect(new ServeCommand(options).parseArgs(['-lr', 'false'])).to.have.nested.property('options.liveReload', false); }); - it('parseArgs() should parse string options.', function() { + it('parseArgs() should parse string options.', function () { let CustomAliasCommand = Command.extend({ name: 'custom-alias', availableOptions: [ @@ -191,14 +185,14 @@ describe('models/command.js', function() { expect(command).to.have.nested.property('options.options', '--split 2 --random'); }); - describe('#validateAndRun', function() { - it('should reject and print a message if a required option is missing.', function() { - return new DevelopEmberCLICommand(options).validateAndRun([]).catch(function() { + describe('#validateAndRun', function () { + it('should reject and print a message if a required option is missing.', function () { + return new DevelopEmberCLICommand(options).validateAndRun([]).catch(function () { expect(ui.output).to.match(/requires the option.*package-name/); }); }); - it('should print a message if outside a project and command is not valid there.', function() { + it('should print a message if outside a project and command is not valid there.', function () { return new InsideProjectCommand( Object.assign(options, { project: { @@ -212,12 +206,12 @@ describe('models/command.js', function() { }) ) .validateAndRun([]) - .catch(function(reason) { + .catch(function (reason) { expect(reason.message).to.match(/You have to be inside an ember-cli project/); }); }); - it('selects watcher if an option', function() { + it('selects watcher if an option', function () { return new InsideProjectCommand( Object.assign(options, { availableOptions: [{ type: 'string', name: 'watcher' }], @@ -232,12 +226,12 @@ describe('models/command.js', function() { }) ) .validateAndRun([]) - .then(function(options) { + .then(function (options) { expect(options).to.have.property('watcher'); }); }); - it('selects NO watcher if NOT an option', function() { + it('selects NO watcher if NOT an option', function () { return new InsideProjectCommand( Object.assign(options, { availableOptions: [{ type: 'string', name: 'foo' }], @@ -252,19 +246,19 @@ describe('models/command.js', function() { }) ) .validateAndRun([]) - .then(function(options) { + .then(function (options) { expect(options).to.not.have.property('watcher'); }); }); - it('should print a message if inside a project and command is not valid there.', function() { - return new OutsideProjectCommand(options).validateAndRun([]).catch(function(reason) { + it('should print a message if inside a project and command is not valid there.', function () { + return new OutsideProjectCommand(options).validateAndRun([]).catch(function (reason) { expect(reason.message).to.match(/You cannot use.*inside an ember-cli project/); }); }); }); - it('should be able to set availableOptions within init', function() { + it('should be able to set availableOptions within init', function () { let AvailableOptionsInitCommand = Command.extend({ name: 'available-options-init-command', init() { @@ -296,12 +290,12 @@ describe('models/command.js', function() { }) ) .validateAndRun([]) - .then(function(commandOptions) { + .then(function (commandOptions) { expect(commandOptions).to.deep.equal({ spicy: true }); }); }); - it('should be able to set availableOptions within beforeRun', function() { + it('should be able to set availableOptions within beforeRun', function () { let AvailableOptionsInitCommand = Command.extend({ name: 'available-options-init-command', @@ -314,7 +308,7 @@ describe('models/command.js', function() { ], beforeRun() { - return new Promise(resolve => { + return new Promise((resolve) => { resolve( this.availableOptions.push({ name: 'foobar', @@ -343,13 +337,13 @@ describe('models/command.js', function() { ); return command.beforeRun().then(() => - command.validateAndRun([]).then(commandOptions => { + command.validateAndRun([]).then((commandOptions) => { expect(commandOptions).to.deep.equal({ spicy: true, foobar: 'bazbaz' }); }) ); }); - it('availableOptions with aliases should work.', function() { + it('availableOptions with aliases should work.', function () { expect(new OptionsAliasCommand(options).parseArgs(['-soft-shell'])).to.deep.equal({ options: { taco: 'soft-shell', @@ -359,7 +353,7 @@ describe('models/command.js', function() { }); }); - it('availableOptions with aliases should work with minimum characters.', function() { + it('availableOptions with aliases should work with minimum characters.', function () { expect(new OptionsAliasCommand(options).parseArgs(['-so'])).to.deep.equal({ options: { taco: 'soft-shell', @@ -369,7 +363,7 @@ describe('models/command.js', function() { }); }); - it('availableOptions with aliases should work with hyphenated options', function() { + it('availableOptions with aliases should work with hyphenated options', function () { expect(new OptionsAliasCommand(options).parseArgs(['-dm', 'hi'])).to.deep.equal({ options: { taco: 'traditional', @@ -389,7 +383,7 @@ describe('models/command.js', function() { }); }); - it('registerOptions() should allow adding availableOptions.', function() { + it('registerOptions() should allow adding availableOptions.', function () { let optionsAlias = new OptionsAliasCommand(options); let extendedAvailableOptions = [ { @@ -430,7 +424,7 @@ describe('models/command.js', function() { }); }); - it('registerOptions() should allow overriding availableOptions.', function() { + it('registerOptions() should allow overriding availableOptions.', function () { let optionsAlias = new OptionsAliasCommand(options); let extendedAvailableOptions = [ { @@ -489,7 +483,7 @@ describe('models/command.js', function() { }); }); - it('registerOptions() should not allow aliases with the same name.', function() { + it('registerOptions() should not allow aliases with the same name.', function () { let optionsAlias = new OptionsAliasCommand(options); let extendedAvailableOptions = [ { @@ -514,7 +508,7 @@ describe('models/command.js', function() { ); }); - it('registerOptions() should warn on options override attempts.', function() { + it('registerOptions() should warn on options override attempts.', function () { let optionsAlias = new OptionsAliasCommand(options); let extendedAvailableOptions = [ { @@ -528,7 +522,7 @@ describe('models/command.js', function() { expect(ui.output).to.match(/The ".*" alias cannot be overridden. Please use a different alias./); }); - it('registerOptions() should handle invalid alias definitions.', function() { + it('registerOptions() should handle invalid alias definitions.', function () { //check for different types, validate proper errors are thrown let optionsAlias = new OptionsAliasCommand(options); let badArrayAvailableOptions = [ @@ -562,7 +556,7 @@ describe('models/command.js', function() { ); }); - it('parseAlias() should parse aliases and return an object', function() { + it('parseAlias() should parse aliases and return an object', function () { let optionsAlias = new OptionsAliasCommand(options); let option = { name: 'filling', @@ -579,7 +573,7 @@ describe('models/command.js', function() { }); }); - it('validateOption() should validate options', function() { + it('validateOption() should validate options', function () { let option = { name: 'filling', type: String, @@ -601,7 +595,7 @@ describe('models/command.js', function() { expect(optionsAliasCommand.validateOption(dupe)).to.be.false; }); - it('validateOption() should throw an error when option is missing name or type', function() { + it('validateOption() should throw an error when option is missing name or type', function () { let optionsAlias = new OptionsAliasCommand(options); let notype = { name: 'taco' }; let noname = { type: Boolean }; @@ -614,7 +608,7 @@ describe('models/command.js', function() { ); }); - it('validateOption() should throw an error when option name is camelCase or capitalized', function() { + it('validateOption() should throw an error when option name is camelCase or capitalized', function () { let optionsAlias = new OptionsAliasCommand(options); let capital = { name: 'Taco', @@ -633,7 +627,7 @@ describe('models/command.js', function() { ); }); - it('mergeDuplicateOption() should merge duplicate options together', function() { + it('mergeDuplicateOption() should merge duplicate options together', function () { let optionsAlias = new OptionsAliasCommand(options); let garbageAvailableOptions = [{ name: 'spicy', type: Boolean, default: true, aliases: [{ mild: true }] }]; optionsAlias.registerOptions({ availableOptions: garbageAvailableOptions }); @@ -696,7 +690,7 @@ describe('models/command.js', function() { ]); }); - it('implicit shorthands work with values.', function() { + it('implicit shorthands work with values.', function () { expect(new OptionsAliasCommand(options).parseArgs(['-s', 'false', '-t', 'hard-shell'])).to.deep.equal({ options: { taco: 'hard-shell', @@ -706,12 +700,12 @@ describe('models/command.js', function() { }); }); - describe('runTask', function() { + describe('runTask', function () { let command; class AsyncTask extends Task { run(options) { - return new Promise(function(resolve) { + return new Promise(function (resolve) { setTimeout(() => resolve(options), 50); }); } @@ -729,7 +723,7 @@ describe('models/command.js', function() { } } - beforeEach(function() { + beforeEach(function () { // this should be changed to new Command(), but needs more mocking command = new ServeCommand( Object.assign({}, options, { @@ -742,15 +736,15 @@ describe('models/command.js', function() { ); }); - it('always handles task as a promise', function() { - return command.runTask('Sync', { param: 'value' }).then(result => { + it('always handles task as a promise', function () { + return command.runTask('Sync', { param: 'value' }).then((result) => { expect(result).to.eql({ param: 'value', }); }); }); - it('command environment should be shared with a task', function() { + it('command environment should be shared with a task', function () { let taskRun = command.runTask('Async', { param: 'value' }); expect(command._currentTask.ui).to.eql(command.ui); @@ -760,7 +754,7 @@ describe('models/command.js', function() { return taskRun; }); - it('_currentTask should store a reference to the current task', function() { + it('_currentTask should store a reference to the current task', function () { expect(command._currentTask).to.be.undefined; let taskRun = command.runTask('Sync', { param: 'value' }).then(() => { expect(command._currentTask).to.be.undefined; @@ -770,13 +764,13 @@ describe('models/command.js', function() { return taskRun; }); - it('_currentTask should cleanup current task on fail', function() { + it('_currentTask should cleanup current task on fail', function () { return expect(command.runTask('Failing', { param: 'value' })).to.be.rejected.then(() => { expect(command._currentTask).to.be.undefined; }); }); - it('throws on attempt to launch concurrent tasks', function() { + it('throws on attempt to launch concurrent tasks', function () { let asyncTaskRun, syncTaskRun; expect(() => { @@ -787,7 +781,7 @@ describe('models/command.js', function() { return Promise.all([asyncTaskRun, syncTaskRun]); }); - it('throws if the task is not found', function() { + it('throws if the task is not found', function () { try { let taskRun = command.runTask('notfound'); @@ -800,25 +794,25 @@ describe('models/command.js', function() { }); }); - describe('help', function() { + describe('help', function () { let command; - beforeEach(function() { + beforeEach(function () { // this should be changed to new Command(), but needs more mocking command = new ServeCommand(options); }); - describe('printBasicHelp', function() { - beforeEach(function() { + describe('printBasicHelp', function () { + beforeEach(function () { td.replace(command, '_printCommand', td.function()); td.when(command._printCommand(), { ignoreExtraArgs: true }).thenReturn(' command printed'); }); - afterEach(function() { + afterEach(function () { td.reset(); }); - it('calls printCommand', function() { + it('calls printCommand', function () { let output = command.printBasicHelp(); let testString = processHelpString(`ember serve command printed${EOL}`); @@ -826,7 +820,7 @@ describe('models/command.js', function() { expect(output).to.equal(testString); }); - it('is root', function() { + it('is root', function () { command.isRoot = true; let output = command.printBasicHelp(); @@ -837,30 +831,30 @@ describe('models/command.js', function() { }); }); - describe('printDetailedHelp', function() { - it('has no-op function', function() { + describe('printDetailedHelp', function () { + it('has no-op function', function () { let output = command.printDetailedHelp(); expect(output).to.be.undefined; }); }); - describe('hasOption', function() { - it('reports false if no option with that name is present', function() { + describe('hasOption', function () { + it('reports false if no option with that name is present', function () { expect(command.hasOption('no-option-by-this-name')).to.be.false; }); - it('reports true if option with that name is present', function() { + it('reports true if option with that name is present', function () { expect(command.hasOption('port')).to.be.true; }); }); - describe('getJson', function() { - beforeEach(function() { + describe('getJson', function () { + beforeEach(function () { command._printableProperties = ['test1', 'test2']; }); - it('iterates options', function() { + it('iterates options', function () { Object.assign(command, { test1: 'a test', test2: 'another test', @@ -874,7 +868,7 @@ describe('models/command.js', function() { }); }); - it('calls detailed json', function() { + it('calls detailed json', function () { td.replace(command, 'addAdditionalJsonForHelp', td.function()); let options = {}; diff --git a/tests/unit/models/file-info-test.js b/tests/unit/models/file-info-test.js index 5fa1835c87..25c8e81cd4 100644 --- a/tests/unit/models/file-info-test.js +++ b/tests/unit/models/file-info-test.js @@ -6,21 +6,17 @@ const FileInfo = require('../../../lib/models/file-info'); const path = require('path'); const fs = require('fs-extra'); const EOL = require('os').EOL; -const RSVP = require('rsvp'); const mkTmpDirIn = require('../../../lib/utilities/mk-tmp-dir-in'); const td = require('testdouble'); -const Promise = RSVP.Promise; -const writeFile = RSVP.denodeify(fs.writeFile); - let root = process.cwd(); let tmproot = path.join(root, 'tmp'); -describe('Unit - FileInfo', function() { +describe('Unit - FileInfo', function () { let validOptions, ui, testOutputPath; - beforeEach(function() { - return mkTmpDirIn(tmproot).then(function(tmpdir) { + beforeEach(function () { + return mkTmpDirIn(tmproot).then(function (tmpdir) { testOutputPath = path.join(tmpdir, 'outputfile'); ui = new MockUI(); @@ -37,43 +33,43 @@ describe('Unit - FileInfo', function() { }); }); - afterEach(function(done) { + afterEach(function (done) { td.reset(); fs.remove(tmproot, done); }); - it('can instantiate with options', function() { + it('can instantiate with options', function () { new FileInfo(validOptions); }); // eslint-disable-next-line no-template-curly-in-string - it('does not interpolate {{ }} or ${ }', function() { + it('does not interpolate {{ }} or ${ }', function () { let options = {}; Object.assign(options, validOptions, { inputPath: path.resolve(__dirname, '../../fixtures/file-info/interpolate.txt'), templateVariables: { name: 'tacocat' }, }); let fileInfo = new FileInfo(options); - return fileInfo.render().then(function(output) { + return fileInfo.render().then(function (output) { // eslint-disable-next-line no-template-curly-in-string expect(output.trim()).to.equal('{{ name }} ${ name } tacocat tacocat'); }); }); - it('renders an input file', function() { + it('renders an input file', function () { validOptions.templateVariables.friend = 'Billy'; let fileInfo = new FileInfo(validOptions); - return fileInfo.render().then(function(output) { + return fileInfo.render().then(function (output) { expect(output.trim()).to.equal('Howdy Billy', 'expects the template to have been run'); }); }); - it('allows mutation to the rendered file', function() { + it('allows mutation to the rendered file', function () { validOptions.templateVariables.friend = 'Billy'; let fileInfo; - validOptions.replacer = function(content, theFileInfo) { + validOptions.replacer = function (content, theFileInfo) { expect(theFileInfo).to.eql(fileInfo); expect(content).to.eql('Howdy Billy\n'); @@ -82,12 +78,12 @@ describe('Unit - FileInfo', function() { fileInfo = new FileInfo(validOptions); - return fileInfo.render().then(function(output) { + return fileInfo.render().then(function (output) { expect(output.trim()).to.equal('HOWDY BILLY', 'expects the template to have been run'); }); }); - it('rejects if templating throws', function() { + it('rejects if templating throws', function () { let templateWithUndefinedVariable = path.resolve( __dirname, '../../fixtures/blueprints/with-templating/files/with-undefined-variable.txt' @@ -98,37 +94,38 @@ describe('Unit - FileInfo', function() { return fileInfo .render() - .then(function() { + .then(function () { throw new Error('FileInfo.render should reject if templating throws'); }) - .catch(function(e) { + .catch(function (e) { if (!e.toString().match(/ReferenceError/)) { throw e; } }); }); - it('does not explode when trying to template binary files', function() { + it('does not explode when trying to template binary files', function () { let binary = path.resolve(__dirname, '../../fixtures/problem-binary.png'); validOptions.inputPath = binary; let fileInfo = new FileInfo(validOptions); - return fileInfo.render().then(function(output) { + return fileInfo.render().then(function (output) { expect(!!output, 'expects the file to be processed without error').to.equal(true); }); }); - it('renders a diff to the UI', function() { + it('renders a diff to the UI', function () { validOptions.templateVariables.friend = 'Billy'; let fileInfo = new FileInfo(validOptions); - return writeFile(testOutputPath, `Something Old${EOL}`) - .then(function() { + return fs + .writeFile(testOutputPath, `Something Old${EOL}`) + .then(function () { return fileInfo.displayDiff(); }) - .then(function() { + .then(function () { let output = ui.output.trim().split(EOL); expect(output.shift()).to.equal(`Index: ${testOutputPath}`); expect(output.shift()).to.match(/=+/); @@ -140,50 +137,50 @@ describe('Unit - FileInfo', function() { }); }); - it('renders a menu with an overwrite option', function() { + it('renders a menu with an overwrite option', function () { td.when(ui.prompt(td.matchers.anything())).thenReturn(Promise.resolve({ answer: 'overwrite' })); let fileInfo = new FileInfo(validOptions); - return fileInfo.confirmOverwrite('test.js').then(function(action) { + return fileInfo.confirmOverwrite('test.js').then(function (action) { td.verify(ui.prompt(td.matchers.anything()), { times: 1 }); expect(action).to.equal('overwrite'); }); }); - it('renders a menu with a skip option', function() { + it('renders a menu with a skip option', function () { td.when(ui.prompt(td.matchers.anything())).thenReturn(Promise.resolve({ answer: 'skip' })); let fileInfo = new FileInfo(validOptions); - return fileInfo.confirmOverwrite('test.js').then(function(action) { + return fileInfo.confirmOverwrite('test.js').then(function (action) { td.verify(ui.prompt(td.matchers.anything()), { times: 1 }); expect(action).to.equal('skip'); }); }); - it('renders a menu with a diff option', function() { + it('renders a menu with a diff option', function () { td.when(ui.prompt(td.matchers.anything())).thenReturn(Promise.resolve({ answer: 'diff' })); let fileInfo = new FileInfo(validOptions); - return fileInfo.confirmOverwrite('test.js').then(function(action) { + return fileInfo.confirmOverwrite('test.js').then(function (action) { td.verify(ui.prompt(td.matchers.anything()), { times: 1 }); expect(action).to.equal('diff'); }); }); - it('renders a menu without diff and edit options when dealing with binary files', function() { + it('renders a menu without diff and edit options when dealing with binary files', function () { td.when(ui.prompt(td.matchers.anything())).thenReturn(Promise.resolve({ answer: 'skip' })); let binary = path.resolve(__dirname, '../../fixtures/problem-binary.png'); validOptions.inputPath = binary; let fileInfo = new FileInfo(validOptions); - return fileInfo.confirmOverwrite('test.png').then(function(/* action */) { + return fileInfo.confirmOverwrite('test.png').then(function (/* action */) { td.verify( ui.prompt( - td.matchers.argThat(function(options) { + td.matchers.argThat(function (options) { return options.choices.length === 2 && options.choices[0].key === 'y' && options.choices[1].key === 'n'; }) ) @@ -191,7 +188,7 @@ describe('Unit - FileInfo', function() { }); }); - it('normalizes line endings before comparing files', function() { + it('normalizes line endings before comparing files', function () { if (EOL === '\n') { return; } @@ -200,7 +197,7 @@ describe('Unit - FileInfo', function() { validOptions.outputPath = path.resolve(__dirname, '../../fixtures/file-info/test_lf.js'); let fileInfo = new FileInfo(validOptions); - return fileInfo.checkForConflict().then(function(type) { + return fileInfo.checkForConflict().then(function (type) { expect(type).to.equal('identical'); }); }); diff --git a/tests/unit/models/hardware-info-test.js b/tests/unit/models/hardware-info-test.js index 68812885a6..bc422eafdb 100644 --- a/tests/unit/models/hardware-info-test.js +++ b/tests/unit/models/hardware-info-test.js @@ -81,18 +81,18 @@ function stdout(value) { }); } -describe('models/hardware-info.js', function() { - afterEach(function() { +describe('models/hardware-info.js', function () { + afterEach(function () { td.reset(); }); - describe('.isUsingBattery', function() { - it('returns null for unsupported platforms', function() { + describe('.isUsingBattery', function () { + it('returns null for unsupported platforms', function () { expect(hwinfo.isUsingBattery('not-a-real-platform')).to.be.null; }); - describe('on FreeBSD', function() { - it('returns false via apm when not on battery', function() { + describe('on FreeBSD', function () { + it('returns false via apm when not on battery', function () { const stub = td.function(execa.sync); td.when(stub('apm'), { ignoreExtraArgs: true }).thenReturn({ stdout: '1\n' }); @@ -102,7 +102,7 @@ describe('models/hardware-info.js', function() { expect(hwinfo.isUsingBattery('freebsd')).to.be.false; }); - it('returns true via apm when on battery', function() { + it('returns true via apm when on battery', function () { const stub = td.function(execa.sync); td.when(stub('apm'), { ignoreExtraArgs: true }).thenReturn({ stdout: '0\n' }); @@ -112,7 +112,7 @@ describe('models/hardware-info.js', function() { expect(hwinfo.isUsingBattery('freebsd')).to.be.true; }); - it('returns false via upower when not on battery', function() { + it('returns false via upower when not on battery', function () { const stub = td.function(execa.sync); td.when(stub('apm'), { ignoreExtraArgs: true }).thenThrow(new Error('command not found')); @@ -122,7 +122,7 @@ describe('models/hardware-info.js', function() { expect(hwinfo.isUsingBattery('freebsd')).to.be.false; }); - it('returns true via upower when on battery', function() { + it('returns true via upower when on battery', function () { const stub = td.function(execa.sync); td.when(stub('apm'), { ignoreExtraArgs: true }).thenThrow(new Error('command not found')); @@ -132,7 +132,7 @@ describe('models/hardware-info.js', function() { expect(hwinfo.isUsingBattery('freebsd')).to.be.true; }); - it('returns null when battery status cannot be determined', function() { + it('returns null when battery status cannot be determined', function () { const stub = td.function(execa.sync); td.when(stub(), { ignoreExtraArgs: true }).thenThrow(new Error('command not found')); @@ -142,8 +142,8 @@ describe('models/hardware-info.js', function() { }); }); - describe('on Linux', function() { - it('returns false via /sys/class/power_supply when not on battery', function() { + describe('on Linux', function () { + it('returns false via /sys/class/power_supply when not on battery', function () { const execaStub = td.function(execa.sync); td.when(execaStub(), { ignoreExtraArgs: true }).thenThrow(new Error('command not found')); @@ -157,7 +157,7 @@ describe('models/hardware-info.js', function() { expect(hwinfo.isUsingBattery('linux')).to.be.false; }); - it('returns true via /sys/class/power_supply when on battery', function() { + it('returns true via /sys/class/power_supply when on battery', function () { const execaStub = td.function(execa.sync); td.when(execaStub(), { ignoreExtraArgs: true }).thenThrow(new Error('command not found')); @@ -171,7 +171,7 @@ describe('models/hardware-info.js', function() { expect(hwinfo.isUsingBattery('linux')).to.be.true; }); - it('returns false via acpi when not on battery', function() { + it('returns false via acpi when not on battery', function () { const execaStub = td.function(execa.sync); td.when(execaStub('acpi'), { ignoreExtraArgs: true }).thenReturn({ stdout: 'Adapter 0: on-line\n' }); @@ -186,7 +186,7 @@ describe('models/hardware-info.js', function() { expect(hwinfo.isUsingBattery('linux')).to.be.false; }); - it('returns true via acpi when on battery', function() { + it('returns true via acpi when on battery', function () { const execaStub = td.function(execa.sync); td.when(execaStub('acpi'), { ignoreExtraArgs: true }).thenReturn({ stdout: 'Adapter 0: off-line\n' }); @@ -201,7 +201,7 @@ describe('models/hardware-info.js', function() { expect(hwinfo.isUsingBattery('linux')).to.be.true; }); - it('returns false via upower when not on battery', function() { + it('returns false via upower when not on battery', function () { const execaStub = td.function(execa.sync); td.when(execaStub('acpi'), { ignoreExtraArgs: true }).thenThrow(new Error('command not found')); @@ -217,7 +217,7 @@ describe('models/hardware-info.js', function() { expect(hwinfo.isUsingBattery('linux')).to.be.false; }); - it('returns true via upower when on battery', function() { + it('returns true via upower when on battery', function () { const execaStub = td.function(execa.sync); td.when(execaStub('acpi'), { ignoreExtraArgs: true }).thenThrow(new Error('command not found')); @@ -233,7 +233,7 @@ describe('models/hardware-info.js', function() { expect(hwinfo.isUsingBattery('linux')).to.be.true; }); - it('returns null when battery status cannot be determined', function() { + it('returns null when battery status cannot be determined', function () { const execaStub = td.function(execa.sync); td.when(execaStub(), { ignoreExtraArgs: true }).thenThrow(new Error('command not found')); @@ -248,8 +248,8 @@ describe('models/hardware-info.js', function() { }); }); - describe('on macOS', function() { - it('returns false when not on battery', function() { + describe('on macOS', function () { + it('returns false when not on battery', function () { td.replace( execa, 'sync', @@ -261,7 +261,7 @@ describe('models/hardware-info.js', function() { expect(hwinfo.isUsingBattery('darwin')).to.be.false; }); - it('returns true when on battery', function() { + it('returns true when on battery', function () { td.replace( execa, 'sync', @@ -273,7 +273,7 @@ describe('models/hardware-info.js', function() { expect(hwinfo.isUsingBattery('darwin')).to.be.true; }); - it('returns null when an error occurs', function() { + it('returns null when an error occurs', function () { const stub = td.function(execa.sync); td.when(stub(), { ignoreExtraArgs: true }).thenThrow(new Error('whoops!')); @@ -283,8 +283,8 @@ describe('models/hardware-info.js', function() { }); }); - describe('on OpenBSD', function() { - it('returns false via apm when not on battery', function() { + describe('on OpenBSD', function () { + it('returns false via apm when not on battery', function () { const stub = td.function(execa.sync); td.when(stub('apm'), { ignoreExtraArgs: true }).thenReturn({ stdout: '1\n' }); @@ -294,7 +294,7 @@ describe('models/hardware-info.js', function() { expect(hwinfo.isUsingBattery('openbsd')).to.be.false; }); - it('returns true via apm when on battery', function() { + it('returns true via apm when on battery', function () { const stub = td.function(execa.sync); td.when(stub('apm'), { ignoreExtraArgs: true }).thenReturn({ stdout: '0\n' }); @@ -304,7 +304,7 @@ describe('models/hardware-info.js', function() { expect(hwinfo.isUsingBattery('openbsd')).to.be.true; }); - it('returns false via upower when not on battery', function() { + it('returns false via upower when not on battery', function () { const stub = td.function(execa.sync); td.when(stub('apm'), { ignoreExtraArgs: true }).thenThrow(new Error('command not found')); @@ -314,7 +314,7 @@ describe('models/hardware-info.js', function() { expect(hwinfo.isUsingBattery('openbsd')).to.be.false; }); - it('returns true via upower when on battery', function() { + it('returns true via upower when on battery', function () { const stub = td.function(execa.sync); td.when(stub('apm'), { ignoreExtraArgs: true }).thenThrow(new Error('command not found')); @@ -324,7 +324,7 @@ describe('models/hardware-info.js', function() { expect(hwinfo.isUsingBattery('openbsd')).to.be.true; }); - it('returns null when battery status cannot be determined', function() { + it('returns null when battery status cannot be determined', function () { const stub = td.function(execa.sync); td.when(stub(), { ignoreExtraArgs: true }).thenThrow(new Error('command not found')); @@ -334,8 +334,8 @@ describe('models/hardware-info.js', function() { }); }); - describe('on Windows', function() { - it('returns false when not on battery', function() { + describe('on Windows', function () { + it('returns false when not on battery', function () { td.replace( execa, 'sync', @@ -351,7 +351,7 @@ PowerOnline=TRUE expect(hwinfo.isUsingBattery('win32')).to.be.false; }); - it('returns true when on battery', function() { + it('returns true when on battery', function () { td.replace( execa, 'sync', @@ -367,7 +367,7 @@ PowerOnline=FALSE expect(hwinfo.isUsingBattery('win32')).to.be.true; }); - it('returns null when an error occurs', function() { + it('returns null when an error occurs', function () { const stub = td.function(execa.sync); td.when(stub(), { ignoreExtraArgs: true }).thenThrow(new Error('whoops!')); @@ -378,12 +378,12 @@ PowerOnline=FALSE }); }); - describe('.memorySwapUsed', function() { - it('returns null for unsupported platforms', function() { + describe('.memorySwapUsed', function () { + it('returns null for unsupported platforms', function () { expect(hwinfo.memorySwapUsed('not-a-real-platform')).to.be.null; }); - it('returns the expected value on FreeBSD', function() { + it('returns the expected value on FreeBSD', function () { td.replace( execa, 'sync', @@ -396,7 +396,7 @@ PowerOnline=FALSE expect(hwinfo.memorySwapUsed('freebsd')).to.equal(1865728); }); - it('returns null on FreeBSD when an error occurs', function() { + it('returns null on FreeBSD when an error occurs', function () { const stub = td.function(execa.sync); td.when(stub(), { ignoreExtraArgs: true }).thenThrow(new Error('whoops!')); @@ -405,7 +405,7 @@ PowerOnline=FALSE expect(hwinfo.memorySwapUsed('freebsd')).to.be.null; }); - it('returns the expected value on Linux', function() { + it('returns the expected value on Linux', function () { td.replace( execa, 'sync', @@ -418,7 +418,7 @@ Swap: 67448598528 121593856 67327004672 expect(hwinfo.memorySwapUsed('linux')).to.equal(121593856); }); - it('returns null on Linux when an error occurs', function() { + it('returns null on Linux when an error occurs', function () { const stub = td.function(execa.sync); td.when(stub(), { ignoreExtraArgs: true }).thenThrow(new Error('whoops!')); @@ -427,7 +427,7 @@ Swap: 67448598528 121593856 67327004672 expect(hwinfo.memorySwapUsed('linux')).to.be.null; }); - it('returns the expected value on macOS', function() { + it('returns the expected value on macOS', function () { td.replace( execa, 'sync', @@ -438,7 +438,7 @@ Swap: 67448598528 121593856 67327004672 expect(hwinfo.memorySwapUsed('darwin')).to.equal(5230034944); }); - it('returns null on macOS when an error occurs', function() { + it('returns null on macOS when an error occurs', function () { const stub = td.function(execa.sync); td.when(stub(), { ignoreExtraArgs: true }).thenThrow(new Error('whoops!')); @@ -447,7 +447,7 @@ Swap: 67448598528 121593856 67327004672 expect(hwinfo.memorySwapUsed('darwin')).to.be.null; }); - it('returns the expected value on OpenBSD', function() { + it('returns the expected value on OpenBSD', function () { td.replace( execa, 'sync', @@ -460,7 +460,7 @@ Swap: 67448598528 121593856 67327004672 expect(hwinfo.memorySwapUsed('openbsd')).to.equal(932864); }); - it('returns null on OpenBSD when an error occurs', function() { + it('returns null on OpenBSD when an error occurs', function () { const stub = td.function(execa.sync); td.when(stub(), { ignoreExtraArgs: true }).thenThrow(new Error('whoops!')); @@ -469,7 +469,7 @@ Swap: 67448598528 121593856 67327004672 expect(hwinfo.memorySwapUsed('openbsd')).to.be.null; }); - it('returns the expected value on Windows', function() { + it('returns the expected value on Windows', function () { td.replace( execa, 'sync', @@ -485,7 +485,7 @@ CurrentUsage=325 expect(hwinfo.memorySwapUsed('win32')).to.equal(340787200); }); - it('returns null on Windows when an error occurs', function() { + it('returns null on Windows when an error occurs', function () { const stub = td.function(execa.sync); td.when(stub(), { ignoreExtraArgs: true }).thenThrow(new Error('whoops!')); @@ -495,14 +495,14 @@ CurrentUsage=325 }); }); - describe('.processorLoad', function() { - it('returns null on Windows', function() { + describe('.processorLoad', function () { + it('returns null on Windows', function () { expect(hwinfo.processorLoad('win32')).to.be.null; }); }); - describe('.processorSpeed', function() { - it("averages the processors' speeds", function() { + describe('.processorSpeed', function () { + it("averages the processors' speeds", function () { td.replace(os, 'cpus', () => [{ speed: 1 }, { speed: 2 }, { speed: 3 }, { speed: 4 }, { speed: 5 }]); expect(hwinfo.processorSpeed()).to.equal(3); diff --git a/tests/unit/models/host-info-cache-test.js b/tests/unit/models/host-info-cache-test.js new file mode 100644 index 0000000000..99d8c16116 --- /dev/null +++ b/tests/unit/models/host-info-cache-test.js @@ -0,0 +1,192 @@ +'use strict'; + +/** + * Tests for the various proxies and instances once the project has initialized + * its addons + */ +const expect = require('chai').expect; +const FixturifyProject = require('../../helpers/fixturify-project'); + +describe('Unit | host-addons-utils', function () { + let fixturifyProject; + + beforeEach(function () { + fixturifyProject = new FixturifyProject('awesome-proj', '1.0.0'); + fixturifyProject.addDevDependency('ember-cli', '*'); + }); + + afterEach(function () { + fixturifyProject.dispose(); + }); + + it('multiple lazy engines in project, including nested lazy engines', function () { + fixturifyProject.addEngine('lazy-engine-a', '1.0.0', { enableLazyLoading: true }); + + fixturifyProject.addAddon('addon-a', '1.0.0', { + enableLazyLoading: true, + callback: (addon) => { + addon.addEngine('lazy-engine-b', '1.0.0', { + enableLazyLoading: true, + callback: (engine) => { + engine.addReferenceDependency('lazy-engine-a'); + engine.addEngine('lazy-engine-c', '1.0.0', { enableLazyLoading: true }); + }, + }); + }, + }); + + fixturifyProject.writeSync(); + let project = fixturifyProject.buildProjectModel(); + + project.initializeAddons(); + + const app = {}; + + const lazyEngineA = project.addons.find((addon) => addon.name === 'lazy-engine-a'); + lazyEngineA.app = app; + const pkgInfoLazyEngineA = lazyEngineA._packageInfo; + + const addonA = project.addons.find((addon) => addon.name === 'addon-a'); + addonA.app = app; + const pkgInfoAddonA = addonA._packageInfo; + + let { hostPackageInfo, hostAndAncestorBundledPackageInfos } = project.hostInfoCache.getHostAddonInfo( + pkgInfoLazyEngineA + ); + + expect(hostPackageInfo).to.equal(project._packageInfo, 'host package-info for lazy-engine A is the project'); + expect(project.hostInfoCache.findLCAHost(lazyEngineA)).to.equal(lazyEngineA.app, 'LCA host is the app'); + + expect(hostAndAncestorBundledPackageInfos).to.deep.equal( + new Set([pkgInfoAddonA]), + 'host packge-infos for lazy-engine A includes only addon-a' + ); + + const lazyEngineB = project.addons + .find((addon) => addon.name === 'addon-a') + .addons.find((addon) => addon.name === 'lazy-engine-b'); + + const pkgInfoLazyEngineB = lazyEngineB._packageInfo; + + ({ hostPackageInfo, hostAndAncestorBundledPackageInfos } = project.hostInfoCache.getHostAddonInfo( + pkgInfoLazyEngineB + )); + + expect(hostPackageInfo).to.equal(project._packageInfo, 'host package-info for lazy-engine B is the project'); + expect(project.hostInfoCache.findLCAHost(lazyEngineB)).to.equal(lazyEngineA.app, 'LCA host is the app'); + expect(hostAndAncestorBundledPackageInfos).to.deep.equal( + new Set([pkgInfoAddonA]), + 'host packge-infos for lazy-engine B includes only addon-a' + ); + + const lazyEngineC = project.addons + .find((addon) => addon.name === 'addon-a') + .addons.find((addon) => addon.name === 'lazy-engine-b') + .addons.find((addon) => addon.name === 'lazy-engine-c'); + + const pkgInfoLazyEngineC = lazyEngineC._packageInfo; + + ({ hostPackageInfo, hostAndAncestorBundledPackageInfos } = project.hostInfoCache.getHostAddonInfo( + pkgInfoLazyEngineC + )); + + expect(hostPackageInfo).to.equal(pkgInfoLazyEngineB, 'host package-info for lazy-engine C is lazy engine B'); + + expect(project.hostInfoCache.findLCAHost(lazyEngineC)).to.equal( + lazyEngineB, + 'LCA host for lazy engine C is lazy engine B' + ); + + expect(hostAndAncestorBundledPackageInfos).to.deep.equal( + new Set([pkgInfoAddonA]), + 'host packge-infos for lazy-engine C includes addon-a' + ); + }); + + it('multiple lazy engines in project, including nested lazy engines; some nested lazy engines have non-lazy deps', function () { + fixturifyProject.addEngine('lazy-engine-a', '1.0.0', { enableLazyLoading: true }); + + fixturifyProject.addAddon('addon-a', '1.0.0', { + enableLazyLoading: true, + callback: (addon) => { + addon.addEngine('lazy-engine-b', '1.0.0', { + enableLazyLoading: true, + callback: (engine) => { + engine.addReferenceDependency('lazy-engine-a'); + engine.addAddon('addon-b', '1.0.0'); + engine.addEngine('lazy-engine-c', '1.0.0', { enableLazyLoading: true }); + }, + }); + }, + }); + + fixturifyProject.writeSync(); + let project = fixturifyProject.buildProjectModel(); + + project.initializeAddons(); + + const pkgInfoAddonA = project.addons.find((addon) => addon.name === 'addon-a')._packageInfo; + + const pkgInfoLazyEngineB = project.addons + .find((addon) => addon.name === 'addon-a') + .addons.find((addon) => addon.name === 'lazy-engine-b')._packageInfo; + + const pkgInfoAddonB = project.addons + .find((addon) => addon.name === 'addon-a') + .addons.find((addon) => addon.name === 'lazy-engine-b') + .addons.find((addon) => addon.name === 'addon-b')._packageInfo; + + const pkgInfoLazyEngineC = project.addons + .find((addon) => addon.name === 'addon-a') + .addons.find((addon) => addon.name === 'lazy-engine-b') + .addons.find((addon) => addon.name === 'lazy-engine-c')._packageInfo; + + let { hostPackageInfo, hostAndAncestorBundledPackageInfos } = project.hostInfoCache.getHostAddonInfo( + pkgInfoLazyEngineC + ); + + expect(hostPackageInfo).to.equal(pkgInfoLazyEngineB, 'host package-info for lazy-engine C is lazy engine B'); + expect(hostAndAncestorBundledPackageInfos).to.deep.equal( + new Set([pkgInfoAddonA, pkgInfoAddonB]), + 'host packge-infos for lazy-engine C includes addon-a, addon-b' + ); + }); + + it('multiple lazy engines at same level with a common ancestor host', function () { + fixturifyProject.addInRepoEngine('lazy-engine-a', '1.0.0', { enableLazyLoading: true }); + fixturifyProject.pkg['ember-addon'].paths = []; + + fixturifyProject.addInRepoEngine('lazy-engine-b', '1.0.0', { + enableLazyLoading: true, + callback: (engine) => { + engine.pkg['ember-addon'].paths = ['../lazy-engine-a']; + }, + }); + + fixturifyProject.addInRepoEngine('lazy-engine-c', '1.0.0', { + enableLazyLoading: true, + callback: (engine) => { + engine.pkg['ember-addon'].paths = ['../lazy-engine-a']; + }, + }); + + fixturifyProject.writeSync(); + let project = fixturifyProject.buildProjectModel(); + + project.initializeAddons(); + + const pkgInfoLazyEngineA = project.addons + .find((addon) => addon.name === 'lazy-engine-b') + .addons.find((addon) => addon.name === 'lazy-engine-a')._packageInfo; + + let { hostPackageInfo, hostAndAncestorBundledPackageInfos } = project.hostInfoCache.getHostAddonInfo( + pkgInfoLazyEngineA + ); + + expect(hostPackageInfo).to.equal(project._packageInfo, 'host package-info for lazy-engine A is the project'); + expect(hostAndAncestorBundledPackageInfos).to.deep.equal( + new Set([]), + 'host packge-infos for lazy-engine A has no non-lazy deps' + ); + }); +}); diff --git a/tests/unit/models/installation-checker-test.js b/tests/unit/models/installation-checker-test.js index 7e6eecc18a..0462bf35eb 100644 --- a/tests/unit/models/installation-checker-test.js +++ b/tests/unit/models/installation-checker-test.js @@ -4,7 +4,7 @@ const expect = require('chai').expect; const InstallationChecker = require('../../../lib/models/installation-checker'); const path = require('path'); -describe('Installation Checker', function() { +describe('Installation Checker', function () { let installationChecker; function fixturePath(pathToFile) { @@ -15,8 +15,8 @@ describe('Installation Checker', function() { installationChecker.checkInstallations(); } - describe('bower', function() { - it('works when installation directory exist', function() { + describe('bower', function () { + it('works when installation directory exist', function () { let project = { root: fixturePath('installation-checker/valid-bower-installation'), bowerDirectory: fixturePath('installation-checker/valid-bower-installation/bower_components'), @@ -26,7 +26,7 @@ describe('Installation Checker', function() { expect(checkInstallations).to.not.throw(/No dependencies installed/); }); - it("fails when installation directory doesn't exist", function() { + it("fails when installation directory doesn't exist", function () { let project = { root: fixturePath('installation-checker/invalid-bower-installation'), bowerDirectory: fixturePath('installation-checker/invalid-bower-installation/bower_components'), @@ -37,8 +37,8 @@ describe('Installation Checker', function() { }); }); - describe('npm', function() { - it('works when installation directory exist', function() { + describe('npm', function () { + it('works when installation directory exist', function () { let project = { root: fixturePath('installation-checker/valid-npm-installation'), }; @@ -47,7 +47,7 @@ describe('Installation Checker', function() { expect(checkInstallations).to.not.throw(/No dependencies installed/); }); - it("fails when installation directory doesn't exist", function() { + it("fails when installation directory doesn't exist", function () { let project = { root: fixturePath('installation-checker/invalid-npm-installation'), }; @@ -57,8 +57,8 @@ describe('Installation Checker', function() { }); }); - describe('npm and bower', function() { - it('fails reporting both dependencies', function() { + describe('npm and bower', function () { + it('fails reporting both dependencies', function () { let project = { root: fixturePath('installation-checker/invalid-bower-and-npm'), bowerDirectory: fixturePath('installation-checker/invalid-bower-and-npm/bower_components'), @@ -68,7 +68,7 @@ describe('Installation Checker', function() { expect(checkInstallations).to.throw(/^InstallationChecker: Unable to parse: .*package.json/); }); - it('ignores directories without bower.js and package.json files', function() { + it('ignores directories without bower.js and package.json files', function () { let project = { root: fixturePath('installation-checker/empty'), bowerDirectory: fixturePath('installation-checker/empty/bower_components'), diff --git a/tests/unit/models/instantiate-addons-test.js b/tests/unit/models/instantiate-addons-test.js index 082e748a93..f21fa2da18 100644 --- a/tests/unit/models/instantiate-addons-test.js +++ b/tests/unit/models/instantiate-addons-test.js @@ -3,19 +3,19 @@ const FixturifyProject = require('../../helpers/fixturify-project'); const expect = require('chai').expect; -describe('models/instatiate-addons.js', function() { +describe('models/instatiate-addons.js', function () { let fixturifyProject; - beforeEach(function() { + beforeEach(function () { fixturifyProject = new FixturifyProject('awesome-proj', '0.0.0'); fixturifyProject.addDevDependency('ember-cli', '*'); }); - afterEach(function() { + afterEach(function () { fixturifyProject.dispose(); }); - it('ordering without before/after', function() { + it('ordering without before/after', function () { // this tests ordering is very important to maintain, it tests some naunced // details which must be maintained fixturifyProject.addAddon('foo', '1.0.0'); @@ -38,7 +38,7 @@ describe('models/instatiate-addons.js', function() { project.initializeAddons(); - expect(project.addons.map(a => ({ name: a.pkg.name, version: a.pkg.version }))).to.deep.eql([ + expect(project.addons.map((a) => ({ name: a.pkg.name, version: a.pkg.version }))).to.deep.eql([ { name: 'a', version: '2.0.0' }, { name: 'b', version: '2.0.0' }, { name: 'c', version: '2.0.0' }, @@ -49,10 +49,10 @@ describe('models/instatiate-addons.js', function() { ]); }); - it('ordering with before specified', function() { + it('ordering with before specified', function () { fixturifyProject.addAddon('foo', '1.0.0'); fixturifyProject.addAddon('bar', '1.0.0'); - fixturifyProject.addAddon('qux', '1.0.0', a => (a.pkg['ember-addon'].before = 'foo')); + fixturifyProject.addAddon('qux', '1.0.0', (a) => (a.pkg['ember-addon'].before = 'foo')); fixturifyProject.writeSync(); @@ -60,13 +60,13 @@ describe('models/instatiate-addons.js', function() { project.initializeAddons(); - expect(project.addons.map(a => a.name)).to.deep.eql(['bar', 'qux', 'foo']); + expect(project.addons.map((a) => a.name)).to.deep.eql(['bar', 'qux', 'foo']); }); - it('ordering with after specified', function() { + it('ordering with after specified', function () { fixturifyProject.addAddon('foo', '1.0.0'); fixturifyProject.addAddon('bar', '1.0.0'); - fixturifyProject.addAddon('qux', '1.0.0', a => (a.pkg['ember-addon'].after = 'foo')); + fixturifyProject.addAddon('qux', '1.0.0', (a) => (a.pkg['ember-addon'].after = 'foo')); fixturifyProject.writeSync(); @@ -74,13 +74,13 @@ describe('models/instatiate-addons.js', function() { project.initializeAddons(); - expect(project.addons.map(a => a.name)).to.deep.eql(['bar', 'foo', 'qux']); + expect(project.addons.map((a) => a.name)).to.deep.eql(['bar', 'foo', 'qux']); }); - it('ordering always matches package.json name (index.js name is ignored)', function() { + it('ordering always matches package.json name (index.js name is ignored)', function () { let foo = fixturifyProject.addAddon('lol', '1.0.0'); foo.files['index.js'] = 'module.exports = { name: "foo" };'; - fixturifyProject.addAddon('qux', '1.0.0', a => (a.pkg['ember-addon'].before = 'foo')); + fixturifyProject.addAddon('qux', '1.0.0', (a) => (a.pkg['ember-addon'].before = 'foo')); fixturifyProject.writeSync(); @@ -88,12 +88,12 @@ describe('models/instatiate-addons.js', function() { project.initializeAddons(); - expect(project.addons.map(a => a.name)).to.deep.eql(['foo', 'qux']); + expect(project.addons.map((a) => a.name)).to.deep.eql(['foo', 'qux']); }); - it('errors when there is a cycle detected', function() { - fixturifyProject.addAddon('foo', '1.0.0', a => (a.pkg['ember-addon'].after = 'qux')); - fixturifyProject.addAddon('qux', '1.0.0', a => (a.pkg['ember-addon'].after = 'foo')); + it('errors when there is a cycle detected', function () { + fixturifyProject.addAddon('foo', '1.0.0', (a) => (a.pkg['ember-addon'].after = 'qux')); + fixturifyProject.addAddon('qux', '1.0.0', (a) => (a.pkg['ember-addon'].after = 'foo')); fixturifyProject.writeSync(); diff --git a/tests/unit/models/instrumentation-test.js b/tests/unit/models/instrumentation-test.js index c54819b46f..8305a86daa 100644 --- a/tests/unit/models/instrumentation-test.js +++ b/tests/unit/models/instrumentation-test.js @@ -24,8 +24,8 @@ const tmproot = path.join(root, 'tmp'); let instrumentation; -describe('models/instrumentation.js', function() { - afterEach(async function() { +describe('models/instrumentation.js', function () { + afterEach(async function () { delete process.env.BROCCOLI_VIZ; delete process.env.EMBER_CLI_INSTRUMENTATION; @@ -33,20 +33,20 @@ describe('models/instrumentation.js', function() { await fse.remove(tmproot); }); - describe('._enableFSMonitorIfInstrumentationEnabled', function() { + describe('._enableFSMonitorIfInstrumentationEnabled', function () { let originalStatSync = fs.statSync; - beforeEach(function() { + beforeEach(function () { expect(!!process.env.BROCCOLI_VIZ).to.eql(false); expect(!!process.env.EMBER_CLI_INSTRUMENTATION).to.eql(false); expect(fs.statSync).to.equal(originalStatSync); }); - afterEach(function() { + afterEach(function () { td.reset(); }); - it('if VIZ is NOT enabled, do not monitor', function() { + it('if VIZ is NOT enabled, do not monitor', function () { let monitor = Instrumentation._enableFSMonitorIfInstrumentationEnabled(); try { expect(fs.statSync).to.equal(originalStatSync); @@ -58,7 +58,7 @@ describe('models/instrumentation.js', function() { } }); - it('if VIZ is enabled, monitor', function() { + it('if VIZ is enabled, monitor', function () { process.env.BROCCOLI_VIZ = '1'; let monitor = Instrumentation._enableFSMonitorIfInstrumentationEnabled(); try { @@ -70,7 +70,7 @@ describe('models/instrumentation.js', function() { } }); - it('if instrumentation is enabled, monitor', function() { + it('if instrumentation is enabled, monitor', function () { process.env.EMBER_CLI_INSTRUMENTATION = '1'; let monitor = Instrumentation._enableFSMonitorIfInstrumentationEnabled(); try { @@ -82,7 +82,7 @@ describe('models/instrumentation.js', function() { } }); - it('if enableInstrumentation is NOT enabled in .ember-cli, do not monitor', function() { + it('if enableInstrumentation is NOT enabled in .ember-cli, do not monitor', function () { let mockedYam = new Yam('ember-cli', { primary: `${process.cwd()}/tests/fixtures/instrumentation-disabled-config`, }); @@ -97,7 +97,7 @@ describe('models/instrumentation.js', function() { } }); - it('if enableInstrumentation is enabled in .ember-cli, monitor', function() { + it('if enableInstrumentation is enabled in .ember-cli, monitor', function () { let mockedYam = new Yam('ember-cli', { primary: `${process.cwd()}/tests/fixtures/instrumentation-enabled-config`, }); @@ -112,25 +112,25 @@ describe('models/instrumentation.js', function() { }); }); - describe('constructor', function() { + describe('constructor', function () { const heimdall = require('heimdalljs'); let heimdallStart; - beforeEach(function() { + beforeEach(function () { heimdallStart = td.replace(heimdall, 'start'); }); - afterEach(function() { + afterEach(function () { delete process.env.EMBER_CLI_INSTRUMENTATION; td.reset(); }); - describe('when instrumentation is enabled', function() { - beforeEach(function() { + describe('when instrumentation is enabled', function () { + beforeEach(function () { process.env.EMBER_CLI_INSTRUMENTATION = '1'; }); - it('starts an init node if init instrumentation is missing', function() { + it('starts an init node if init instrumentation is missing', function () { let mockToken = {}; td.when( @@ -151,7 +151,7 @@ describe('models/instrumentation.js', function() { expect(instrumentation.instrumentations.init.node).to.not.equal(undefined); }); - it('does not create an init node if init instrumentation is included', function() { + it('does not create an init node if init instrumentation is included', function () { let mockToken = {}; let mockInstrumentation = {}; @@ -165,7 +165,7 @@ describe('models/instrumentation.js', function() { td.verify(heimdallStart(), { times: 0, ignoreExtraArgs: true }); }); - it('does not warn if init instrumentation is included', function() { + it('does not warn if init instrumentation is included', function () { td.when(heimdallStart('init')); let mockInstrumentation = {}; @@ -181,12 +181,12 @@ describe('models/instrumentation.js', function() { }); }); - describe('when instrumentation is not enabled', function() { - beforeEach(function() { + describe('when instrumentation is not enabled', function () { + beforeEach(function () { expect(process.env.EMBER_CLI_INSTRUMENTATION).to.eql(undefined); }); - it('does not create an init node if init instrumentation is missing', function() { + it('does not create an init node if init instrumentation is missing', function () { let mockToken = {}; td.when(heimdallStart('init')).thenReturn(mockToken); @@ -197,7 +197,7 @@ describe('models/instrumentation.js', function() { td.verify(heimdallStart(), { times: 0, ignoreExtraArgs: true }); }); - it('does not warn when init instrumentation is missing', function() { + it('does not warn when init instrumentation is missing', function () { td.when(heimdallStart('init')); let ui = new MockUI(); @@ -211,11 +211,11 @@ describe('models/instrumentation.js', function() { }); }); - describe('.isVizEnabled', function() { + describe('.isVizEnabled', function () { let originalWarn = console.warn; let warnInvocations; - beforeEach(function() { + beforeEach(function () { instrumentation = new Instrumentation({ ui: new MockUI(), }); @@ -223,37 +223,37 @@ describe('models/instrumentation.js', function() { delete process.env.BROCCOLI_VIZ; delete process.env.EMBER_CLI_INSTRUMENTATION; warnInvocations = []; - console.warn = function() { + console.warn = function () { warnInvocations.push.apply(warnInvocations, Array.prototype.slice.call(arguments)); }; }); - afterEach(function() { + afterEach(function () { console.warn = originalWarn; }); - it('is true and does not warn if BROCCOLI_VIZ=1', function() { + it('is true and does not warn if BROCCOLI_VIZ=1', function () { process.env.BROCCOLI_VIZ = '1'; expect(instrumentation.isVizEnabled()).to.eql(true); expect(warnInvocations).to.eql([]); }); - it('is true and warns at most once if BROCCOLI_VIZ is set but not 1', function() { + it('is true and warns at most once if BROCCOLI_VIZ is set but not 1', function () { process.env.BROCCOLI_VIZ = 'on'; expect(instrumentation.isVizEnabled()).to.eql(true); expect(instrumentation.isVizEnabled()).to.eql(true); expect(warnInvocations).to.eql(["Please set BROCCOLI_VIZ=1 to enable visual instrumentation, rather than 'on'"]); }); - it('is false if BROCCOLI_VIZ is unset', function() { + it('is false if BROCCOLI_VIZ is unset', function () { expect('BROCCOLI_VIZ' in process.env).to.eql(false); expect(instrumentation.isVizEnabled()).to.eql(false); expect(warnInvocations).to.eql([]); }); }); - describe('.isEnabled', function() { - beforeEach(function() { + describe('.isEnabled', function () { + beforeEach(function () { instrumentation = new Instrumentation({ ui: new MockUI(), }); @@ -261,41 +261,41 @@ describe('models/instrumentation.js', function() { delete process.env.EMBER_CLI_INSTRUMENTATION; }); - it('is true if BROCCOLI_VIZ=1', function() { + it('is true if BROCCOLI_VIZ=1', function () { process.env.BROCCOLI_VIZ = '1'; expect(instrumentation.isEnabled()).to.eql(true); }); - it('is true if EMBER_CLI_INSTRUMENTATION=1', function() { + it('is true if EMBER_CLI_INSTRUMENTATION=1', function () { process.env.EMBER_CLI_INSTRUMENTATION = '1'; expect(instrumentation.isEnabled()).to.eql(true); }); - it('is false if EMBER_CLI_INSTRUMENTATION != 1', function() { + it('is false if EMBER_CLI_INSTRUMENTATION != 1', function () { process.env.EMBER_CLI_INSTRUMENTATION = 'on'; expect(instrumentation.isEnabled()).to.eql(false); }); - it('is false if both BROCCOLI_VIZ and EMBER_CLI_INSTRUMENTATION are unset', function() { + it('is false if both BROCCOLI_VIZ and EMBER_CLI_INSTRUMENTATION are unset', function () { expect('BROCCOLI_VIZ' in process.env).to.eql(false); expect('EMBER_CLI_INSTRUMENTATION' in process.env).to.eql(false); expect(instrumentation.isEnabled()).to.eql(false); }); }); - describe('.start', function() { + describe('.start', function () { let project; let instrumentation; let heimdall; - beforeEach(function() { + beforeEach(function () { project = new MockProject(); instrumentation = project._instrumentation; instrumentation._heimdall = heimdall = new Heimdall(); process.env.EMBER_CLI_INSTRUMENTATION = '1'; }); - it('starts a new subtree for name', function() { + it('starts a new subtree for name', function () { let heimdallStart = td.replace(heimdall, 'start'); instrumentation.start('init'); @@ -343,7 +343,7 @@ describe('models/instrumentation.js', function() { ); }); - it('does not start a subtree if instrumentation is disabled', function() { + it('does not start a subtree if instrumentation is disabled', function () { process.env.EMBER_CLI_INSTRUMENTATION = 'no thanks'; let heimdallStart = td.replace(heimdall, 'start'); @@ -353,13 +353,13 @@ describe('models/instrumentation.js', function() { td.verify(heimdallStart(), { times: 0, ignoreExtraArgs: true }); }); - it('throws if name is unexpected', function() { + it('throws if name is unexpected', function () { expect(() => { instrumentation.start('a party!'); }).to.throw('No such instrumentation "a party!"'); }); - it('removes any prior instrumentation information to avoid leaks', function() { + it('removes any prior instrumentation information to avoid leaks', function () { function build() { instrumentation.start('build'); let a = heimdall.start('a'); @@ -391,13 +391,13 @@ describe('models/instrumentation.js', function() { }); }); - describe('.stopAndReport', function() { + describe('.stopAndReport', function () { let project; let instrumentation; let heimdall; let addon; - beforeEach(function() { + beforeEach(function () { project = new MockProject(); instrumentation = project._instrumentation; heimdall = instrumentation._heimdall = new Heimdall(); @@ -414,17 +414,17 @@ describe('models/instrumentation.js', function() { ]; }); - it('throws if name is unexpected', function() { + it('throws if name is unexpected', function () { expect(() => instrumentation.stopAndReport('the weather')).to.throw('No such instrumentation "the weather"'); }); - it('throws if name has not yet started', function() { + it('throws if name has not yet started', function () { expect(() => instrumentation.stopAndReport('init')).to.throw( 'Cannot stop instrumentation "init". It has not started.' ); }); - it('warns if heimdall stop throws (eg when unbalanced)', function() { + it('warns if heimdall stop throws (eg when unbalanced)', function () { instrumentation.start('init'); heimdall.start('a ruckus'); @@ -436,7 +436,7 @@ describe('models/instrumentation.js', function() { expect(() => instrumentation.stopAndReport('init')).to.not.throw(); }); - it('computes summary for name', function() { + it('computes summary for name', function () { let buildSummary = td.replace(instrumentation, '_buildSummary'); let initSummary = td.replace(instrumentation, '_initSummary'); let treeFor = td.replace(instrumentation, '_instrumentationTreeFor'); @@ -499,8 +499,8 @@ describe('models/instrumentation.js', function() { ); }); - describe('writes to disk', function() { - beforeEach(function() { + describe('writes to disk', function () { + beforeEach(function () { let buildSummary = td.replace(instrumentation, '_buildSummary'); let initSummary = td.replace(instrumentation, '_initSummary'); let treeFor = td.replace(instrumentation, '_instrumentationTreeFor'); @@ -526,7 +526,7 @@ describe('models/instrumentation.js', function() { process.env.EMBER_CLI_INSTRUMENTATION = '1'; }); - it('writes instrumentation info if viz is enabled', async function() { + it('writes instrumentation info if viz is enabled', async function () { process.env.BROCCOLI_VIZ = '1'; await mkTmpDirIn(tmproot); @@ -561,7 +561,7 @@ describe('models/instrumentation.js', function() { }); }); - it('does not write instrumentation info if viz is disabled', async function() { + it('does not write instrumentation info if viz is disabled', async function () { delete process.env.BROCCOLI_VIZ; await mkTmpDirIn(tmproot); @@ -585,13 +585,13 @@ describe('models/instrumentation.js', function() { }); }); - describe('addons', function() { + describe('addons', function () { let mockInitSummary; let mockInitTree; let mockBuildSummary; let mockBuildTree; - beforeEach(function() { + beforeEach(function () { let buildSummary = td.replace(instrumentation, '_buildSummary'); let initSummary = td.replace(instrumentation, '_initSummary'); let treeFor = td.replace(instrumentation, '_instrumentationTreeFor'); @@ -607,7 +607,7 @@ describe('models/instrumentation.js', function() { td.when(treeFor('build')).thenReturn(mockBuildTree); }); - it('invokes addons that have [INSTRUMENTATION] for init', function() { + it('invokes addons that have [INSTRUMENTATION] for init', function () { process.env.EMBER_CLI_INSTRUMENTATION = '1'; let hook = td.function(); @@ -619,7 +619,7 @@ describe('models/instrumentation.js', function() { td.verify(hook('init', { summary: mockInitSummary, tree: mockInitTree })); }); - it('invokes addons that have [INSTRUMENTATION] for build', function() { + it('invokes addons that have [INSTRUMENTATION] for build', function () { process.env.EMBER_CLI_INSTRUMENTATION = '1'; let hook = td.function(); @@ -631,7 +631,7 @@ describe('models/instrumentation.js', function() { td.verify(hook('build', { summary: mockBuildSummary, tree: mockBuildTree })); }); - it('does not invoke addons if instrumentation is disabled', function() { + it('does not invoke addons if instrumentation is disabled', function () { process.env.EMBER_CLI_INSTRUMENTATION = 'not right now thanks'; let hook = td.function(); @@ -645,7 +645,7 @@ describe('models/instrumentation.js', function() { }); }); - describe('._instrumenationTreeFor', function() { + describe('._instrumenationTreeFor', function () { function StatsSchema() { this.x = 0; this.y = 0; @@ -696,9 +696,9 @@ describe('models/instrumentation.js', function() { expect(Object.keys(json)).to.eql(['nodes']); expect(json.nodes.length).to.eql(8); - expect(json.nodes.map(x => x.id)).to.eql([1, 2, 3, 4, 5, 6, 7, 8]); + expect(json.nodes.map((x) => x.id)).to.eql([1, 2, 3, 4, 5, 6, 7, 8]); - expect(json.nodes.map(x => x.label)).to.eql([ + expect(json.nodes.map((x) => x.label)).to.eql([ { name, emberCLI: true }, { name: 'a' }, { name: 'b1', broccoliNode: true, broccoliCachedNode: false }, @@ -709,10 +709,10 @@ describe('models/instrumentation.js', function() { { name: 'c3' }, ]); - expect(json.nodes.map(x => x.children)).to.eql([[2], [3, 5], [4], [], [6, 8], [7], [], []]); + expect(json.nodes.map((x) => x.children)).to.eql([[2], [3, 5], [4], [], [6, 8], [7], [], []]); - let stats = json.nodes.map(x => x.stats); - stats.forEach(nodeStats => { + let stats = json.nodes.map((x) => x.stats); + stats.forEach((nodeStats) => { expect('own' in nodeStats).to.eql(true); expect('time' in nodeStats).to.eql(true); expect(nodeStats.time.self).to.be.within(0, 2000000); //2ms in nanoseconds @@ -726,15 +726,15 @@ describe('models/instrumentation.js', function() { } function assertTreeValidAPI(name, tree) { - let depthFirstNames = Array.from(tree.dfsIterator()).map(x => x.label.name); + let depthFirstNames = Array.from(tree.dfsIterator()).map((x) => x.label.name); expect(depthFirstNames, 'depth first name order').to.eql([name, 'a', 'b1', 'c1', 'b2', 'c2', 'd1', 'c3']); - let breadthFirstNames = Array.from(tree.bfsIterator()).map(x => x.label.name); + let breadthFirstNames = Array.from(tree.bfsIterator()).map((x) => x.label.name); expect(breadthFirstNames, 'breadth first name order').to.eql([name, 'a', 'b1', 'b2', 'c1', 'c2', 'c3', 'd1']); - let c2 = Array.from(tree.dfsIterator()).filter(x => x.label.name === 'c2')[0]; + let c2 = Array.from(tree.dfsIterator()).filter((x) => x.label.name === 'c2')[0]; - let ancestorNames = Array.from(c2.ancestorsIterator()).map(x => x.label.name); + let ancestorNames = Array.from(c2.ancestorsIterator()).map((x) => x.label.name); expect(ancestorNames).to.eql(['b2', 'a', name]); } @@ -743,24 +743,24 @@ describe('models/instrumentation.js', function() { assertTreeValidAPI(name, tree); } - it('produces a valid tree for init', function() { + it('produces a valid tree for init', function () { process.env.EMBER_CLI_INSTRUMENTATION = '1'; makeTree('init'); assertTreeValid('init', instrumentation._instrumentationTreeFor('init')); }); - it('produces a valid tree for build', function() { + it('produces a valid tree for build', function () { process.env.EMBER_CLI_INSTRUMENTATION = '1'; makeTree('build'); assertTreeValid('build', instrumentation._instrumentationTreeFor('build')); }); }); - describe('summaries', function() { + describe('summaries', function () { let instrTree; let instrumentation; - beforeEach(function() { + beforeEach(function () { instrumentation = new Instrumentation({ ui: new MockUI() }); let heimdall = new Heimdall(); @@ -779,8 +779,8 @@ describe('models/instrumentation.js', function() { process.env.EMBER_CLI_INSTRUMENTATION = '1'; }); - describe('._buildSummary', function() { - it('computes initial build sumamries', function() { + describe('._buildSummary', function () { + it('computes initial build sumamries', function () { let result = { directory: 'tmp/someplace', outputChanges: ['assets/foo.js', 'assets/foo.css'], @@ -806,7 +806,7 @@ describe('models/instrumentation.js', function() { expect(Object.keys(summary.platform)).to.eql(['name', ...Object.keys(hwinfo), 'collectionTime']); }); - it('computes rebuild summaries', function() { + it('computes rebuild summaries', function () { let result = { directory: 'tmp/someplace', outputChanges: ['assets/foo.js', 'assets/foo.css'], @@ -843,8 +843,8 @@ describe('models/instrumentation.js', function() { }); }); - describe('._initSummary', function() { - it('computes an init summary', function() { + describe('._initSummary', function () { + it('computes an init summary', function () { let summary = instrumentation._initSummary(instrTree); expect(Object.keys(summary)).to.eql(['totalTime', 'platform']); @@ -855,8 +855,8 @@ describe('models/instrumentation.js', function() { }); }); - describe('._commandSummary', function() { - it('computes a command summary', function() { + describe('._commandSummary', function () { + it('computes a command summary', function () { let summary = instrumentation._commandSummary(instrTree, 'build', ['--like', '--whatever']); expect(Object.keys(summary)).to.eql(['name', 'args', 'totalTime', 'platform']); @@ -869,8 +869,8 @@ describe('models/instrumentation.js', function() { }); }); - describe('._shutdownSummary', function() { - it('computes a shutdown summary', function() { + describe('._shutdownSummary', function () { + it('computes a shutdown summary', function () { let summary = instrumentation._shutdownSummary(instrTree); expect(Object.keys(summary)).to.eql(['totalTime', 'platform']); diff --git a/tests/unit/models/package-info-cache/node-module-list-test.js b/tests/unit/models/package-info-cache/node-module-list-test.js index 921d5c8cf1..72412b8940 100644 --- a/tests/unit/models/package-info-cache/node-module-list-test.js +++ b/tests/unit/models/package-info-cache/node-module-list-test.js @@ -3,14 +3,14 @@ const expect = require('chai').expect; const NodeModulesList = require('../../../../lib/models/package-info-cache/node-modules-list'); -describe('models/package-info-cache/node-modules-list-test', function() { - it('correctly constructs', function() { +describe('models/package-info-cache/node-modules-list-test', function () { + it('correctly constructs', function () { expect(new NodeModulesList()).to.be.ok; expect(new NodeModulesList('/some/path')).to.be.ok; }); - describe('.NULL', function() { - it('returns a singleton, deeply frozen NodeMoudlesList', function() { + describe('.NULL', function () { + it('returns a singleton, deeply frozen NodeMoudlesList', function () { expect(NodeModulesList.NULL).to.equal(NodeModulesList.NULL); expect(NodeModulesList.NULL).to.be.frozen; expect(NodeModulesList.NULL.entries).to.be.frozen; @@ -19,13 +19,13 @@ describe('models/package-info-cache/node-modules-list-test', function() { }); }); - describe('findPackage', function() { - it('works with no entries', function() { + describe('findPackage', function () { + it('works with no entries', function () { let list = new NodeModulesList(); expect(list.findPackage('omg')).to.eql(null); }); - it('supports basic entries (missing, present, scoped)', function() { + it('supports basic entries (missing, present, scoped)', function () { let list = new NodeModulesList(); let scoped = new NodeModulesList(); let omg = { name: 'omg' }; diff --git a/tests/unit/models/package-info-cache/package-info-cache-test.js b/tests/unit/models/package-info-cache/package-info-cache-test.js index 5f58776ce2..0de1cddc7a 100644 --- a/tests/unit/models/package-info-cache/package-info-cache-test.js +++ b/tests/unit/models/package-info-cache/package-info-cache-test.js @@ -9,17 +9,17 @@ const MockUI = require('console-ui/mock'); const MockCLI = require('../../../helpers/mock-cli'); const FixturifyProject = require('../../../helpers/fixturify-project'); -describe('models/package-info-cache/package-info-cache-test.js', function() { +describe('models/package-info-cache/package-info-cache-test.js', function () { let project, projectPath, packageJsonPath, packageContents, projectPackageInfo, resolvedFile, ui, cli, pic; this.timeout(20000); - beforeEach(function() { + beforeEach(function () { ui = new MockUI(); cli = new MockCLI({ ui }); }); - describe('lexicographically', function() { - it('works', function() { + describe('lexicographically', function () { + it('works', function () { expect( [ { name: 'c' }, @@ -50,49 +50,49 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { }); }); - describe('pushUnique', function() { - it('works (and does last write win)', function() { + describe('pushUnique', function () { + it('works (and does last write win)', function () { let a = { name: 'a' }; let b = { name: 'b' }; let c = { name: 'c' }; let result = []; - [a, a, a, b, a, c, a, c].forEach(entry => PackageInfo.pushUnique(result, entry)); + [a, a, a, b, a, c, a, c].forEach((entry) => PackageInfo.pushUnique(result, entry)); expect(result).to.eql([b, a, c]); }); }); - describe('packageInfo contents tests on valid project', function() { - let projectPath, packageJsonPath, packageContents, projectPackageInfo; + describe('packageInfo contents tests on valid project', function () { + let project, projectPath, packageJsonPath, packageContents, projectPackageInfo; - beforeEach(function() { + beforeEach(function () { projectPath = path.resolve(addonFixturePath, 'simple'); packageJsonPath = path.join(projectPath, 'package.json'); packageContents = require(packageJsonPath); - let project = new Project(projectPath, packageContents, ui, cli); + project = new Project(projectPath, packageContents, ui, cli); let pic = project.packageInfoCache; projectPackageInfo = pic.getEntry(projectPath); }); - it('finds project PackageInfo entry for project root', function() { + it('finds project PackageInfo entry for project root', function () { expect(projectPackageInfo).to.exist; }); - it('projectPackageInfo has a "pkg" field', function() { + it('projectPackageInfo has a "pkg" field', function () { expect(projectPackageInfo.pkg).to.exist; }); - it('shows projectPackageInfo is considered valid', function() { + it('shows projectPackageInfo is considered valid', function () { expect(projectPackageInfo.valid).to.be.true; }); - it('is a project, so it may have addons', function() { + it('is a project, so it may have addons', function () { expect(projectPackageInfo.mayHaveAddons).to.eql(true); }); - it('shows projectPackageInfo has cliInfo at ember-cli root dir', function() { + it('shows projectPackageInfo has cliInfo at ember-cli root dir', function () { expect(projectPackageInfo.cliInfo).to.exist; let cliRealPath = projectPackageInfo.cliInfo.realPath; @@ -100,7 +100,7 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { expect(cliRealPath).to.equal(emberCliRealPath); }); - it('shows projectPackageInfo has 1 error', function() { + it('shows projectPackageInfo has 1 error', function () { expect(projectPackageInfo.hasErrors()).to.be.true; let errorArray = projectPackageInfo.errors.getErrors(); @@ -109,14 +109,14 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { }); // TODO: the input to this test is polluted by other tests: https://github.com/ember-cli/ember-cli/issues/7981 - it.skip('shows projectPackageInfo error is "3 dependencies missing"', function() { + it.skip('shows projectPackageInfo error is "3 dependencies missing"', function () { let errorArray = projectPackageInfo.errors.getErrors(); let error = errorArray[0]; expect(error.type).to.equal('dependenciesMissing'); expect(error.data.length).to.equal(3); }); - it('shows projectPackageInfo has 1 dependencyPackage', function() { + it('shows projectPackageInfo has 1 dependencyPackage', function () { let dependencyPackages = projectPackageInfo.dependencyPackages; expect(dependencyPackages).to.exist; @@ -125,13 +125,13 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { }); // TODO: the input to this test is polluted by other tests: https://github.com/ember-cli/ember-cli/issues/7981 - it.skip('shows projectPackageInfo has 8 devDependencyPackages', function() { + it.skip('shows projectPackageInfo has 8 devDependencyPackages', function () { let devDependencyPackages = projectPackageInfo.devDependencyPackages; expect(devDependencyPackages).to.exist; expect(Object.keys(devDependencyPackages).length).to.equal(8); }); - it('shows projectPackageInfo.devDependencyPackages + missing dependencies = project.devDependencies', function() { + it('shows projectPackageInfo.devDependencyPackages + missing dependencies = project.devDependencies', function () { let devDependencyPackages = projectPackageInfo.devDependencyPackages; expect(devDependencyPackages).to.exist; let devDependencyPackageNames = Object.keys(devDependencyPackages); @@ -151,37 +151,152 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { expect(packageAndErrorNames).to.deep.equal(devDependencyNames); }); - it('shows projectPackageInfo has 1 in-repo addon named "ember-super-button"', function() { + it('shows projectPackageInfo has 2 in-repo addons', function () { let inRepoAddons = projectPackageInfo.inRepoAddons; + expect(inRepoAddons).to.exist; - expect(inRepoAddons.length).to.equal(1); + expect(inRepoAddons.length).to.equal(2); + expect(inRepoAddons[0].realPath.indexOf(`simple${path.sep}lib${path.sep}ember-super-button`)).to.be.above(0); expect(inRepoAddons[0].pkg.name).to.equal('ember-super-button'); + + expect( + inRepoAddons[1].realPath.indexOf( + `simple${path.sep}lib${path.sep}ember-super-button${path.sep}lib${path.sep}ember-with-addon-main` + ) + ).to.be.above(0); + expect(inRepoAddons[1].pkg.name).to.equal('ember-with-addon-main'); }); - it('shows projectPackageInfo has 7 internal addon packages', function() { + it('shows projectPackageInfo has 7 internal addon packages', function () { let internalAddons = projectPackageInfo.internalAddons; expect(internalAddons).to.exist; expect(internalAddons.length).to.equal(7); }); // TODO: the input to this test is polluted by other tests: https://github.com/ember-cli/ember-cli/issues/7981 - it.skip('shows projectPackageInfo has 9 node-module entries', function() { + it.skip('shows projectPackageInfo has 9 node-module entries', function () { let nodeModules = projectPackageInfo.nodeModules; expect(nodeModules).to.exist; expect(nodeModules.entries).to.exist; expect(Object.keys(nodeModules.entries).length).to.equal(9); }); + + it('returns stable package infos for a package info representing the same addon', function () { + project.initializeAddons(); + + const findAddonsByName = (projectOrAddon, addonToFind, _foundAddons = []) => { + if (!projectOrAddon) { + return _foundAddons; + } + + projectOrAddon.addons.forEach((addon) => { + if (addon.name === addonToFind) { + _foundAddons.push(addon); + } + + findAddonsByName(addon, addonToFind, _foundAddons); + }); + + return _foundAddons; + }; + + const findAllInRepoPackageInfosByPredicate = (packageInfo, predicate, _foundPackageInfos = []) => { + if (predicate(packageInfo)) { + _foundPackageInfos.push(packageInfo); + } + + (packageInfo.inRepoAddons || []).forEach((addonPackageInfo) => + findAllInRepoPackageInfosByPredicate(addonPackageInfo, predicate, _foundPackageInfos) + ); + + return _foundPackageInfos; + }; + + let allAddonsWithAddonMain = findAddonsByName(project, 'ember-with-addon-main'); + + let projectAddonWithMainPackageInfo = findAllInRepoPackageInfosByPredicate( + project._packageInfo, + (packageInfo) => + typeof packageInfo.addonMainPath === 'string' && + packageInfo.addonMainPath.endsWith(path.join('ember-with-addon-main', 'lib', 'main.js')) + ); + + let allPackageInfosForAddonWithMain = [ + ...allAddonsWithAddonMain.map((addon) => addon._packageInfo), + ...projectAddonWithMainPackageInfo, + ]; + + let areAllPackageInfosEqual = allPackageInfosForAddonWithMain.every( + (packageInfo) => packageInfo === allPackageInfosForAddonWithMain[0] + ); + + expect(allAddonsWithAddonMain.length).to.equal(2); + expect(allPackageInfosForAddonWithMain.length).to.equal(4); + expect(areAllPackageInfosEqual).to.equal(true); + }); }); - describe('packageInfo', function() { - describe('valid project', function() { + describe('packageInfo', function () { + describe('project with invalid paths', function () { let project, fixturifyProject; - before(function() { + beforeEach(function () { // create a new ember-app - fixturifyProject = new FixturifyProject('simple-ember-app', '0.0.0', project => { + fixturifyProject = new FixturifyProject('simple-ember-app', '0.0.0', (project) => { project.addAddon('ember-resolver', '^5.0.1'); project.addAddon('ember-random-addon', 'latest'); + project.addAddon('loader.js', 'latest'); + project.addAddon('something-else', 'latest'); + project.addInRepoAddon('ember-super-button', 'latest', function (project) { + project.pkg['ember-addon'].paths = ['lib/herp-not-here']; + }); + project.addDevDependency('ember-cli', 'latest'); + project.addDevDependency('non-ember-thingy', 'latest'); + project.pkg['ember-addon'].paths.push('lib/no-such-path'); + }); + + fixturifyProject.writeSync(); + + project = fixturifyProject.buildProjectModel(Project); + }); + + afterEach(function () { + fixturifyProject.dispose(); + delete process.env.EMBER_CLI_ERROR_ON_INVALID_ADDON; + }); + + it('shows a warning with invalid ember-addon#path', function () { + project.discoverAddons(); + expect(project.cli.ui.output).to.include( + `specifies an invalid, malformed or missing addon at relative path 'lib${path.sep}no-such-path'` + ); + }); + + it('throws an error with flag on', function () { + process.env.EMBER_CLI_ERROR_ON_INVALID_ADDON = 'true'; + expect(() => project.discoverAddons()).to.throw( + /specifies an invalid, malformed or missing addon at relative path 'lib[\\/]no-such-path'/ + ); + }); + }); + describe('valid project', function () { + let project, fixturifyProject; + before(function () { + // create a new ember-app + fixturifyProject = new FixturifyProject('simple-ember-app', '0.0.0', (project) => { + project.addAddon('ember-resolver', '^5.0.1'); + project.addAddon('ember-random-addon', 'latest', (addon) => { + addon.addAddon('other-nested-addon', 'latest', (addon) => { + addon.addAddon('ember-resolver', '*'); + addon.toJSON = function () { + const json = Object.getPrototypeOf(this).toJSON.call(this); + // here we introduce an empty folder in our node_modules. + json[this.name].node_modules['ember-resolver'] = {}; + return json; + }; + }); + }); + project.addAddon('loader.js', 'latest'); project.addAddon('something-else', 'latest'); @@ -198,11 +313,19 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { projectPackageInfo = pic.getEntry(path.join(fixturifyProject.root, 'simple-ember-app')); }); - after(function() { + after(function () { fixturifyProject.dispose(); }); - it('has dependencies who have their mayHaveAddons correctly set', function() { + it('was able to find ember-resolver even if an empty directory was left', function () { + const emberResolver = project.findAddonByName('ember-resolver'); + const nestedEmberResolver = project.findAddonByName('ember-random-addon').addons[0].addons[0]; + expect(emberResolver.name).to.eql('ember-resolver'); + expect(nestedEmberResolver.name).to.eql('ember-resolver'); + expect(emberResolver.root).to.eql(nestedEmberResolver.root); + }); + + it('has dependencies who have their mayHaveAddons correctly set', function () { expect(projectPackageInfo.devDependencyPackages['non-ember-thingy']).to.have.property('mayHaveAddons', false); expect(projectPackageInfo.devDependencyPackages['ember-cli']).to.have.property('mayHaveAddons', false); expect(projectPackageInfo.dependencyPackages['loader.js']).to.have.property('mayHaveAddons', true); @@ -211,18 +334,18 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { expect(projectPackageInfo.dependencyPackages['something-else']).to.have.property('mayHaveAddons', true); }); - it('validates projectPackageInfo', function() { + it('validates projectPackageInfo', function () { expect(projectPackageInfo).to.exist; expect(projectPackageInfo.pkg).to.exist; expect(projectPackageInfo.valid).to.be.true; }); - it('shows projectPackageInfo has 0 errors', function() { + it('shows projectPackageInfo has 0 errors', function () { expect(projectPackageInfo.hasErrors()).to.be.false; expect(projectPackageInfo.errors.getErrors()).to.have.property('length', 0); }); - it('shows projectPackageInfo has 1 dependencyPackage', function() { + it('shows projectPackageInfo has 1 dependencyPackage', function () { let dependencyPackages = projectPackageInfo.dependencyPackages; expect(dependencyPackages).to.exist; @@ -230,14 +353,14 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { expect(dependencyPackages['something-else']).to.exist; }); - it('shows projectPackageInfo has 82devDependencyPackages', function() { + it('shows projectPackageInfo has 82devDependencyPackages', function () { let devDependencyPackages = projectPackageInfo.devDependencyPackages; expect(devDependencyPackages).to.exist; expect(Object.keys(devDependencyPackages).length).to.equal(2); }); - it('shows projectPackageInfo has 1 in-repo addon named "ember-super-button"', function() { + it('shows projectPackageInfo has 1 in-repo addon named "ember-super-button"', function () { let inRepoAddons = projectPackageInfo.inRepoAddons; expect(inRepoAddons).to.exist; @@ -246,14 +369,14 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { expect(inRepoAddons[0].pkg.name).to.equal('ember-super-button'); }); - it('shows projectPackageInfo has 7 internal addon packages', function() { + it('shows projectPackageInfo has 7 internal addon packages', function () { let internalAddons = projectPackageInfo.internalAddons; expect(internalAddons).to.exist; expect(internalAddons.length).to.equal(7); }); - it('shows projectPackageInfo has 7 node-module entries', function() { + it('shows projectPackageInfo has 7 node-module entries', function () { let nodeModules = projectPackageInfo.nodeModules; expect(nodeModules).to.exist; @@ -263,8 +386,8 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { }); }); - describe('packageInfo contents tests on missing project', function() { - beforeEach(function() { + describe('packageInfo contents tests on missing project', function () { + beforeEach(function () { projectPath = path.resolve(addonFixturePath, 'fakepackage'); let deps = { @@ -287,27 +410,27 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { projectPackageInfo = pic.getEntry(projectPath); }); - it('creates a packageInfo object for the missing path', function() { + it('creates a packageInfo object for the missing path', function () { expect(projectPackageInfo).to.exist; }); - it('has 3 errors', function() { + it('has 3 errors', function () { let errors = projectPackageInfo.errors; expect(errors).to.exist; expect(errors.hasErrors()).to.be.true; expect(errors.getErrors().length).to.equal(3); }); - it('has a "packageDirectoryMissing" error', function() { + it('has a "packageDirectoryMissing" error', function () { let errorArray = projectPackageInfo.errors.getErrors(); - let pkgDirMissingErr = errorArray.find(function(err) { + let pkgDirMissingErr = errorArray.find(function (err) { return err.type === 'packageDirectoryMissing'; }); expect(pkgDirMissingErr).to.exist; expect(pkgDirMissingErr.data).to.equal(projectPath); }); - it('has empty "dependencyPackages" and "devDependencyPackages" objects', function() { + it('has empty "dependencyPackages" and "devDependencyPackages" objects', function () { expect(projectPackageInfo.dependencyPackages).to.exist; expect(projectPackageInfo.devDependencyPackages).to.exist; expect(Object.keys(projectPackageInfo.dependencyPackages).length).to.equal(0); @@ -315,8 +438,8 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { }); }); - describe('packageInfo contents tests on with-nested-addons project', function() { - beforeEach(function() { + describe('packageInfo contents tests on with-nested-addons project', function () { + beforeEach(function () { projectPath = path.resolve(addonFixturePath, 'with-nested-addons'); packageJsonPath = path.join(projectPath, 'package.json'); packageContents = null; // there is no actual package.json @@ -326,9 +449,9 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { projectPackageInfo = pic.getEntry(projectPath); }); - it('shows projectPackageInfo has a "packageJsonMissing" error', function() { + it('shows projectPackageInfo has a "packageJsonMissing" error', function () { let errorArray = projectPackageInfo.errors.getErrors(); - let pkgJsonMissingErr = errorArray.find(function(err) { + let pkgJsonMissingErr = errorArray.find(function (err) { return err.type === 'packageJsonMissing'; }); expect(pkgJsonMissingErr).to.exist; @@ -336,8 +459,8 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { }); }); - describe('packageInfo contents tests on external-dependency project', function() { - beforeEach(function() { + describe('packageInfo contents tests on external-dependency project', function () { + beforeEach(function () { projectPath = path.resolve(addonFixturePath, 'external-dependency'); packageJsonPath = path.join(projectPath, 'package.json'); packageContents = require(packageJsonPath); @@ -347,7 +470,7 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { projectPackageInfo = pic.getEntry(projectPath); }); - it('shows projectPackageInfo finds a dependency above project root', function() { + it('shows projectPackageInfo finds a dependency above project root', function () { expect(projectPackageInfo.dependencyPackages).to.exist; let emberCliStringUtilsPkgInfo = projectPackageInfo.dependencyPackages['ember-cli-string-utils']; @@ -359,7 +482,7 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { ); }); - it('shows projectPackageInfo finds an external dependency involving a scope', function() { + it('shows projectPackageInfo finds an external dependency involving a scope', function () { expect(projectPackageInfo.dependencyPackages).to.exist; let restPkgInfo = projectPackageInfo.dependencyPackages['@octokit/rest']; @@ -370,18 +493,18 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { }); }); - describe('discoverProjectAddons', function() { + describe('discoverProjectAddons', function () { let fixturifyProject; - afterEach(function() { + afterEach(function () { if (fixturifyProject) { fixturifyProject.dispose(); } }); - describe('within an addon', function() { - beforeEach(function() { - fixturifyProject = new FixturifyProject('external-dependency', '0.0.0', project => { + describe('within an addon', function () { + beforeEach(function () { + fixturifyProject = new FixturifyProject('external-dependency', '0.0.0', (project) => { project.addDevDependency('ember-cli-string-utils', 'latest'); project.addDevDependency('@octokit/rest', 'latest'); project.addAddon('ember-cli-blueprint-test-helpers', 'latest'); @@ -401,7 +524,7 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { }); }); - it('lock down dependency orderings', function() { + it('lock down dependency orderings', function () { let project = fixturifyProject.buildProjectModel(); project.discoverAddons(); @@ -429,10 +552,10 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { }); }); - describe('tests for projectPackageInfo.addonMainPath', function() { + describe('tests for projectPackageInfo.addonMainPath', function () { let origPackageContents; - beforeEach(function() { + beforeEach(function () { projectPath = path.resolve(addonFixturePath, 'external-dependency'); packageJsonPath = path.join(projectPath, 'package.json'); // Because we allow the tests to modify packageContents, and the original @@ -447,7 +570,7 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { packageContents = JSON.parse(JSON.stringify(origPackageContents)); }); - it('adds .js if not present', function() { + it('adds .js if not present', function () { packageContents['ember-addon']['main'] = 'index'; project = new Project(projectPath, packageContents, ui, cli); @@ -457,7 +580,7 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { expect(resolvedFile).to.equal('index.js'); }); - it("doesn't add .js if it is .js", function() { + it("doesn't add .js if it is .js", function () { packageContents['ember-addon']['main'] = 'index.js'; project = new Project(projectPath, packageContents, ui, cli); @@ -467,7 +590,7 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { expect(resolvedFile).to.equal('index.js'); }); - it("doesn't add .js if it has another extension", function() { + it("doesn't add .js if it has another extension", function () { packageContents['ember-addon']['main'] = 'index.coffee'; project = new Project(projectPath, packageContents, ui, cli); @@ -477,7 +600,7 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { expect(resolvedFile).to.equal('index.coffee'); }); - it('allows lookup of existing non-`index.js` `main` entry points', function() { + it('allows lookup of existing non-`index.js` `main` entry points', function () { delete packageContents['ember-addon']; packageContents['main'] = 'some/other/path.js'; @@ -488,7 +611,7 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { expect(resolvedFile).to.equal(path.join(projectPath, 'some/other/path.js')); }); - it('fails invalid other `main` entry points', function() { + it('fails invalid other `main` entry points', function () { delete packageContents['ember-addon']; packageContents['main'] = 'some/other/non-existent-file.js'; @@ -501,7 +624,7 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { expect(error.type).to.equal('emberAddonMainMissing'); }); - it('falls back to `index.js` if `main` and `ember-addon` are not found', function() { + it('falls back to `index.js` if `main` and `ember-addon` are not found', function () { delete packageContents['ember-addon']; project = new Project(projectPath, packageContents, ui, cli); @@ -511,7 +634,7 @@ describe('models/package-info-cache/package-info-cache-test.js', function() { expect(resolvedFile).to.equal(path.join(projectPath, 'index.js')); }); - it('falls back to `index.js` if `main` and `ember-addon.main` are not found', function() { + it('falls back to `index.js` if `main` and `ember-addon.main` are not found', function () { delete packageContents['ember-addon'].main; project = new Project(projectPath, packageContents, ui, cli); diff --git a/tests/unit/models/per-bundle-addon-cache/addon-proxy-test.js b/tests/unit/models/per-bundle-addon-cache/addon-proxy-test.js new file mode 100644 index 0000000000..6e7231bf28 --- /dev/null +++ b/tests/unit/models/per-bundle-addon-cache/addon-proxy-test.js @@ -0,0 +1,48 @@ +'use strict'; + +const expect = require('chai').expect; + +const { getAddonProxy } = require('../../../../lib/models/per-bundle-addon-cache/addon-proxy'); +const { TARGET_INSTANCE } = require('../../../../lib/models/per-bundle-addon-cache/target-instance'); + +describe('Unit | addon-proxy-test', function () { + it('it allows a patched `preprocessJs` and is never set on the original addon instance', function () { + const realAddon = { + addons: ['foo'], + preprocessJs() {}, + }; + + const proxy1 = getAddonProxy({ [TARGET_INSTANCE]: realAddon }, {}); + const proxy2 = getAddonProxy({ [TARGET_INSTANCE]: realAddon }, {}); + + const originalPreprocessJs1 = proxy1.preprocessJs; + const originalPreprocessJs2 = proxy2.preprocessJs; + + proxy1.preprocessJs = () => {}; + + expect(proxy1[TARGET_INSTANCE].preprocessJs).to.equal( + realAddon.preprocessJs, + "original addon's `preprocessJs` has not been modified" + ); + + proxy2.preprocessJs = () => {}; + + expect(proxy2[TARGET_INSTANCE].preprocessJs).to.equal( + realAddon.preprocessJs, + "original addon's `preprocessJs` has not been modified" + ); + + proxy1.preprocessJs(); + + // restore original + proxy1.preprocessJs = originalPreprocessJs1; + + proxy2.preprocessJs(); + + // restore original + proxy2.preprocessJs = originalPreprocessJs2; + + proxy1.preprocessJs(); + proxy2.preprocessJs(); + }); +}); diff --git a/tests/unit/models/per-bundle-addon-cache/cache-bundle-hosts-test.js b/tests/unit/models/per-bundle-addon-cache/cache-bundle-hosts-test.js new file mode 100644 index 0000000000..26db85fe72 --- /dev/null +++ b/tests/unit/models/per-bundle-addon-cache/cache-bundle-hosts-test.js @@ -0,0 +1,44 @@ +'use strict'; + +/** + * Tests for checking that the list of 'bundle hosts' in the cache is correct. + * A 'bundle host' is either the project or a lazy engine. + */ +const expect = require('chai').expect; +const Project = require('../../../../lib/models/project'); + +const { createStandardCacheFixture } = require('../../../../tests/helpers/per-bundle-addon-cache'); + +describe('Unit | per-bundle-addon-cache bundle host', function () { + let project; + + before('setup fixture', function setup() { + let fixture = createStandardCacheFixture(); + project = fixture.buildProjectModel(Project); + project.initializeAddons(); + }); + + it('Should have 4 inRepo addons in project', function () { + // project should contain 4 children, 3 of which are engines and 1 is a regular addon. + expect(project._packageInfo.inRepoAddons.length).to.equal(4); + }); + + it('project.perBundleAddonCache should exist', function () { + expect(project.perBundleAddonCache).to.exist; + }); + + it("Should have 0 bundle hosts since they're constructed lazily", function () { + const bundleHostCache = project.perBundleAddonCache.bundleHostCache; + expect(bundleHostCache.size).to.equal(0); + }); + + it('Should not have any addonInstanceCache entries', function () { + const bundleHostCache = project.perBundleAddonCache.bundleHostCache; + + for (const [key] of bundleHostCache) { + let value = bundleHostCache.get(key); + expect(value.addonInstanceCache && value.addonInstanceCache.size).to.equal(0); + expect(value.realPath).to.exist; + } + }); +}); diff --git a/tests/unit/models/per-bundle-addon-cache/enable-cache-test.js b/tests/unit/models/per-bundle-addon-cache/enable-cache-test.js new file mode 100644 index 0000000000..b89d9f6222 --- /dev/null +++ b/tests/unit/models/per-bundle-addon-cache/enable-cache-test.js @@ -0,0 +1,49 @@ +'use strict'; + +/** + * Tests for enabling and disabling per-bundle-addon-cache support + */ +const expect = require('chai').expect; +const { createStandardCacheFixture } = require('../../../../tests/helpers/per-bundle-addon-cache'); +const Project = require('../../../../lib/models/project'); + +function enablePerBundleAddonCache(explicitValue) { + // default is opt-out + if (explicitValue !== null && explicitValue !== undefined) { + process.env.EMBER_CLI_ADDON_INSTANCE_CACHING = explicitValue; + } else { + delete process.env.EMBER_CLI_ADDON_INSTANCE_CACHING; + } +} + +function disablePerBundleAddonCache() { + process.env.EMBER_CLI_ADDON_INSTANCE_CACHING = false; +} + +function createProject() { + let fixture = createStandardCacheFixture(); + let project = fixture.buildProjectModel(Project); + return project; +} + +describe('Unit | per-bundle-addon-cache enable caching', function () { + afterEach(function () { + enablePerBundleAddonCache(); + }); + + it('perBundleAddonCache should be set in Project if EMBER_CLI_ADDON_INSTANCE_CACHING is not false', function () { + enablePerBundleAddonCache('foo'); + let project = createProject(); + expect(project.perBundleAddonCache).to.exist; + + enablePerBundleAddonCache(); + project = createProject(); + expect(project.perBundleAddonCache).to.exist; + }); + + it('perBundleAddonCache should not be set in Project if EMBER_CLI_ADDON_INSTANCE_CACHING is false', function () { + disablePerBundleAddonCache(); + let project = createProject(); + expect(project.perBundleAddonCache).not.to.exist; + }); +}); diff --git a/tests/unit/models/per-bundle-addon-cache/proxy-test.js b/tests/unit/models/per-bundle-addon-cache/proxy-test.js new file mode 100644 index 0000000000..b1373fd8f0 --- /dev/null +++ b/tests/unit/models/per-bundle-addon-cache/proxy-test.js @@ -0,0 +1,1159 @@ +'use strict'; + +/** + * Tests for the various proxies and instances once the project has initialized + * its addons + */ +const expect = require('chai').expect; +const path = require('path'); +const FixturifyProject = require('../../../helpers/fixturify-project'); + +const { + findAddonCacheEntriesByName, + createStandardCacheFixture, + getAllAddonsByNameWithinHost, + areAllInstancesEqualWithinHost, + countAddons, +} = require('../../../../tests/helpers/per-bundle-addon-cache'); + +const Project = require('../../../../lib/models/project'); +const { TARGET_INSTANCE } = require('../../../../lib/models/per-bundle-addon-cache/target-instance'); + +describe('models/per-bundle-addon-cache', function () { + let fixturifyProject; + + beforeEach(function () { + fixturifyProject = new FixturifyProject('awesome-proj', '1.0.0'); + fixturifyProject.addDevDependency('ember-cli', '*'); + }); + + afterEach(function () { + fixturifyProject.dispose(); + }); + + it('simple case: bundle addon caching within a single project host', function () { + fixturifyProject.addInRepoAddon('foo', '1.0.0', { allowCachingPerBundle: true }); + fixturifyProject.addInRepoAddon('foo-bar', '1.0.0', { + callback: (inRepoAddon) => { + inRepoAddon.pkg['ember-addon'].paths = ['../foo']; + }, + }); + + fixturifyProject.writeSync(); + let project = fixturifyProject.buildProjectModel(); + + project.initializeAddons(); + + expect(areAllInstancesEqualWithinHost(project, 'foo')).to.be.true; + }); + + it('it should create multiple proxies within a project host', function () { + fixturifyProject.addInRepoAddon('foo', '1.0.0', { allowCachingPerBundle: true }); + + for (let i = 0; i < 10; i++) { + fixturifyProject.addInRepoAddon(`foo-bar-${i}`, '1.0.0', { + callback: (inRepoAddon) => { + inRepoAddon.pkg['ember-addon'].paths = ['../foo']; + }, + }); + } + + fixturifyProject.writeSync(); + let project = fixturifyProject.buildProjectModel(); + + project.initializeAddons(); + + expect(areAllInstancesEqualWithinHost(project, 'foo')).to.be.true; + }); + + it('it should create a proxy for a regular addon when added as a dependency to an in-repo addon', function () { + fixturifyProject.addAddon('foo', '1.0.0', { allowCachingPerBundle: true }); + + for (let i = 0; i < 10; i++) { + fixturifyProject.addInRepoAddon(`foo-bar-${i}`, '1.0.0', { + callback: (inRepoAddon) => { + inRepoAddon.addReferenceDependency('foo', '1.0.0'); + }, + }); + } + + fixturifyProject.writeSync(); + let project = fixturifyProject.buildProjectModel(); + + project.initializeAddons(); + + expect(areAllInstancesEqualWithinHost(project, 'foo')).to.be.true; + }); + + it('it should create a proxy for a regular addon when added as a dependency to a regular addon', function () { + fixturifyProject.addAddon('foo', '1.0.0', { allowCachingPerBundle: true }); + + for (let i = 0; i < 10; i++) { + fixturifyProject.addAddon(`foo-bar-${i}`, '1.0.0', { + callback: (addon) => { + addon.addReferenceDependency('foo', '1.0.0'); + }, + }); + } + + fixturifyProject.writeSync(); + let project = fixturifyProject.buildProjectModel(); + + project.initializeAddons(); + + expect(areAllInstancesEqualWithinHost(project, 'foo')).to.be.true; + }); + + it('it should create a proxy to a target "real addon" per host', function () { + fixturifyProject.addAddon('foo', '1.0.0', { allowCachingPerBundle: true }); + + fixturifyProject.addInRepoEngine('in-repo-lazy-engine', '1.0.0', { + enableLazyLoading: true, + callback: (lazyEngine) => { + lazyEngine.addReferenceDependency('foo', '1.0.0'); + lazyEngine.addReferenceDependency('foo-bar', '1.0.0'); + }, + }); + + fixturifyProject.addAddon('foo-bar', '1.0.0', { + callback: (addon) => { + addon.addReferenceDependency('foo', '1.0.0'); + }, + }); + + fixturifyProject.writeSync(); + let project = fixturifyProject.buildProjectModel(); + + project.initializeAddons(); + + expect(areAllInstancesEqualWithinHost(project, 'foo')).to.be.true; + + // addons within lazy engine host are cached + expect( + areAllInstancesEqualWithinHost( + project.addons.find((addon) => addon.name === 'in-repo-lazy-engine'), + 'foo' + ) + ).to.be.true; + }); + + describe('when `EMBER_ENGINES_ADDON_DEDUPE` is enabled', function () { + beforeEach(function () { + process.env.EMBER_ENGINES_ADDON_DEDUPE = true; + }); + + afterEach(function () { + delete process.env.EMBER_ENGINES_ADDON_DEDUPE; + }); + + it('it should create a proxy to a target "real addon" using the project host', function () { + fixturifyProject.addAddon('foo', '1.0.0', { allowCachingPerBundle: true }); + + fixturifyProject.addInRepoEngine('in-repo-lazy-engine', '1.0.0', { + enableLazyLoading: true, + callback: (lazyEngine) => { + lazyEngine.addReferenceDependency('foo', '1.0.0'); + lazyEngine.addReferenceDependency('foo-bar', '1.0.0'); + }, + }); + + fixturifyProject.addAddon('foo-bar', '1.0.0', { + callback: (addon) => { + addon.addReferenceDependency('foo', '1.0.0'); + }, + }); + + fixturifyProject.writeSync(); + let project = fixturifyProject.buildProjectModel(); + + project.initializeAddons(); + + // we use project addon instance as the "real addon" + expect(areAllInstancesEqualWithinHost(project, 'foo')).to.be.true; + + const { realAddon: realAddonForProject, proxies: proxiesForProject } = getAllAddonsByNameWithinHost( + project, + 'foo' + ); + const { proxies: proxiesForEngine } = getAllAddonsByNameWithinHost( + project.addons.find((addon) => addon.name === 'in-repo-lazy-engine'), + 'foo' + ); + + expect( + [...proxiesForProject, ...proxiesForEngine].every((proxy) => proxy[TARGET_INSTANCE] === realAddonForProject) + ).to.be.true; + }); + + it('addon with `allowCachingPerBundle`, 1 in each of 2 lazy engines; project also depends on this addon', function () { + fixturifyProject.addAddon('test-addon-a', '1.0.0', { allowCachingPerBundle: true }); + + fixturifyProject.addEngine('lazy-engine-a', '1.0.0', { + enableLazyLoading: true, + callback: (engine) => { + engine.addReferenceDependency('test-addon-a', '1.0.0'); + }, + }); + + fixturifyProject.addEngine('lazy-engine-b', '1.0.0', { + enableLazyLoading: true, + callback: (engine) => { + engine.addReferenceDependency('test-addon-a', '1.0.0'); + }, + }); + + let project = fixturifyProject.buildProjectModel(); + project.initializeAddons(); + + let counts = countAddons(project); + + expect(counts.byName['lazy-engine-a'].addons.length).to.equal(1); + expect(counts.byName['lazy-engine-b'].addons.length).to.equal(1); + + expect(counts.proxyCount).to.equal(2); + expect(project.perBundleAddonCache.numProxies).to.equal(2); + + expect(counts.byName['test-addon-a'].realAddonInstanceCount).to.equal(1); + expect(counts.byName['test-addon-a'].proxyCount).to.equal(2); + + const lazyEngineAPkgInfo = project.addons.find((addon) => addon.name === 'lazy-engine-a')._packageInfo; + let cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineAPkgInfo, 'test-addon-a'); + + // project cache should be used + expect(cacheEntries).to.not.exist; + + const lazyEngineBPkgInfo = project.addons.find((addon) => addon.name === 'lazy-engine-b')._packageInfo; + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineBPkgInfo, 'test-addon-a'); + + // project cache should be used + expect(cacheEntries).to.not.exist; + }); + + it('2 lazy engines; each depend on two addons; project also depends on these addons, ensure project cache is used', function () { + fixturifyProject.addInRepoAddon('test-addon-a', '1.0.0', { + allowCachingPerBundle: true, + callback: (addon) => { + addon.pkg['ember-addon'].paths = ['../test-addon-b']; + }, + }); + + fixturifyProject.addInRepoAddon('test-addon-b', '1.0.0', { allowCachingPerBundle: true }); + + fixturifyProject.addInRepoEngine('lazy-engine-a', '1.0.0', { + allowCachingPerBundle: true, + enableLazyLoading: true, + callback: (engine) => { + engine.pkg['ember-addon'].paths = ['../test-addon-a', '../test-addon-b']; + }, + }); + + fixturifyProject.addInRepoEngine('lazy-engine-b', '1.0.0', { + allowCachingPerBundle: true, + enableLazyLoading: true, + callback: (engine) => { + engine.pkg['ember-addon'].paths = ['../test-addon-a', '../test-addon-b']; + }, + }); + + let project = fixturifyProject.buildProjectModel(); + project.initializeAddons(); + + let { byName } = countAddons(project); + + expect(byName['lazy-engine-a'].realAddonInstanceCount).to.equal(1); + expect(byName['lazy-engine-a'].proxyCount).to.equal(0); + + expect(byName['lazy-engine-b'].realAddonInstanceCount).to.equal(1); + expect(byName['lazy-engine-b'].proxyCount).to.equal(0); + + expect(byName['test-addon-a'].realAddonInstanceCount).to.equal(1); + expect(byName['test-addon-a'].proxyCount).to.equal(2); + + expect(byName['test-addon-b'].realAddonInstanceCount).to.equal(1); + expect(byName['test-addon-b'].proxyCount).to.equal(3); + + const projectPkgInfo = project._packageInfo; + let cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, projectPkgInfo, 'test-addon-a'); + expect(cacheEntries.length).to.equal(1, 'project cache should have an entry for `test-addon-a`'); + + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, projectPkgInfo, 'test-addon-b'); + expect(cacheEntries.length).to.equal(1, 'project cache should have an entry for `test-addon-b`'); + + const lazyEngineAPkgInfo = project.addons.find((addon) => addon.name === 'lazy-engine-a')._packageInfo; + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineAPkgInfo, 'test-addon-a'); + expect(cacheEntries).to.deep.equal( + [], + 'should exist; lazy-engine A has opted-in to bundle caching, but should have no entries for `test-addon-a`' + ); + + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineAPkgInfo, 'test-addon-b'); + expect(cacheEntries).to.deep.equal( + [], + 'should exist; lazy-engine A has opted-in to bundle caching, but should have no entries for `test-addon-b`' + ); + + const lazyEngineBPkgInfo = project.addons.find((addon) => addon.name === 'lazy-engine-b')._packageInfo; + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineBPkgInfo, 'test-addon-a'); + expect(cacheEntries).to.deep.equal( + [], + 'should exist; lazy engine B has opted into bundle caching, but should have no entries for `test-addon-a`' + ); + + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineBPkgInfo, 'test-addon-b'); + expect(cacheEntries).to.deep.equal( + [], + 'should exist; lazy engine B has opted into bundle caching, but should have no entries for `test-addon-b`' + ); + }); + + it('should work for a common ancestor host that is not the project; i.e., another a lazy engine', function () { + fixturifyProject.addEngine('lazy-engine-a', '1.0.0', { + enableLazyLoading: true, + allowCachingPerBundle: true, + callback: (engine) => { + engine.addAddon('test-addon-a', '1.0.0', { + allowCachingPerBundle: true, + }); + + engine.addEngine('lazy-engine-b', '1.0.0', { + enableLazyLoading: true, + allowCachingPerBundle: true, + callback: (engine) => { + engine.addReferenceDependency('test-addon-a', '1.0.0'); + }, + }); + + engine.addEngine('lazy-engine-c', '1.0.0', { + enableLazyLoading: true, + callback: (engine) => { + engine.addReferenceDependency('test-addon-a', '1.0.0'); + }, + }); + }, + }); + + let project = fixturifyProject.buildProjectModel(); + project.initializeAddons(); + + let { byName } = countAddons(project); + + expect(byName['lazy-engine-a'].realAddonInstanceCount).to.equal(1); + expect(byName['lazy-engine-a'].proxyCount).to.equal(0); + + expect(byName['lazy-engine-b'].realAddonInstanceCount).to.equal(1); + expect(byName['lazy-engine-b'].proxyCount).to.equal(0); + + expect(byName['lazy-engine-c'].realAddonInstanceCount).to.equal(1); + expect(byName['lazy-engine-c'].proxyCount).to.equal(0); + + expect(byName['test-addon-a'].realAddonInstanceCount).to.equal(1); + expect(byName['test-addon-a'].proxyCount).to.equal(2); + + const lazyEngineAPkgInfo = project.addons.find((addon) => addon.name === 'lazy-engine-a')._packageInfo; + let cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineAPkgInfo, 'test-addon-a'); + + expect(cacheEntries.length).to.equal(1, 'should exist; lazy-engine A depends on a non-project addon'); + + const lazyEngineBPkgInfo = project.addons + .find((addon) => addon.name === 'lazy-engine-a') + .addons.find((addon) => addon.name === 'lazy-engine-b')._packageInfo; + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineBPkgInfo, 'test-addon-a'); + + expect(cacheEntries).to.deep.equal( + [], + "lazy engine B's should exist, but be unusued; ultimately lazy engine A is responsible for bundling" + ); + + const lazyEngineCPkgInfo = project.addons + .find((addon) => addon.name === 'lazy-engine-a') + .addons.find((addon) => addon.name === 'lazy-engine-c')._packageInfo; + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineCPkgInfo, 'test-addon-a'); + + expect(cacheEntries).to.be.equal(null, 'should not exist; lazy-engine C has not opted-in to bundle caching'); + }); + + it('should work for a common ancestor host that is not the project where multiple hosts bundle the same addon', function () { + fixturifyProject.addInRepoEngine('test-addon-a', '1.0.0', { + allowCachingPerBundle: true, + }); + + fixturifyProject.pkg['ember-addon'].paths = []; + + fixturifyProject.addInRepoEngine('lazy-engine-a', '1.0.0', { + enableLazyLoading: true, + allowCachingPerBundle: true, + callback: (engine) => { + engine.pkg['ember-addon'].paths = ['../test-addon-a']; + + engine.addInRepoEngine('lazy-engine-a-lazy-engine-dep', '1.0.0', { + enableLazyLoading: true, + allowCachingPerBundle: true, + callback: (engine) => { + engine.pkg['ember-addon'].paths = ['../../../test-addon-a']; + }, + }); + }, + }); + + fixturifyProject.addInRepoEngine('lazy-engine-b', '1.0.0', { + enableLazyLoading: true, + allowCachingPerBundle: true, + callback: (engine) => { + engine.pkg['ember-addon'].paths = ['../test-addon-a']; + + engine.addInRepoEngine('lazy-engine-b-lazy-engine-dep', '1.0.0', { + enableLazyLoading: true, + allowCachingPerBundle: true, + callback: (engine) => { + engine.pkg['ember-addon'].paths = ['../../../test-addon-a']; + }, + }); + }, + }); + + let project = fixturifyProject.buildProjectModel(); + project.initializeAddons(); + + let { byName } = countAddons(project); + + expect(byName['lazy-engine-a'].realAddonInstanceCount).to.equal(1); + expect(byName['lazy-engine-a'].proxyCount).to.equal(0); + + expect(byName['lazy-engine-b'].realAddonInstanceCount).to.equal(1); + expect(byName['lazy-engine-b'].proxyCount).to.equal(0); + + expect(byName['test-addon-a'].realAddonInstanceCount).to.equal(2); + expect(byName['test-addon-a'].proxyCount).to.equal(2); + + const lazyEngineAPkgInfo = project.addons.find((addon) => addon.name === 'lazy-engine-a')._packageInfo; + let cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineAPkgInfo, 'test-addon-a'); + + expect(cacheEntries.length).to.equal(1, 'should exist; lazy-engine A depends on a non-project addon'); + + const lazyEngineBPkgInfo = project.addons.find((addon) => addon.name === 'lazy-engine-b')._packageInfo; + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineBPkgInfo, 'test-addon-a'); + + expect(cacheEntries.length).to.equal(1, 'should exist; lazy-engine B depends on a non-project addon'); + }); + + it('should work for a common ancestor host that is not the project with an addon bundled by a different host', function () { + fixturifyProject.addAddon('test-addon-a', '1.0.0', { + allowCachingPerBundle: true, + }); + + fixturifyProject.addAddon('test-addon-b', '1.0.0', (addon) => { + addon.addReferenceDependency('test-addon-a', '1.0.0'); + }); + + fixturifyProject.addEngine('lazy-engine-a', '1.0.0', { + enableLazyLoading: true, + allowCachingPerBundle: true, + callback: (engine) => { + engine.addReferenceDependency('test-addon-a', '1.0.0'); + + engine.addEngine('lazy-engine-b', '1.0.0', { + enableLazyLoading: true, + allowCachingPerBundle: true, + callback: (engine) => { + engine.addReferenceDependency('test-addon-a', '1.0.0'); + }, + }); + + engine.addEngine('lazy-engine-c', '1.0.0', { + enableLazyLoading: true, + callback: (engine) => { + engine.addReferenceDependency('test-addon-a', '1.0.0'); + }, + }); + }, + }); + + let project = fixturifyProject.buildProjectModel(); + project.initializeAddons(); + + let { byName } = countAddons(project); + + expect(byName['lazy-engine-a'].realAddonInstanceCount).to.equal(1); + expect(byName['lazy-engine-a'].proxyCount).to.equal(0); + + expect(byName['lazy-engine-b'].realAddonInstanceCount).to.equal(1); + expect(byName['lazy-engine-b'].proxyCount).to.equal(0); + + expect(byName['lazy-engine-c'].realAddonInstanceCount).to.equal(1); + expect(byName['lazy-engine-c'].proxyCount).to.equal(0); + + expect(byName['test-addon-a'].realAddonInstanceCount).to.equal(1); + expect(byName['test-addon-a'].proxyCount).to.equal(4); + + let cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, project._packageInfo, 'test-addon-a'); + + expect(cacheEntries.length).to.equal(1, 'project cache should be used'); + + const lazyEngineAPkgInfo = project.addons.find((addon) => addon.name === 'lazy-engine-a')._packageInfo; + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineAPkgInfo, 'test-addon-a'); + + expect(cacheEntries.length).to.equal(0, 'lazy engine A cache should not be used'); + + const lazyEngineBPkgInfo = project.addons + .find((addon) => addon.name === 'lazy-engine-a') + .addons.find((addon) => addon.name === 'lazy-engine-b')._packageInfo; + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineBPkgInfo, 'test-addon-a'); + + expect(cacheEntries).to.deep.equal([], 'should exist; lazy-engine B has opted-in to bundle caching'); + + expect(cacheEntries.length).to.equal(0, 'lazy engine B should have no entries; the project has bundled this'); + + const lazyEngineCPkgInfo = project.addons + .find((addon) => addon.name === 'lazy-engine-a') + .addons.find((addon) => addon.name === 'lazy-engine-c')._packageInfo; + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineCPkgInfo, 'test-addon-a'); + + expect(cacheEntries).to.be.equal(null, 'should not exist; lazy-engine C has not opted-in to bundle caching'); + }); + + it('should work for a common ancestor host that is not the project with an addon bundled by a different host when building a lazy engine independently', function () { + fixturifyProject.addAddon('test-addon-a', '1.0.0', { + allowCachingPerBundle: true, + }); + + fixturifyProject.addInRepoEngine('lazy-engine-a-project', '1.0.0', { + enableLazyLoading: true, + callback: (projectLazyEngine) => { + projectLazyEngine.addReferenceDependency('test-addon-a', '1.0.0'); + + projectLazyEngine.addAddon('test-addon-b', '1.0.0', { + allowCachingPerBundle: true, + callback: (addon) => { + addon.addReferenceDependency('test-addon-a', '1.0.0'); + }, + }); + + projectLazyEngine.addEngine('lazy-engine-a', '1.0.0', { + enableLazyLoading: true, + allowCachingPerBundle: true, + callback: (engine) => { + engine.addReferenceDependency('test-addon-a', '1.0.0'); + + engine.addEngine('lazy-engine-b', '1.0.0', { + enableLazyLoading: true, + allowCachingPerBundle: true, + callback: (engine) => { + engine.addReferenceDependency('test-addon-a', '1.0.0'); + }, + }); + + engine.addEngine('lazy-engine-c', '1.0.0', { + enableLazyLoading: true, + callback: (engine) => { + engine.addReferenceDependency('test-addon-a', '1.0.0'); + }, + }); + }, + }); + }, + }); + + let project = fixturifyProject.buildProjectModelForInRepoAddon('lazy-engine-a-project'); + project.initializeAddons(); + + let { byName } = countAddons(project); + + expect(byName['lazy-engine-a'].realAddonInstanceCount).to.equal(1); + expect(byName['lazy-engine-a'].proxyCount).to.equal( + 1, + 'engine should be added as a dependency to `ember-cli`, so we should have 1 proxy' + ); + + expect(byName['lazy-engine-b'].realAddonInstanceCount).to.equal(1); + expect(byName['lazy-engine-b'].proxyCount).to.equal(0); + + expect(byName['lazy-engine-c'].realAddonInstanceCount).to.equal(1); + expect(byName['lazy-engine-c'].proxyCount).to.equal(0); + + expect(byName['test-addon-a'].realAddonInstanceCount).to.equal(1); + expect(byName['test-addon-a'].proxyCount).to.equal(5); + + let cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, project._packageInfo, 'test-addon-a'); + + expect(cacheEntries.length).to.equal(1, 'project cache should not be used'); + + const lazyEngineAPkgInfo = project.addons + .find((addon) => addon.name === 'lazy-engine-a-project') + .addons.find((addon) => addon.name === 'lazy-engine-a')._packageInfo; + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineAPkgInfo, 'test-addon-a'); + + expect(cacheEntries.length).to.equal(0, 'lazy engine A cache should not be used'); + + const lazyEngineBPkgInfo = project.addons + .find((addon) => addon.name === 'lazy-engine-a-project') + .addons.find((addon) => addon.name === 'lazy-engine-a') + .addons.find((addon) => addon.name === 'lazy-engine-b')._packageInfo; + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineBPkgInfo, 'test-addon-a'); + + expect(cacheEntries).to.deep.equal([], 'should exist; lazy-engine B has opted-in to bundle caching'); + + expect(cacheEntries.length).to.equal(0, 'lazy engine B should have no entries; the project has bundled this'); + + const lazyEngineCPkgInfo = project.addons + .find((addon) => addon.name === 'lazy-engine-a-project') + .addons.find((addon) => addon.name === 'lazy-engine-a') + .addons.find((addon) => addon.name === 'lazy-engine-c')._packageInfo; + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineCPkgInfo, 'test-addon-a'); + + expect(cacheEntries).to.be.equal(null, 'should not exist; lazy-engine C has not opted-in to bundle caching'); + }); + }); + + describe('proxy checks with addon counts', function () { + it('no `allowCachingPerBundle` set, no proxies, verify instance counts', function () { + let fixture = createStandardCacheFixture(); + let project = fixture.buildProjectModel(Project); + project.initializeAddons(); + + let counts = countAddons(project); + + expect(counts.proxyCount).to.equal(0); + expect(counts.byName['test-addon-a'].addons.length).to.equal(1); + expect(counts.byName['test-addon-dep'].addons.length).to.equal(2); + expect(counts.byName['test-engine-dep'].addons.length).to.equal(3); + expect(counts.byName['lazy-engine-a'].addons.length).to.equal(1); + expect(counts.byName['lazy-engine-b'].addons.length).to.equal(1); + expect(counts.byName['regular-engine-c'].addons.length).to.equal(1); + + // addon cache should also have 0 proxies. test-addon-b was the only addon marked as cacheable, + // so it will end up in the count of addon instances for the addon cache, but have no proxies. + expect(project.perBundleAddonCache.numProxies).to.equal(0); + }); + + it('addon with allowCachingPerBundle, 1 instance, the rest proxies', function () { + // PROJ to TAA, TAB, TAC and TAD. TAB, TAC and TAD have TAA underneath. + fixturifyProject.addAddon('test-addon-a', '1.0.0', { allowCachingPerBundle: true }); + fixturifyProject.addAddon('test-addon-b', '1.0.0', { + callback: (addon) => { + addon.addReferenceDependency('test-addon-a', '*'); + }, + }); + + fixturifyProject.addAddon('test-addon-c', '1.0.0', { + callback: (addon) => { + addon.addReferenceDependency('test-addon-a', '*'); + }, + }); + + fixturifyProject.addAddon('test-addon-d', '1.0.0', { + callback: (addon) => { + addon.addReferenceDependency('test-addon-a', '1.0.0'); + }, + }); + + let project = fixturifyProject.buildProjectModel(); + project.initializeAddons(); + + let counts = countAddons(project); + expect(counts.proxyCount).to.equal(3); + expect(project.perBundleAddonCache.numProxies).to.equal(3); + expect(counts.byName['test-addon-a'].addons.length).to.equal(4); + + expect(counts.byName['test-addon-a'].realAddonInstanceCount).to.equal(1); + expect(counts.byName['test-addon-a'].proxyCount).to.equal(3); + + expect(counts.byName['test-addon-b'].addons.length).to.equal(1); + expect(counts.byName['test-addon-c'].addons.length).to.equal(1); + }); + + it('addon with `allowCachingPerBundle`, 1 in lazy engine, one in regular', function () { + // PROJ to LEA, REB, LEA and REB both depend on TAA + // Neither instance of test-addon-a is declared in the project, but the one in engine B + // will be 'owned' by Project as far as PerBundleAddonCache is concerned. + // Should end with 2 instances of test-addon-a, one in PROJECT, one in lazy-engine-a, + // and no proxies. + fixturifyProject.addEngine('lazy-engine-a', '1.0.0', { + enableLazyLoading: true, + callback: (engine) => { + engine.addAddon('test-addon-a', '1.0.0', { allowCachingPerBundle: true }); + }, + }); + + fixturifyProject.addEngine('regular-engine-b', '1.0.0', { + callback: (engine) => { + engine.addAddon('test-addon-a', '1.0.0', { allowCachingPerBundle: true }); + }, + }); + + let project = fixturifyProject.buildProjectModel(); + project.initializeAddons(); + + let counts = countAddons(project); + + expect(counts.byName['lazy-engine-a'].addons.length).to.equal(1); + expect(counts.byName['regular-engine-b'].addons.length).to.equal(1); + + expect(counts.proxyCount).to.equal(0); + expect(project.perBundleAddonCache.numProxies).to.equal(0); + + expect(counts.byName['test-addon-a'].realAddonInstanceCount).to.equal(2); + expect(counts.byName['test-addon-a'].proxyCount).to.equal(0); + + const lazyEngineAPkgInfo = project.addons.find((addon) => addon.name === 'lazy-engine-a')._packageInfo; + let cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineAPkgInfo, 'test-addon-a'); + expect(cacheEntries).to.exist; + expect(cacheEntries.length).to.equal(1); + + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, project._packageInfo, 'test-addon-a'); + expect(cacheEntries).to.exist; + expect(cacheEntries.length).to.equal(1); + }); + + it('addon with allowCachingPerBundle, 1 in each of 2 lazy engines', function () { + // Same as above, but regular-engine-b is now lazy-engine-b + // Should have 2 instances, 1 in LEA, 1 in LEB, separate paths. + fixturifyProject.addEngine('lazy-engine-a', '1.0.0', { + enableLazyLoading: true, + callback: (engine) => { + engine.addAddon('test-addon-a', '1.0.0', { allowCachingPerBundle: true }); + }, + }); + + fixturifyProject.addEngine('lazy-engine-b', '1.0.0', { + enableLazyLoading: true, + callback: (engine) => { + engine.addAddon('test-addon-a', '1.0.0', { allowCachingPerBundle: true }); + }, + }); + + let project = fixturifyProject.buildProjectModel(); + project.initializeAddons(); + + let counts = countAddons(project); + + expect(counts.byName['lazy-engine-a'].addons.length).to.equal(1); + expect(counts.byName['lazy-engine-b'].addons.length).to.equal(1); + + expect(counts.proxyCount).to.equal(0); + expect(project.perBundleAddonCache.numProxies).to.equal(0); + + expect(counts.byName['test-addon-a'].realAddonInstanceCount).to.equal(2); + expect(counts.byName['test-addon-a'].proxyCount).to.equal(0); + + const lazyEngineAPkgInfo = project.addons.find((addon) => addon.name === 'lazy-engine-a')._packageInfo; + let cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineAPkgInfo, 'test-addon-a'); + expect(cacheEntries).to.exist; + expect(cacheEntries.length).to.equal(1); + + const lazyEngineBPkgInfo = project.addons.find((addon) => addon.name === 'lazy-engine-b')._packageInfo; + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineBPkgInfo, 'test-addon-a'); + expect(cacheEntries).to.exist; + expect(cacheEntries.length).to.equal(1); + }); + + it('addon with `allowCachingPerBundle`, 1 in each of 2 lazy engines; project also depends on this addon', function () { + fixturifyProject.addAddon('test-addon-a', '1.0.0', { allowCachingPerBundle: true }); + + fixturifyProject.addEngine('lazy-engine-a', '1.0.0', { + enableLazyLoading: true, + callback: (engine) => { + engine.addReferenceDependency('test-addon-a'); + }, + }); + + fixturifyProject.addEngine('lazy-engine-b', '1.0.0', { + enableLazyLoading: true, + callback: (engine) => { + engine.addReferenceDependency('test-addon-a'); + }, + }); + + let project = fixturifyProject.buildProjectModel(); + project.initializeAddons(); + + let counts = countAddons(project); + + expect(counts.byName['lazy-engine-a'].addons.length).to.equal(1); + expect(counts.byName['lazy-engine-b'].addons.length).to.equal(1); + + expect(counts.proxyCount).to.equal(0); + expect(project.perBundleAddonCache.numProxies).to.equal(0); + + expect(counts.byName['test-addon-a'].realAddonInstanceCount).to.equal(3); + expect(counts.byName['test-addon-a'].proxyCount).to.equal(0); + + const lazyEngineAPkgInfo = project.addons.find((addon) => addon.name === 'lazy-engine-a')._packageInfo; + let cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineAPkgInfo, 'test-addon-a'); + expect(cacheEntries).to.exist; + expect(cacheEntries.length).to.equal(1); + + const lazyEngineBPkgInfo = project.addons.find((addon) => addon.name === 'lazy-engine-b')._packageInfo; + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineBPkgInfo, 'test-addon-a'); + expect(cacheEntries).to.exist; + expect(cacheEntries.length).to.equal(1); + }); + + it('addon with allowCachingPerBundle, 2 regular engines - cache entries in project but not declared there', function () { + // Project declares an in-repo addon TAA. Then remove the ember-addon.paths entry so the project + // "doesn't know" about it but it's available for engines. Declare 2 non-lazy in-repo engines. + // Then have them add a shared in-repo dependency to TAA, with the path pointing to the one in + // PROJ/lib (i.e. '../test-addon-a') + // Should have 1 instance, 1 proxy, both in project. + fixturifyProject.addInRepoAddon('test-addon-a', '1.0.0', { allowCachingPerBundle: true }); + fixturifyProject.pkg['ember-addon'].paths = []; // remove the 'dependency' (file still exists) + + fixturifyProject.addInRepoEngine('regular-engine-a', '1.0.0', { + enableLazyLoading: false, + shouldShareDependencies: true, + callback: (inRepoEngine) => { + inRepoEngine.pkg['ember-addon'].paths = ['../test-addon-a']; + }, + }); + + fixturifyProject.addInRepoEngine('regular-engine-b', '1.0.0', { + enableLazyLoading: false, + shouldShareDependencies: true, + callback: (inRepoEngine) => { + inRepoEngine.pkg['ember-addon'].paths = ['../test-addon-a']; + }, + }); + + fixturifyProject.writeSync(); + + let project = fixturifyProject.buildProjectModel(); + project.initializeAddons(); + + let { proxyCount, byName } = countAddons(project); + + expect(byName['regular-engine-a'].addons.length).to.equal(1); + expect(byName['regular-engine-b'].addons.length).to.equal(1); + + expect(proxyCount).to.equal(1); + expect(project.perBundleAddonCache.numProxies).to.equal(1); + + expect(byName['test-addon-a'].realAddonInstanceCount).to.equal(1); + expect(byName['test-addon-a'].proxyCount).to.equal(1); + + let cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, project._packageInfo, 'test-addon-a'); + expect(cacheEntries).to.exist; + expect(cacheEntries.length).to.equal(1); + }); + + it('addon with allowCachingPerBundle, 2 regular engines - cache entries in project (also declared there)', function () { + // Same as above, now both are regular engines. + // Should have 1 instance, 2 proxies, both in project. + fixturifyProject.addAddon('test-addon-a', '1.0.0', { allowCachingPerBundle: true }); + + fixturifyProject.addEngine('regular-engine-a', '1.0.0', { + callback: (engine) => { + engine.addReferenceDependency('test-addon-a', '1.0.0'); + }, + }); + + fixturifyProject.addEngine('regular-engine-b', '1.0.0', { + callback: (engine) => { + engine.addReferenceDependency('test-addon-a', '1.0.0'); + }, + }); + + fixturifyProject.writeSync(); + + let project = fixturifyProject.buildProjectModel(); + project.initializeAddons(); + + let { proxyCount, byName } = countAddons(project); + + expect(byName['regular-engine-a'].addons.length).to.equal(1); + expect(byName['regular-engine-b'].addons.length).to.equal(1); + + expect(proxyCount).to.equal(2); + expect(project.perBundleAddonCache.numProxies).to.equal(2); + + expect(byName['test-addon-a'].realAddonInstanceCount).to.equal(1); + expect(byName['test-addon-a'].proxyCount).to.equal(2); + + let cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, project._packageInfo, 'test-addon-a'); + expect(cacheEntries).to.exist; + expect(cacheEntries.length).to.equal(1); + }); + + it('2 lazy engines; each depend on two addons; ensure that each lazy engine has proxy for subsequent instantiations of duplicate addons', function () { + fixturifyProject.addInRepoAddon('test-addon-a', '1.0.0', { + allowCachingPerBundle: true, + callback: (addon) => { + addon.pkg['ember-addon'].paths = ['../test-addon-b']; + }, + }); + + fixturifyProject.addInRepoAddon('test-addon-b', '1.0.0', { allowCachingPerBundle: true }); + fixturifyProject.pkg['ember-addon'].paths = []; // project now 'doesn't know' about test-addon-b + + fixturifyProject.addInRepoEngine('lazy-engine-a', '1.0.0', { + allowCachingPerBundle: true, + enableLazyLoading: true, + callback: (engine) => { + engine.pkg['ember-addon'].paths = ['../test-addon-a', '../test-addon-b']; + }, + }); + + fixturifyProject.addInRepoEngine('lazy-engine-b', '1.0.0', { + allowCachingPerBundle: true, + enableLazyLoading: true, + callback: (engine) => { + engine.pkg['ember-addon'].paths = ['../test-addon-a', '../test-addon-b']; + }, + }); + + let project = fixturifyProject.buildProjectModel(); + project.initializeAddons(); + + let { byName } = countAddons(project); + + expect(byName['lazy-engine-a'].realAddonInstanceCount).to.equal(1); + expect(byName['lazy-engine-a'].proxyCount).to.equal(0); + + expect(byName['lazy-engine-b'].realAddonInstanceCount).to.equal(1); + expect(byName['lazy-engine-b'].proxyCount).to.equal(0); + + expect(byName['test-addon-a'].realAddonInstanceCount).to.equal(2); + expect(byName['test-addon-a'].proxyCount).to.equal(0); + + expect(byName['test-addon-b'].realAddonInstanceCount).to.equal(2); + expect(byName['test-addon-b'].proxyCount).to.equal(2); + + const lazyEngineAPkgInfo = project.addons.find((addon) => addon.name === 'lazy-engine-a')._packageInfo; + let cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineAPkgInfo, 'test-addon-a'); + expect(cacheEntries).to.exist; + expect(cacheEntries.length).to.equal(1); + + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineAPkgInfo, 'test-addon-b'); + expect(cacheEntries).to.exist; + expect(cacheEntries.length).to.equal(1); + + const lazyEngineBPkgInfo = project.addons.find((addon) => addon.name === 'lazy-engine-b')._packageInfo; + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineBPkgInfo, 'test-addon-a'); + expect(cacheEntries).to.exist; + expect(cacheEntries.length).to.equal(1); + + cacheEntries = findAddonCacheEntriesByName(project.perBundleAddonCache, lazyEngineBPkgInfo, 'test-addon-b'); + expect(cacheEntries).to.exist; + expect(cacheEntries.length).to.equal(1); + }); + + it('2 regular engines; each depend on two addons; ensure that project cache is used', function () { + fixturifyProject.addInRepoAddon('test-addon-a', '1.0.0', { + allowCachingPerBundle: true, + callback: (addon) => { + addon.pkg['ember-addon'].paths = ['../test-addon-b']; + }, + }); + + fixturifyProject.addInRepoAddon('test-addon-b', '1.0.0', { allowCachingPerBundle: true }); + fixturifyProject.pkg['ember-addon'].paths = []; + + fixturifyProject.addInRepoEngine('engine-a', '1.0.0', { + allowCachingPerBundle: true, + callback: (engine) => { + engine.pkg['ember-addon'].paths = ['../test-addon-a', '../test-addon-b']; + }, + }); + + fixturifyProject.addInRepoEngine('engine-b', '1.0.0', { + allowCachingPerBundle: true, + callback: (engine) => { + engine.pkg['ember-addon'].paths = ['../test-addon-a', '../test-addon-b']; + }, + }); + + let project = fixturifyProject.buildProjectModel(); + project.initializeAddons(); + + let { byName } = countAddons(project); + + expect(byName['engine-a'].realAddonInstanceCount).to.equal(1); + expect(byName['engine-a'].proxyCount).to.equal(0); + + expect(byName['engine-b'].realAddonInstanceCount).to.equal(1); + expect(byName['engine-b'].proxyCount).to.equal(0); + + expect(byName['test-addon-a'].realAddonInstanceCount).to.equal(1); + expect(byName['test-addon-a'].proxyCount).to.equal(1); + + expect(byName['test-addon-b'].realAddonInstanceCount).to.equal(1); + expect(byName['test-addon-b'].proxyCount).to.equal(2); + }); + + it('multiple references to a single lazy engine that has opted-in to `allowCachingPerBundle`', function () { + fixturifyProject.addInRepoEngine('lazy-engine-a', '1.0.0', { + allowCachingPerBundle: true, + enableLazyLoading: true, + }); + + fixturifyProject.addInRepoAddon('test-addon-a', '1.0.0', { + callback: (addon) => { + addon.pkg['ember-addon'].paths = ['../lazy-engine-a']; + }, + }); + + fixturifyProject.addInRepoAddon('test-addon-b', '1.0.0', { + callback: (addon) => { + addon.pkg['ember-addon'].paths = ['../lazy-engine-a']; + }, + }); + + fixturifyProject.addInRepoAddon('test-addon-c', '1.0.0', { + callback: (addon) => { + addon.pkg['ember-addon'].paths = ['../lazy-engine-a']; + }, + }); + + let project = fixturifyProject.buildProjectModel(); + project.initializeAddons(); + + let { byName } = countAddons(project); + + expect(byName['lazy-engine-a'].realAddonInstanceCount).to.equal(1); + expect(byName['lazy-engine-a'].proxyCount).to.equal(3); + + expect(areAllInstancesEqualWithinHost(project, 'lazy-engine-a')).to.be.true; + }); + + it('uses the custom `per-bundle-addon-cache.js` util if it exists', function () { + fixturifyProject.addInRepoAddon('test-addon-a', '1.0.0'); + + fixturifyProject.addInRepoAddon('test-addon-b', '1.0.0', (addon) => { + addon.pkg['ember-addon'].paths = ['../test-addon-a']; + }); + + fixturifyProject.pkg['ember-addon'].perBundleAddonCacheUtil = './per-bundle-addon-cache.js'; + + fixturifyProject.files['per-bundle-addon-cache.js'] = ` +'use strict'; + +function allowCachingPerBundle({ addonEntryPointModule }) { + if (addonEntryPointModule.name === 'test-addon-a') { + return true; + } + + return false; +} + +module.exports = { + allowCachingPerBundle, +}; + `; + + let project = fixturifyProject.buildProjectModel(); + project.initializeAddons(); + + let { byName } = countAddons(project); + + expect(byName['test-addon-a'].realAddonInstanceCount).to.equal(1); + expect(byName['test-addon-a'].proxyCount).to.equal(1); + }); + + it('throws an error if the provided `perBundleAddonCacheUtil` does not exist', function () { + fixturifyProject.addInRepoAddon('test-addon-a', '1.0.0'); + + fixturifyProject.addInRepoAddon('test-addon-b', '1.0.0', (addon) => { + addon.pkg['ember-addon'].paths = ['../test-addon-a']; + }); + + fixturifyProject.pkg['ember-addon'].perBundleAddonCacheUtil = './per-bundle-addon-cache.js'; + + expect(() => { + fixturifyProject.buildProjectModel(); + }).to.throw( + '[ember-cli] the provided `./per-bundle-addon-cache.js` for `ember-addon.perBundleAddonCacheUtil` does not exist' + ); + }); + + it('bundle caching works for addons that opt-in via `prototype.allowCachingPerBundle`', function () { + fixturifyProject.addInRepoAddon('test-addon-a', '1.0.0', { + addonEntryPoint: ` + function Addon() {} + Addon.prototype.allowCachingPerBundle = true; + Addon.prototype.pkg = require('./package.json'); + Addon.prototype.name = 'test-addon-a'; + module.exports = Addon; + `, + }); + + fixturifyProject.addInRepoAddon('test-addon-b', '1.0.0', (addon) => { + addon.pkg['ember-addon'].paths = ['../test-addon-a']; + }); + + let project = fixturifyProject.buildProjectModel(); + project.initializeAddons(); + + let { byName } = countAddons(project); + + expect(byName['test-addon-a'].realAddonInstanceCount).to.equal(1); + expect(byName['test-addon-a'].proxyCount).to.equal(1); + }); + }); + + describe('validation', function () { + function findAllAddonsByName(projectOrAddon, addonToFind, _foundAddons = []) { + projectOrAddon.addons.forEach((addon) => { + if (addon.name === addonToFind) { + _foundAddons.push(addon); + } + + findAllAddonsByName(addon, addonToFind, _foundAddons); + }); + + return _foundAddons; + } + + it('does not throw if the addon returns a stable cache key', function () { + fixturifyProject.addInRepoAddon('foo', '1.0.0', { + allowCachingPerBundle: true, + }); + + fixturifyProject.addInRepoAddon('foo-bar', '1.0.0', { + callback: (inRepoAddon) => { + inRepoAddon.pkg['ember-addon'].paths = ['../foo']; + }, + }); + + fixturifyProject.writeSync(); + let project = fixturifyProject.buildProjectModel(); + + project.initializeAddons(); + + findAllAddonsByName(project, 'foo').forEach((addon) => { + addon.cacheKeyForTree(); + addon.cacheKeyForTree(); + + addon.cacheKeyForTree('addon'); + addon.cacheKeyForTree('addon'); + }); + }); + + it('throws an error for addons that do not have stable cache keys', function () { + fixturifyProject.addInRepoAddon('foo', '1.0.0', { + allowCachingPerBundle: true, + additionalContent: `init() {this._super.init.apply(this, arguments); this.current = 0;}, cacheKeyForTree() {return this.current++;},`, + }); + + fixturifyProject.addInRepoAddon('foo-bar', '1.0.0', { + callback: (inRepoAddon) => { + inRepoAddon.pkg['ember-addon'].paths = ['../foo']; + }, + }); + + fixturifyProject.writeSync(); + let project = fixturifyProject.buildProjectModel(); + + project.initializeAddons(); + + expect(() => { + findAllAddonsByName(project, 'foo').forEach((addon) => { + addon.cacheKeyForTree(); + addon.cacheKeyForTree(); + }); + }).to.throw( + `[ember-cli] addon bundle caching can only be used on addons that have stable cache keys (previously \`2\`, now \`3\`; for addon \`foo\` located at \`${path.join( + fixturifyProject.baseDir, + 'lib/foo' + )}\`)` + ); + }); + }); +}); diff --git a/tests/unit/models/project-test.js b/tests/unit/models/project-test.js index 59f94db5bf..173e6b3be1 100644 --- a/tests/unit/models/project-test.js +++ b/tests/unit/models/project-test.js @@ -10,9 +10,8 @@ const expect = require('chai').expect; const emberCLIVersion = require('../../../lib/utilities/version-utils').emberCLIVersion; const td = require('testdouble'); const MockCLI = require('../../helpers/mock-cli'); -const { isExperimentEnabled } = require('../../../lib/experiments'); -describe('models/project.js', function() { +describe('models/project.js', function () { let project, projectPath, packageContents; function makeProject() { @@ -20,24 +19,24 @@ describe('models/project.js', function() { project = new Project(projectPath, packageContents, cli.ui, cli); } - beforeEach(function() { + beforeEach(function () { packageContents = {}; }); - afterEach(function() { + afterEach(function () { if (project) { project = null; } }); - describe('constructor', function() { - it('sets up bidirectional instrumentation', function() { + describe('constructor', function () { + it('sets up bidirectional instrumentation', function () { let cli = new MockCLI(); expect(cli.instrumentation.project).to.equal(null); projectPath = 'tmp/test-app'; - return tmp.setup(projectPath).then(function() { + return tmp.setup(projectPath).then(function () { touch(`${projectPath}/config/environment.js`, { baseURL: '/foo/bar', }); @@ -49,13 +48,13 @@ describe('models/project.js', function() { }); }); - describe('Project.prototype.config', function() { + describe('Project.prototype.config', function () { let called; projectPath = 'tmp/test-app'; - beforeEach(function() { + beforeEach(function () { called = false; - return tmp.setup(projectPath).then(function() { + return tmp.setup(projectPath).then(function () { touch(`${projectPath}/config/environment.js`, { baseURL: '/foo/bar', }); @@ -70,24 +69,24 @@ describe('models/project.js', function() { makeProject(); - project.require = function() { + project.require = function () { called = true; - return function() {}; + return function () {}; }; }); }); - afterEach(function() { + afterEach(function () { called = null; return tmp.teardown(projectPath); }); - it('config() finds and requires config/environment', function() { + it('config() finds and requires config/environment', function () { project.config('development'); expect(called).to.equal(true); }); - it('config() with no args returns the config corresponding to the current EMBER_ENV', function() { + it('config() with no args returns the config corresponding to the current EMBER_ENV', function () { process.env.EMBER_ENV = 'testing'; project.config(); @@ -100,8 +99,8 @@ describe('models/project.js', function() { expect(configCacheKey).to.match(/testing/i); }); - describe('memoizes', function() { - it('memoizes', function() { + describe('memoizes', function () { + it('memoizes', function () { project.config('development'); expect(called).to.equal(true); called = false; @@ -109,8 +108,8 @@ describe('models/project.js', function() { expect(called).to.equal(false); }); - it('considers configPath when memoizing', function() { - project.configPath = function() { + it('considers configPath when memoizing', function () { + project.configPath = function () { return `${projectPath}/config/a`; }; project.config('development'); @@ -118,7 +117,7 @@ describe('models/project.js', function() { expect(called).to.equal(true); called = false; - project.configPath = function() { + project.configPath = function () { return `${projectPath}/config/a`; }; project.config('development'); @@ -126,7 +125,7 @@ describe('models/project.js', function() { expect(called).to.equal(false); called = false; - project.configPath = function() { + project.configPath = function () { return `${projectPath}/config/b`; }; project.config('development'); @@ -141,7 +140,7 @@ describe('models/project.js', function() { }); }); - it('configPath() returns tests/dummy/config/environment', function() { + it('configPath() returns tests/dummy/config/environment', function () { project.pkg = { 'ember-addon': { configPath: 'tests/dummy/config', @@ -153,10 +152,10 @@ describe('models/project.js', function() { expect(project.configPath().slice(-expected.length)).to.equal(expected); }); - it('calls getAddonsConfig', function() { + it('calls getAddonsConfig', function () { let addonConfigCalled = false; - project.getAddonsConfig = function() { + project.getAddonsConfig = function () { addonConfigCalled = true; return {}; @@ -166,12 +165,12 @@ describe('models/project.js', function() { expect(addonConfigCalled).to.equal(true); }); - it('returns getAddonsConfig result when configPath is not present', function() { + it('returns getAddonsConfig result when configPath is not present', function () { let expected = { foo: 'bar', }; - project.getAddonsConfig = function() { + project.getAddonsConfig = function () { return expected; }; @@ -179,10 +178,10 @@ describe('models/project.js', function() { expect(actual).to.deep.equal(expected); }); - describe('merges getAddonsConfig result with app config', function() { + describe('merges getAddonsConfig result with app config', function () { let projectConfig, addon1Config, addon2Config; - beforeEach(function() { + beforeEach(function () { addon1Config = { addon: { derp: 'herp' } }; addon2Config = { addon: { blammo: 'blahzorz' } }; @@ -202,14 +201,14 @@ describe('models/project.js', function() { project._addonsInitialized = true; - project.require = function() { - return function() { + project.require = function () { + return function () { return projectConfig; }; }; }); - it('merges getAddonsConfig result with app config', function() { + it('merges getAddonsConfig result with app config', function () { let expected = { foo: 'bar', baz: 'qux', @@ -223,7 +222,7 @@ describe('models/project.js', function() { expect(actual).to.deep.equal(expected); }); - it('getAddonsConfig does NOT override project config', function() { + it('getAddonsConfig does NOT override project config', function () { let expected = { foo: 'bar', baz: 'qux', @@ -241,18 +240,18 @@ describe('models/project.js', function() { }); }); - describe('Project.prototype.targets', function() { - beforeEach(function() { + describe('Project.prototype.targets', function () { + beforeEach(function () { projectPath = 'tmp/test-app'; }); - afterEach(function() { + afterEach(function () { return tmp.teardown(projectPath); }); - describe('when the is a `/config/targets.js` file', function() { - beforeEach(function() { - return tmp.setup(projectPath).then(function() { + describe('when the is a `/config/targets.js` file', function () { + beforeEach(function () { + return tmp.setup(projectPath).then(function () { let targetsPath = path.join(projectPath, 'config', 'targets.js'); fs.createFileSync(targetsPath); fs.writeFileSync(targetsPath, 'module.exports = { browsers: ["last 2 versions", "safari >= 7"] };', { @@ -261,27 +260,27 @@ describe('models/project.js', function() { makeProject(); - project.require = function() { + project.require = function () { return { browsers: ['last 2 versions', 'safari >= 7'] }; }; }); }); - it('returns the object defined in `/config/targets` if present', function() { + it('returns the object defined in `/config/targets` if present', function () { expect(project.targets).to.deep.equal({ browsers: ['last 2 versions', 'safari >= 7'], }); }); }); - describe("when there isn't a `/config/targets.js` file", function() { - beforeEach(function() { - return tmp.setup(projectPath).then(function() { + describe("when there isn't a `/config/targets.js` file", function () { + beforeEach(function () { + return tmp.setup(projectPath).then(function () { makeProject(); }); }); - it('returns the default targets', function() { + it('returns the default targets', function () { expect(project.targets).to.deep.equal({ browsers: ['ie 11', 'last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions'], }); @@ -289,8 +288,8 @@ describe('models/project.js', function() { }); }); - describe('addons', function() { - beforeEach(function() { + describe('addons', function () { + beforeEach(function () { projectPath = path.resolve(__dirname, '../../fixtures/addon/simple'); packageContents = require(path.join(projectPath, 'package.json')); @@ -299,11 +298,11 @@ describe('models/project.js', function() { project.initializeAddons(); }); - it("returns a listing of all dependencies in the project's package.json", function() { + it("returns a listing of all dependencies in the project's package.json", function () { let expected = { 'ember-cli': 'latest', 'ember-random-addon': 'latest', - 'ember-resolver': '^5.0.1', + 'ember-resolver': '^7.0.0', 'ember-non-root-addon': 'latest', 'ember-generated-with-export-addon': 'latest', 'non-ember-thingy': 'latest', @@ -318,7 +317,7 @@ describe('models/project.js', function() { expect(project.dependencies()).to.deep.equal(expected); }); - it("returns a listing of all dependencies in the project's bower.json", function() { + it("returns a listing of all dependencies in the project's bower.json", function () { let expected = { jquery: '^1.11.1', ember: '1.7.0', @@ -331,7 +330,7 @@ describe('models/project.js', function() { expect(project.bowerDependencies()).to.deep.equal(expected); }); - it('returns a listing of all ember-cli-addons directly depended on by the project', function() { + it('returns a listing of all ember-cli-addons directly depended on by the project', function () { let expected = [ 'amd-transform', 'broccoli-serve-files', @@ -348,11 +347,12 @@ describe('models/project.js', function() { 'ember-non-root-addon', 'ember-random-addon', 'ember-super-button', + 'ember-with-addon-main', ]; expect(Object.keys(project.addonPackages)).to.deep.equal(expected); }); - it('returns instances of the addons', function() { + it('returns instances of the addons', function () { let addons = project.addons; expect(addons[8].name).to.equal('ember-before-blueprint-addon'); @@ -361,25 +361,25 @@ describe('models/project.js', function() { expect(addons[14].addons[1].name).to.equal('ember-ng'); }); - it('addons get passed the project instance', function() { + it('addons get passed the project instance', function () { let addons = project.addons; expect(addons[1].project).to.equal(project); }); - it('returns an instance of an addon that uses `ember-addon-main`', function() { + it('returns an instance of an addon that uses `ember-addon-main`', function () { let addons = project.addons; expect(addons[10].name).to.equal('ember-random-addon'); }); - it('returns the default blueprints path', function() { + it('returns the default blueprints path', function () { let expected = project.root + path.normalize('/blueprints'); expect(project.localBlueprintLookupPath()).to.equal(expected); }); - it('returns a listing of all addon blueprints paths ordered by last loaded when called once', function() { + it('returns a listing of all addon blueprints paths ordered by last loaded when called once', function () { let loadedBlueprintPaths = [ project.root + path.normalize('/node_modules/ember-before-blueprint-addon/blueprints'), project.root + path.normalize('/node_modules/ember-random-addon/blueprints'), @@ -393,7 +393,7 @@ describe('models/project.js', function() { expect(first).to.deep.equal(expected); }); - it('returns a listing of all addon blueprints paths ordered by last loaded when called twice', function() { + it('returns a listing of all addon blueprints paths ordered by last loaded when called twice', function () { let loadedBlueprintPaths = [ project.root + path.normalize('/node_modules/ember-before-blueprint-addon/blueprints'), project.root + path.normalize('/node_modules/ember-random-addon/blueprints'), @@ -408,7 +408,7 @@ describe('models/project.js', function() { expect(second).to.deep.equal(expected); }); - it('returns a listing of all blueprints paths', function() { + it('returns a listing of all blueprints paths', function () { let expected = [ project.root + path.normalize('/blueprints'), project.root + path.normalize('/node_modules/ember-after-blueprint-addon/blueprints'), @@ -419,24 +419,24 @@ describe('models/project.js', function() { expect(project.blueprintLookupPaths()).to.deep.equal(expected); }); - it('does not include blueprint path relative to root if outside a project', function() { - project.isEmberCLIProject = function() { + it('does not include blueprint path relative to root if outside a project', function () { + project.isEmberCLIProject = function () { return false; }; expect(project.blueprintLookupPaths()).to.deep.equal(project.addonBlueprintLookupPaths()); }); - it('returns an instance of an addon with an object export', function() { + it('returns an instance of an addon with an object export', function () { let addons = project.addons; expect(addons[13] instanceof Addon).to.equal(true); expect(addons[13].name).to.equal('ember-generated-with-export-addon'); }); - it('adds the project itself if it is an addon', function() { + it('adds the project itself if it is an addon', function () { project.addonPackages = {}; - project.isEmberCLIAddon = function() { + project.isEmberCLIAddon = function () { return true; }; @@ -445,7 +445,7 @@ describe('models/project.js', function() { expect(project.addonPackages[project.name()]).to.exist; }); - it('should catch addon constructor errors', function() { + it('should catch addon constructor errors', function () { projectPath = path.resolve(__dirname, '../../fixtures/addon/invalid-constructor'); packageContents = require(path.join(projectPath, 'package.json')); @@ -461,8 +461,8 @@ describe('models/project.js', function() { }); }); - describe('reloadAddon', function() { - beforeEach(function() { + describe('reloadAddon', function () { + beforeEach(function () { projectPath = path.resolve(__dirname, '../../fixtures/addon/simple'); packageContents = require(path.join(projectPath, 'package.json')); @@ -476,26 +476,27 @@ describe('models/project.js', function() { project.reloadAddons(); }); - afterEach(function() { + afterEach(function () { td.reset(); }); - it('sets _addonsInitialized to false', function() { + it('sets _addonsInitialized to false', function () { expect(project._addonsInitialized).to.equal(false); }); - it('reloads the package', function() { + it('reloads the package', function () { td.verify(Project.prototype.reloadPkg(), { ignoreExtraArgs: true }); }); - it('initializes the addons', function() { + it('initializes the addons', function () { td.verify(Project.prototype.initializeAddons(), { ignoreExtraArgs: true }); }); }); - describe('reloadPkg', function() { + describe('reloadPkg', function () { let newProjectPath, oldPkg; - beforeEach(function() { + + beforeEach(function () { projectPath = path.resolve(__dirname, '../../fixtures/addon/simple'); packageContents = require(path.join(projectPath, 'package.json')); @@ -509,57 +510,65 @@ describe('models/project.js', function() { project.root = newProjectPath; }); - it('reloads the package from disk', function() { + it('reloads the package from disk', function () { project.reloadPkg(); expect(oldPkg).to.not.deep.equal(project.pkg); }); + + it('reloads the pkginfo', function () { + let pkgInfo = project._packageInfo; + + project.reloadPkg(); + + expect(pkgInfo).to.not.equal(project._packageInfo); + }); }); - describe('emberCLIVersion', function() { - beforeEach(function() { + describe('emberCLIVersion', function () { + beforeEach(function () { projectPath = `${process.cwd()}/tmp/test-app`; makeProject(); }); - it('should return the same value as the utility function', function() { + it('should return the same value as the utility function', function () { expect(project.emberCLIVersion()).to.equal(emberCLIVersion()); }); }); - describe('isEmberCLIProject', function() { - beforeEach(function() { + describe('isEmberCLIProject', function () { + beforeEach(function () { projectPath = `${process.cwd()}/tmp/test-app`; makeProject(); }); - it('returns false when `ember-cli` is not a dependency', function() { + it('returns false when `ember-cli` is not a dependency', function () { expect(project.isEmberCLIProject()).to.equal(false); }); - it('returns true when `ember-cli` is a devDependency', function() { + it('returns true when `ember-cli` is a devDependency', function () { project.pkg.devDependencies = { 'ember-cli': '*' }; expect(project.isEmberCLIProject()).to.equal(true); }); - it('returns true when `ember-cli` is a dependency', function() { + it('returns true when `ember-cli` is a dependency', function () { project.pkg.dependencies = { 'ember-cli': '*' }; expect(project.isEmberCLIProject()).to.equal(true); }); }); - describe('isEmberCLIAddon', function() { - beforeEach(function() { + describe('isEmberCLIAddon', function () { + beforeEach(function () { projectPath = `${process.cwd()}/tmp/test-app`; makeProject(); project.initializeAddons(); }); - it('should return true if `ember-addon` is included in keywords', function() { + it('should return true if `ember-addon` is included in keywords', function () { project.pkg = { keywords: ['ember-addon'], }; @@ -567,7 +576,7 @@ describe('models/project.js', function() { expect(project.isEmberCLIAddon()).to.equal(true); }); - it('should return false if `ember-addon` is not included in keywords', function() { + it('should return false if `ember-addon` is not included in keywords', function () { project.pkg = { keywords: [], }; @@ -576,29 +585,8 @@ describe('models/project.js', function() { }); }); - if (isExperimentEnabled('MODULE_UNIFICATION')) { - describe('isModuleUnification', function() { - beforeEach(function() { - projectPath = `${process.cwd()}/tmp/test-app`; - - makeProject(); - }); - - it('returns false when `./src` does not exist', function() { - expect(project.isModuleUnification()).to.equal(false); - }); - - it('returns true when `./src` exists', function() { - let srcPath = path.join(projectPath, 'src'); - fs.ensureDirSync(srcPath); - - expect(project.isModuleUnification()).to.equal(true); - }); - }); - } - - describe('findAddonByName', function() { - beforeEach(function() { + describe('findAddonByName', function () { + beforeEach(function () { projectPath = `${process.cwd()}/tmp/test-app`; makeProject(); @@ -620,16 +608,16 @@ describe('models/project.js', function() { ]; }); - afterEach(function() { + afterEach(function () { td.reset(); }); - it('should call initialize addons', function() { + it('should call initialize addons', function () { project.findAddonByName('foo'); td.verify(project.initializeAddons(), { ignoreExtraArgs: true }); }); - it('generally should work and defer to findAddonByName utlity', function() { + it('generally should work and defer to findAddonByName utlity', function () { let addon; addon = project.findAddonByName('foo'); expect(addon.name).to.equal('foo', 'should have found the foo addon'); @@ -639,49 +627,49 @@ describe('models/project.js', function() { }); }); - describe('bowerDirectory', function() { - beforeEach(function() { + describe('bowerDirectory', function () { + beforeEach(function () { projectPath = path.resolve(__dirname, '../../fixtures/addon/simple'); makeProject(); }); - it('should be initialized in constructor', function() { + it('should be initialized in constructor', function () { expect(project.bowerDirectory).to.equal('bower_components'); }); - it('should be set to directory property in .bowerrc', function() { + it('should be set to directory property in .bowerrc', function () { projectPath = path.resolve(__dirname, '../../fixtures/bower-directory-tests/bowerrc-with-directory'); makeProject(); expect(project.bowerDirectory).to.equal('vendor'); }); - it('should default to ‘bower_components’ unless directory property is set in .bowerrc', function() { + it('should default to ‘bower_components’ unless directory property is set in .bowerrc', function () { projectPath = path.resolve(__dirname, '../../fixtures/bower-directory-tests/bowerrc-without-directory'); makeProject(); expect(project.bowerDirectory).to.equal('bower_components'); }); - it('should default to ‘bower_components’ if .bowerrc is not present', function() { + it('should default to ‘bower_components’ if .bowerrc is not present', function () { projectPath = path.resolve(__dirname, '../../fixtures/bower-directory-tests/no-bowerrc'); makeProject(); expect(project.bowerDirectory).to.equal('bower_components'); }); - it('should default to ‘bower_components’ if .bowerrc json is invalid', function() { + it('should default to ‘bower_components’ if .bowerrc json is invalid', function () { projectPath = path.resolve(__dirname, '../../fixtures/bower-directory-tests/invalid-bowerrc'); makeProject(); expect(project.bowerDirectory).to.equal('bower_components'); }); }); - describe('.nullProject', function() { - it('is a singleton', function() { + describe('.nullProject', function () { + it('is a singleton', function () { expect(Project.nullProject()).to.equal(Project.nullProject()); }); }); - describe('generateTestFile()', function() { - it('returns empty file and shows warning', function() { + describe('generateTestFile()', function () { + it('returns empty file and shows warning', function () { projectPath = path.resolve(__dirname, '../../fixtures/project'); makeProject(); @@ -691,4 +679,22 @@ describe('models/project.js', function() { ); }); }); + + describe('Project.closestSync', function () { + it('should use the `actual-project` specified by `ember-addon.projectRoot` in the top-level `package.json`', function () { + let cli = new MockCLI(); + projectPath = path.resolve(__dirname, '../../fixtures/app/nested-project'); + project = Project.closestSync(projectPath, cli.ui, cli); + expect(project.root).to.equal(path.resolve(__dirname, '../../fixtures/app/nested-project/actual-project')); + }); + + it('should throw if both `ember-addon.projectRoot` and `ember-cli-build.js` exist', function () { + let cli = new MockCLI(); + projectPath = path.resolve(__dirname, '../../fixtures/app/project-root-with-ember-cli-build'); + + expect(() => Project.closestSync(projectPath, cli.ui, cli)).to.throw( + `Both \`ember-addon.projectRoot\` and \`ember-cli-build.js\` exist as part of \`${projectPath}\`` + ); + }); + }); }); diff --git a/tests/unit/models/server-watcher-test.js b/tests/unit/models/server-watcher-test.js index 4b247170f8..11b5ff88f4 100644 --- a/tests/unit/models/server-watcher-test.js +++ b/tests/unit/models/server-watcher-test.js @@ -7,12 +7,12 @@ const MockAnalytics = require('../../helpers/mock-analytics'); const MockServerWatcher = require('../../helpers/mock-watcher'); const ServerWatcher = require('../../../lib/models/server-watcher'); -describe('Server Watcher', function() { +describe('Server Watcher', function () { let ui; let analytics; let watcher; - beforeEach(function() { + beforeEach(function () { ui = new MockUI(); analytics = new MockAnalytics(); watcher = new MockServerWatcher(); @@ -24,44 +24,44 @@ describe('Server Watcher', function() { }); }); - describe('watcher:change', function() { - beforeEach(function() { + describe('watcher:change', function () { + beforeEach(function () { watcher.emit('change', 'foo.txt'); }); - it('logs that the file was changed', function() { + it('logs that the file was changed', function () { expect(ui.output).to.equal(`File changed: "foo.txt"${EOL}`); }); - it('does NOT tracks changes', function() { + it('does NOT tracks changes', function () { expect(analytics.tracks).to.deep.equal([]); }); }); - describe('watcher:add', function() { - beforeEach(function() { + describe('watcher:add', function () { + beforeEach(function () { watcher.emit('add', 'foo.txt'); }); - it('logs that the file was added', function() { + it('logs that the file was added', function () { expect(ui.output).to.equal(`File added: "foo.txt"${EOL}`); }); - it('does NOT track additions', function() { + it('does NOT track additions', function () { expect(analytics.tracks).to.deep.equal([]); }); }); - describe('watcher:delete', function() { - beforeEach(function() { + describe('watcher:delete', function () { + beforeEach(function () { watcher.emit('delete', 'foo.txt'); }); - it('logs that the file was deleted', function() { + it('logs that the file was deleted', function () { expect(ui.output).to.equal(`File deleted: "foo.txt"${EOL}`); }); - it('does NOT tracks deletions', function() { + it('does NOT tracks deletions', function () { expect(analytics.tracks).to.deep.equal([]); }); }); diff --git a/tests/unit/models/watcher-test.js b/tests/unit/models/watcher-test.js index e882d08d5c..4f31df86af 100644 --- a/tests/unit/models/watcher-test.js +++ b/tests/unit/models/watcher-test.js @@ -4,16 +4,13 @@ const expect = require('chai').expect; const MockUI = require('console-ui/mock'); const MockAnalytics = require('../../helpers/mock-analytics'); -const MockWatcher = require('../../helpers/mock-watcher'); const MockBroccoliWatcher = require('../../helpers/mock-broccoli-watcher'); const Watcher = require('../../../lib/models/watcher'); const EOL = require('os').EOL; const chalk = require('chalk'); const BuildError = require('../../helpers/build-error'); -const { isExperimentEnabled } = require('../../../lib/experiments'); -const buildEvent = isExperimentEnabled('BROCCOLI_WATCHER') ? 'buildSuccess' : 'change'; -describe('Watcher', function() { +describe('Watcher', function () { let ui; let subject; let builder; @@ -38,15 +35,11 @@ describe('Watcher', function() { }, }; - beforeEach(function() { + beforeEach(function () { ui = new MockUI(); analytics = new MockAnalytics(); - if (isExperimentEnabled('BROCCOLI_WATCHER')) { - watcher = new MockBroccoliWatcher(); - } else { - watcher = new MockWatcher(); - } + watcher = new MockBroccoliWatcher(); subject = new Watcher({ ui, @@ -56,8 +49,8 @@ describe('Watcher', function() { }); }); - describe('watcher strategy selection', function() { - it('selects the events-based watcher by default', function() { + describe('watcher strategy selection', function () { + it('selects the events-based watcher by default', function () { subject.options = null; expect(subject.buildOptions()).to.deep.equal({ @@ -68,7 +61,7 @@ describe('Watcher', function() { }); }); - it('selects the events-based watcher when given events watcher option', function() { + it('selects the events-based watcher when given events watcher option', function () { subject.options = { watcher: 'events', }; @@ -81,7 +74,7 @@ describe('Watcher', function() { }); }); - it('selects the polling watcher when given polling watcher option', function() { + it('selects the polling watcher when given polling watcher option', function () { subject.options = { watcher: 'polling', }; @@ -95,29 +88,27 @@ describe('Watcher', function() { }); }); - if (isExperimentEnabled('BROCCOLI_WATCHER')) { - describe('underlining watcher properly logs change events', function() { - it('logs that the file was added', function() { - watcher.emit('change', 'add', 'foo.txt'); - expect(ui.output).to.equal(`file added foo.txt${EOL}`); - }); - it('logs that the file was changed', function() { - watcher.emit('change', 'change', 'foo.txt'); - expect(ui.output).to.equal(`file changed foo.txt${EOL}`); - }); - it('logs that the file was deleted', function() { - watcher.emit('change', 'delete', 'foo.txt'); - expect(ui.output).to.equal(`file deleted foo.txt${EOL}`); - }); + describe('underlining watcher properly logs change events', function () { + it('logs that the file was added', function () { + watcher.emit('change', 'add', 'foo.txt'); + expect(ui.output).to.equal(`file added foo.txt${EOL}`); + }); + it('logs that the file was changed', function () { + watcher.emit('change', 'change', 'foo.txt'); + expect(ui.output).to.equal(`file changed foo.txt${EOL}`); }); - } + it('logs that the file was deleted', function () { + watcher.emit('change', 'delete', 'foo.txt'); + expect(ui.output).to.equal(`file deleted foo.txt${EOL}`); + }); + }); - describe(`watcher:${buildEvent}`, function() { - beforeEach(function() { - watcher.emit(buildEvent, mockResult); + describe(`watcher:buildSuccess`, function () { + beforeEach(function () { + watcher.emit(`buildSuccess`, mockResult); }); - it('tracks events', function() { + it('tracks events', function () { expect(analytics.tracks).to.deep.equal([ { name: 'ember rebuild', @@ -126,7 +117,7 @@ describe('Watcher', function() { ]); }); - it('tracks timings', function() { + it('tracks timings', function () { expect(analytics.trackTimings).to.deep.equal([ { category: 'rebuild', @@ -137,15 +128,15 @@ describe('Watcher', function() { ]); }); - it('logs that the build was successful', function() { + it('logs that the build was successful', function () { expect(ui.output).to.equal(EOL + chalk.green('Build successful (12344ms)') + EOL); }); }); - describe('output', function() { + describe('output', function () { this.timeout(40000); - it('with ssl', function() { + it('with ssl', function () { let subject = new Watcher({ ui, analytics, @@ -175,7 +166,7 @@ describe('Watcher', function() { expect(output[0]).to.equal(`${chalk.green('Build successful (12344ms)')} – Serving on https://localhost:1337/`); }); - it('with baseURL', function() { + it('with baseURL', function () { let subject = new Watcher({ ui, analytics, @@ -205,7 +196,7 @@ describe('Watcher', function() { expect(output.length).to.equal(1, 'expected only one line of output'); }); - it('with rootURL', function() { + it('with rootURL', function () { let subject = new Watcher({ ui, analytics, @@ -236,7 +227,7 @@ describe('Watcher', function() { expect(output.length).to.equal(1, 'expected only one line of output'); }); - it('with empty rootURL', function() { + it('with empty rootURL', function () { let subject = new Watcher({ ui, analytics, @@ -265,7 +256,7 @@ describe('Watcher', function() { expect(output.length).to.equal(1, 'expected only one line of output'); }); - it('with customURL', function() { + it('with customURL', function () { let subject = new Watcher({ ui, analytics, @@ -286,7 +277,7 @@ describe('Watcher', function() { }, }, }); - subject.serveURL = function() { + subject.serveURL = function () { return `http://customurl.com/`; }; subject.didChange(mockResult); @@ -296,8 +287,8 @@ describe('Watcher', function() { }); }); - describe('watcher:error', function() { - it('tracks errors', function() { + describe('watcher:error', function () { + it('tracks errors', function () { watcher.emit('error', { message: 'foo', stack: new Error().stack, @@ -310,7 +301,36 @@ describe('Watcher', function() { ]); }); - it('emits without error.file', function() { + it('watcher error', function () { + watcher.emit('error', { + message: 'foo', + stack: new Error().stack, + }); + + expect(ui.output).to.equal(''); + + let outs = ui.errors.split(EOL); + + expect(outs[0]).to.equal(chalk.red('foo')); + }); + + it('watcher buildFailure', function () { + watcher.emit('buildFailure', { + isBuilderError: true, + message: 'I am a build error', + file: 'the-file.txt', + stack: new Error().stack, + }); + + expect(ui.output).to.equal(''); + + let outs = ui.errors.split(EOL); + + expect(outs[0]).to.equal(chalk.red('File: the-file.txt')); + expect(outs[2]).to.equal(chalk.red('I am a build error')); + }); + + it('emits without error.file', function () { subject.didError( new BuildError({ file: 'someFile', @@ -326,7 +346,7 @@ describe('Watcher', function() { expect(outs[2]).to.equal(chalk.red('buildFailed')); }); - it('emits with error.file with error.line without err.col', function() { + it('emits with error.file with error.line without err.col', function () { subject.didError( new BuildError({ file: 'someFile', @@ -343,7 +363,7 @@ describe('Watcher', function() { expect(outs[2]).to.equal(chalk.red('buildFailed')); }); - it('emits with error.file without error.line with err.col', function() { + it('emits with error.file without error.line with err.col', function () { subject.didError( new BuildError({ file: 'someFile', @@ -360,7 +380,7 @@ describe('Watcher', function() { expect(outs[2]).to.equal(chalk.red('buildFailed')); }); - it('emits with error.file with error.line with err.col', function() { + it('emits with error.file with error.line with err.col', function () { subject.didError( new BuildError({ file: 'someFile', @@ -379,21 +399,21 @@ describe('Watcher', function() { }); }); - describe('watcher:change afterError', function() { - beforeEach(function() { + describe('watcher:change afterError', function () { + beforeEach(function () { watcher.emit('error', { message: 'foo', stack: new Error().stack, }); - watcher.emit(buildEvent, mockResult); + watcher.emit(`buildSuccess`, mockResult); }); - it('log that the build was green', function() { + it('log that the build was green', function () { expect(ui.output).to.match(/Build successful./, 'has successful build output'); }); - it('keep tracking analytics', function() { + it('keep tracking analytics', function () { expect(analytics.tracks).to.deep.equal([ { name: 'ember rebuild', diff --git a/tests/unit/package/dependency-version-test.js b/tests/unit/package/dependency-version-test.js index 3e052b6815..0a92f422a4 100644 --- a/tests/unit/package/dependency-version-test.js +++ b/tests/unit/package/dependency-version-test.js @@ -2,15 +2,15 @@ const assertVersionLock = require('../../helpers/assert-version-lock'); -describe('dependencies', function() { +describe('dependencies', function () { let pkg; - describe('in package.json', function() { - before(function() { + describe('in package.json', function () { + before(function () { pkg = require('../../../package.json'); }); - it('are locked down for pre-1.0 versions', function() { + it('are locked down for pre-1.0 versions', function () { assertVersionLock(pkg.dependencies); assertVersionLock(pkg.devDependencies); }); diff --git a/tests/unit/settings-file/leek-options-test.js b/tests/unit/settings-file/leek-options-test.js index deeb4ce530..7876de3a14 100644 --- a/tests/unit/settings-file/leek-options-test.js +++ b/tests/unit/settings-file/leek-options-test.js @@ -1,6 +1,5 @@ 'use strict'; -const co = require('co'); const expect = require('chai').expect; const MockUI = require('console-ui/mock'); const Yam = require('yam'); @@ -10,53 +9,49 @@ const broccoliTestHelper = require('broccoli-test-helper'); const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; -describe('.ember-cli leek options', function() { +describe('.ember-cli leek options', function () { let passedOptions, leekConfigFolder; - before( - co.wrap(function*() { - leekConfigFolder = yield createTempDir(); - - leekConfigFolder.write({ - '.ember-cli': JSON.stringify({ - leekOptions: { - adapterUrls: { - event: 'http://www.example.com/event', - exception: 'http://www.example.com/error', - timing: 'http://www.example.com/timing', - appview: 'http://www.example.com/track', - }, + before(async function () { + leekConfigFolder = await createTempDir(); + + leekConfigFolder.write({ + '.ember-cli': JSON.stringify({ + leekOptions: { + adapterUrls: { + event: 'http://www.example.com/event', + exception: 'http://www.example.com/error', + timing: 'http://www.example.com/timing', + appview: 'http://www.example.com/track', }, - }), - }); + }, + }), + }); - let primaryPath = leekConfigFolder.path(); + let primaryPath = leekConfigFolder.path(); - yield buildOutput(primaryPath); + await buildOutput(primaryPath); - let mockedYam = new Yam('ember-cli', { - primary: primaryPath, - }); + let mockedYam = new Yam('ember-cli', { + primary: primaryPath, + }); - let mockedLeek = function(options) { - passedOptions = options; - }; + let mockedLeek = function (options) { + passedOptions = options; + }; - cliEntry({ - UI: MockUI, - Leek: mockedLeek, - Yam: mockedYam, - }); - }) - ); + cliEntry({ + UI: MockUI, + Leek: mockedLeek, + Yam: mockedYam, + }); + }); - after( - co.wrap(function*() { - yield leekConfigFolder.dispose(); - }) - ); + after(async function () { + await leekConfigFolder.dispose(); + }); - it('should contain the leek options from .ember-cli file', function() { + it('should contain the leek options from .ember-cli file', function () { expect(passedOptions.adapterUrls).to.contain.keys(['event', 'exception', 'timing', 'appview']); }); }); diff --git a/tests/unit/settings-file/precedence-settings-test.js b/tests/unit/settings-file/precedence-settings-test.js index 31793bd99d..6e47537b64 100644 --- a/tests/unit/settings-file/precedence-settings-test.js +++ b/tests/unit/settings-file/precedence-settings-test.js @@ -7,14 +7,14 @@ const MockAnalytics = require('../../helpers/mock-analytics'); const Command = require('../../../lib/models/command'); const Yam = require('yam'); -describe('.ember-cli', function() { +describe('.ember-cli', function () { let ui; let analytics; let project; let settings; let homeSettings; - before(function() { + before(function () { ui = new MockUI(); analytics = new MockAnalytics(); project = { @@ -36,7 +36,7 @@ describe('.ember-cli', function() { }).getAll(); }); - it('local settings take precedence over global settings', function() { + it('local settings take precedence over global settings', function () { let command = new Command({ ui, analytics, diff --git a/tests/unit/tasks/addon-install-test.js b/tests/unit/tasks/addon-install-test.js index 27287741fe..4ee1b71fc7 100644 --- a/tests/unit/tasks/addon-install-test.js +++ b/tests/unit/tasks/addon-install-test.js @@ -3,26 +3,27 @@ const AddonInstallTask = require('../../../lib/tasks/addon-install'); const expect = require('chai').expect; const CoreObject = require('core-object'); -const Promise = require('rsvp').Promise; +const MockUi = require('console-ui/mock'); -describe('addon install task', function() { +describe('addon install task', function () { let ui; let project; - beforeEach(function() { - ui = { - startProgress() {}, + beforeEach(function () { + ui = new MockUi(); + project = { + reloadAddons() {}, }; }); - afterEach(function() { + afterEach(function () { // ui.stopProgress(); ui = undefined; project = undefined; }); - describe('when no save flag specified in blueprintOptions', function() { - it('calls npm install with --save-dev as a default', function(done) { + describe('when no save flag specified in blueprintOptions', function () { + it('calls npm install with --save-dev as a default', function (done) { let MockNpmInstallTask = CoreObject.extend({ run(options) { expect(options.save).to.not.be.true; @@ -42,8 +43,8 @@ describe('addon install task', function() { }); }); - describe('when save flag specified in blueprintOptions', function() { - it('calls npm install with --save', function(done) { + describe('when save flag specified in blueprintOptions', function () { + it('calls npm install with --save', function (done) { let MockNpmInstallTask = CoreObject.extend({ run(options) { expect(options.save).to.be.true; @@ -67,8 +68,8 @@ describe('addon install task', function() { }); }); - describe('when saveDev flag specified in blueprintOptions', function() { - it('calls npm install with --save-dev', function(done) { + describe('when saveDev flag specified in blueprintOptions', function () { + it('calls npm install with --save-dev', function (done) { let MockNpmInstallTask = CoreObject.extend({ run(options) { expect(options.save).to.not.be.true; @@ -92,8 +93,8 @@ describe('addon install task', function() { }); }); - describe('when both save and saveDev flag specified in blueprintOptions', function() { - it('calls npm install with --save', function(done) { + describe('when both save and saveDev flag specified in blueprintOptions', function () { + it('calls npm install with --save', function (done) { let MockNpmInstallTask = CoreObject.extend({ run(options) { expect(options.save).to.be.true; diff --git a/tests/unit/tasks/bower-install-test.js b/tests/unit/tasks/bower-install-test.js index 6045016679..926dcc3ad0 100644 --- a/tests/unit/tasks/bower-install-test.js +++ b/tests/unit/tasks/bower-install-test.js @@ -5,22 +5,22 @@ const MockUI = require('console-ui/mock'); const expect = require('../../chai').expect; const td = require('testdouble'); -describe('BowerInstallTask', function() { +describe('BowerInstallTask', function () { let task, ui; - beforeEach(function() { + beforeEach(function () { ui = new MockUI(); task = new BowerInstallTask({ ui }); }); - describe('ensureBower()', function() { - beforeEach(function() { + describe('ensureBower()', function () { + beforeEach(function () { task.resolveBower = td.function(); task.importBower = td.function(); task.installBower = td.function(); }); - it('resolves if "bower" property is set', async function() { + it('resolves if "bower" property is set', async function () { let mockValue = 'foobar'; task.bower = mockValue; @@ -30,14 +30,14 @@ describe('BowerInstallTask', function() { expect(task.bower).to.equal(mockValue); }); - it('imports "bower" if it can be resolved', async function() { + it('imports "bower" if it can be resolved', async function () { td.when(task.resolveBower()).thenResolve('path/to/bower'); td.when(task.importBower('path/to/bower')).thenReturn('ok'); expect(await task.ensureBower()).to.equal('ok'); }); - it('install "bower" if it can not be resolved', async function() { + it('install "bower" if it can not be resolved', async function () { let error = new Error("Cannot find module 'bower'"); td.when(task.resolveBower()).thenReturn(Promise.reject(error), Promise.resolve('path/to/bower')); @@ -47,7 +47,7 @@ describe('BowerInstallTask', function() { expect(await task.ensureBower()).to.equal('ok'); }); - it('pass other resolve errors on', async function() { + it('pass other resolve errors on', async function () { let error = new Error('foobar'); td.when(task.resolveBower()).thenReturn(Promise.reject(error)); @@ -55,7 +55,7 @@ describe('BowerInstallTask', function() { await expect(task.ensureBower()).to.be.rejectedWith('foobar'); }); - it('pass install errors on', async function() { + it('pass install errors on', async function () { let error = new Error("Cannot find module 'bower'"); td.when(task.resolveBower()).thenReturn(Promise.reject(error)); diff --git a/tests/unit/tasks/build-watch-test.js b/tests/unit/tasks/build-watch-test.js index a9162943b9..111cfbeeaa 100644 --- a/tests/unit/tasks/build-watch-test.js +++ b/tests/unit/tasks/build-watch-test.js @@ -5,7 +5,7 @@ const Builder = require('../../../lib/models/builder'); const MockProject = require('../../helpers/mock-project'); const expect = require('chai').expect; -describe('build-watch task', function() { +describe('build-watch task', function () { let task, ui; function setupBroccoliBuilder() { @@ -58,8 +58,8 @@ describe('build-watch task', function() { return task.run(options); } - describe('onInterrupt', function() { - it('fulfills the run promise and cleans up the builder', async function() { + describe('onInterrupt', function () { + it('fulfills the run promise and cleans up the builder', async function () { let runPromise = runBuildWatchTask(); Promise.resolve().then(() => task.onInterrupt()); diff --git a/tests/unit/tasks/git-init-test.js b/tests/unit/tasks/git-init-test.js index 104d29bdc3..be24e96fe0 100644 --- a/tests/unit/tasks/git-init-test.js +++ b/tests/unit/tasks/git-init-test.js @@ -11,10 +11,10 @@ const mkTmpDirIn = require('../../../lib/utilities/mk-tmp-dir-in'); let tmproot = path.join(root, 'tmp'); const td = require('testdouble'); -describe('git-init', function() { +describe('git-init', function () { let task; - beforeEach(async function() { + beforeEach(async function () { task = new GitInitTask({ ui: new MockUI(), project: new MockProject(), @@ -28,13 +28,13 @@ describe('git-init', function() { process.chdir(tmpdir); }); - afterEach(async function() { + afterEach(async function () { process.chdir(root); await fs.remove(tmproot); }); - describe('skipGit: true', function() { - it('does not initialize git', async function() { + describe('skipGit: true', function () { + it('does not initialize git', async function () { await task.run({ skipGit: true, }); @@ -43,7 +43,7 @@ describe('git-init', function() { }); }); - it('correctly initializes git if git is around, and more or less works', async function() { + it('correctly initializes git if git is around, and more or less works', async function () { td.when(task._gitVersion()).thenResolve(); td.when(task._gitInit()).thenResolve(); td.when(task._gitAdd()).thenResolve(); @@ -59,11 +59,10 @@ describe('git-init', function() { expect(task.ui.errors).to.equal(''); }); - it('skips initializing git, if `git --version` fails', async function() { + it('skips initializing git, if `git --version` fails', async function () { td.when(task._gitVersion()).thenReject(); await task.run(); - td.verify(task._gitVersion(), { times: 1 }); td.verify(task._gitInit(), { times: 0 }); td.verify(task._gitAdd(), { times: 0 }); td.verify(task._gitCommit(td.matchers.anything()), { times: 0 }); @@ -72,7 +71,7 @@ describe('git-init', function() { expect(task.ui.errors).to.equal(''); }); - it('includes the HOME environment variable in the environment passed to git', function() { + it('includes the HOME environment variable in the environment passed to git', function () { let env = task.buildGitEnvironment(); expect(env.HOME).to.equal(process.env.HOME); }); diff --git a/tests/unit/tasks/install-blueprint-test.js b/tests/unit/tasks/install-blueprint-test.js deleted file mode 100644 index c9c7e854d9..0000000000 --- a/tests/unit/tasks/install-blueprint-test.js +++ /dev/null @@ -1,239 +0,0 @@ -'use strict'; - -const path = require('path'); -const fs = require('fs-extra'); -const td = require('testdouble'); -const SilentError = require('silent-error'); - -const expect = require('../../chai').expect; -const mkTmpDirIn = require('../../../lib/utilities/mk-tmp-dir-in'); -const InstallBlueprintTask = require('../../../lib/tasks/install-blueprint'); - -let root = path.join(__dirname, '../../..'); -let tmproot = path.join(root, 'tmp'); - -describe('InstallBlueprintTask', function() { - let task; - beforeEach(function() { - task = new InstallBlueprintTask(); - }); - - describe('_resolveBlueprint', function() { - beforeEach(function() { - task._lookupBlueprint = td.function(); - task._tryNpmBlueprint = td.function(); - task._createTempFolder = td.function(); - task._gitClone = td.function(); - task._npmInstall = td.function(); - task._loadBlueprintFromPath = td.function(); - - task._tempPath = '/tmp/foobar'; - td.when(task._createTempFolder()).thenResolve(task._tempPath); - }); - - it('resolves "foobar" by looking up the "foobar" blueprint locally', async function() { - let foobarBlueprint = { name: 'foobar blueprint' }; - td.when(task._lookupBlueprint('foobar')).thenResolve(foobarBlueprint); - - expect(await task._resolveBlueprint('foobar')).to.equal(foobarBlueprint); - }); - - it('rejects invalid npm package name "foo:bar"', async function() { - let error = new Error('foobar not found'); - td.when(task._lookupBlueprint('foo:bar')).thenReject(error); - - await expect(task._resolveBlueprint('foo:bar')).to.be.rejectedWith(error); - }); - - it('tries to resolve "foobar" as npm package as a fallback', async function() { - let error = new Error('foobar not found'); - td.when(task._lookupBlueprint('foobar')).thenReject(error); - - let foobarBlueprint = { name: 'foobar npm blueprint' }; - td.when(task._tryNpmBlueprint('foobar', 'latest')).thenResolve(foobarBlueprint); - - expect(await task._resolveBlueprint('foobar')).to.equal(foobarBlueprint); - }); - - it('tries to resolve "@foo/bar@1.2.3" as npm package with a scope and a version', async function() { - let error = new Error('@foo/bar@1.2.3 not found'); - td.when(task._lookupBlueprint('@foo/bar@1.2.3')).thenReject(error); - - let foobarBlueprint = { name: 'foobar npm blueprint' }; - td.when(task._tryNpmBlueprint('@foo/bar', '1.2.3')).thenResolve(foobarBlueprint); - - expect(await task._resolveBlueprint('@foo/bar@1.2.3')).to.equal(foobarBlueprint); - }); - - it('rejects if npm module resolution failed', async function() { - let error = new Error('foobar not found'); - td.when(task._lookupBlueprint('foobar')).thenReject(error); - - let npmError = new Error('npm failure'); - td.when(task._tryNpmBlueprint('foobar', 'latest')).thenReject(npmError); - - await expect(task._resolveBlueprint('foobar')).to.be.rejectedWith(npmError); - }); - - it( - 'resolves "https://github.com/ember-cli/app-blueprint-test.git" blueprint by cloning, ' + - 'installing dependencies and loading the blueprint', - async function() { - let url = 'https://github.com/ember-cli/app-blueprint-test.git'; - let gitBlueprint = { name: 'git blueprint' }; - td.when(task._gitClone(url, task._tempPath)).thenResolve(); - td.when(task._npmInstall(task._tempPath)).thenResolve(); - td.when(task._loadBlueprintFromPath(task._tempPath)).thenResolve(gitBlueprint); - - expect(await task._resolveBlueprint(url)).to.equal(gitBlueprint); - } - ); - - it('rejects if temp folder creation fails', async function() { - let url = 'https://github.com/ember-cli/app-blueprint-test.git'; - let error = new Error('temp folder creation failed'); - td.when(task._createTempFolder()).thenReject(error); - - await expect(task._resolveBlueprint(url)).to.be.rejectedWith(error); - }); - - it('rejects if "git clone" fails', function() { - let url = 'https://github.com/ember-cli/app-blueprint-test.git'; - let error = new Error('git clone failed'); - td.when(task._gitClone(url, task._tempPath)).thenReject(error); - - return expect(task._resolveBlueprint(url)).to.be.rejectedWith(error); - }); - - it('rejects if "npm install" fails', async function() { - let url = 'https://github.com/ember-cli/app-blueprint-test.git'; - let error = new Error('npm install failed'); - td.when(task._gitClone(url, task._tempPath)).thenResolve(); - td.when(task._npmInstall(task._tempPath)).thenReject(error); - - await expect(task._resolveBlueprint(url)).to.be.rejectedWith(error); - }); - - it('rejects if loading the blueprint fails', async function() { - let url = 'https://github.com/ember-cli/app-blueprint-test.git'; - let error = new Error('loading blueprint failed'); - td.when(task._gitClone(url, task._tempPath)).thenResolve(); - td.when(task._npmInstall(task._tempPath)).thenResolve(); - td.when(task._loadBlueprintFromPath(task._tempPath)).thenReject(error); - - await expect(task._resolveBlueprint(url)).to.be.rejectedWith(error); - }); - }); - - describe('_tryNpmBlueprint', function() { - beforeEach(async function() { - task._createTempFolder = td.function(); - task._npmInstallModule = td.function(); - task._validateNpmModule = td.function(); - task._loadBlueprintFromPath = td.function(); - - task._tempPath = '/tmp/foobar'; - td.when(task._createTempFolder()).thenResolve(task._tempPath); - - const tmpdir = await mkTmpDirIn(tmproot); - - task.project = { root: tmpdir }; - process.chdir(tmpdir); - }); - - afterEach(function() { - process.chdir(root); - return fs.remove(tmproot); - }); - - it('if .npmrc exists in the project root it copys it to tmp location', function() { - let dir = 'foo'; - - fs.writeFileSync(path.join(task.project.root, '.npmrc'), 'foo'); - - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } - - task._copyNpmrc(dir); - expect(fs.existsSync(path.join(task.project.root, dir, '.npmrc'))).to.be.true; - }); - - it('if .npmrc does not exists in the project root it is not copied', function() { - let dir = 'foo'; - - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } - - task._copyNpmrc(dir); - expect(fs.existsSync(path.join(task.project.root, dir, '.npmrc'))).to.be.false; - }); - - it('resolves with blueprint after successful "npm install"', async function() { - let modulePath = '/path/to/foobar'; - td.when(task._npmInstallModule('foobar', 'latest', task._tempPath)).thenResolve(modulePath); - - let foobarBlueprint = { name: 'foobar blueprint' }; - td.when(task._loadBlueprintFromPath(modulePath)).thenResolve(foobarBlueprint); - - expect(await task._tryNpmBlueprint('foobar', 'latest')).to.equal(foobarBlueprint); - }); - - it('resolves with blueprint after successful "npm install" with a scope and a version', async function() { - let modulePath = '/path/to/@foo/bar'; - td.when(task._npmInstallModule('@foo/bar', '1.2.3', task._tempPath)).thenResolve(modulePath); - - let foobarBlueprint = { name: '@foo/bar blueprint' }; - td.when(task._loadBlueprintFromPath(modulePath)).thenResolve(foobarBlueprint); - - expect(await task._tryNpmBlueprint('@foo/bar', '1.2.3')).to.equal(foobarBlueprint); - }); - - it('rejects with SilentError if npm module "foobar" could not be found', async function() { - let error = new Error(); - error.stderr = ` - npm ERR! 404 Registry returned 404 for GET on https://registry.npmjs.org/ember-cli-app-blueprint-tes - npm ERR! 404 - npm ERR! 404 'foobar' is not in the npm registry. - npm ERR! 404 You should bug the author to publish it (or use the name yourself!) - npm ERR! 404 - npm ERR! 404 Note that you can also install from a - npm ERR! 404 tarball, folder, http url, or git url.`; - - td.when(task._npmInstallModule('foobar', 'latest', task._tempPath)).thenReject(error); - - await expect(task._tryNpmBlueprint('foobar', 'latest')).to.be.rejectedWith( - SilentError, - `The package 'foobar' was not found in the npm registry.` - ); - }); - - it('rejects if "npm install" fails', async function() { - let error = new Error('npm install failed'); - td.when(task._npmInstallModule('foobar', 'latest', task._tempPath)).thenReject(error); - - await expect(task._tryNpmBlueprint('foobar', 'latest')).to.be.rejectedWith(error); - }); - - it('rejects if npm module validation fails', async function() { - let modulePath = '/path/to/foobar'; - td.when(task._npmInstallModule('foobar', 'latest', task._tempPath)).thenResolve(modulePath); - - let error = new Error('module validation failed'); - td.when(task._validateNpmModule(modulePath, 'foobar')).thenThrow(error); - - await expect(task._tryNpmBlueprint('foobar', 'latest')).to.be.rejectedWith(error); - }); - - it('rejects if loading blueprint fails', async function() { - let modulePath = '/path/to/foobar'; - td.when(task._npmInstallModule('foobar', 'latest', task._tempPath)).thenResolve(modulePath); - - let error = new Error('loading blueprint failed'); - td.when(task._loadBlueprintFromPath(modulePath)).thenReject(error); - - await expect(task._tryNpmBlueprint('foobar', 'latest')).to.be.rejectedWith(error); - }); - }); -}); diff --git a/tests/unit/tasks/npm-install-test.js b/tests/unit/tasks/npm-install-test.js index 52ea9573cb..b9c3ba8b6c 100644 --- a/tests/unit/tasks/npm-install-test.js +++ b/tests/unit/tasks/npm-install-test.js @@ -4,11 +4,11 @@ const NpmInstallTask = require('../../../lib/tasks/npm-install'); const MockUI = require('console-ui/mock'); const expect = require('chai').expect; -describe('npm install task', function() { +describe('npm install task', function () { let npmInstallTask; let ui; - beforeEach(function() { + beforeEach(function () { let project = { root: __dirname, }; @@ -20,12 +20,12 @@ describe('npm install task', function() { }); }); - afterEach(function() { + afterEach(function () { ui = undefined; npmInstallTask = undefined; }); - it('skips npm installs if there is no package.json', function() { + it('skips npm installs if there is no package.json', function () { return npmInstallTask.run({}).then(() => { expect(ui.output).to.include('Skipping npm install: package.json not found'); }); diff --git a/tests/unit/tasks/npm-task-test.js b/tests/unit/tasks/npm-task-test.js index b2bca390a5..91eefedb99 100644 --- a/tests/unit/tasks/npm-task-test.js +++ b/tests/unit/tasks/npm-task-test.js @@ -6,17 +6,17 @@ const expect = require('../../chai').expect; const td = require('testdouble'); const SilentError = require('silent-error'); -describe('NpmTask', function() { - describe('checkNpmVersion', function() { +describe('NpmTask', function () { + describe('checkNpmVersion', function () { let task, ui; - beforeEach(function() { + beforeEach(function () { ui = new MockUI(); task = new NpmTask({ ui }); task.npm = td.function(); }); - it('resolves when a compatible version is found', function() { + it('resolves when a compatible version is found', function () { td.when(task.npm(['--version'])).thenResolve({ stdout: '5.2.1' }); return expect(task.checkNpmVersion()).to.be.fulfilled.then(() => { @@ -25,7 +25,7 @@ describe('NpmTask', function() { }); }); - it('resolves with warning when a newer version is found', function() { + it('resolves with warning when a newer version is found', function () { td.when(task.npm(['--version'])).thenResolve({ stdout: '7.0.0' }); return expect(task.checkNpmVersion()).to.be.fulfilled.then(() => { @@ -34,7 +34,7 @@ describe('NpmTask', function() { }); }); - it('rejects when an older version is found', function() { + it('rejects when an older version is found', function () { td.when(task.npm(['--version'])).thenResolve({ stdout: '2.9.9' }); return expect(task.checkNpmVersion()) @@ -45,7 +45,7 @@ describe('NpmTask', function() { }); }); - it('rejects when npm is not found', function() { + it('rejects when npm is not found', function () { let error = new Error('npm not found'); error.code = 'ENOENT'; @@ -59,7 +59,7 @@ describe('NpmTask', function() { }); }); - it('rejects when npm returns an unreadable version', function() { + it('rejects when npm returns an unreadable version', function () { td.when(task.npm(['--version'])).thenResolve({ stdout: '5' }); return expect(task.checkNpmVersion()) @@ -70,7 +70,7 @@ describe('NpmTask', function() { }); }); - it('rejects when an unknown error is thrown', function() { + it('rejects when an unknown error is thrown', function () { td.when(task.npm(['--version'])).thenReject(new Error('foobar?')); return expect(task.checkNpmVersion()) @@ -82,72 +82,85 @@ describe('NpmTask', function() { }); }); - describe('checkYarn', function() { - let task, ui, yarn; + describe('checkYarn', function () { + let task, ui; - beforeEach(function() { + beforeEach(function () { ui = new MockUI(); - yarn = td.function(); - task = new NpmTask({ ui, yarn }); + task = new NpmTask({ ui }); + task.yarn = td.function(); }); - it('resolves when yarn is found', function() { - td.when(yarn(['--version'])).thenResolve({ stdout: '0.22.1' }); + it('resolves when yarn <2.0.0 is found', function () { + td.when(task.yarn(['--version'])).thenResolve({ stdout: '1.22.0' }); - return expect(task.checkYarn()).to.be.fulfilled; + return expect(task.checkYarn()).to.be.fulfilled.then(() => { + expect(ui.output).to.be.empty; + expect(ui.errors).to.be.empty; + }); }); - it('rejects when yarn is not found', function() { + it('resolves with warning when yarn >=2.0.0 is found', function () { + td.when(task.yarn(['--version'])).thenResolve({ stdout: '2.0.0' }); + + return expect(task.checkYarn()).to.be.fulfilled.then(() => { + expect(ui.output).to.contain('WARNING'); + expect(ui.errors).to.be.empty; + }); + }); + + it('rejects when yarn is not found', function () { let error = new Error('yarn not found'); error.code = 'ENOENT'; - td.when(yarn(['--version'])).thenReject(error); + td.when(task.yarn(['--version'])).thenReject(error); - return expect(task.checkYarn()).to.be.rejectedWith('yarn not found'); + return expect(task.checkYarn()).to.be.rejectedWith( + SilentError, + /instructions at https:\/\/classic.yarnpkg.com\/en\/docs\/install/ + ); }); - it('rejects when an unknown error is thrown', function() { - td.when(yarn(['--version'])).thenReject(new Error('foobar?')); + it('rejects when an unknown error is thrown', function () { + td.when(task.yarn(['--version'])).thenReject(new Error('foobar?')); return expect(task.checkYarn()).to.be.rejectedWith('foobar?'); }); }); - describe('findPackageManager', function() { + describe('findPackageManager', function () { let task; - beforeEach(function() { + beforeEach(function () { task = new NpmTask(); task.hasYarnLock = td.function(); task.checkYarn = td.function(); task.checkNpmVersion = td.function(); }); - it('resolves when no yarn.lock file was found and npm is compatible', function() { + it('resolves when no yarn.lock file was found and npm is compatible', function () { td.when(task.hasYarnLock()).thenReturn(false); td.when(task.checkNpmVersion()).thenResolve(); return expect(task.findPackageManager()).to.be.fulfilled; }); - it('resolves when no yarn.lock file was found and npm is incompatible', function() { + it('resolves when no yarn.lock file was found and npm is incompatible', function () { td.when(task.hasYarnLock()).thenReturn(false); td.when(task.checkNpmVersion()).thenReject(); return expect(task.findPackageManager()).to.be.rejected; }); - it('resolves when yarn.lock file and yarn were found and sets useYarn = true', function() { + it('resolves when yarn.lock file and yarn were found', function () { td.when(task.hasYarnLock()).thenReturn(true); - td.when(task.checkYarn()).thenResolve(); + td.when(task.checkYarn()).thenResolve({ yarnVersion: '1.22.0' }); expect(task.useYarn).to.be.undefined; - return expect(task.findPackageManager()).to.be.fulfilled.then(() => { - expect(task.useYarn).to.be.true; - }); + return expect(task.findPackageManager()).to.eventually.have.property('yarnVersion', '1.22.0'); }); - it('resolves when yarn.lock file was found, yarn was not found and npm is compatible', function() { + it('resolves when yarn.lock file was found, yarn was not found and npm is compatible', function () { td.when(task.hasYarnLock()).thenReturn(true); td.when(task.checkYarn()).thenReject(); td.when(task.checkNpmVersion()).thenResolve(); @@ -158,7 +171,7 @@ describe('NpmTask', function() { }); }); - it('rejects when yarn.lock file was found, yarn was not found and npm is incompatible', function() { + it('rejects when yarn.lock file was found, yarn was not found and npm is incompatible', function () { td.when(task.hasYarnLock()).thenReturn(true); td.when(task.checkYarn()).thenReject(); td.when(task.checkNpmVersion()).thenReject(); @@ -166,26 +179,31 @@ describe('NpmTask', function() { return expect(task.findPackageManager()).to.be.rejected; }); - it('resolves when yarn is requested and found', function() { + it('resolves when yarn is requested and found', function () { task.useYarn = true; - td.when(task.checkYarn()).thenResolve(); + td.when(task.checkYarn()).thenResolve({ yarnVersion: '1.22.0' }); return expect(task.findPackageManager()).to.be.fulfilled; }); - it('rejects with SilentError when yarn is requested but not found', function() { + it('rejects with SilentError when yarn is requested but not found', function () { task.useYarn = true; - let error = new Error('yarn not found'); - error.code = 'ENOENT'; + let error = new SilentError( + 'Ember CLI is now using yarn, but was not able to find it.\n' + + 'Please install yarn using the instructions at https://classic.yarnpkg.com/en/docs/install' + ); td.when(task.checkYarn()).thenReject(error); - return expect(task.findPackageManager()).to.be.rejectedWith(SilentError, /Yarn could not be found/); + return expect(task.findPackageManager()).to.be.rejectedWith( + SilentError, + /instructions at https:\/\/classic.yarnpkg.com\/en\/docs\/install/ + ); }); - it('rejects when yarn is requested and yarn check errors', function() { + it('rejects when yarn is requested and yarn check errors', function () { task.useYarn = true; td.when(task.checkYarn()).thenReject(new Error('foobar')); @@ -193,7 +211,7 @@ describe('NpmTask', function() { return expect(task.findPackageManager()).to.be.rejectedWith('foobar'); }); - it('resolves when npm is requested and compatible', function() { + it('resolves when npm is requested and compatible', function () { task.useYarn = false; td.when(task.checkNpmVersion()).thenResolve(); @@ -201,7 +219,7 @@ describe('NpmTask', function() { return expect(task.findPackageManager()).to.be.fulfilled; }); - it('rejects when npm is requested but incompatible', function() { + it('rejects when npm is requested but incompatible', function () { task.useYarn = false; td.when(task.checkNpmVersion()).thenReject(); @@ -210,26 +228,38 @@ describe('NpmTask', function() { }); }); - describe('toYarnArgs', function() { + describe('toYarnArgs', function () { let task; - beforeEach(function() { + beforeEach(function () { task = new NpmTask(); + task.yarnVersion = '1.22.0'; + }); + + it('correctly adds "--non-interactive" for yarn versions <2.0.0', function () { + let args = task.toYarnArgs('install', {}); + expect(args).to.deep.equal(['install', '--non-interactive']); + }); + + it('skips "--non-interactive" for yarn versions >=2.0.0', function () { + task.yarnVersion = '2.0.1'; + let args = task.toYarnArgs('install', {}); + expect(args).to.deep.equal(['install']); }); - it('converts "npm install --no-optional" to "yarn install --ignore-optional"', function() { + it('converts "npm install --no-optional" to "yarn install --ignore-optional"', function () { let args = task.toYarnArgs('install', { optional: false }); expect(args).to.deep.equal(['install', '--ignore-optional', '--non-interactive']); }); - it('converts "npm install --save foobar" to "yarn add foobar"', function() { + it('converts "npm install --save foobar" to "yarn add foobar"', function () { let args = task.toYarnArgs('install', { save: true, packages: ['foobar'] }); expect(args).to.deep.equal(['add', 'foobar', '--non-interactive']); }); - it('converts "npm install --save-dev --save-exact foo" to "yarn add --dev --exact foo"', function() { + it('converts "npm install --save-dev --save-exact foo" to "yarn add --dev --exact foo"', function () { let args = task.toYarnArgs('install', { 'save-dev': true, 'save-exact': true, @@ -239,13 +269,13 @@ describe('NpmTask', function() { expect(args).to.deep.equal(['add', '--dev', '--exact', 'foo', '--non-interactive']); }); - it('converts "npm uninstall bar" to "yarn remove bar"', function() { + it('converts "npm uninstall bar" to "yarn remove bar"', function () { let args = task.toYarnArgs('uninstall', { packages: ['bar'] }); expect(args).to.deep.equal(['remove', 'bar', '--non-interactive']); }); - it('throws when "yarn install" is called with packages', function() { + it('throws when "yarn install" is called with packages', function () { expect(() => task.toYarnArgs('install', { packages: ['foo'] })).to.throw(Error, /install foo/); }); }); diff --git a/tests/unit/tasks/serve-test.js b/tests/unit/tasks/serve-test.js index 9346a5467c..d2a6d9f512 100644 --- a/tests/unit/tasks/serve-test.js +++ b/tests/unit/tasks/serve-test.js @@ -5,7 +5,7 @@ const Builder = require('../../../lib/models/builder'); const MockProject = require('../../helpers/mock-project'); const expect = require('chai').expect; -describe('serve task', function() { +describe('serve task', function () { let task, ui; function setupBroccoliBuilder() { @@ -63,14 +63,14 @@ describe('serve task', function() { return task.run(options); } - describe('run with path', function() { - it(`Throws error if path doesn't exist`, function() { - expect(runServeTask.bind(this, 'xyz')).to.throw( + describe('run with path', function () { + it(`Throws error if path doesn't exist`, function () { + expect(runServeTask('xyz')).to.be.rejectedWith( 'The path xyz does not exist. Please specify a valid build directory to serve.' ); }); - it(`Serves ember app from given path`, async function() { + it(`Serves ember app from given path`, async function () { runServeTask('docs'); await Promise.resolve(); @@ -78,8 +78,8 @@ describe('serve task', function() { }); }); - describe('onInterrupt', function() { - it('fulfills the run promise and cleans up the builder', async function() { + describe('onInterrupt', function () { + it('fulfills the run promise and cleans up the builder', async function () { let servePromise = runServeTask(); await Promise.resolve(); diff --git a/tests/unit/tasks/server/express-server-test.js b/tests/unit/tasks/server/express-server-test.js index 59e9b25eac..4d2a237790 100644 --- a/tests/unit/tasks/server/express-server-test.js +++ b/tests/unit/tasks/server/express-server-test.js @@ -2,7 +2,6 @@ const expect = require('../../../chai').expect; const ExpressServer = require('../../../../lib/tasks/server/express-server'); -const Promise = require('rsvp').Promise; const MockUI = require('console-ui/mock'); const MockProject = require('../../../helpers/mock-project'); const MockWatcher = require('../../../helpers/mock-watcher'); @@ -14,18 +13,109 @@ const net = require('net'); const EOL = require('os').EOL; const nock = require('nock'); const express = require('express'); -const co = require('co'); const WebSocket = require('websocket').w3cwebsocket; +const FixturifyProject = require('../../../helpers/fixturify-project'); function checkMiddlewareOptions(options) { - expect(options).to.satisfy(option => option.baseURL || option.rootURL); + expect(options).to.satisfy((option) => option.baseURL || option.rootURL); } -describe('express-server', function() { +function sleep(timeout) { + return new Promise((resolve) => setTimeout(resolve, timeout)); +} + +describe('express-server: processAppMiddlewares', function () { + let subject, fixturifyProject; + + nock.enableNetConnect(); + + function makeSubject() { + subject = new ExpressServer({ + ui: new MockUI(), + project: fixturifyProject.buildProjectModel(), + watcher: new MockWatcher(), + serverWatcher: new MockServerWatcher(), + serverRestartDelayTime: 100, + serverRoot: './server', + environment: 'development', + }); + return subject; + } + + beforeEach(function () { + this.timeout(1000); + fixturifyProject = new FixturifyProject('awesome-proj', '0.0.0'); + fixturifyProject.addDevDependency('ember-cli', '*'); + }); + + afterEach(async function () { + fixturifyProject.dispose(); + await subject.stopHttpServer().catch(() => {}); + }); + + it('has a good error message if a file "server.js" exists, but does not export a function', function () { + fixturifyProject.addFiles({ + 'server.js': 'module.exports = { name: "foo" }', + }); + let subject = makeSubject(); + + expect(() => { + subject.processAppMiddlewares(); + }).to.throw(TypeError, 'ember-cli expected ./server/index.js to be the entry for your mock or proxy server'); + }); + + it('has a good error message if a file "server/index.js" exists, but does not export a function', function () { + fixturifyProject.addFiles({ + server: { + 'index.js': 'module.exports = { name: "foo" }', + }, + }); + + let subject = makeSubject(); + expect(() => { + subject.processAppMiddlewares(); + }).to.throw(TypeError, 'ember-cli expected ./server/index.js to be the entry for your mock or proxy server'); + }); + + it('returns values returned by server/index.js', function () { + fixturifyProject.addFiles({ + server: { + 'index.js': 'module.exports = function() {return "foo"}', + }, + }); + let subject = makeSubject(); + expect(subject.processAppMiddlewares()).to.equal('foo'); + }); + + it('returns values returned by server.js', function () { + fixturifyProject.addFiles({ + 'server.js': 'module.exports = function() {return "foo"}', + }); + let subject = makeSubject(); + expect(subject.processAppMiddlewares()).to.equal('foo'); + }); + + it('returns undefined if middleware files does not exists', function () { + let subject = makeSubject(); + expect(subject.processAppMiddlewares()).to.equal(undefined); + }); + + it('allow non MODULE_NOT_FOUND errors bubbling if issue happens during module initialization', function () { + fixturifyProject.addFiles({ + server: { + 'index.js': 'throw new Error("OOPS")', + }, + }); + let subject = makeSubject(); + expect(() => subject.processAppMiddlewares()).to.throw(Error, 'OOPS'); + }); +}); + +describe('express-server', function () { let subject, ui, project, proxy, nockProxy; nock.enableNetConnect(); - beforeEach(function() { + beforeEach(function () { this.timeout(10000); ui = new MockUI(); project = new MockProject(); @@ -41,8 +131,8 @@ describe('express-server', function() { }); }); - afterEach(function() { - return subject + afterEach(async function () { + await subject .stopHttpServer() .catch(() => {}) .then(() => { @@ -54,7 +144,7 @@ describe('express-server', function() { }); }); - it('address in use', function() { + it('address in use', function () { let preexistingServer = net.createServer(); preexistingServer.listen(1337); @@ -63,65 +153,33 @@ describe('express-server', function() { host: undefined, port: '1337', }) - .then(function() { + .then(function () { expect(false, 'should have rejected').to.be.ok; }) - .catch(function(reason) { + .catch(function (reason) { expect(reason.message).to.equal( 'Could not serve on http://localhost:1337. It is either in use or you do not have permission.' ); }) - .finally(function() { + .finally(function () { preexistingServer.close(); }); }); - describe('displayHost', function() { - it('should use the specified host if specified', function() { + describe('displayHost', function () { + it('should use the specified host if specified', function () { expect(subject.displayHost('1.2.3.4')).to.equal('1.2.3.4'); }); - it('should use the use localhost if host is not specified', function() { + it('should use the use localhost if host is not specified', function () { expect(subject.displayHost(undefined)).to.equal('localhost'); }); }); - describe('processAppMiddlewares', function() { - it('has a good error message if a file exists, but does not export a function', function() { - subject.project = { - has() { - return true; - }, - require() { - return {}; - }, - }; - - expect(() => { - subject.processAppMiddlewares(); - }).to.throw(TypeError, 'ember-cli expected ./server/index.js to be the entry for your mock or proxy server'); - }); - - it('returns values returned by server/index', function() { - subject.project = { - has() { - return true; - }, - require() { - return function() { - return 'foo'; - }; - }, - }; - - expect(subject.processAppMiddlewares()).to.equal('foo'); - }); - }); - - describe('output', function() { + describe('output', function () { this.timeout(40000); - it('address in use', function() { + it('address in use', function () { let preexistingServer = net.createServer(); preexistingServer.listen(1337); @@ -131,19 +189,19 @@ describe('express-server', function() { port: '1337', }) ) - .to.be.rejected.then(reason => { + .to.be.rejected.then((reason) => { expect(reason.message).to.equal( 'Could not serve on http://localhost:1337. It is either in use or you do not have permission.' ); }) - .finally(function() { + .finally(function () { preexistingServer.close(); }); }); }); - describe('behaviour', function() { - it('starts with ssl if ssl option is passed', function() { + describe('behaviour', function () { + it('starts with ssl if ssl option is passed', function () { return subject .start({ host: 'localhost', @@ -153,12 +211,12 @@ describe('express-server', function() { sslKey: 'tests/fixtures/ssl/server.key', rootURL: '/', }) - .then(function() { - return new Promise(function(resolve, reject) { + .then(function () { + return new Promise(function (resolve, reject) { process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; request('https://localhost:1337', { strictSSL: false }) .get('/') - .expect(200, function(err, value) { + .expect(200, function (err, value) { process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'; if (err) { reject(err); @@ -170,12 +228,12 @@ describe('express-server', function() { }); }); - it('app middlewares are processed before the proxy', function(done) { + it('app middlewares are processed before the proxy', function (done) { let expected = '/foo was hit'; - project.require = function() { - return function(app) { - app.use('/foo', function(req, res) { + project.require = function () { + return function (app) { + app.use('/foo', function (req, res) { res.send(expected); }); }; @@ -188,14 +246,14 @@ describe('express-server', function() { port: '1337', rootURL: '/', }) - .then(function() { + .then(function () { request(subject.app) .get('/foo') .set('accept', 'application/json, */*') - .expect(res => { + .expect((res) => { expect(res.text).to.equal(expected); }) - .end(function(err) { + .end(function (err) { if (err) { return done(err); } @@ -205,12 +263,12 @@ describe('express-server', function() { }); }); - it('works with a regular express app', function(done) { + it('works with a regular express app', function (done) { let expected = '/foo was hit'; - project.require = function() { + project.require = function () { let app = express(); - app.use('/foo', function(req, res) { + app.use('/foo', function (req, res) { res.send(expected); }); return app; @@ -223,14 +281,14 @@ describe('express-server', function() { port: '1337', rootURL: '/', }) - .then(function() { + .then(function () { request(subject.app) .get('/foo') .set('accept', 'application/json, */*') - .expect(res => { + .expect((res) => { expect(res.text).to.equal(expected); }) - .end(function(err) { + .end(function (err) { if (err) { return done(err); } @@ -240,16 +298,16 @@ describe('express-server', function() { }); }); - describe('compression', function() { + describe('compression', function () { let longText = ''; for (let i = 0; i < 10000; ++i) { longText += 'x'; } longText += ''; - it('uses compression by default for long texts', function(done) { - project.require = function() { + it('uses compression by default for long texts', function (done) { + project.require = function () { let app = express(); - app.use('/foo', function(req, res) { + app.use('/foo', function (req, res) { res.send(longText); }); return app; @@ -262,14 +320,14 @@ describe('express-server', function() { port: '1337', rootURL: '/', }) - .then(function() { + .then(function () { request(subject.app) .get('/foo') - .expect(function(res) { + .expect(function (res) { expect(res.text).to.equal(longText); expect(res.header['content-encoding']).to.equal('gzip'); }) - .end(function(err) { + .end(function (err) { if (err) { return done(err); } @@ -279,10 +337,10 @@ describe('express-server', function() { }); }); - it('does not use compression even for long texts when the x-no-compression header is sent in the response', function(done) { - project.require = function() { + it('does not use compression even for long texts when the x-no-compression header is sent in the response', function (done) { + project.require = function () { let app = express(); - app.use('/foo', function(req, res) { + app.use('/foo', function (req, res) { res.set('x-no-compression', 'true'), res.send(longText); }); return app; @@ -295,16 +353,16 @@ describe('express-server', function() { port: '1337', rootURL: '/', }) - .then(function() { + .then(function () { request(subject.app) .get('/foo') .set('accept', 'application/json, */*') - .expect(function(res) { + .expect(function (res) { expect(res.text).to.equal(longText); expect(res.header['content-encoding']).to.not.exist; expect(parseInt(res.header['content-length'], 10)).to.equal(longText.length); }) - .end(function(err) { + .end(function (err) { if (err) { return done(err); } @@ -314,42 +372,39 @@ describe('express-server', function() { }); }); - it( - 'does not use compression for server sent events', - co.wrap(function*() { - project.require = function() { - let app = express(); - app.use('/foo', function(req, res) { - res.set('Content-Type', 'text/event-stream'); - res.send(longText); - }); - return app; - }; - - yield subject.start({ - proxy: 'http://localhost:3001/', - host: undefined, - port: '1337', - rootURL: '/', - compression: true, + it('does not use compression for server sent events', async function () { + project.require = function () { + let app = express(); + app.use('/foo', function (req, res) { + res.set('Content-Type', 'text/event-stream'); + res.send(longText); }); + return app; + }; - yield request(subject.app) - .get('/foo') - .set('accept', 'application/json, */*') - .expect(function(res) { - expect(res.text).to.equal(longText); - expect(res.header['content-encoding']).to.not.exist; - expect(parseInt(res.header['content-length'], 10)).to.equal(longText.length); - }); + await subject.start({ + proxy: 'http://localhost:3001/', + host: undefined, + port: '1337', + rootURL: '/', + compression: true, + }); - expect(proxy.called).to.equal(false); - }) - ); + await request(subject.app) + .get('/foo') + .set('accept', 'application/json, */*') + .expect(function (res) { + expect(res.text).to.equal(longText); + expect(res.header['content-encoding']).to.not.exist; + expect(parseInt(res.header['content-length'], 10)).to.equal(longText.length); + }); + + expect(proxy.called).to.equal(false); + }); }); - describe('with proxy', function() { - beforeEach(function() { + describe('with proxy', function () { + beforeEach(function () { return subject.start({ proxy: 'http://localhost:3001/', host: undefined, @@ -363,7 +418,7 @@ describe('express-server', function() { request(app) .get(url) .set('accept', 'text/html') - .end(function(err, response) { + .end(function (err, response) { if (err) { return done(err); } @@ -375,12 +430,12 @@ describe('express-server', function() { }); } - it('bypasses proxy for /', function(done) { + it('bypasses proxy for /', function (done) { bypassTest(subject.app, '/', done); }); - it('bypasses proxy for files that exist', function(done) { - bypassTest(subject.app, '/test-file.txt', done, function(response) { + it('bypasses proxy for files that exist', function (done) { + bypassTest(subject.app, '/test-file.txt', done, function (response) { expect(response.text.trim()).to.equal('some contents'); }); }); @@ -391,7 +446,7 @@ describe('express-server', function() { .call(req, url) .set('content-length', 0) .set('accept', 'text/json') - .end(function(err) { + .end(function (err) { if (err) { return done(err); } @@ -403,27 +458,27 @@ describe('express-server', function() { }); } - it('proxies GET', function(done) { + it('proxies GET', function (done) { apiTest(subject.app, 'get', '/api/get', done); }); - it('proxies PUT', function(done) { + it('proxies PUT', function (done) { apiTest(subject.app, 'put', '/api/put', done); }); - it('proxies POST', function(done) { + it('proxies POST', function (done) { apiTest(subject.app, 'post', '/api/post', done); }); - it('proxies DELETE', function(done) { + it('proxies DELETE', function (done) { apiTest(subject.app, 'delete', '/api/delete', done); }); - it('proxies websockets', function(done) { + it('proxies websockets', function (done) { let number = Math.round(Math.random() * 0xffffff); let client = new WebSocket('ws://localhost:1337/foo'); - client.onerror = error => { + client.onerror = (error) => { done(error); // fail the test }; @@ -442,11 +497,11 @@ describe('express-server', function() { }); // test for #1263 - it('proxies when accept contains */*', function(done) { + it('proxies when accept contains */*', function (done) { request(subject.app) .get('/api/get') .set('accept', 'application/json, */*') - .end(function(err) { + .end(function (err) { if (err) { return done(err); } @@ -456,8 +511,8 @@ describe('express-server', function() { }); }); - describe('proxy with subdomain', function() { - beforeEach(function() { + describe('proxy with subdomain', function () { + beforeEach(function () { nockProxy = { called: null, method: null, @@ -477,7 +532,7 @@ describe('express-server', function() { return req[method] .call(req, url) .set('accept', 'text/json') - .end(function(err) { + .end(function (err) { if (err) { return done(err); } @@ -488,14 +543,14 @@ describe('express-server', function() { }); } - it('proxies GET', function(done) { + it('proxies GET', function (done) { nock('http://api.lvh.me', { reqheaders: { host: 'api.lvh.me', }, }) .get('/api/get') - .reply(200, function() { + .reply(200, function () { nockProxy.called = true; nockProxy.method = 'GET'; nockProxy.url = '/api/get'; @@ -506,14 +561,14 @@ describe('express-server', function() { apiTest(subject.app, 'get', '/api/get', done); }); - it('proxies PUT', function(done) { + it('proxies PUT', function (done) { nock('http://api.lvh.me', { reqheaders: { host: 'api.lvh.me', }, }) .put('/api/put') - .reply(204, function() { + .reply(204, function () { nockProxy.called = true; nockProxy.method = 'PUT'; nockProxy.url = '/api/put'; @@ -524,14 +579,14 @@ describe('express-server', function() { apiTest(subject.app, 'put', '/api/put', done); }); - it('proxies POST', function(done) { + it('proxies POST', function (done) { nock('http://api.lvh.me', { reqheaders: { host: 'api.lvh.me', }, }) .post('/api/post') - .reply(201, function() { + .reply(201, function () { nockProxy.called = true; nockProxy.method = 'POST'; nockProxy.url = '/api/post'; @@ -542,14 +597,14 @@ describe('express-server', function() { apiTest(subject.app, 'post', '/api/post', done); }); - it('proxies DELETE', function(done) { + it('proxies DELETE', function (done) { nock('http://api.lvh.me', { reqheaders: { host: 'api.lvh.me', }, }) .delete('/api/delete') - .reply(204, function() { + .reply(204, function () { nockProxy.called = true; nockProxy.method = 'DELETE'; nockProxy.url = '/api/delete'; @@ -561,10 +616,10 @@ describe('express-server', function() { }); // test for #1263 - it('proxies when accept contains */*', function(done) { + it('proxies when accept contains */*', function (done) { nock('http://api.lvh.me') .get('/api/get') - .reply(200, function() { + .reply(200, function () { nockProxy.called = true; nockProxy.method = 'GET'; nockProxy.url = '/api/get'; @@ -575,7 +630,7 @@ describe('express-server', function() { request(subject.app) .get('/api/get') .set('accept', 'application/json, */*') - .end(function(err) { + .end(function (err) { if (err) { return done(err); } @@ -585,7 +640,7 @@ describe('express-server', function() { }); }); - describe('without proxy', function() { + describe('without proxy', function () { function startServer(rootURL) { return subject.start({ environment: 'development', @@ -595,14 +650,14 @@ describe('express-server', function() { }); } - it('serves index.html when file not found with auto/history location', function(done) { - startServer().then(function() { + it('serves index.html when file not found with auto/history location', function (done) { + startServer().then(function () { request(subject.app) .get('/someurl.withperiod') .set('accept', 'text/html') .expect(200) .expect('Content-Type', /html/) - .end(function(err) { + .end(function (err) { if (err) { return done(err); } @@ -611,19 +666,19 @@ describe('express-server', function() { }); }); - it('GET /tests serves tests/index.html for mime of */* (hash location)', function(done) { + it('GET /tests serves tests/index.html for mime of */* (hash location)', function (done) { project._config = { rootURL: '/', locationType: 'hash', }; - startServer().then(function() { + startServer().then(function () { request(subject.app) .get('/tests') .set('accept', '*/*') .expect(200) .expect('Content-Type', /html/) - .end(function(err) { + .end(function (err) { if (err) { return done(err); } @@ -632,14 +687,14 @@ describe('express-server', function() { }); }); - it('GET /tests serves tests/index.html for mime of */* (auto location)', function(done) { - startServer().then(function() { + it('GET /tests serves tests/index.html for mime of */* (auto location)', function (done) { + startServer().then(function () { request(subject.app) .get('/tests') .set('accept', '*/*') .expect(200) .expect('Content-Type', /html/) - .end(function(err) { + .end(function (err) { if (err) { return done(err); } @@ -648,14 +703,14 @@ describe('express-server', function() { }); }); - it('GET /tests/whatever serves tests/index.html when file not found', function(done) { - startServer().then(function() { + it('GET /tests/whatever serves tests/index.html when file not found', function (done) { + startServer().then(function () { request(subject.app) .get('/tests/whatever') .set('accept', 'text/html') .expect(200) .expect('Content-Type', /html/) - .end(function(err) { + .end(function (err) { if (err) { return done(err); } @@ -664,15 +719,15 @@ describe('express-server', function() { }); }); - it('GET /tests/an-existing-file.tla serves tests/an-existing-file.tla if it is found', function(done) { - startServer().then(function() { + it('GET /tests/an-existing-file.tla serves tests/an-existing-file.tla if it is found', function (done) { + startServer().then(function () { request(subject.app) .get('/tests/test-file.txt') .set('accept', 'text/html') .expect(200) .expect(/some contents/) .expect('Content-Type', /text/) - .end(function(err) { + .end(function (err) { if (err) { return done(err); } @@ -681,14 +736,14 @@ describe('express-server', function() { }); }); - it('serves index.html when file not found (with rootURL) with auto/history location', function(done) { - startServer('/foo').then(function() { + it('serves index.html when file not found (with rootURL) with auto/history location', function (done) { + startServer('/foo').then(function () { request(subject.app) .get('/foo/someurl') .set('accept', 'text/html') .expect(200) .expect('Content-Type', /html/) - .end(function(err) { + .end(function (err) { if (err) { return done(err); } @@ -697,20 +752,20 @@ describe('express-server', function() { }); }); - it('serves index.html when file not found (with rootURL) with custom history location', function(done) { + it('serves index.html when file not found (with rootURL) with custom history location', function (done) { project._config = { rootURL: '/', locationType: 'blahr', historySupportMiddleware: true, }; - startServer('/foo').then(function() { + startServer('/foo').then(function () { request(subject.app) .get('/foo/someurl') .set('accept', 'text/html') .expect(200) .expect('Content-Type', /html/) - .end(function(err) { + .end(function (err) { if (err) { return done(err); } @@ -719,38 +774,34 @@ describe('express-server', function() { }); }); - it('returns a 404 when file not found with hash location', function(done) { + it('returns a 404 when file not found with hash location', function (done) { project._config = { rootURL: '/', locationType: 'hash', }; - startServer().then(function() { - request(subject.app) - .get('/someurl.withperiod') - .set('accept', 'text/html') - .expect(404) - .end(done); + startServer().then(function () { + request(subject.app).get('/someurl.withperiod').set('accept', 'text/html').expect(404).end(done); }); }); - it('files that exist in broccoli directory are served up', function(done) { - startServer().then(function() { + it('files that exist in broccoli directory are served up', function (done) { + startServer().then(function () { request(subject.app) .get('/test-file.txt') - .end(function(err, response) { + .end(function (err, response) { expect(response.text.trim()).to.equal('some contents'); done(); }); }); }); - it('serves static asset up from build output without a period in name', function(done) { - startServer().then(function() { + it('serves static asset up from build output without a period in name', function (done) { + startServer().then(function () { request(subject.app) .get('/someurl-without-period') .expect(200) - .end(function(err, response) { + .end(function (err, response) { if (err) { return done(err); } @@ -762,12 +813,12 @@ describe('express-server', function() { }); }); - it('serves a static wasm file up from build output with correct Content-Type header', function(done) { - startServer().then(function() { + it('serves a static wasm file up from build output with correct Content-Type header', function (done) { + startServer().then(function () { request(subject.app) .get('/vendor/foo.wasm') .expect(200) - .end(function(err, response) { + .end(function (err, response) { if (err) { return done(err); } @@ -779,16 +830,16 @@ describe('express-server', function() { }); }); - it('serves static asset up from build output without a period in name (with rootURL)', function(done) { + it('serves static asset up from build output without a period in name (with rootURL)', function (done) { project._config = { rootURL: '/foo', }; - startServer('/foo').then(function() { + startServer('/foo').then(function () { request(subject.app) .get('/foo/someurl-without-period') .expect(200) - .end(function(err, response) { + .end(function (err, response) { if (err) { return done(err); } @@ -801,37 +852,37 @@ describe('express-server', function() { }); }); - describe('addons', function() { + describe('addons', function () { let calls; - beforeEach(function() { + beforeEach(function () { calls = 0; - subject.processAddonMiddlewares = function(options) { + subject.processAddonMiddlewares = function (options) { checkMiddlewareOptions(options); calls++; }; }); - it('calls processAddonMiddlewares upon start', function() { + it('calls processAddonMiddlewares upon start', function () { return subject .start({ host: undefined, port: '1337', }) - .then(function() { + .then(function () { expect(calls).to.equal(1); }); }); }); - describe('addon middleware', function() { + describe('addon middleware', function () { let firstCalls; let secondCalls; - beforeEach(function() { + beforeEach(function () { firstCalls = 0; secondCalls = 0; - project.initializeAddons = function() {}; + project.initializeAddons = function () {}; project.addons = [ { serverMiddleware({ options }) { @@ -851,39 +902,39 @@ describe('express-server', function() { ]; }); - it('calls serverMiddleware on the addons on start', function() { + it('calls serverMiddleware on the addons on start', function () { return subject .start({ host: undefined, port: '1337', }) - .then(function() { + .then(function () { expect(firstCalls).to.equal(1); expect(secondCalls).to.equal(1); }); }); - it('calls serverMiddleware on the addons on restart', function() { + it('calls serverMiddleware on the addons on restart', function () { return subject .start({ host: undefined, port: '1337', }) - .then(function() { + .then(function () { subject.changedFiles = ['bar.js']; return subject.restartHttpServer(); }) - .then(function() { + .then(function () { expect(firstCalls).to.equal(2); expect(secondCalls).to.equal(2); }); }); }); - describe('addon middleware is async', function() { + describe('addon middleware is async', function () { let order = []; - beforeEach(function() { - project.initializeAddons = function() {}; + beforeEach(function () { + project.initializeAddons = function () {}; project.addons = [ { serverMiddleware() { @@ -892,8 +943,8 @@ describe('express-server', function() { }, { serverMiddleware() { - return new Promise(function(resolve) { - setTimeout(function() { + return new Promise(function (resolve) { + setTimeout(function () { order.push('second'); resolve(); }, 50); @@ -908,13 +959,13 @@ describe('express-server', function() { ]; }); - it('waits for async middleware to complete before the next middleware', function() { + it('waits for async middleware to complete before the next middleware', function () { return subject .start({ host: undefined, port: '1337', }) - .then(function() { + .then(function () { expect(order[0]).to.equal('first'); expect(order[1]).to.equal('second'); expect(order[2]).to.equal('third'); @@ -922,9 +973,9 @@ describe('express-server', function() { }); }); - describe('addon middleware bubble errors', function() { - beforeEach(function() { - project.initializeAddons = function() {}; + describe('addon middleware bubble errors', function () { + beforeEach(function () { + project.initializeAddons = function () {}; project.addons = [ { serverMiddleware() { @@ -933,33 +984,33 @@ describe('express-server', function() { }, ]; }); - it('up to server start', function() { + it('up to server start', function () { return subject .start({ host: undefined, port: '1337', }) - .catch(function(reason) { + .catch(function (reason) { expect(reason).to.equal('addon middleware fail'); }); }); }); - describe('app middleware', function() { + describe('app middleware', function () { let passedOptions; let calls; - beforeEach(function() { + beforeEach(function () { passedOptions = null; calls = 0; - subject.processAppMiddlewares = function(options) { + subject.processAppMiddlewares = function (options) { passedOptions = options; calls++; }; }); - it('calls processAppMiddlewares upon start', function() { + it('calls processAppMiddlewares upon start', function () { let realOptions = { baseURL: '/', rootURL: undefined, @@ -967,13 +1018,13 @@ describe('express-server', function() { port: '1337', }; - return subject.start(realOptions).then(function() { + return subject.start(realOptions).then(function () { expect(passedOptions).to.deep.equal(realOptions); expect(calls).to.equal(1); }); }); - it('calls processAppMiddlewares upon restart', function() { + it('calls processAppMiddlewares upon restart', function () { let realOptions = { baseURL: '/', rootURL: undefined, @@ -985,12 +1036,12 @@ describe('express-server', function() { return subject .start(realOptions) - .then(function() { + .then(function () { originalApp = subject.app; subject.changedFiles = ['bar.js']; return subject.restartHttpServer(); }) - .then(function() { + .then(function () { expect(subject.app).to.be.ok; expect(originalApp).to.not.equal(subject.app); expect(passedOptions).to.deep.equal(realOptions); @@ -998,10 +1049,10 @@ describe('express-server', function() { }); }); - it('includes httpServer instance in options', function() { + it('includes httpServer instance in options', function () { let passedOptions; - subject.processAppMiddlewares = function(options) { + subject.processAppMiddlewares = function (options) { passedOptions = options; }; @@ -1010,16 +1061,16 @@ describe('express-server', function() { port: '1337', }; - return subject.start(realOptions).then(function() { + return subject.start(realOptions).then(function () { expect(!!passedOptions.httpServer.listen).to.be.ok; }); }); }); - describe('serverWatcherDidChange', function() { - it('is called on file change', function() { + describe('serverWatcherDidChange', function () { + it('is called on file change', function () { let calls = 0; - subject.serverWatcherDidChange = function() { + subject.serverWatcherDidChange = function () { calls++; }; @@ -1028,15 +1079,15 @@ describe('express-server', function() { host: undefined, port: '1337', }) - .then(function() { + .then(function () { subject.serverWatcher.emit('change', 'foo.txt'); expect(calls).to.equal(1); }); }); - it('schedules a server restart', function() { + it('schedules a server restart', function () { let calls = 0; - subject.scheduleServerRestart = function() { + subject.scheduleServerRestart = function () { calls++; }; @@ -1045,7 +1096,7 @@ describe('express-server', function() { host: undefined, port: '1337', }) - .then(function() { + .then(function () { subject.serverWatcher.emit('change', 'foo.txt'); subject.serverWatcher.emit('change', 'bar.txt'); expect(calls).to.equal(2); @@ -1053,28 +1104,38 @@ describe('express-server', function() { }); }); - describe('scheduleServerRestart', function() { - it('schedules exactly one call of restartHttpServer', function(done) { + describe('scheduleServerRestart', function () { + it('schedules exactly one call of restartHttpServer', async function () { let calls = 0; - subject.restartHttpServer = function() { + + subject.restartHttpServer = function () { calls++; }; subject.scheduleServerRestart(); + // scheduleServerRestart is debounced and only ran after 100ms, + // restartHttpServer shouldn't be called yet expect(calls).to.equal(0); - setTimeout(function() { - expect(calls).to.equal(0); - subject.scheduleServerRestart(); - }, 50); - setTimeout(function() { - expect(calls).to.equal(1); - done(); - }, 175); + + await sleep(50); + + // after a 50ms wait, we still haven't called restartHttpServer since + // we are still within our 100ms debounce time. + expect(calls).to.equal(0); + subject.scheduleServerRestart(); + + await sleep(175); + + // finally, after 175ms we have finally called restartHttpServer, but + // importantly only called it once (all of the other + // `subject.scheduleServerRestart()` calls were within the debounce + // window) + expect(calls).to.equal(1); }); }); - describe('restartHttpServer', function() { - it('restarts the server', function() { + describe('restartHttpServer', function () { + it('restarts the server', function () { let originalHttpServer; let originalApp; return subject @@ -1082,14 +1143,14 @@ describe('express-server', function() { host: undefined, port: '1337', }) - .then(function() { + .then(function () { ui.output = ''; originalHttpServer = subject.httpServer; originalApp = subject.app; subject.changedFiles = ['bar.js']; return subject.restartHttpServer(); }) - .then(function() { + .then(function () { expect(ui.output).to.contains(EOL + chalk.green('Server restarted.') + EOL + EOL); expect(subject.httpServer, 'HTTP server exists').to.be.ok; expect(subject.httpServer).to.not.equal(originalHttpServer, 'HTTP server has changed'); @@ -1098,7 +1159,7 @@ describe('express-server', function() { }); }); - it('restarts the server again if one or more files change during a previous restart', function() { + it('restarts the server again if one or more files change during a previous restart', function () { let originalHttpServer; let originalApp; return subject @@ -1106,11 +1167,11 @@ describe('express-server', function() { host: undefined, port: '1337', }) - .then(function() { + .then(function () { originalHttpServer = subject.httpServer; originalApp = subject.app; - subject.serverRestartPromise = new Promise(function(resolve) { - setTimeout(function() { + subject.serverRestartPromise = new Promise(function (resolve) { + setTimeout(function () { subject.serverRestartPromise = null; resolve(); }, 20); @@ -1118,7 +1179,7 @@ describe('express-server', function() { subject.changedFiles = ['bar.js']; return subject.restartHttpServer(); }) - .then(function() { + .then(function () { expect(!!subject.httpServer).to.equal(true, 'HTTP server exists'); expect(subject.httpServer).to.not.equal(originalHttpServer, 'HTTP server has changed'); expect(!!subject.app).to.equal(true, 'App exists'); @@ -1126,9 +1187,9 @@ describe('express-server', function() { }); }); - it('emits the restart event', function() { + it('emits the restart event', function () { let calls = 0; - subject.on('restart', function() { + subject.on('restart', function () { calls++; }); return subject @@ -1136,11 +1197,11 @@ describe('express-server', function() { host: undefined, port: '1337', }) - .then(function() { + .then(function () { subject.changedFiles = ['bar.js']; return subject.restartHttpServer(); }) - .then(function() { + .then(function () { expect(calls).to.equal(1); }); }); diff --git a/tests/unit/tasks/server/livereload-server-test.js b/tests/unit/tasks/server/livereload-server-test.js index c0bd427995..d371172e94 100644 --- a/tests/unit/tasks/server/livereload-server-test.js +++ b/tests/unit/tasks/server/livereload-server-test.js @@ -12,14 +12,14 @@ const express = require('express'); const FSTree = require('fs-tree-diff'); const http = require('http'); -describe('livereload-server', function() { +describe('livereload-server', function () { let subject; let ui; let watcher; let httpServer; let app; - beforeEach(function() { + beforeEach(function () { ui = new MockUI(); watcher = new MockWatcher(); httpServer = new MockExpressServer(); @@ -37,7 +37,7 @@ describe('livereload-server', function() { }); }); - afterEach(function() { + afterEach(function () { try { if (subject.liveReloadServer) { subject.liveReloadServer.close(); @@ -47,39 +47,37 @@ describe('livereload-server', function() { } }); - describe('start', function() { - it('does not start the server if `liveReload` option is not true', function() { + describe('start', function () { + it('does not start the server if `liveReload` option is not true', function () { return subject .setupMiddleware({ liveReload: false, liveReloadPort: 4200, liveReloadPrefix: '/', }) - .then(function() { + .then(function () { expect(ui.output).to.contains('WARNING: Livereload server manually disabled.'); expect(!!subject.liveReloadServer).to.equal(false); }); }); - it('informs of error during startup with custom port', function(done) { + it('informs of error during startup with custom port', async function () { let preexistingServer = net.createServer(); preexistingServer.listen(1337); - subject - .setupMiddleware({ + + try { + await subject.setupMiddleware({ liveReload: true, liveReloadPort: 1337, liveReloadPrefix: '/', port: 4200, - }) - .catch(function(reason) { - expect(reason).to.equal( - `Livereload failed on http://localhost:1337. It is either in use or you do not have permission.` - ); - }) - .finally(function() { - preexistingServer.close(done); }); + } catch (e) { + expect(e.message).to.include(`Livereload failed on 'http://localhost:1337', It may be in use.`); + } finally { + await new Promise((resolve) => preexistingServer.close(resolve)); + } }); - it('starts with custom host, custom port', function() { + it('starts with custom host, custom port', function () { return subject .setupMiddleware({ liveReloadHost: '127.0.0.1', @@ -88,12 +86,12 @@ describe('livereload-server', function() { liveReloadPrefix: '/', port: 4200, }) - .then(function() { + .then(function () { expect(subject.liveReloadServer.options.port).to.equal(1377); expect(subject.liveReloadServer.options.host).to.equal('127.0.0.1'); }); }); - it('Livereload responds to livereload requests and returns livereload file', function(done) { + it('Livereload responds to livereload requests and returns livereload file', function (done) { let server = app.listen(4200); subject .setupMiddleware({ @@ -101,16 +99,16 @@ describe('livereload-server', function() { liveReloadPrefix: '_lr', port: 4200, }) - .then(function() { - http.get('http://localhost:4200/_lr/livereload.js', function(response) { + .then(function () { + http.get('http://localhost:4200/_lr/livereload.js', function (response) { expect(response.statusCode).to.equal(200); server.close(done); }); }); }); }); - describe('start with https', function() { - it('correctly runs in https mode', function() { + describe('start with https', function () { + it('correctly runs in https mode', function () { return subject .setupMiddleware({ liveReload: true, @@ -121,34 +119,35 @@ describe('livereload-server', function() { sslCert: 'tests/fixtures/ssl/server.crt', port: 4200, }) - .then(function() { + .then(function () { expect(subject.liveReloadServer.options.key).to.be.an.instanceof(Buffer); expect(subject.liveReloadServer.options.cert).to.be.an.instanceof(Buffer); }); }); - it('informs of error during startup', function(done) { + + it('informs of error during startup', async function () { let preexistingServer = net.createServer(); preexistingServer.listen(1337); - subject - .setupMiddleware({ + try { + await subject.setupMiddleware({ liveReloadPort: 1337, liveReload: true, ssl: true, sslKey: 'tests/fixtures/ssl/server.key', sslCert: 'tests/fixtures/ssl/server.crt', port: 4200, - }) - .catch(function(reason) { - expect(reason).to.equal( - `Livereload failed on https://localhost:1337. It is either in use or you do not have permission.` - ); - }) - .finally(function() { - preexistingServer.close(done); }); + + expect.fail('expected rejection'); + } catch (e) { + expect(e.message).to.include(`Livereload failed on 'https://localhost:1337', It may be in use.`); + } finally { + await new Promise((resolve) => preexistingServer.close(resolve)); + } }); - it('correctly runs in https mode with custom port', function() { + + it('correctly runs in https mode with custom port', function () { return subject .setupMiddleware({ liveReload: true, @@ -159,17 +158,17 @@ describe('livereload-server', function() { sslCert: 'tests/fixtures/ssl/server.crt', port: 4200, }) - .then(function() { + .then(function () { expect(subject.liveReloadServer.options.key).to.be.an.instanceof(Buffer); expect(subject.liveReloadServer.options.cert).to.be.an.instanceof(Buffer); }); }); }); - describe('express server restart', function() { - it('triggers when the express server restarts', function() { + describe('express server restart', function () { + it('triggers when the express server restarts', function () { let calls = 0; - subject.didRestart = function() { + subject.didRestart = function () { calls++; }; return subject @@ -178,14 +177,14 @@ describe('livereload-server', function() { liveReloadPrefix: '/', port: 4200, }) - .then(function() { + .then(function () { subject.app.emit('restart'); expect(calls).to.equal(1); }); }); - it('triggers when the express server restarts with custom port', function() { + it('triggers when the express server restarts with custom port', function () { let calls = 0; - subject.didRestart = function() { + subject.didRestart = function () { calls++; }; return subject @@ -195,28 +194,28 @@ describe('livereload-server', function() { liveReloadPort: 1337, port: 4200, }) - .then(function() { + .then(function () { subject.app.emit('restart'); expect(calls).to.equal(1); }); }); }); - describe('livereload changes', function() { + describe('livereload changes', function () { let liveReloadServer; let changedCount; let oldChanged; - let stubbedChanged = function() { + let stubbedChanged = function () { changedCount += 1; }; let trackCount; let oldTrack; - let stubbedTrack = function() { + let stubbedTrack = function () { trackCount += 1; }; - let createStubbedGetDirectoryEntries = function(files) { - return function() { - return files.map(function(file) { + let createStubbedGetDirectoryEntries = function (files) { + return function () { + return files.map(function (file) { return { relativePath: file, isDirectory() { @@ -227,7 +226,7 @@ describe('livereload-server', function() { }; }; - beforeEach(function() { + beforeEach(function () { subject.setupMiddleware({ liveReload: true, liveReloadPort: 4200, @@ -246,13 +245,13 @@ describe('livereload-server', function() { subject.tree = FSTree.fromEntries([]); }); - afterEach(function() { + afterEach(function () { liveReloadServer.changed = oldChanged; subject.analytics.track = oldTrack; subject.project.liveReloadFilterPatterns = []; }); - describe('watcher events', function() { + describe('watcher events', function () { function watcherEventTest(eventName, expectedCount) { subject.getDirectoryEntries = createStubbedGetDirectoryEntries(['test/fixtures/proxy/file-a.js']); subject.project.liveReloadFilterPatterns = []; @@ -262,20 +261,20 @@ describe('livereload-server', function() { return expect(changedCount).to.equal(expectedCount); } - it('triggers a livereload change on a watcher change event', function() { + it('triggers a livereload change on a watcher change event', function () { return watcherEventTest('change', 1); }); - it('triggers a livereload change on a watcher error event', function() { + it('triggers a livereload change on a watcher error event', function () { return watcherEventTest('error', 1); }); - it('does not trigger a livereload change on other watcher events', function() { + it('does not trigger a livereload change on other watcher events', function () { return watcherEventTest('not-an-event', 0); }); - it('recovers from error when file is already cached in previous cache step', function() { - let compileError = function() { + it('recovers from error when file is already cached in previous cache step', function () { + let compileError = function () { try { throw new Error('Compile time error'); } catch (error) { @@ -295,8 +294,8 @@ describe('livereload-server', function() { expect(changedCount).to.equal(2); }); - describe('filter pattern', function() { - it('shouldTriggerReload must be true if there are no liveReloadFilterPatterns', function() { + describe('filter pattern', function () { + it('shouldTriggerReload must be true if there are no liveReloadFilterPatterns', function () { subject.project.liveReloadFilterPatterns = []; let result = subject.shouldTriggerReload({ filePath: '/home/user/my-project/app/styles/app.css', @@ -304,7 +303,7 @@ describe('livereload-server', function() { expect(result).to.be.true; }); - it('shouldTriggerReload is true when no liveReloadFilterPatterns matches the filePath', function() { + it('shouldTriggerReload is true when no liveReloadFilterPatterns matches the filePath', function () { let basePath = path.normalize('test/fixtures/proxy').replace(/\\/g, '\\\\'); let filter = new RegExp(`^${basePath}`); @@ -315,7 +314,7 @@ describe('livereload-server', function() { expect(result).to.be.true; }); - it('shouldTriggerReload is false when one or more of the liveReloadFilterPatterns matches filePath', function() { + it('shouldTriggerReload is false when one or more of the liveReloadFilterPatterns matches filePath', function () { let basePath = path.normalize('test/fixtures/proxy').replace(/\\/g, '\\\\'); let filter = new RegExp(`^${basePath}`); @@ -326,7 +325,7 @@ describe('livereload-server', function() { expect(result).to.be.false; }); - it('shouldTriggerReload writes a banner after skipping reload for a file', function() { + it('shouldTriggerReload writes a banner after skipping reload for a file', function () { let basePath = path.normalize('test/fixtures/proxy').replace(/\\/g, '\\\\'); let filter = new RegExp(`^${basePath}`); @@ -339,7 +338,7 @@ describe('livereload-server', function() { ); }); - it('triggers the livereload server of a change when no pattern matches', function() { + it('triggers the livereload server of a change when no pattern matches', function () { subject.getDirectoryEntries = createStubbedGetDirectoryEntries([]); subject.didChange({ filePath: '/home/user/my-project/test/fixtures/proxy/file-a.js', @@ -348,7 +347,7 @@ describe('livereload-server', function() { expect(trackCount).to.equal(1); }); - it('does not trigger livereload server of a change when there is a pattern match', function() { + it('does not trigger livereload server of a change when there is a pattern match', function () { // normalize test regex for windows // path.normalize with change forward slashes to back slashes if test is running on windows // we then replace backslashes with double backslahes to escape the backslash in the regex @@ -368,22 +367,22 @@ describe('livereload-server', function() { }); }); - describe('specific files', function() { + describe('specific files', function () { let reloadedFiles; - let stubbedChanged = function(options) { + let stubbedChanged = function (options) { reloadedFiles = options.body.files; }; - beforeEach(function() { + beforeEach(function () { liveReloadServer.changed = stubbedChanged; }); - afterEach(function() { + afterEach(function () { reloadedFiles = undefined; }); - it('triggers livereload with modified files', function() { + it('triggers livereload with modified files', function () { let changedFiles = ['assets/my-project.css', 'assets/my-project.js']; subject.getDirectoryEntries = createStubbedGetDirectoryEntries(changedFiles); @@ -394,7 +393,7 @@ describe('livereload-server', function() { expect(reloadedFiles).to.deep.equal(changedFiles); }); - it('triggers livereload with deleted directories', function() { + it('triggers livereload with deleted directories', function () { let changedFiles = ['assets/my-project.css', 'assets/my-project.js']; subject.getDirectoryEntries = createStubbedGetDirectoryEntries(changedFiles); @@ -411,7 +410,7 @@ describe('livereload-server', function() { expect(reloadedFiles).to.deep.equal([]); }); - it('triggers livereload ignoring source map files', function() { + it('triggers livereload ignoring source map files', function () { let changedFiles = ['assets/my-project.css', 'assets/my-project.css.map']; let expectedResult = ['assets/my-project.css']; @@ -424,7 +423,7 @@ describe('livereload-server', function() { expect(reloadedFiles).to.deep.equal(expectedResult); }); - it('triggers livereload with "LiveReload files" if no results.directory was provided', function() { + it('triggers livereload with "LiveReload files" if no results.directory was provided', function () { let changedOptions; subject.liveReloadServer = { changed(options) { @@ -442,21 +441,21 @@ describe('livereload-server', function() { }); }); }); - describe('livereload changes with custom port', function() { + describe('livereload changes with custom port', function () { let liveReloadServer; let changedCount; let oldChanged; - let stubbedChanged = function() { + let stubbedChanged = function () { changedCount += 1; }; let trackCount; let oldTrack; - let stubbedTrack = function() { + let stubbedTrack = function () { trackCount += 1; }; - let createStubbedGetDirectoryEntries = function(files) { - return function() { - return files.map(function(file) { + let createStubbedGetDirectoryEntries = function (files) { + return function () { + return files.map(function (file) { return { relativePath: file, isDirectory() { @@ -467,7 +466,7 @@ describe('livereload-server', function() { }; }; - beforeEach(function() { + beforeEach(function () { subject.setupMiddleware({ liveReload: true, liveReloadPort: 1337, @@ -486,13 +485,13 @@ describe('livereload-server', function() { subject.tree = FSTree.fromEntries([]); }); - afterEach(function() { + afterEach(function () { liveReloadServer.changed = oldChanged; subject.analytics.track = oldTrack; subject.project.liveReloadFilterPatterns = []; }); - describe('watcher events', function() { + describe('watcher events', function () { function watcherEventTest(eventName, expectedCount) { subject.getDirectoryEntries = createStubbedGetDirectoryEntries(['test/fixtures/proxy/file-a.js']); subject.project.liveReloadFilterPatterns = []; @@ -502,20 +501,20 @@ describe('livereload-server', function() { return expect(changedCount).to.equal(expectedCount); } - it('triggers a livereload change on a watcher change event', function() { + it('triggers a livereload change on a watcher change event', function () { return watcherEventTest('change', 1); }); - it('triggers a livereload change on a watcher error event', function() { + it('triggers a livereload change on a watcher error event', function () { return watcherEventTest('error', 1); }); - it('does not trigger a livereload change on other watcher events', function() { + it('does not trigger a livereload change on other watcher events', function () { return watcherEventTest('not-an-event', 0); }); - it('recovers from error when file is already cached in previous cache step', function() { - let compileError = function() { + it('recovers from error when file is already cached in previous cache step', function () { + let compileError = function () { try { throw new Error('Compile time error'); } catch (error) { @@ -535,8 +534,8 @@ describe('livereload-server', function() { expect(changedCount).to.equal(2); }); - describe('filter pattern', function() { - it('shouldTriggerReload must be true if there are no liveReloadFilterPatterns', function() { + describe('filter pattern', function () { + it('shouldTriggerReload must be true if there are no liveReloadFilterPatterns', function () { subject.project.liveReloadFilterPatterns = []; let result = subject.shouldTriggerReload({ filePath: '/home/user/my-project/app/styles/app.css', @@ -544,7 +543,7 @@ describe('livereload-server', function() { expect(result).to.be.true; }); - it('shouldTriggerReload is true when no liveReloadFilterPatterns matches the filePath', function() { + it('shouldTriggerReload is true when no liveReloadFilterPatterns matches the filePath', function () { let basePath = path.normalize('test/fixtures/proxy').replace(/\\/g, '\\\\'); let filter = new RegExp(`^${basePath}`); @@ -555,7 +554,7 @@ describe('livereload-server', function() { expect(result).to.be.true; }); - it('shouldTriggerReload is false when one or more of the liveReloadFilterPatterns matches filePath', function() { + it('shouldTriggerReload is false when one or more of the liveReloadFilterPatterns matches filePath', function () { let basePath = path.normalize('test/fixtures/proxy').replace(/\\/g, '\\\\'); let filter = new RegExp(`^${basePath}`); @@ -566,7 +565,7 @@ describe('livereload-server', function() { expect(result).to.be.false; }); - it('shouldTriggerReload writes a banner after skipping reload for a file', function() { + it('shouldTriggerReload writes a banner after skipping reload for a file', function () { let basePath = path.normalize('test/fixtures/proxy').replace(/\\/g, '\\\\'); let filter = new RegExp(`^${basePath}`); @@ -579,7 +578,7 @@ describe('livereload-server', function() { ); }); - it('triggers the livereload server of a change when no pattern matches', function() { + it('triggers the livereload server of a change when no pattern matches', function () { subject.getDirectoryEntries = createStubbedGetDirectoryEntries([]); subject.didChange({ filePath: '/home/user/my-project/test/fixtures/proxy/file-a.js', @@ -588,7 +587,7 @@ describe('livereload-server', function() { expect(trackCount).to.equal(1); }); - it('does not trigger livereload server of a change when there is a pattern match', function() { + it('does not trigger livereload server of a change when there is a pattern match', function () { // normalize test regex for windows // path.normalize with change forward slashes to back slashes if test is running on windows // we then replace backslashes with double backslahes to escape the backslash in the regex @@ -608,24 +607,24 @@ describe('livereload-server', function() { }); }); - describe('specific files', function() { + describe('specific files', function () { let reloadedFiles; let changedOptions; - let stubbedChanged = function(options) { + let stubbedChanged = function (options) { reloadedFiles = options.body.files; changedOptions = options; }; - beforeEach(function() { + beforeEach(function () { liveReloadServer.changed = stubbedChanged; }); - afterEach(function() { + afterEach(function () { reloadedFiles = undefined; }); - it('triggers livereload with modified files', function() { + it('triggers livereload with modified files', function () { let changedFiles = ['assets/my-project.css', 'assets/my-project.js']; subject.getDirectoryEntries = createStubbedGetDirectoryEntries(changedFiles); @@ -636,7 +635,7 @@ describe('livereload-server', function() { expect(reloadedFiles).to.deep.equal(changedFiles); }); - it('triggers livereload with deleted directories', function() { + it('triggers livereload with deleted directories', function () { let changedFiles = ['assets/my-project.css', 'assets/my-project.js']; subject.getDirectoryEntries = createStubbedGetDirectoryEntries(changedFiles); @@ -653,7 +652,7 @@ describe('livereload-server', function() { expect(reloadedFiles).to.deep.equal([]); }); - it('triggers livereload ignoring source map files', function() { + it('triggers livereload ignoring source map files', function () { let changedFiles = ['assets/my-project.css', 'assets/my-project.css.map']; let expectedResult = ['assets/my-project.css']; @@ -666,7 +665,7 @@ describe('livereload-server', function() { expect(reloadedFiles).to.deep.equal(expectedResult); }); - it('triggers livereload with "LiveReload files" if no results.directory was provided', function() { + it('triggers livereload with "LiveReload files" if no results.directory was provided', function () { subject.didChange({}); expect(changedOptions).to.deep.equal({ diff --git a/tests/unit/tasks/server/middleware/history-support-test.js b/tests/unit/tasks/server/middleware/history-support-test.js index fe1f4de1ac..4643174cf3 100644 --- a/tests/unit/tasks/server/middleware/history-support-test.js +++ b/tests/unit/tasks/server/middleware/history-support-test.js @@ -3,9 +3,9 @@ const expect = require('chai').expect; const HistorySupportAddon = require('../../../../../lib/tasks/server/middleware/history-support'); -describe('HistorySupportAddon', function() { - describe('.serverMiddleware', function() { - it('add middleware when locationType is auto', function() { +describe('HistorySupportAddon', function () { + describe('.serverMiddleware', function () { + it('add middleware when locationType is auto', function () { let addon = new HistorySupportAddon({ config() { return { @@ -17,7 +17,7 @@ describe('HistorySupportAddon', function() { expect(addon.shouldAddMiddleware()).to.true; }); - it('add middleware when locationType is history', function() { + it('add middleware when locationType is history', function () { let addon = new HistorySupportAddon({ config() { return { @@ -29,7 +29,7 @@ describe('HistorySupportAddon', function() { expect(addon.shouldAddMiddleware()).to.true; }); - it('add middleware when locationType is an unknown type', function() { + it('add middleware when locationType is an unknown type', function () { let addon = new HistorySupportAddon({ config() { return { @@ -42,7 +42,7 @@ describe('HistorySupportAddon', function() { expect(addon.shouldAddMiddleware()).to.true; }); - it('add middleware when historySupportMiddleware is true', function() { + it('add middleware when historySupportMiddleware is true', function () { let addon = new HistorySupportAddon({ config() { return { @@ -54,7 +54,7 @@ describe('HistorySupportAddon', function() { expect(addon.shouldAddMiddleware()).to.true; }); - it('do not add middleware when historySupportMiddleware is false and locationType is history', function() { + it('do not add middleware when historySupportMiddleware is false and locationType is history', function () { let addon = new HistorySupportAddon({ config() { return { diff --git a/tests/unit/tasks/server/middleware/proxy-server-test.js b/tests/unit/tasks/server/middleware/proxy-server-test.js index 636212b9c9..bed1087931 100644 --- a/tests/unit/tasks/server/middleware/proxy-server-test.js +++ b/tests/unit/tasks/server/middleware/proxy-server-test.js @@ -4,15 +4,15 @@ const MockProject = require('../../../../helpers/mock-project'); const ProxyServerAddon = require('../../../../../lib/tasks/server/middleware/proxy-server'); const expect = require('chai').expect; -describe('proxy-server', function() { +describe('proxy-server', function () { let project, proxyServer; - beforeEach(function() { + beforeEach(function () { project = new MockProject(); proxyServer = new ProxyServerAddon(project); }); - it(`bypass livereload request`, function() { + it(`bypass livereload request`, function () { let options = { liveReloadPrefix: 'test/', }; diff --git a/tests/unit/tasks/server/middleware/tests-server-test.js b/tests/unit/tasks/server/middleware/tests-server-test.js index 6ec4e1dd43..56dd569fcd 100644 --- a/tests/unit/tasks/server/middleware/tests-server-test.js +++ b/tests/unit/tasks/server/middleware/tests-server-test.js @@ -2,13 +2,12 @@ const expect = require('chai').expect; const TestsServerAddon = require('../../../../../lib/tasks/server/middleware/tests-server'); -const Promise = require('rsvp').Promise; -describe('TestServerAddon', function() { - describe('.serverMiddleware', function() { +describe('TestServerAddon', function () { + describe('.serverMiddleware', function () { let addon, nextWasCalled, mockRequest, app; - beforeEach(function() { + beforeEach(function () { addon = new TestsServerAddon(); nextWasCalled = false; mockRequest = { @@ -19,18 +18,20 @@ describe('TestServerAddon', function() { }; app = { use(callback) { - return callback(mockRequest, null, function() { + return callback(mockRequest, null, function () { nextWasCalled = true; }); }, }; }); - it('invokes next when the watcher succeeds', function(done) { + it('invokes next when the watcher succeeds', function (done) { addon.serverMiddleware({ app, options: { - watcher: Promise.resolve(), + watcher: Promise.resolve({ + directory: 'some-output-directory', + }), }, finally() { try { @@ -43,7 +44,7 @@ describe('TestServerAddon', function() { }); }); - it('invokes next when the watcher fails', function(done) { + it('invokes next when the watcher fails', function (done) { let mockError = 'bad things are bad'; addon.serverMiddleware({ @@ -62,7 +63,7 @@ describe('TestServerAddon', function() { }); }); - it('allows baseURL containing `+` character', function(done) { + it('allows baseURL containing `+` character', function (done) { mockRequest.path = '/braden/+/tests/any-old-file'; mockRequest.headers.accept = ['*/*']; addon.serverMiddleware({ @@ -82,7 +83,7 @@ describe('TestServerAddon', function() { }); }); - it('allows rootURL containing `+` character', function(done) { + it('allows rootURL containing `+` character', function (done) { mockRequest.path = '/grayson/+/tests/any-old-file'; mockRequest.headers.accept = ['text/html']; addon.serverMiddleware({ diff --git a/tests/unit/tasks/test-server-test.js b/tests/unit/tasks/test-server-test.js index f25120f68c..e5da221fd4 100644 --- a/tests/unit/tasks/test-server-test.js +++ b/tests/unit/tasks/test-server-test.js @@ -7,10 +7,10 @@ const MockProject = require('../../helpers/mock-project'); const MockUI = require('console-ui/mock'); const MockWatcher = require('../../helpers/mock-watcher'); -describe('test server', function() { +describe('test server', function () { let subject; - it('transforms and sets defaultOptions in testem and invokes testem properly', function() { + it('transforms and sets defaultOptions in testem and invokes testem properly', function () { let ui = new MockUI(); let watcher = new MockWatcher(); @@ -48,17 +48,17 @@ describe('test server', function() { watcher, testPage: 'http://my/test/page', }) - .then(function(value) { + .then(function (value) { expect(value, 'expected exist status of 0').to.eql(0); }); watcher.emit('change'); return runResult; }); - describe('completion', function() { + describe('completion', function () { let ui, watcher, subject, runOptions; - before(function() { + before(function () { ui = new MockUI(); watcher = new MockWatcher(); @@ -83,18 +83,18 @@ describe('test server', function() { }); }); - describe('firstRun', function() { - it('rejects with testem exceptions', function() { + describe('firstRun', function () { + it('rejects with testem exceptions', function () { let error = new Error('OMG'); - subject.testem.setDefaultOptions = function(options) { + subject.testem.setDefaultOptions = function (options) { this.defaultOptions = options; }; - subject.testem.startDev = function(options, finalizer) { + subject.testem.startDev = function (options, finalizer) { finalizer(1, error); }; - let runResult = expect(subject.run(runOptions)).to.be.rejected.then(reason => { + let runResult = expect(subject.run(runOptions)).to.be.rejected.then((reason) => { expect(reason).to.eql(error); }); @@ -103,17 +103,17 @@ describe('test server', function() { return runResult; }); - it('rejects with exit status (1)', function() { + it('rejects with exit status (1)', function () { let error = new SilentError('Testem finished with non-zero exit code. Tests failed.'); - subject.testem.setDefaultOptions = function(options) { + subject.testem.setDefaultOptions = function (options) { this.defaultOptions = options; }; - subject.testem.startDev = function(options, finalizer) { + subject.testem.startDev = function (options, finalizer) { finalizer(1); }; - let runResult = expect(subject.run(runOptions)).to.be.rejected.then(reason => { + let runResult = expect(subject.run(runOptions)).to.be.rejected.then((reason) => { expect(reason).to.eql(error); }); @@ -121,16 +121,16 @@ describe('test server', function() { return runResult; }); - it('resolves with exit status (0)', function() { - subject.testem.setDefaultOptions = function(options) { + it('resolves with exit status (0)', function () { + subject.testem.setDefaultOptions = function (options) { this.defaultOptions = options; }; - subject.testem.startDev = function(options, finalizer) { + subject.testem.startDev = function (options, finalizer) { finalizer(0); }; - let runResult = subject.run(runOptions).then(function(value) { + let runResult = subject.run(runOptions).then(function (value) { expect(value, 'expected exist status of 0').to.eql(0); }); @@ -140,14 +140,14 @@ describe('test server', function() { }); }); - describe('restart', function() { - it('rejects with testem exceptions', function() { + describe('restart', function () { + it('rejects with testem exceptions', function () { let error = new Error('OMG'); - subject.testem.setDefaultOptions = function(options) { + subject.testem.setDefaultOptions = function (options) { this.defaultOptions = options; }; - subject.testem.startDev = function(options, finalizer) { + subject.testem.startDev = function (options, finalizer) { finalizer(0); }; @@ -155,12 +155,12 @@ describe('test server', function() { watcher.emit('change'); - return runResult.then(function() { - subject.testem.startDev = function(options, finalizer) { + return runResult.then(function () { + subject.testem.startDev = function (options, finalizer) { finalizer(0, error); }; - runResult = expect(subject.run(runOptions)).to.be.rejected.then(reason => { + runResult = expect(subject.run(runOptions)).to.be.rejected.then((reason) => { expect(reason).to.eql(error); }); diff --git a/tests/unit/tasks/test-test.js b/tests/unit/tasks/test-test.js index e2d7f28ee4..b246346f7b 100644 --- a/tests/unit/tasks/test-test.js +++ b/tests/unit/tasks/test-test.js @@ -4,10 +4,36 @@ const expect = require('chai').expect; const TestTask = require('../../../lib/tasks/test'); const MockProject = require('../../helpers/mock-project'); -describe('test task test', function() { +describe('test task test', function () { let subject; - it('transforms options for testem configuration', function() { + it('call testem middleware with options', async function () { + let testemMiddlewareOptions; + let project = new MockProject(); + + project.initializeAddons = function () {}; + project.addons = [ + { + testemMiddleware(_, options) { + testemMiddlewareOptions = options; + }, + }, + ]; + + let options = { + reporter: 'xunit', + configFile: 'tests/fixtures/tasks/testem-config/testem-dummy.json', + path: 'dist', + ssl: false, + }; + + subject = new TestTask({ project }); + await subject.run(options); + expect(testemMiddlewareOptions).to.deep.equal(options); + expect(testemMiddlewareOptions.path).to.equal('dist'); + }); + + it('transforms options for testem configuration', function () { subject = new TestTask({ project: new MockProject(), addonMiddlewares() { @@ -55,7 +81,7 @@ describe('test task test', function() { }); }); - it('supports conditionally passing SSL configuration forward', function() { + it('supports conditionally passing SSL configuration forward', function () { subject = new TestTask({ project: new MockProject(), diff --git a/tests/unit/utilities/attempt-never-index-test.js b/tests/unit/utilities/attempt-never-index-test.js deleted file mode 100644 index 121a7705d1..0000000000 --- a/tests/unit/utilities/attempt-never-index-test.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -const attemptNeverIndex = require('../../../lib/utilities/attempt-never-index'); -const quickTemp = require('quick-temp'); -let isDarwin = /darwin/i.test(require('os').type()); - -const chai = require('../../chai'); -let expect = chai.expect; -let file = chai.file; - -describe('attempt-never-index', function() { - let context = {}; - let tmpPath; - before(function() { - tmpPath = quickTemp.makeOrRemake(context, 'attempt-never-index'); - }); - - after(function() { - quickTemp.remove(context, 'attempt-never-index'); - }); - - it('sets the hint to spotlight if possible', function() { - expect(file(`${tmpPath}/.metadata_never_index`)).to.not.exist; - - attemptNeverIndex(tmpPath); - - if (isDarwin) { - expect(file(`${tmpPath}/.metadata_never_index`)).to.exist; - } else { - expect(file(`${tmpPath}/.metadata_never_index`)).to.not.exist; - } - }); -}); diff --git a/tests/unit/utilities/command-generator-test.js b/tests/unit/utilities/command-generator-test.js index def5978264..542788b356 100644 --- a/tests/unit/utilities/command-generator-test.js +++ b/tests/unit/utilities/command-generator-test.js @@ -3,19 +3,19 @@ const td = require('testdouble'); const Command = require('../../../tests/helpers/command-generator'); -describe('command-generator', function() { +describe('command-generator', function () { let yarn, _invoke; - beforeEach(function() { + beforeEach(function () { yarn = new Command('yarn'); _invoke = yarn._invoke = td.function('invoke'); }); - afterEach(function() { + afterEach(function () { td.reset(); }); - it('invoke passes options', function() { + it('invoke passes options', function () { // Works with subcommand or argument. yarn.invoke('install'); td.verify(_invoke(td.matchers.isA(Array), {})); @@ -45,7 +45,7 @@ describe('command-generator', function() { td.verify(_invoke(), { times: 8, ignoreExtraArgs: true }); }); - it('builds the proper invocation', function() { + it('builds the proper invocation', function () { yarn.invoke(); td.verify(_invoke([]), { ignoreExtraArgs: true }); diff --git a/tests/unit/utilities/ember-app-utils-test.js b/tests/unit/utilities/ember-app-utils-test.js index 50c8c3e861..8d76738cb8 100644 --- a/tests/unit/utilities/ember-app-utils-test.js +++ b/tests/unit/utilities/ember-app-utils-test.js @@ -12,14 +12,14 @@ const normalizeUrl = emberAppUtils.normalizeUrl; const calculateBaseTag = emberAppUtils.calculateBaseTag; const convertObjectToString = emberAppUtils.convertObjectToString; -describe('ember-app-utils', function() { - describe(`rootURL`, function() { - it('`rootURL` regex accepts space-padded padded variation', function() { +describe('ember-app-utils', function () { + describe(`rootURL`, function () { + it('`rootURL` regex accepts space-padded padded variation', function () { const regex = configReplacePatterns()[0].match; const variations = ['{{rootURL}}', '{{ rootURL }}', 'foo']; const results = []; - variations.forEach(variation => { + variations.forEach((variation) => { const match = variation.match(regex); if (match !== null) { @@ -32,13 +32,13 @@ describe('ember-app-utils', function() { }); }); - describe(`EMBER_ENV`, function() { - it('`EMBER_ENV` regex accepts space-padded padded variation', function() { + describe(`EMBER_ENV`, function () { + it('`EMBER_ENV` regex accepts space-padded padded variation', function () { const regex = configReplacePatterns()[1].match; const variations = ['{{EMBER_ENV}}', '{{ EMBER_ENV }}', 'foo']; const results = []; - variations.forEach(variation => { + variations.forEach((variation) => { const match = variation.match(regex); if (match !== null) { @@ -51,13 +51,13 @@ describe('ember-app-utils', function() { }); }); - describe(`MODULE_PREFIX`, function() { - it('`MODULE_PREFIX` regex accepts space-padded padded variation', function() { + describe(`MODULE_PREFIX`, function () { + it('`MODULE_PREFIX` regex accepts space-padded padded variation', function () { const regex = configReplacePatterns()[3].match; const variations = ['{{MODULE_PREFIX}}', '{{ MODULE_PREFIX }}', 'foo']; const results = []; - variations.forEach(variation => { + variations.forEach((variation) => { const match = variation.match(regex); if (match !== null) { @@ -70,7 +70,7 @@ describe('ember-app-utils', function() { }); }); - describe(`contentFor`, function() { + describe(`contentFor`, function () { let config = { modulePrefix: 'cool-foo', }; @@ -81,10 +81,9 @@ describe('ember-app-utils', function() { storeConfigInMeta: true, autoRun: true, addons: [], - isModuleUnification: false, }; - it('`content-for` regex returns all matches presents in a same line', function() { + it('`content-for` regex returns all matches presents in a same line', function () { const contentForRegex = configReplacePatterns(defaultOptions)[2].match; const content = "{{content-for 'foo'}} {{content-for 'bar'}}"; const results = []; @@ -94,24 +93,27 @@ describe('ember-app-utils', function() { results.push(match); } - expect(results).to.deep.equal([["{{content-for 'foo'}}", 'foo'], ["{{content-for 'bar'}}", 'bar']]); + expect(results).to.deep.equal([ + ["{{content-for 'foo'}}", 'foo'], + ["{{content-for 'bar'}}", 'bar'], + ]); }); - it('returns an empty string if invalid type is specified', function() { + it('returns an empty string if invalid type is specified', function () { expect(contentFor(config, defaultMatch, 'foo', defaultOptions)).to.equal(''); expect(contentFor(config, defaultMatch, 'body', defaultOptions)).to.equal(''); expect(contentFor(config, defaultMatch, 'blah', defaultOptions)).to.equal(''); }); - describe('"head"', function() { - it('returns `` tag by default', function() { + describe('"head"', function () { + it('returns `` tag by default', function () { let actual = contentFor(config, defaultMatch, 'head', defaultOptions); let expected = ``; expect(actual, '`` tag was included by default').to.contain(expected); }); - it('handles multibyte characters in `` tag', function() { + it('handles multibyte characters in `` tag', function () { let configWithMultibyteChars = { modulePrefix: 'cool-å' }; let actual = contentFor(configWithMultibyteChars, defaultMatch, 'head', defaultOptions); let escapedConfig = encodeURIComponent(JSON.stringify(configWithMultibyteChars)); @@ -120,7 +122,7 @@ describe('ember-app-utils', function() { expect(actual, '`` tag was included with multibyte characters').to.contain(expected); }); - it('omits `` tag if `storeConfigInMeta` is false', function() { + it('omits `` tag if `storeConfigInMeta` is false', function () { let options = Object.assign({}, defaultOptions, { storeConfigInMeta: false }); let output = contentFor(config, defaultMatch, 'head', options); @@ -129,7 +131,7 @@ describe('ember-app-utils', function() { expect(output, '`` tag was not included').not.to.contain(expected); }); - it('returns `` tag if `locationType` is "auto"', function() { + it('returns `` tag if `locationType` is "auto"', function () { config.locationType = 'auto'; config.baseURL = '/'; @@ -140,7 +142,7 @@ describe('ember-app-utils', function() { }); // this is required by testem - it('returns `` tag if `locationType` is "none"', function() { + it('returns `` tag if `locationType` is "none"', function () { config.locationType = 'none'; config.baseURL = '/'; @@ -150,7 +152,7 @@ describe('ember-app-utils', function() { expect(output, '`` tag was included').to.contain(expected); }); - it('omits `` tag if `locationType` is "hash"', function() { + it('omits `` tag if `locationType` is "hash"', function () { config.locationType = 'hash'; config.baseURL = '/foo/bar'; @@ -160,7 +162,7 @@ describe('ember-app-utils', function() { expect(output, '`` tag was not included').to.not.contain(expected); }); - it('omits `` tag if `baseURL` is `undefined`', function() { + it('omits `` tag if `baseURL` is `undefined`', function () { let expected = '` tag gathering snippet by default', function() { + describe('"config-module"', function () { + it('returns `` tag gathering snippet by default', function () { let metaSnippetPath = path.join(__dirname, '..', '..', '..', 'lib', 'broccoli', 'app-config-from-meta.js'); let expected = fs.readFileSync(metaSnippetPath, { encoding: 'utf8' }); @@ -178,7 +180,7 @@ describe('ember-app-utils', function() { expect(output, 'includes `` tag snippet').to.contain(expected); }); - it('returns "raw" config if `storeConfigInMeta` is false', function() { + it('returns "raw" config if `storeConfigInMeta` is false', function () { let options = Object.assign({}, defaultOptions, { storeConfigInMeta: false }); let expected = JSON.stringify(config); let output = contentFor(config, defaultMatch, 'config-module', options); @@ -187,8 +189,8 @@ describe('ember-app-utils', function() { }); }); - describe('"app-boot"', function() { - it('returns application bootstrap snippet by default', function() { + describe('"app-boot"', function () { + it('returns application bootstrap snippet by default', function () { let output = contentFor(config, defaultMatch, 'app-boot', defaultOptions); expect(output, 'includes application bootstrap snippet').to.contain( @@ -196,16 +198,7 @@ describe('ember-app-utils', function() { ); }); - it('returns application bootstrap snippet with MU module name if `isModuleUnification` is true', function() { - let options = Object.assign({}, defaultOptions, { isModuleUnification: true }); - let output = contentFor(config, defaultMatch, 'app-boot', options); - - expect(output, 'includes application bootstrap snippet').to.contain( - 'require("cool-foo/src/main")["default"].create({});' - ); - }); - - it('omits application bootstrap snippet if `autoRun` is false', function() { + it('omits application bootstrap snippet if `autoRun` is false', function () { let options = Object.assign({}, defaultOptions, { autoRun: false }); let output = contentFor(config, defaultMatch, 'app-boot', options); @@ -213,18 +206,24 @@ describe('ember-app-utils', function() { }); }); - describe('"test-body-footer"', function() { - it('returns `` + `` ); }); }); - describe(`for addons`, function() { - it('allows later addons to inspect previous content', function() { + describe(`for addons`, function () { + it('allows later addons to inspect previous content', function () { let calledContent; let addons = [ { @@ -254,7 +253,7 @@ describe('ember-app-utils', function() { expect(output).to.equal('zero\ntwo'); }); - it('calls `contentFor` on addons', function() { + it('calls `contentFor` on addons', function () { let addons = [ { contentFor() { @@ -276,40 +275,40 @@ describe('ember-app-utils', function() { }); }); - describe(`calculateBaseTag`, function() { - ['auto', 'history'].forEach(locationType => { - it(`generates a base tag correctly for location: ${locationType}`, function() { + describe(`calculateBaseTag`, function () { + ['auto', 'history'].forEach((locationType) => { + it(`generates a base tag correctly for location: ${locationType}`, function () { expect(calculateBaseTag('/', locationType), `base tag was generated correctly`).to.equal(''); }); }); - it('returns an empty string if location is "hash"', function() { + it('returns an empty string if location is "hash"', function () { expect(calculateBaseTag('/', 'hash'), `base tag was generated correctly`).to.equal(''); }); - [null, undefined, ''].forEach(url => { - it(`returns an empty string if the url is ${url === '' ? 'empty string' : url}`, function() { + [null, undefined, ''].forEach((url) => { + it(`returns an empty string if the url is ${url === '' ? 'empty string' : url}`, function () { expect(calculateBaseTag(url, 'hash')).to.equal(''); }); }); }); - describe(`convertObjectToString`, function() { - it('transforms config object into a string', function() { + describe(`convertObjectToString`, function () { + it('transforms config object into a string', function () { expect(convertObjectToString({ foobar: 'baz' }), `config was transformed correctly`).to.equal('{"foobar":"baz"}'); }); - it('returns empty object string for "falsy" values', function() { + it('returns empty object string for "falsy" values', function () { let invalidValues = [null, undefined, 0]; - invalidValues.forEach(value => { + invalidValues.forEach((value) => { expect(convertObjectToString(value), `${value} was transformed correctly`).to.equal('{}'); }); }); }); - describe(`normalizeUrl`, function() { - it('transforms input values to valid urls', function() { + describe(`normalizeUrl`, function () { + it('transforms input values to valid urls', function () { expect(normalizeUrl('local/people'), '`local/people` was transformed correctly').to.equal('/local/people/'); expect(normalizeUrl('people'), '`people` was transformed correctly').to.equal('/people/'); @@ -319,10 +318,10 @@ describe('ember-app-utils', function() { expect(normalizeUrl('/'), '`/` is transformed correctly').to.equal('/'); }); - it('returns an empty string for `null`, `undefined` and empty string', function() { + it('returns an empty string for `null`, `undefined` and empty string', function () { let invalidUrls = [null, undefined, '']; - invalidUrls.forEach(url => { + invalidUrls.forEach((url) => { expect(normalizeUrl(url), `${url} was transformed correctly`).to.equal(''); }); }); diff --git a/tests/unit/utilities/find-addon-by-name-test.js b/tests/unit/utilities/find-addon-by-name-test.js index 2d78b5bd18..d862acb26a 100644 --- a/tests/unit/utilities/find-addon-by-name-test.js +++ b/tests/unit/utilities/find-addon-by-name-test.js @@ -4,14 +4,14 @@ const expect = require('chai').expect; const findAddonByName = require('../../../lib/utilities/find-addon-by-name'); const clearCaches = findAddonByName._clearCaches; -describe('findAddonByName', function() { +describe('findAddonByName', function () { let addons, originalConsole, consoleOutput; - beforeEach(function() { + beforeEach(function () { originalConsole = Object.assign({}, console); consoleOutput = []; - console.warn = message => consoleOutput.push(['warn', message]); - console.trace = message => consoleOutput.push(['trace', message]); + console.warn = (message) => consoleOutput.push(['warn', message]); + console.trace = (message) => consoleOutput.push(['trace', message]); addons = [ { @@ -46,54 +46,54 @@ describe('findAddonByName', function() { ]; }); - afterEach(function() { + afterEach(function () { Object.assign(console, originalConsole); clearCaches(); }); - it('should return the foo addon from name', function() { + it('should return the foo addon from name', function () { let addon = findAddonByName(addons, 'foo'); expect(addon.name).to.equal('foo', 'should have found the foo addon'); expect(consoleOutput).to.deep.equal([]); }); - it('should return the foo-bar addon from name when a foo also exists', function() { + it('should return the foo-bar addon from name when a foo also exists', function () { let addon = findAddonByName(addons, 'foo-bar'); expect(addon.name).to.equal('foo-bar', 'should have found the foo-bar addon'); expect(consoleOutput).to.deep.equal([]); }); - it('should return the bar-pkg addon from package name', function() { + it('should return the bar-pkg addon from package name', function () { let addon = findAddonByName(addons, 'bar-pkg'); expect(addon.pkg.name).to.equal('bar-pkg', 'should have found the bar-pkg addon'); expect(consoleOutput).to.deep.equal([]); }); - it("should return null if addon doesn't exist", function() { + it("should return null if addon doesn't exist", function () { let addon = findAddonByName(addons, 'not-an-addon'); expect(addon).to.equal(null, 'not found addon should be null'); expect(consoleOutput).to.deep.equal([]); }); - it('should not return an addon that is a substring of requested name', function() { + it('should not return an addon that is a substring of requested name', function () { let addon = findAddonByName(addons, 'foo-ba'); expect(addon).to.equal(null, 'foo-ba should not be found'); expect(consoleOutput).to.deep.equal([]); }); - it('should not guess addon name from string with slashes', function() { + it('should not guess addon name from string with slashes', function () { let addon = findAddonByName(addons, 'qux/foo'); expect(addon).to.equal(null, 'should not have found the foo addon'); expect(consoleOutput).to.deep.equal([]); }); - it('matches scoped packages when names match exactly', function() { + it('matches scoped packages when names match exactly', function () { let addon = findAddonByName(addons, '@scoped/other'); expect(addon.pkg.name).to.equal('@scoped/other'); expect(consoleOutput).to.deep.equal([]); }); - it('matches unscoped name of scoped package when no exact match is found with logging', function() { + it('matches unscoped name of scoped package when no exact match is found with logging', function () { let addon = findAddonByName(addons, 'other'); expect(addon.pkg.name).to.equal('@scoped/other'); expect(consoleOutput).to.deep.equal([ @@ -104,7 +104,7 @@ describe('findAddonByName', function() { ]); }); - it('matches unscoped name of scoped package repeatedly when no exact match is found with logging', function() { + it('matches unscoped name of scoped package repeatedly when no exact match is found with logging', function () { let addon = findAddonByName(addons, 'other'); expect(addon.pkg.name).to.equal('@scoped/other'); @@ -122,13 +122,13 @@ describe('findAddonByName', function() { ]); }); - it('if exact match is found, it "wins" over unscoped matches', function() { + it('if exact match is found, it "wins" over unscoped matches', function () { let addon = findAddonByName(addons, 'foo-bar'); expect(addon.pkg.name).to.equal('foo-bar'); expect(consoleOutput).to.deep.equal([]); }); - it('if exact match by addon name is found, it "wins" with a warning', function() { + it('if exact match by addon name is found, it "wins" with a warning', function () { let addon = findAddonByName(addons, 'thing'); expect(addon.pkg.name).to.equal('@scope/thing'); expect(consoleOutput).to.deep.equal([ @@ -139,7 +139,7 @@ describe('findAddonByName', function() { ]); }); - it('if exact match by addon name is found, it "wins" repeatedly', function() { + it('if exact match by addon name is found, it "wins" repeatedly', function () { let addon = findAddonByName(addons, 'thing'); expect(addon.pkg.name).to.equal('@scope/thing'); diff --git a/tests/unit/utilities/find-build-file-test.js b/tests/unit/utilities/find-build-file-test.js index 175601e1cc..f1e9c2610f 100644 --- a/tests/unit/utilities/find-build-file-test.js +++ b/tests/unit/utilities/find-build-file-test.js @@ -6,24 +6,24 @@ const path = require('path'); const tmp = require('../../helpers/tmp'); const findBuildFile = require('../../../lib/utilities/find-build-file'); -describe('find-build-file', function() { +describe('find-build-file', function () { let tmpPath = 'tmp/find-build-file-test'; let tmpFilename = 'ember-cli-build.js'; - beforeEach(function() { - return tmp.setup(tmpPath).then(function() { + beforeEach(function () { + return tmp.setup(tmpPath).then(function () { process.chdir(tmpPath); }); }); - afterEach(function() { + afterEach(function () { let tmpFilePath = path.resolve(tmpFilename); delete require.cache[require.resolve(tmpFilePath)]; return tmp.teardown(tmpPath); }); - it('does not throw an error when the file is valid syntax', function() { + it('does not throw an error when the file is valid syntax', function () { fs.writeFileSync(tmpFilename, "module.exports = function() {return {'a': 'A', 'b': 'B'};}", { encoding: 'utf8' }); let result = findBuildFile(tmpFilename); @@ -31,7 +31,7 @@ describe('find-build-file', function() { expect(result()).to.deep.equal({ a: 'A', b: 'B' }); }); - it('throws a SyntaxError if the file contains a syntax mistake', function() { + it('throws a SyntaxError if the file contains a syntax mistake', function () { fs.writeFileSync(tmpFilename, "module.exports = function() {return {'a': 'A' 'b': 'B'};}", { encoding: 'utf8' }); expect(() => { @@ -39,7 +39,7 @@ describe('find-build-file', function() { }).to.throw(SyntaxError, /Could not require '.*':/); }); - it('does not throw an error when the file is mss', function() { + it('does not throw an error when the file is mss', function () { let result = findBuildFile('missing-file.js'); expect(result).to.be.null; }); diff --git a/tests/unit/utilities/format-package-list-test.js b/tests/unit/utilities/format-package-list-test.js index c521a16ec2..54c8e20619 100644 --- a/tests/unit/utilities/format-package-list-test.js +++ b/tests/unit/utilities/format-package-list-test.js @@ -3,28 +3,28 @@ const expect = require('chai').expect; const formatPackageList = require('../../../lib/utilities/format-package-list'); -describe('format-package-list', function() { - it('correctly formats package list with 1 item', function() { +describe('format-package-list', function () { + it('correctly formats package list with 1 item', function () { let result = formatPackageList(['ember-foo']); expect(result).to.equal('ember-foo'); }); - it('correctly formats package list with 2 items', function() { + it('correctly formats package list with 2 items', function () { let result = formatPackageList(['ember-foo', 'bar']); expect(result).to.equal('ember-foo and bar'); }); - it('correctly formats package list with 3 items', function() { + it('correctly formats package list with 3 items', function () { let result = formatPackageList(['ember-foo', 'bar', 'baaaaz']); expect(result).to.equal('ember-foo, bar and baaaaz'); }); - it('correctly formats package list with 4 items', function() { + it('correctly formats package list with 4 items', function () { let result = formatPackageList(['ember-foo', 'bar', 'baaaaz', 'ember-blabla']); expect(result).to.equal('ember-foo, bar and 2 other packages'); }); - it('correctly formats package list with 100 items', function() { + it('correctly formats package list with 100 items', function () { let list = []; for (let i = 1; i <= 100; i++) { list.push(`package-${i}`); @@ -34,17 +34,17 @@ describe('format-package-list', function() { expect(result).to.equal('package-1, package-2 and 98 other packages'); }); - it('returns generic "dependencies" without input', function() { + it('returns generic "dependencies" without input', function () { let result = formatPackageList(); expect(result).to.equal('dependencies'); }); - it('returns generic "dependencies" for invalid input', function() { + it('returns generic "dependencies" for invalid input', function () { let result = formatPackageList('foo'); expect(result).to.equal('dependencies'); }); - it('returns generic "dependencies" for empty input', function() { + it('returns generic "dependencies" for empty input', function () { let result = formatPackageList([]); expect(result).to.equal('dependencies'); }); diff --git a/tests/unit/utilities/get-lang-arg-test.js b/tests/unit/utilities/get-lang-arg-test.js new file mode 100644 index 0000000000..5f6f223d96 --- /dev/null +++ b/tests/unit/utilities/get-lang-arg-test.js @@ -0,0 +1,231 @@ +'use strict'; + +const getLangArg = require('../../../lib/utilities/get-lang-arg'); +const expect = require('chai').expect; +const MockUI = require('console-ui/mock'); + +describe('lib/utilities/get-lang-arg', function () { + // Reference object with snippets of case-dependent messages for comparison + let msgRef = { + severity: 'WARNING', + head: 'An issue with the `--lang` flag returned the following message:', + help: { + edit: 'If this was not your intention, you may edit the `` element', + info: { + head: 'Information about using the `--lang` flag:', + desc: 'The `--lang` flag sets the base human language of an app or test app:', + usage: 'If used, the lang option must specfify a valid language code.', + default: 'For default behavior, remove the flag', + more: 'See `ember help` for more information.', + }, + }, + body: { + valid: '', + validAndProg: 'which is BOTH a valid language code AND an abbreviation for a programming language', + prog: 'Trying to set the app programming language to ', + cliOpt: 'Detected a `--lang` specification starting with command flag `-`', + }, + status: { + willSet: 'The human language of the application will be set to', + willNotSet: 'The human language of the application will NOT be set', + }, + }; + + let ui; + + beforeEach(function () { + ui = new MockUI(); + }); + + describe('Valid language codes', function () { + ['en', 'en-gb', 'en-GB', 'EN', 'EN-gb', 'EN-GB'].forEach((langArg) => { + it(`'${langArg}' is a valid language code`, function () { + expect(() => { + getLangArg(langArg, ui); + }).not.to.throw(); + expect(getLangArg(langArg, ui)).to.equal(langArg); + expect(ui.output).to.equal(msgRef.body.valid); + }); + }); + }); + + describe('Edge Cases: valid language codes + programming languages', function () { + [ + 'ts', // Tsonga + 'TS', // Tsonga (case insensitivity check) + 'xml', // Malaysian Sign Language + 'xht', // Hattic + 'css', // Costanoan + ].forEach((langArg) => { + it(`'${langArg}' is a valid language code and programming language`, function () { + expect(() => { + getLangArg(langArg, ui); + }).not.to.throw(); + expect(getLangArg(langArg, ui)).to.equal(langArg); + expect(ui.output).to.contain(msgRef.severity); + expect(ui.output).to.contain(msgRef.head); + expect(ui.output).to.contain(msgRef.help.edit); + expect(ui.output).to.contain(msgRef.help.info.head); + expect(ui.output).to.contain(msgRef.help.info.desc); + expect(ui.output).to.contain(msgRef.help.info.usage); + expect(ui.output).to.contain(msgRef.help.info.default); + expect(ui.output).to.contain(msgRef.help.info.more); + expect(ui.output).to.contain(msgRef.status.willSet); + expect(ui.output).to.contain(msgRef.body.validAndProg); + expect(ui.output).to.contain(msgRef.body.prog); + expect(ui.output).to.not.contain(msgRef.body.cliOpt); + }); + }); + }); + + describe('Invalid lang Flags: Misc.', function () { + ['', '..-..', '12-34', ' en', 'en ', 'en-uk', 'en-UK', 'EN-uk', 'EN-UK', 'en-cockney'].forEach((langArg) => { + it(`'${langArg}' is an invalid language argument; not related misuse cases`, function () { + expect(() => { + getLangArg(langArg, ui); + }).not.to.throw(); + expect(getLangArg(langArg, ui)).to.equal(undefined); + expect(ui.output).to.contain(msgRef.severity); + expect(ui.output).to.contain(msgRef.head); + expect(ui.output).to.contain(msgRef.help.edit); + expect(ui.output).to.contain(msgRef.help.info.head); + expect(ui.output).to.contain(msgRef.help.info.desc); + expect(ui.output).to.contain(msgRef.help.info.usage); + expect(ui.output).to.contain(msgRef.help.info.default); + expect(ui.output).to.contain(msgRef.help.info.more); + expect(ui.output).to.contain(msgRef.status.willNotSet); + expect(ui.output).to.not.contain(msgRef.body.validAndProg); + expect(ui.output).to.not.contain(msgRef.body.prog); + expect(ui.output).to.not.contain(msgRef.body.cliOpt); + }); + }); + }); + + describe('Invalid Language Flags, Misuse case: Programming Languages', function () { + [ + 'javascript', + '.js', + 'js', + 'emcascript2015', + 'emcascript6', + 'es6', + 'emcascript2016', + 'emcascript7', + 'es7', + 'emcascript2017', + 'emcascript8', + 'es8', + 'emcascript2018', + 'emcascript9', + 'es9', + 'emcascript2019', + 'emcascript10', + 'es10', + 'typescript', + '.ts', + 'node.js', + 'node', + 'handlebars', + '.hbs', + 'hbs', + 'glimmer', + 'glimmer.js', + 'glimmer-vm', + 'markdown', + 'markup', + 'html5', + 'html4', + '.md', + '.html', + '.htm', + '.xhtml', + '.xml', + '.xht', + 'md', + 'html', + 'htm', + 'xhtml', + '.sass', + '.scss', + '.css', + 'sass', + 'scss', + + // + case-insensitivity + 'JavaScript', + 'JAVASCRIPT', + 'JS', + '.JS', + 'EMCAScript2015', + 'EMCAScript6', + 'ES6', + 'TypeScript', + 'TYPESCRIPT', + '.TS', + ].forEach((langArg) => { + it(`'${langArg}' is an invalid lang argument; possibly an attempt to set app programming language`, function () { + expect(() => { + getLangArg(langArg, ui); + }).not.to.throw(); + expect(getLangArg(langArg, ui)).to.equal(undefined); + expect(ui.output).to.contain(msgRef.severity); + expect(ui.output).to.contain(msgRef.head); + expect(ui.output).to.contain(msgRef.help.edit); + expect(ui.output).to.contain(msgRef.help.info.head); + expect(ui.output).to.contain(msgRef.help.info.desc); + expect(ui.output).to.contain(msgRef.help.info.usage); + expect(ui.output).to.contain(msgRef.help.info.default); + expect(ui.output).to.contain(msgRef.help.info.more); + expect(ui.output).to.contain(msgRef.status.willNotSet); + expect(ui.output).to.not.contain(msgRef.body.validAndProg); + expect(ui.output).to.contain(msgRef.body.prog); + expect(ui.output).to.not.contain(msgRef.body.cliOpt); + }); + }); + }); + + describe('Invalid Language Flags, Misuse case: ember-cli `new` and `init` options / aliases', function () { + [ + '--disable-analytics', + '--watcher=node', + '--dry-run', + '-d', + '--verbose', + '-v', + '--blueprint', + '-b', + '--skip-npm', + '-sn', + '--skip-bower', + '-sb', + '--welcome', + '--no-welcome', + '--yarn', + '--name', + '--skip-git', + '-sg', + '--directory', + '-dir', + ].forEach((langArg) => { + it(`'${langArg}' is an invalid language argument; possibly an absorbed ember-cli command option`, function () { + expect(() => { + getLangArg(langArg, ui); + }).not.to.throw(); + expect(getLangArg(langArg, ui)).to.equal(undefined); + expect(getLangArg(langArg, ui)).to.equal(undefined); + expect(ui.output).to.contain(msgRef.severity); + expect(ui.output).to.contain(msgRef.head); + expect(ui.output).to.contain(msgRef.help.edit); + expect(ui.output).to.contain(msgRef.help.info.head); + expect(ui.output).to.contain(msgRef.help.info.desc); + expect(ui.output).to.contain(msgRef.help.info.usage); + expect(ui.output).to.contain(msgRef.help.info.default); + expect(ui.output).to.contain(msgRef.help.info.more); + expect(ui.output).to.contain(msgRef.status.willNotSet); + expect(ui.output).to.not.contain(msgRef.body.validAndProg); + expect(ui.output).to.not.contain(msgRef.body.prog); + expect(ui.output).to.contain(msgRef.body.cliOpt); + }); + }); + }); +}); diff --git a/tests/unit/utilities/get-package-base-name-test.js b/tests/unit/utilities/get-package-base-name-test.js index 52ee9e0211..f6820f865e 100644 --- a/tests/unit/utilities/get-package-base-name-test.js +++ b/tests/unit/utilities/get-package-base-name-test.js @@ -3,16 +3,16 @@ const expect = require('chai').expect; const getPackageBaseName = require('../../../lib/utilities/get-package-base-name'); -describe('getPackageBaseName', function() { - it('should return the full package name if it is unscoped', function() { +describe('getPackageBaseName', function () { + it('should return the full package name if it is unscoped', function () { expect(getPackageBaseName('my-addon')).to.equal('my-addon'); }); - it('should return the full name when scoped', function() { + it('should return the full name when scoped', function () { expect(getPackageBaseName('@scope/my-addon')).to.equal('@scope/my-addon'); }); - it('should strip away version numbers', function() { + it('should strip away version numbers', function () { expect(getPackageBaseName('my-addon@~1.2.0')).to.equal('my-addon'); }); }); diff --git a/tests/unit/utilities/git-repo-test.js b/tests/unit/utilities/git-repo-test.js index 12df4c71d6..47d71a1f7d 100644 --- a/tests/unit/utilities/git-repo-test.js +++ b/tests/unit/utilities/git-repo-test.js @@ -3,11 +3,18 @@ const isGitRepo = require('is-git-url'); const expect = require('chai').expect; -describe('is-git-url', function() { - it('recognizes git-style urls in various formats', function() { +describe('is-git-url', function () { + it('recognizes git-style urls in various formats', function () { + // without ref expect(isGitRepo('https://github.com/trek/app-blueprint-test.git')).to.be.ok; expect(isGitRepo('git@github.com:trek/app-blueprint-test.git')).to.be.ok; expect(isGitRepo('git+ssh://user@server/project.git')).to.be.ok; expect(isGitRepo('git+https://user@server/project.git')).to.be.ok; + + // with ref + expect(isGitRepo('https://github.com/trek/app-blueprint-test.git#named-ref')).to.be.ok; + expect(isGitRepo('git@github.com:trek/app-blueprint-test.git#named-ref')).to.be.ok; + expect(isGitRepo('git+ssh://user@server/project.git#named-ref')).to.be.ok; + expect(isGitRepo('git+https://user@server/project.git#named-ref')).to.be.ok; }); }); diff --git a/tests/unit/utilities/heimdall-progres-test.js b/tests/unit/utilities/heimdall-progres-test.js index 9c29a33f72..468026b243 100644 --- a/tests/unit/utilities/heimdall-progres-test.js +++ b/tests/unit/utilities/heimdall-progres-test.js @@ -3,8 +3,8 @@ const progress = require('../../../lib/utilities/heimdall-progress'); const { expect } = require('chai'); const chalk = require('chalk'); -describe('heimdall-progress', function() { - it('supports the root node', function() { +describe('heimdall-progress', function () { + it('supports the root node', function () { // fake the heimdall graph (public heimdall API); const heimdall = { current: { @@ -17,7 +17,7 @@ describe('heimdall-progress', function() { expect(progress(heimdall)).to.eql(''); }); - it('complex example', function() { + it('complex example', function () { // fake the heimdall graph (public heimdall API); const heimdall = { current: { @@ -47,8 +47,8 @@ describe('heimdall-progress', function() { expect(progress(heimdall)).to.eql('broccoli > babel > applyPatches'); }); - describe('.format', function() { - it('works', function() { + describe('.format', function () { + it('works', function () { expect(progress.format('test content')).to.eql(`${chalk.green('building... ')}[test content]`); }); }); diff --git a/tests/unit/utilities/insert-into-file-test.js b/tests/unit/utilities/insert-into-file-test.js index 49e3d4df90..d892a0f73c 100644 --- a/tests/unit/utilities/insert-into-file-test.js +++ b/tests/unit/utilities/insert-into-file-test.js @@ -8,22 +8,22 @@ const insertIntoFile = require('../../../lib/utilities/insert-into-file'); const expect = require('chai').expect; -describe('insertIntoFile()', function() { +describe('insertIntoFile()', function () { let tempDir, filePath; - beforeEach(function() { + beforeEach(function () { tempDir = temp.mkdirSync('insert-into-file-test'); filePath = path.join(tempDir, 'foo-bar-baz.txt'); }); - afterEach(function() { + afterEach(function () { fs.removeSync(tempDir); }); - it('will create the file if not already existing', function() { + it('will create the file if not already existing', function () { let toInsert = 'blahzorz blammo'; - return insertIntoFile(filePath, toInsert).then(function(result) { + return insertIntoFile(filePath, toInsert).then(function (result) { let contents = fs.readFileSync(filePath, { encoding: 'utf8' }); expect(contents).to.contain(toInsert); @@ -33,10 +33,10 @@ describe('insertIntoFile()', function() { }); }); - it('will not create the file if not already existing and creation disabled', function() { + it('will not create the file if not already existing and creation disabled', function () { let toInsert = 'blahzorz blammo'; - return insertIntoFile(filePath, toInsert, { create: false }).then(function(result) { + return insertIntoFile(filePath, toInsert, { create: false }).then(function (result) { expect(fs.existsSync(filePath)).to.equal(false, 'file should not exist'); expect(result.originalContents).to.equal('', 'returned object should contain original contents'); expect(result.inserted).to.equal(false, 'inserted should indicate that the file was not modified'); @@ -44,13 +44,13 @@ describe('insertIntoFile()', function() { }); }); - it('will insert into the file if it already exists', function() { + it('will insert into the file if it already exists', function () { let toInsert = 'blahzorz blammo'; let originalContent = 'some original content\n'; fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' }); - return insertIntoFile(filePath, toInsert).then(function(result) { + return insertIntoFile(filePath, toInsert).then(function (result) { let contents = fs.readFileSync(filePath, { encoding: 'utf8' }); expect(contents).to.equal(originalContent + toInsert, 'inserted contents should be appended to original'); @@ -59,12 +59,12 @@ describe('insertIntoFile()', function() { }); }); - it('will not insert into the file if it already contains the content', function() { + it('will not insert into the file if it already contains the content', function () { let toInsert = 'blahzorz blammo'; fs.writeFileSync(filePath, toInsert, { encoding: 'utf8' }); - return insertIntoFile(filePath, toInsert).then(function(result) { + return insertIntoFile(filePath, toInsert).then(function (result) { let contents = fs.readFileSync(filePath, { encoding: 'utf8' }); expect(contents).to.equal(toInsert, 'contents should be unchanged'); @@ -72,12 +72,12 @@ describe('insertIntoFile()', function() { }); }); - it('will insert into the file if it already contains the content if force option is passed', function() { + it('will insert into the file if it already contains the content if force option is passed', function () { let toInsert = 'blahzorz blammo'; fs.writeFileSync(filePath, toInsert, { encoding: 'utf8' }); - return insertIntoFile(filePath, toInsert, { force: true }).then(function(result) { + return insertIntoFile(filePath, toInsert, { force: true }).then(function (result) { let contents = fs.readFileSync(filePath, { encoding: 'utf8' }); expect(contents).to.equal(toInsert + toInsert, 'contents should be unchanged'); @@ -85,7 +85,7 @@ describe('insertIntoFile()', function() { }); }); - it('will insert into the file after a specified string if options.after is specified', function() { + it('will insert into the file after a specified string if options.after is specified', function () { let toInsert = 'blahzorz blammo'; let line1 = 'line1 is here'; let line2 = 'line2 here'; @@ -94,7 +94,7 @@ describe('insertIntoFile()', function() { fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' }); - return insertIntoFile(filePath, toInsert, { after: line2 + EOL }).then(function(result) { + return insertIntoFile(filePath, toInsert, { after: line2 + EOL }).then(function (result) { let contents = fs.readFileSync(filePath, { encoding: 'utf8' }); expect(contents).to.equal( @@ -106,7 +106,7 @@ describe('insertIntoFile()', function() { }); }); - it('will insert into the file after the first instance of options.after only', function() { + it('will insert into the file after the first instance of options.after only', function () { let toInsert = 'blahzorz blammo'; let line1 = 'line1 is here'; let line2 = 'line2 here'; @@ -115,7 +115,7 @@ describe('insertIntoFile()', function() { fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' }); - return insertIntoFile(filePath, toInsert, { after: line2 + EOL }).then(function(result) { + return insertIntoFile(filePath, toInsert, { after: line2 + EOL }).then(function (result) { let contents = fs.readFileSync(filePath, { encoding: 'utf8' }); expect(contents).to.equal( @@ -127,7 +127,7 @@ describe('insertIntoFile()', function() { }); }); - it('will insert into the file before a specified string if options.before is specified', function() { + it('will insert into the file before a specified string if options.before is specified', function () { let toInsert = 'blahzorz blammo'; let line1 = 'line1 is here'; let line2 = 'line2 here'; @@ -136,7 +136,7 @@ describe('insertIntoFile()', function() { fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' }); - return insertIntoFile(filePath, toInsert, { before: line2 + EOL }).then(function(result) { + return insertIntoFile(filePath, toInsert, { before: line2 + EOL }).then(function (result) { let contents = fs.readFileSync(filePath, { encoding: 'utf8' }); expect(contents).to.equal( @@ -148,7 +148,7 @@ describe('insertIntoFile()', function() { }); }); - it('will insert into the file before the first instance of options.before only', function() { + it('will insert into the file before the first instance of options.before only', function () { let toInsert = 'blahzorz blammo'; let line1 = 'line1 is here'; let line2 = 'line2 here'; @@ -157,7 +157,7 @@ describe('insertIntoFile()', function() { fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' }); - return insertIntoFile(filePath, toInsert, { before: line2 + EOL }).then(function(result) { + return insertIntoFile(filePath, toInsert, { before: line2 + EOL }).then(function (result) { let contents = fs.readFileSync(filePath, { encoding: 'utf8' }); expect(contents).to.equal( @@ -169,13 +169,13 @@ describe('insertIntoFile()', function() { }); }); - it('it will make no change if options.after is not found in the original', function() { + it('it will make no change if options.after is not found in the original', function () { let toInsert = 'blahzorz blammo'; let originalContent = 'the original content'; fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' }); - return insertIntoFile(filePath, toInsert, { after: `not found${EOL}` }).then(function(result) { + return insertIntoFile(filePath, toInsert, { after: `not found${EOL}` }).then(function (result) { let contents = fs.readFileSync(filePath, { encoding: 'utf8' }); expect(contents).to.equal(originalContent, 'original content is unchanged'); @@ -184,13 +184,13 @@ describe('insertIntoFile()', function() { }); }); - it('it will make no change if options.before is not found in the original', function() { + it('it will make no change if options.before is not found in the original', function () { let toInsert = 'blahzorz blammo'; let originalContent = 'the original content'; fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' }); - return insertIntoFile(filePath, toInsert, { before: `not found${EOL}` }).then(function(result) { + return insertIntoFile(filePath, toInsert, { before: `not found${EOL}` }).then(function (result) { let contents = fs.readFileSync(filePath, { encoding: 'utf8' }); expect(contents).to.equal(originalContent, 'original content is unchanged'); @@ -199,8 +199,8 @@ describe('insertIntoFile()', function() { }); }); - describe('regex', function() { - it('options.after supports regex', function() { + describe('regex', function () { + it('options.after supports regex', function () { let toInsert = 'blahzorz blammo'; let line1 = 'line1 is here'; let line2 = 'line2 here'; @@ -209,7 +209,7 @@ describe('insertIntoFile()', function() { fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' }); - return insertIntoFile(filePath, toInsert, { after: /line2 here(\r?\n)/ }).then(function() { + return insertIntoFile(filePath, toInsert, { after: /line2 here(\r?\n)/ }).then(function () { let contents = fs.readFileSync(filePath, { encoding: 'utf8' }); expect(contents).to.equal( @@ -219,7 +219,7 @@ describe('insertIntoFile()', function() { }); }); - it('options.before supports regex', function() { + it('options.before supports regex', function () { let toInsert = 'blahzorz blammo'; let line1 = 'line1 is here'; let line2 = 'line2 here'; @@ -228,7 +228,7 @@ describe('insertIntoFile()', function() { fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' }); - return insertIntoFile(filePath, toInsert, { before: /line2 here(\r?\n)/ }).then(function() { + return insertIntoFile(filePath, toInsert, { before: /line2 here(\r?\n)/ }).then(function () { let contents = fs.readFileSync(filePath, { encoding: 'utf8' }); expect(contents).to.equal( @@ -238,7 +238,7 @@ describe('insertIntoFile()', function() { }); }); - it("options.after doesn't treat strings as regex", function() { + it("options.after doesn't treat strings as regex", function () { let toInsert = 'blahzorz blammo'; fs.writeFileSync(filePath, '', { encoding: 'utf8' }); @@ -246,7 +246,7 @@ describe('insertIntoFile()', function() { expect(() => insertIntoFile(filePath, toInsert, { after: '"predef": [\n' })).to.not.throw(); }); - it("options.before doesn't treat strings as regex", function() { + it("options.before doesn't treat strings as regex", function () { let toInsert = 'blahzorz blammo'; fs.writeFileSync(filePath, '', { encoding: 'utf8' }); @@ -255,10 +255,10 @@ describe('insertIntoFile()', function() { }); }); - it('will return the file path', function() { + it('will return the file path', function () { let toInsert = 'blahzorz blammo'; - return insertIntoFile(filePath, toInsert).then(function(result) { + return insertIntoFile(filePath, toInsert).then(function (result) { expect(result.path).to.equal(filePath, 'path should always match'); }); }); diff --git a/tests/unit/utilities/is-engine-test.js b/tests/unit/utilities/is-engine-test.js new file mode 100644 index 0000000000..b29e26aa75 --- /dev/null +++ b/tests/unit/utilities/is-engine-test.js @@ -0,0 +1,19 @@ +'use strict'; + +const expect = require('chai').expect; +const isEngine = require(`../../../lib/utilities/is-engine`); + +describe('Unit | is-engine', function () { + it('it identifies an engine correctly', function () { + expect(isEngine([null, 'ember-engine', 'foo', 'bar'])).to.equal(true); + expect(isEngine(['ember-engine'])).to.equal(true); + }); + + it("it returns false if it's not an engine", function () { + expect(isEngine({ 'ember-engine': true })).to.equal(false); + expect(isEngine('ember-engine')).to.equal(false); + expect(isEngine(['foo', 'bar'])).to.equal(false); + expect(isEngine({})).to.equal(false); + expect(isEngine(undefined)).to.equal(false); + }); +}); diff --git a/tests/unit/utilities/is-lazy-engine-test.js b/tests/unit/utilities/is-lazy-engine-test.js new file mode 100644 index 0000000000..3a14c1bdc4 --- /dev/null +++ b/tests/unit/utilities/is-lazy-engine-test.js @@ -0,0 +1,18 @@ +'use strict'; + +const expect = require('chai').expect; +const isLazyEngine = require(`../../../lib/utilities/is-lazy-engine`); + +describe('Unit | is-lazy-engine', function () { + it('it identifies a lazy engine correctly', function () { + expect(isLazyEngine({ options: { lazyLoading: { enabled: true } } })).to.equal(true); + }); + + it("it returns false if it's not a lazy engine", function () { + expect(isLazyEngine({ options: { lazyLoading: { enabled: false } } })).to.equal(false); + expect(isLazyEngine({ options: { lazyLoading: {} } })).to.equal(false); + expect(isLazyEngine({ options: {} })).equal(false); + expect(isLazyEngine({})).to.equal(false); + expect(isLazyEngine()).to.equal(false); + }); +}); diff --git a/tests/unit/utilities/is-live-reload-request-test.js b/tests/unit/utilities/is-live-reload-request-test.js index 16e1c8659a..f66f78b042 100644 --- a/tests/unit/utilities/is-live-reload-request-test.js +++ b/tests/unit/utilities/is-live-reload-request-test.js @@ -3,29 +3,29 @@ const expect = require('chai').expect; const isLiveReloadRequest = require('../../../lib/utilities/is-live-reload-request'); -describe('isLiveReloadRequest()', function() { - it('/livereload', function() { +describe('isLiveReloadRequest()', function () { + it('/livereload', function () { expect(isLiveReloadRequest('/livereload', '/')).to.be.true; }); - it('path/livereload', function() { + it('path/livereload', function () { expect(isLiveReloadRequest('path/livereload', 'path/')).to.be.true; }); - it('path/path/livereload', function() { + it('path/path/livereload', function () { expect(isLiveReloadRequest('path/path/livereload', 'path/path/')).to.be.true; }); - it('livereload', function() { + it('livereload', function () { expect(isLiveReloadRequest('livereload', '/')).to.be.false; }); - it('livereload/path', function() { + it('livereload/path', function () { expect(isLiveReloadRequest('livereload/path', '/')).to.be.false; }); - it('/livereload.js', function() { + it('/livereload.js', function () { expect(isLiveReloadRequest('/livereload.js', '/')).to.be.false; }); - it('path/livereload.js', function() { + it('path/livereload.js', function () { expect(isLiveReloadRequest('path/livereload.js', 'path/')).to.be.false; }); - it('path/livereload/path', function() { + it('path/livereload/path', function () { expect(isLiveReloadRequest('path/livereload/path', 'path/')).to.be.false; }); }); diff --git a/tests/unit/utilities/lint-addons-by-type-test.js b/tests/unit/utilities/lint-addons-by-type-test.js index 8c39172caf..60d7f1f493 100644 --- a/tests/unit/utilities/lint-addons-by-type-test.js +++ b/tests/unit/utilities/lint-addons-by-type-test.js @@ -4,10 +4,10 @@ const td = require('testdouble'); const expect = require('chai').expect; const lintAddonsByType = require('../../../lib/utilities/lint-addons-by-type'); -describe('lintAddonsByType', function() { +describe('lintAddonsByType', function () { let addons; - beforeEach(function() { + beforeEach(function () { addons = [ { name: 'foo', @@ -17,13 +17,13 @@ describe('lintAddonsByType', function() { ]; }); - it('calls lintTree on the addon', function() { + it('calls lintTree on the addon', function () { lintAddonsByType(addons, 'app', { foo: 'bar' }); td.verify(addons[0].lintTree('app', { foo: 'bar' })); }); - it('filters out tree if `lintTree` returns false-y', function() { + it('filters out tree if `lintTree` returns false-y', function () { td.when(addons[0].lintTree).thenReturn({}); expect(lintAddonsByType(addons, 'app')).to.deep.equal([]); diff --git a/tests/unit/utilities/load-config-test.js b/tests/unit/utilities/load-config-test.js index 8808414cb8..bb00b91fcd 100644 --- a/tests/unit/utilities/load-config-test.js +++ b/tests/unit/utilities/load-config-test.js @@ -1,6 +1,5 @@ 'use strict'; -const co = require('co'); const expect = require('chai').expect; const path = require('path'); const loadConfig = require('../../../lib/utilities/load-config'); @@ -9,57 +8,53 @@ const broccoliTestHelper = require('broccoli-test-helper'); const buildOutput = broccoliTestHelper.buildOutput; const createTempDir = broccoliTestHelper.createTempDir; -describe('load-config', function() { +describe('load-config', function () { let fixtureDirectoryPath, fixtureDirectory; - before( - co.wrap(function*() { - fixtureDirectory = yield createTempDir(); - fixtureDirectoryPath = fixtureDirectory.path(); - - fixtureDirectory.write({ - '.something': 'hello: world\n', - '.travis.yaml': 'hello: world', - '.travis.yml': 'hello: world', - 'package.json': '{ "hello": "world" }', - child: { - 'test.json': '{ "hello": "world" }', - }, - }); - - yield buildOutput(fixtureDirectoryPath); - }) - ); + before(async function () { + fixtureDirectory = await createTempDir(); + fixtureDirectoryPath = fixtureDirectory.path(); + + fixtureDirectory.write({ + '.something': 'hello: world\n', + '.travis.yaml': 'hello: world', + '.travis.yml': 'hello: world', + 'package.json': '{ "hello": "world" }', + child: { + 'test.json': '{ "hello": "world" }', + }, + }); + + await buildOutput(fixtureDirectoryPath); + }); - after( - co.wrap(function*() { - yield fixtureDirectory.dispose(); - }) - ); + after(async function () { + await fixtureDirectory.dispose(); + }); - it('loads and parses a yml file', function() { + it('loads and parses a yml file', function () { expect(loadConfig('.travis.yml', fixtureDirectoryPath).hello).to.equal('world'); }); - it('loads and parses a yaml file', function() { + it('loads and parses a yaml file', function () { expect(loadConfig('.travis.yml', fixtureDirectoryPath).hello).to.equal('world'); }); - it('loads and parses a json file', function() { + it('loads and parses a json file', function () { expect(loadConfig('package.json', fixtureDirectoryPath).hello).to.equal('world'); }); - it('loads a vanilla file', function() { + it('loads a vanilla file', function () { expect(loadConfig('.something', fixtureDirectoryPath)).to.equal('hello: world\n'); }); - it('works across directories', function() { + it('works across directories', function () { expect(loadConfig('.something', path.join(fixtureDirectoryPath, 'child'))).to.equal('hello: world\n'); expect(loadConfig('test.json', path.join(fixtureDirectoryPath, 'child')).hello).to.equal('world'); }); }); -describe('publishes the appropriate config files', function() { +describe('publishes the appropriate config files', function () { let EmberCLIDir = path.resolve(__dirname, '../../../'); let npmignore = loadConfig('.npmignore', EmberCLIDir); - it('has the config files', function() { - expect(npmignore).to.include('!/.travis.yml'); + it('has the config files', function () { + expect(npmignore).to.include('!/.github/workflows/ci.yml'); }); }); diff --git a/tests/unit/utilities/markdown-color-test.js b/tests/unit/utilities/markdown-color-test.js index 4295829984..b224eac3ce 100644 --- a/tests/unit/utilities/markdown-color-test.js +++ b/tests/unit/utilities/markdown-color-test.js @@ -5,20 +5,20 @@ const expect = require('chai').expect; const path = require('path'); const chalk = require('chalk'); -describe('MarkdownColor', function() { +describe('MarkdownColor', function () { let mc; - before(function() { + before(function () { if (!chalk.supportsColor) { this.skip(); } }); - beforeEach(function() { + beforeEach(function () { mc = new MarkdownColor(); }); - it('parses default markdown', function() { + it('parses default markdown', function () { // console.log(mc.render('# foo\n__bold__ **words**\n* un\n* ordered\n* list')) expect(mc.render('# foo\n__bold__ words\n* un\n* ordered\n* list')).to.equal( '\u001b[35m\u001b[4m\u001b[1m\nfoo\u001b[22m\u001b[24m\u001b[39m\n\u001b[0m' + @@ -27,7 +27,7 @@ describe('MarkdownColor', function() { ); }); - it('parses color tokens', function() { + it('parses color tokens', function () { expect(mc.render('red')).to.equal('\u001b[0m\u001b[31mred\u001b[39m\u001b[0m\n\n'); expect(mc.render('green')).to.equal('\u001b[0m\u001b[32mgreen\u001b[39m\u001b[0m\n\n'); expect(mc.render('blue')).to.equal('\u001b[0m\u001b[34mblue\u001b[39m\u001b[0m\n\n'); @@ -49,13 +49,13 @@ describe('MarkdownColor', function() { expect(mc.render('bgBlack')).to.equal('\u001b[0m\u001b[40mbgBlack\u001b[49m\u001b[0m\n\n'); }); - it('parses custom tokens', function() { + it('parses custom tokens', function () { expect(mc.render('--option')).to.equal('\u001b[0m\u001b[36m--option\u001b[39m\u001b[0m\n\n'); expect(mc.render('(Default: value)')).to.equal('\u001b[0m\u001b[36m(Default: value)\u001b[39m\u001b[0m\n\n'); expect(mc.render('(Required)')).to.equal('\u001b[0m\u001b[36m(Required)\u001b[39m\u001b[0m\n\n'); }); - it('accepts tokens on instantiation', function() { + it('accepts tokens on instantiation', function () { let mctemp = new MarkdownColor({ tokens: { foo: { @@ -70,17 +70,17 @@ describe('MarkdownColor', function() { ); }); - it('parses markdown files', function() { + it('parses markdown files', function () { // console.log(mc.renderFile(path.join(__dirname,'../../../tests/fixtures/markdown/foo.md'))) expect(mc.renderFile(path.join(__dirname, '../../../tests/fixtures/markdown/foo.md'))).to.equal( - '\u001b[0m\u001b[36mtacos are \u001b[33mdelicious\u001b[36m \u001b[34mand I\u001b[39m enjoy eating them\u001b[39m\u001b[0m\n\n' + '\u001b[0m\u001b[36mtacos are \u001b[33mdelicious\u001b[39m\u001b[36m \u001b[34mand I\u001b[39m enjoy eating them\u001b[39m\u001b[0m\n\n' ); }); - it('allows tokens inside other token bounds', function() { + it('allows tokens inside other token bounds', function () { // console.log(mc.render('tacos are delicious and I enjoy eating them')) expect(mc.render('tacos are delicious and I enjoy eating them')).to.equal( - '\u001b[0m\u001b[36mtacos are \u001b[33mdelicious\u001b[36m and I enjoy eating' + ' them\u001b[39m\u001b[0m\n\n' + '\u001b[0m\u001b[36mtacos are \u001b[33mdelicious\u001b[39m\u001b[36m and I enjoy eating them\u001b[39m\u001b[0m\n\n' ); }); }); diff --git a/tests/unit/utilities/merge-blueprint-options-test-disabled.js b/tests/unit/utilities/merge-blueprint-options-test-disabled.js index 48505d690e..3375f9a022 100644 --- a/tests/unit/utilities/merge-blueprint-options-test-disabled.js +++ b/tests/unit/utilities/merge-blueprint-options-test-disabled.js @@ -8,7 +8,7 @@ const Command = require('../../../lib/models/command'); const mergeBlueprintOptions = require('../../../lib/utilities/merge-blueprint-options'); const td = require('testdouble'); -describe('merge-blueprint-options', function() { +describe('merge-blueprint-options', function () { let TestCommand = Command.extend({ name: 'test-command', description: 'Runs a test command.', @@ -20,7 +20,7 @@ describe('merge-blueprint-options', function() { beforeRun: mergeBlueprintOptions, }); - afterEach(function() { + afterEach(function () { td.reset(); }); @@ -30,7 +30,7 @@ describe('merge-blueprint-options', function() { }); } - it("it works as a command's beforeRun()", function() { + it("it works as a command's beforeRun()", function () { let command, availableOptions; td.replace(Blueprint, 'lookup', td.function()); diff --git a/tests/unit/utilities/open-editor-test.js b/tests/unit/utilities/open-editor-test.js index e8a0e005b2..19f5f1a344 100644 --- a/tests/unit/utilities/open-editor-test.js +++ b/tests/unit/utilities/open-editor-test.js @@ -5,43 +5,73 @@ const openEditor = require('../../../lib/utilities/open-editor'); const expect = require('chai').expect; const td = require('testdouble'); -describe('open-editor', function() { - beforeEach(function() { +describe('open-editor', function () { + beforeEach(function () { td.replace(openEditor, '_env'); td.replace(openEditor, '_spawn'); }); - afterEach(function() { + afterEach(function () { td.reset(); }); - it('throws if EDITOR is not set', function() { + it('throws if EDITOR is not set', async function () { td.when(openEditor._env()).thenReturn({}); - expect(() => { - openEditor('test'); - }).to.throw('EDITOR environment variable is not set'); + try { + await openEditor('test'); + expect.fail('expected rejection'); + } catch (e) { + expect(e.message).to.eql('EDITOR environment variable is not set'); + } }); - it('spawns EDITOR with passed file', function() { + it('spawns EDITOR with passed file', async function () { td.when(openEditor._env()).thenReturn({ EDITOR: 'vi' }); - openEditor('test'); + td.when(openEditor._spawn(td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenReturn({ + on(event, cb) { + cb(0); + }, + }); + + await openEditor('test'); td.verify(openEditor._spawn('vi', ['test'], { stdio: 'inherit' })); }); - it('throws if no file option is provided', function() { + it('spawns EDITOR with passed file (rejection scenario)', async function () { td.when(openEditor._env()).thenReturn({ EDITOR: 'vi' }); - expect(() => { - openEditor(); - }).to.throw('No `file` option provided'); + td.when(openEditor._spawn(td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenReturn({ + on(event, cb) { + cb(-1); + }, + }); + + try { + await openEditor('test'); + expect.fail('expected rejection'); + } catch (e) { + expect(e.message).to.include(`exited with a non zero error status code: '-1'`); + } + + td.verify(openEditor._spawn('vi', ['test'], { stdio: 'inherit' })); + }); + it('throws if no file option is provided', async function () { + td.when(openEditor._env()).thenReturn({ EDITOR: 'vi' }); + + try { + await openEditor(); + expect.fail('expected rejection'); + } catch (e) { + expect(e.message).to.eql('No `file` option provided'); + } }); - describe('.canEdit()', function() { - it('returns false if EDITOR is not set', function() { + describe('.canEdit()', function () { + it('returns false if EDITOR is not set', function () { td.when(openEditor._env()).thenReturn({}); expect(openEditor.canEdit()).to.be.false; }); - it('returns true if EDITOR is set', function() { + it('returns true if EDITOR is set', function () { td.when(openEditor._env()).thenReturn({ EDITOR: 'vi' }); expect(openEditor.canEdit()).to.be.true; }); diff --git a/tests/unit/utilities/package-cache-test.js b/tests/unit/utilities/package-cache-test.js index 8b595a8699..8bd86c6f78 100644 --- a/tests/unit/utilities/package-cache-test.js +++ b/tests/unit/utilities/package-cache-test.js @@ -13,14 +13,14 @@ let expect = chai.expect; let file = chai.file; let dir = chai.dir; -describe('PackageCache', function() { +describe('PackageCache', function () { let testPackageCache; let bower = td.function('bower'); let npm = td.function('npm'); let yarn = td.function('yarn'); - beforeEach(function() { + beforeEach(function () { testPackageCache = new PackageCache(); testPackageCache._conf = new Configstore('package-cache-test'); @@ -33,13 +33,13 @@ describe('PackageCache', function() { }); }); - afterEach(function() { + afterEach(function () { td.reset(); testPackageCache.__resetForTesting(); fs.unlinkSync(testPackageCache._conf.path); }); - it('defaults rootPath', function() { + it('defaults rootPath', function () { testPackageCache = new PackageCache(); expect(testPackageCache.rootPath).to.equal(process.cwd()); @@ -47,7 +47,7 @@ describe('PackageCache', function() { expect(testPackageCache.rootPath).to.equal('./'); }); - it('successfully uses getter/setter', function() { + it('successfully uses getter/setter', function () { testPackageCache._conf.set('foo', true); expect(testPackageCache.dirs['foo']).to.be.true; testPackageCache._conf.delete('foo'); @@ -58,7 +58,7 @@ describe('PackageCache', function() { }).to.throw(Error); }); - it('_cleanDirs', function() { + it('_cleanDirs', function () { testPackageCache._conf.set('existing', __dirname); testPackageCache._conf.set('nonexisting', path.join(__dirname, 'nonexisting')); @@ -68,7 +68,7 @@ describe('PackageCache', function() { expect(testPackageCache.dirs['nonexisting']).to.not.exist; }); - it('_readManifest', function() { + it('_readManifest', function () { let emberCLIPath = path.resolve(__dirname, '../../..'); testPackageCache._conf.set('self', emberCLIPath); testPackageCache._conf.set('boom', __dirname); @@ -84,7 +84,7 @@ describe('PackageCache', function() { expect(manifest).to.be.null; }); - it('_writeManifest', function() { + it('_writeManifest', function () { let manifest = JSON.stringify({ name: 'foo', dependencies: { @@ -127,7 +127,7 @@ describe('PackageCache', function() { testPackageCache.destroy('yarn'); }); - it('_checkManifest', function() { + it('_checkManifest', function () { let manifest = JSON.stringify({ name: 'foo', dependencies: { @@ -163,7 +163,7 @@ describe('PackageCache', function() { testPackageCache.destroy('bower'); }); - it('_removeLinks', function() { + it('_removeLinks', function () { // This is our package that is linked in. let srcDir = path.join(process.cwd(), 'tmp', 'beta'); expect(dir(srcDir)).to.not.exist; @@ -258,7 +258,7 @@ describe('PackageCache', function() { fs.removeSync(targetDir); }); - it('_restoreLinks', function() { + it('_restoreLinks', function () { // This is our package that is linked in. let srcDir = path.join(process.cwd(), 'tmp', 'beta'); expect(dir(srcDir)).to.not.exist; @@ -345,27 +345,27 @@ describe('PackageCache', function() { fs.removeSync(targetDir); }); - describe('_install', function() { + describe('_install', function () { // We're only going to test the invocation pattern boundary. // Don't want to wait for the install to execute. - beforeEach(function() { + beforeEach(function () { // Fake in the dir label. testPackageCache._conf.set('label', 'hello'); }); - afterEach(function() { + afterEach(function () { td.reset(); testPackageCache.destroy('label'); }); - it('Triggers install.', function() { + it('Triggers install.', function () { testPackageCache._install('label', 'npm'); td.verify(npm('install', { cwd: 'hello' }), { times: 1 }); td.verify(npm(), { times: 1, ignoreExtraArgs: true }); }); - it('Attempts to link when it is supposed to.', function() { + it('Attempts to link when it is supposed to.', function () { // Add a link. testPackageCache._writeManifest( 'label', @@ -385,141 +385,127 @@ describe('PackageCache', function() { }); }); - describe('_upgrade (npm)', function() { + describe('_upgrade (yarn)', function () { // We're only going to test the invocation pattern boundary. // Don't want to wait for the install to execute. let testCounter = 0; let label; - beforeEach(function() { - label = `npm-upgrade-test-${testCounter++}`; - testPackageCache._conf.set(label, 'hello'); - }); - - afterEach(function() { - td.reset(); - testPackageCache.destroy(label); - }); - - it('Trigger upgrade.', function() { - // npm is dumb. Upgrades are inconsistent and therefore invalid. - // Make sure npm does an install. - testPackageCache._upgrade(label, 'npm'); - td.verify(npm('install', { cwd: 'hello' }), { times: 1 }); - td.verify(npm(), { times: 1, ignoreExtraArgs: true }); - }); - - it('Make sure npm unlinks, installs, re-links.', function() { - // Add a link. - testPackageCache._writeManifest( - label, - 'npm', - JSON.stringify({ - _packageCache: { - links: ['ember-cli'], - }, - }) - ); - testPackageCache._upgrade(label, 'npm'); - td.verify(npm('unlink', 'ember-cli', { cwd: 'hello' }), { times: 1 }); - td.verify(npm('install', { cwd: 'hello' }), { times: 1 }); - td.verify(npm('link', 'ember-cli', { cwd: 'hello' }), { times: 1 }); - td.verify(npm(), { times: 3, ignoreExtraArgs: true }); - }); - - it('Make sure multiple invocations lock out.', function() { - testPackageCache._upgrade(label, 'npm'); - testPackageCache._upgrade(label, 'npm'); - td.verify(npm('install', { cwd: 'hello' }), { times: 1 }); - td.verify(npm(), { times: 1, ignoreExtraArgs: true }); - }); - - it('locks out _upgrade after _install', function() { - testPackageCache._install(label, 'npm'); - testPackageCache._upgrade(label, 'npm'); - td.verify(npm('install', { cwd: 'hello' }), { times: 1 }); - td.verify(npm(), { times: 1, ignoreExtraArgs: true }); - }); - }); - - describe('_upgrade (yarn)', function() { - // We're only going to test the invocation pattern boundary. - // Don't want to wait for the install to execute. - let testCounter = 0; - let label; - - beforeEach(function() { + beforeEach(function () { label = `yarn-upgrade-test-${testCounter++}`; testPackageCache._conf.set(label, 'hello'); }); - afterEach(function() { + afterEach(function () { td.reset(); testPackageCache.destroy(label); }); - it('Trigger upgrade.', function() { - testPackageCache._upgrade(label, 'yarn'); - td.verify(yarn('upgrade', { cwd: 'hello' }), { times: 1 }); - td.verify(yarn(), { times: 1, ignoreExtraArgs: true }); - }); - - it('Make sure it unlinks, upgrades, re-links.', function() { - // Add a link. - testPackageCache._writeManifest( - label, - 'yarn', - JSON.stringify({ - _packageCache: { - links: ['ember-cli'], - }, - }) - ); - testPackageCache._upgrade(label, 'yarn'); - td.verify(yarn('unlink', 'ember-cli', { cwd: 'hello' }), { times: 1 }); - td.verify(yarn('upgrade', { cwd: 'hello' }), { times: 1 }); - td.verify(yarn('link', 'ember-cli', { cwd: 'hello' }), { times: 1 }); - td.verify(yarn(), { times: 3, ignoreExtraArgs: true }); - }); - - it('Make sure multiple invocations lock out.', function() { - testPackageCache._upgrade(label, 'yarn'); - testPackageCache._upgrade(label, 'yarn'); - td.verify(yarn('upgrade', { cwd: 'hello' }), { times: 1 }); - td.verify(yarn(), { times: 1, ignoreExtraArgs: true }); + describe('without yarn.lock', function () { + beforeEach(function () { + testPackageCache._canUpgrade = () => false; + }); + + it('Trigger upgrade.', function () { + testPackageCache._upgrade(label, 'yarn'); + td.verify(yarn('install', { cwd: 'hello' }), { times: 1 }); + td.verify(yarn(), { times: 1, ignoreExtraArgs: true }); + }); + + it('Make sure it unlinks, upgrades, re-links.', function () { + // Add a link. + testPackageCache._writeManifest( + label, + 'yarn', + JSON.stringify({ + _packageCache: { + links: ['ember-cli'], + }, + }) + ); + testPackageCache._upgrade(label, 'yarn'); + td.verify(yarn('unlink', 'ember-cli', { cwd: 'hello' }), { times: 1 }); + td.verify(yarn('install', { cwd: 'hello' }), { times: 1 }); + td.verify(yarn('link', 'ember-cli', { cwd: 'hello' }), { times: 1 }); + td.verify(yarn(), { times: 3, ignoreExtraArgs: true }); + }); + + it('Make sure multiple invocations lock out.', function () { + testPackageCache._upgrade(label, 'yarn'); + testPackageCache._upgrade(label, 'yarn'); + td.verify(yarn('install', { cwd: 'hello' }), { times: 1 }); + td.verify(yarn(), { times: 1, ignoreExtraArgs: true }); + }); }); - it('locks out _upgrade after _install', function() { - testPackageCache._install(label, 'yarn'); - testPackageCache._upgrade(label, 'yarn'); - td.verify(yarn('install', { cwd: 'hello' }), { times: 1 }); - td.verify(yarn(), { times: 1, ignoreExtraArgs: true }); + describe('with yarn.lock', function () { + beforeEach(function () { + testPackageCache._canUpgrade = () => true; + }); + + it('Trigger upgrade.', function () { + testPackageCache._upgrade(label, 'yarn'); + td.verify(yarn('upgrade', { cwd: 'hello' }), { times: 1 }); + td.verify(yarn(), { times: 1, ignoreExtraArgs: true }); + }); + + it('Make sure it unlinks, upgrades, re-links.', function () { + // Add a link. + testPackageCache._writeManifest( + label, + 'yarn', + JSON.stringify({ + _packageCache: { + links: ['ember-cli'], + }, + }) + ); + testPackageCache._upgrade(label, 'yarn'); + td.verify(yarn('unlink', 'ember-cli', { cwd: 'hello' }), { times: 1 }); + td.verify(yarn('upgrade', { cwd: 'hello' }), { times: 1 }); + td.verify(yarn('link', 'ember-cli', { cwd: 'hello' }), { times: 1 }); + td.verify(yarn(), { times: 3, ignoreExtraArgs: true }); + }); + + it('Make sure multiple invocations lock out.', function () { + testPackageCache._upgrade(label, 'yarn'); + testPackageCache._upgrade(label, 'yarn'); + td.verify(yarn('upgrade', { cwd: 'hello' }), { times: 1 }); + td.verify(yarn(), { times: 1, ignoreExtraArgs: true }); + }); + + it('locks out _upgrade after _install', function () { + testPackageCache._install(label, 'yarn'); + testPackageCache._upgrade(label, 'yarn'); + td.verify(yarn('install', { cwd: 'hello' }), { times: 1 }); + td.verify(yarn(), { times: 1, ignoreExtraArgs: true }); + }); }); }); - describe('_upgrade (bower)', function() { + describe('_upgrade (bower)', function () { // We're only going to test the invocation pattern boundary. // Don't want to wait for the install to execute. let testCounter = 0; let label; - beforeEach(function() { + beforeEach(function () { label = `bower-upgrade-test-${testCounter++}`; testPackageCache._conf.set(label, 'hello'); }); - afterEach(function() { + afterEach(function () { td.reset(); testPackageCache.destroy(label); }); - it('Trigger upgrade.', function() { + it('Trigger upgrade.', function () { testPackageCache._upgrade(label, 'bower'); td.verify(bower('update', { cwd: 'hello' }), { times: 1 }); td.verify(bower(), { times: 1, ignoreExtraArgs: true }); }); - it('Make sure it unlinks, updates, re-links.', function() { + it('Make sure it unlinks, updates, re-links.', function () { // Add a link. testPackageCache._writeManifest( label, @@ -537,14 +523,14 @@ describe('PackageCache', function() { td.verify(bower(), { times: 3, ignoreExtraArgs: true }); }); - it('Make sure multiple invocations lock out.', function() { + it('Make sure multiple invocations lock out.', function () { testPackageCache._upgrade(label, 'bower'); testPackageCache._upgrade(label, 'bower'); td.verify(bower('update', { cwd: 'hello' }), { times: 1 }); td.verify(bower(), { times: 1, ignoreExtraArgs: true }); }); - it('locks out _upgrade after _install', function() { + it('locks out _upgrade after _install', function () { testPackageCache._install(label, 'bower'); testPackageCache._upgrade(label, 'bower'); td.verify(bower('install', { cwd: 'hello' }), { times: 1 }); @@ -552,7 +538,7 @@ describe('PackageCache', function() { }); }); - it('create', function() { + it('create', function () { td.when(npm('--version')).thenReturn({ stdout: '1.0.0' }); let dir = testPackageCache.create('npm', 'npm', '{}'); let manifestFilePath = path.join(dir, 'package.json'); @@ -612,12 +598,12 @@ describe('PackageCache', function() { testPackageCache.destroy('npm'); }); - it('get', function() { + it('get', function () { testPackageCache._conf.set('label', 'foo'); expect(testPackageCache.get('label')).to.equal('foo'); }); - it('destroy', function() { + it('destroy', function () { testPackageCache._writeManifest('label', 'bower', '{}'); let dir = testPackageCache.get('label'); @@ -629,7 +615,7 @@ describe('PackageCache', function() { expect(testPackageCache.dirs['label']).to.be.undefined; }); - it('clone', function() { + it('clone', function () { testPackageCache._writeManifest('from', 'bower', '{}'); let fromDir = testPackageCache.dirs['from']; @@ -647,7 +633,7 @@ describe('PackageCache', function() { testPackageCache.destroy('to'); }); - it('succeeds at a clean install', function() { + it('succeeds at a clean install', function () { this.timeout(15000); // Intentionally turning off testing mode. diff --git a/tests/unit/utilities/platform-checker-test.js b/tests/unit/utilities/platform-checker-test.js index 1dab12829b..e29ce4b003 100644 --- a/tests/unit/utilities/platform-checker-test.js +++ b/tests/unit/utilities/platform-checker-test.js @@ -3,8 +3,26 @@ const expect = require('chai').expect; const PlatformChecker = require('../../../lib/utilities/platform-checker'); -describe('platform-checker', function() { - it('checkIsDeprecated', function() { +describe('platform-checker', function () { + describe('known versions', function () { + function check(version, { isTested, isDeprecated, isValid }) { + it(`${version}`, function () { + let checker = new PlatformChecker(version); + expect(checker.isDeprecated).to.equal(isDeprecated); + expect(checker.isValid).to.equal(isValid); + expect(checker.isTested).to.equal(isTested); + }); + } + + check('v8.0.0', { isTested: false, isDeprecated: true, isValid: false }); + check('v10.0.0', { isTested: false, isDeprecated: true, isValid: false }); + check('v12.0.0', { isTested: true, isDeprecated: false, isValid: true }); + check('v13.0.0', { isTested: false, isDeprecated: false, isValid: true }); + check('v14.0.0', { isTested: true, isDeprecated: false, isValid: true }); + check('v15.0.0', { isTested: false, isDeprecated: false, isValid: true }); + }); + + it('checkIsDeprecated', function () { expect(new PlatformChecker('v0.10.1').checkIsDeprecated('4 || 6')).to.be.equal( true, 'versions below the range are deprecated' @@ -23,7 +41,7 @@ describe('platform-checker', function() { ); }); - it('checkIsValid', function() { + it('checkIsValid', function () { expect(new PlatformChecker('v0.10.1').checkIsValid('4 || 6')).to.be.equal( false, 'versions below the range are not valid' @@ -36,7 +54,7 @@ describe('platform-checker', function() { ); }); - it('checkIsTested', function() { + it('checkIsTested', function () { expect(new PlatformChecker('v0.10.1').checkIsTested('4 || 6')).to.be.equal( false, 'versions not in range are untested' diff --git a/tests/unit/utilities/print-command-test.js b/tests/unit/utilities/print-command-test.js index 5795e38ed1..41cbfb90ad 100644 --- a/tests/unit/utilities/print-command-test.js +++ b/tests/unit/utilities/print-command-test.js @@ -5,8 +5,8 @@ const processHelpString = require('../../helpers/process-help-string'); const expect = require('chai').expect; const EOL = require('os').EOL; -describe('printCommand', function() { - it('handles all possible options', function() { +describe('printCommand', function () { + it('handles all possible options', function () { let availableOptions = [ { name: 'test-option', @@ -49,7 +49,7 @@ describe('printCommand', function() { expect(output).to.equal(testString); }); - it('can have no margin or no options', function() { + it('can have no margin or no options', function () { let output = printCommand.call({ availableOptions: [], anonymousOptions: [], @@ -60,7 +60,7 @@ describe('printCommand', function() { expect(output).to.equal(testString); }); - it('can have an uncolored description', function() { + it('can have an uncolored description', function () { let output = printCommand.call({ description: 'a paragraph', availableOptions: [], @@ -73,7 +73,7 @@ describe('printCommand', function() { expect(output).to.equal(testString); }); - it('does not print with empty aliases', function() { + it('does not print with empty aliases', function () { let output = printCommand.call({ availableOptions: [], anonymousOptions: [], @@ -85,7 +85,7 @@ describe('printCommand', function() { expect(output).to.equal(testString); }); - it('quotes empty strings', function() { + it('quotes empty strings', function () { let output = printCommand.call({ availableOptions: [ { diff --git a/tests/unit/utilities/process-template-test.js b/tests/unit/utilities/process-template-test.js index 2848f6eaaa..8db3f154de 100644 --- a/tests/unit/utilities/process-template-test.js +++ b/tests/unit/utilities/process-template-test.js @@ -3,8 +3,8 @@ const expect = require('chai').expect; const processTemplate = require('../../../lib/utilities/process-template'); -describe('process-template', function() { - it('successfully transforms a template', function() { +describe('process-template', function () { + it('successfully transforms a template', function () { expect(processTemplate('hello <%= user %>!', { user: 'fred' })).to.be.equal('hello fred!'); expect(processTemplate('<%- value %>', { value: '