Repository: ember-cli/ember-cli
Pull request #7822 (2018-05-10 07:22:19)

Moves tests packaging out of `ember-app`

kellyselden: Seems good at first glance.

Patch:
diff --git a/lib/broccoli/default-packager.js b/lib/broccoli/default-packager.js
index 98bdf6b63..eee53d20d 100644
--- a/lib/broccoli/default-packager.js
+++ b/lib/broccoli/default-packager.js
@@ -134,6 +134,8 @@ module.exports = class DefaultPackager {
     this.storeConfigInMeta = this.options.storeConfigInMeta;
     this.customTransformsMap = this.options.customTransformsMap;
     this.additionalAssetPaths = this.options.additionalAssetPaths;
+    this.vendorTestStaticStyles = this.options.vendorTestStaticStyles;
+    this.legacyTestFilesToAppend = this.options.legacyTestFilesToAppend;
     this.isModuleUnificationEnabled = this.options.isModuleUnificationEnabled;
   }
 
@@ -778,6 +780,10 @@ module.exports = class DefaultPackager {
    * Given a tree:
    *
    * ```
+   * addon-tree-output/
+   * bower_components/
+   * the-best-app-ever/
+   * tests/
    * ├── acceptance/
    * ├── helpers/
    * ├── index.html
@@ -800,19 +806,23 @@ module.exports = class DefaultPackager {
    * ```
    *
    * @private
-   * @method packageTests
+   * @method processTests
    * @param {BroccoliTree} tree
   */
-  packageTests(tree) {
+  processTests(tree) {
     if (this._cachedTests === null) {
-      tree = addonProcessTree(this.project, 'preprocessTree', 'test', tree);
+      let testTree = new Funnel(tree, {
+        srcDir: 'tests',
+      });
 
-      tree = new Funnel(tree, {
+      testTree = addonProcessTree(this.project, 'preprocessTree', 'test', testTree);
+
+      testTree = new Funnel(testTree, {
         destDir: `${this.name}/tests`,
         annotation: 'Packaged Tests',
       });
 
-      let preprocessedTests = preprocessJs(tree, '/tests', this.name, {
+      let preprocessedTests = preprocessJs(testTree, '/tests', this.name, {
         registry: this.registry,
       });
 
@@ -822,6 +832,304 @@ module.exports = class DefaultPackager {
     return this._cachedTests;
   }
 
+  /*
+   * Concatenates all test files into one, as follows:
+   *
+   * Given an input tree that looks like:
+   *
+   * ```
+   * addon-tree-output/
+   * bower_components/
+   * the-best-app-ever/
+   * tests/
+   * ├── acceptance/
+   * ├── helpers/
+   * ├── index.html
+   * ├── integration/
+   * ├── test-helper.js
+   * └── unit/
+   * vendor/
+   * ```
+   *
+   * Returns:
+   *
+   * ```
+   * assets/
+   *   tests.js
+   *   test.map (if sourcemaps are enabled)
+   *   test-support.js
+   *   test-support.map (if sourcemaps are enabled)
+   *   test-support.css
+   * ```
+   *
+   * @private
+   * @method packageTests
+   * @param {BroccoliTree}
+   * @return {BroccoliTree}
+  */
+  packageTests(tree) {
+    let coreTestTree = this.processTests(tree);
+
+    let testIndex = this.processTestIndex(tree);
+    let appTestTree = this.packageApplicationTests(coreTestTree);
+    let testFilesTree = this.packageTestFiles(tree, coreTestTree);
+
+    return mergeTrees([testIndex, appTestTree, testFilesTree], {
+      annotation: 'Packaged Tests',
+    });
+  }
+
+  /*
+   * Replaces variables in `tests/index.html` file with values from
+   * `config/environment.js` and returns a single tree that contains
+   * `index.html` file with populated values.
+   *
+   * Input tree:
+   *
+   * ```
+   * /
+   * ├── addon-tree-output/
+   * ├── the-best-app-ever/
+   * ├── bower_components/
+   * ├── tests/
+   * └── vendor/
+   * ```
+   *
+   * Returns:
+   *
+   * ```
+   * └── tests
+   *     └── index.html/
+   * ```
+   *
+   * @private
+   * @method processTestIndex
+   * @param {BroccoliTree}
+   * @return {BroccoliTree}
+  */
+  processTestIndex(tree) {
+    let index = new Funnel(tree, {
+      srcDir: '/tests',
+      files: ['index.html'],
+      destDir: '/tests',
+      annotation: 'Funnel (test index)',
+    });
+
+    let patterns = configReplacePatterns({
+      addons: this.project.addons,
+      autoRun: this.autoRun,
+      storeConfigInMeta: this.storeConfigInMeta,
+      isModuleUnification: this.isModuleUnificationEnabled,
+    });
+
+    let configPath = path.join(this.name, 'config', 'environments', 'test.json');
+
+    return new ConfigReplace(index, this.packageConfig(), {
+      configPath,
+      files: ['tests/index.html'],
+      env: 'test',
+      patterns,
+    });
+  }
+
+  /*
+   * Wraps application configuration into AMD module:
+   *
+   * ```javascript
+   * define('the-best-app-ever/config/environment', [], function() {
+   *   // read the meta tag that contains escaped configuration from
+   *   // `index.html` and return as an object
+   * });
+   * ```
+   *
+   * Given a tree:
+   *
+   * ```
+   * environments/
+   * ├── development.json
+   * └── test.json
+   * ```
+   *
+   * Returns:
+   *
+   * ```
+   * └── vendor
+   *     └── ember-cli
+   *         └── app-config.js
+   * ```
+   * @private
+   * @method packageTestApplicationConfig
+  */
+  packageTestApplicationConfig() {
+    let files = ['app-config.js'];
+    let patterns = configReplacePatterns({
+      addons: this.project.addons,
+      autoRun: this.autoRun,
+      storeConfigInMeta: this.storeConfigInMeta,
+      isModuleUnification: this.isModuleUnificationEnabled,
+    });
+
+    let configPath = path.join(this.name, 'config', 'environments', `test.json`);
+    let emberCLITree = new ConfigReplace(new UnwatchedDir(__dirname), this.packageConfig(), {
+      configPath,
+      files,
+      patterns,
+    });
+
+    return new Funnel(emberCLITree, {
+      files,
+      srcDir: '/',
+      destDir: '/vendor/ember-cli/',
+      annotation: 'Funnel (test-app-config-tree)',
+    });
+  }
+
+  /*
+   * Concatenates all application test files into one, as follows:
+   *
+   * Given an input tree that looks like:
+   *
+   * ```
+   * addon-tree-output/
+   * bower_components/
+   * the-best-app-ever/
+   * tests/
+   * ├── acceptance/
+   * ├── helpers/
+   * ├── index.html
+   * ├── integration/
+   * ├── test-helper.js
+   * └── unit/
+   * vendor/
+   * ```
+   *
+   * Returns:
+   *
+   * ```
+   * assets/
+   *   tests.js
+   *   test.map (if sourcemaps are enabled)
+   * ```
+   *
+   * @private
+   * @method packageApplicationTests
+   * @param {BroccoliTree}
+   * @return {BroccoliTree}
+  */
+  packageApplicationTests(tree) {
+    let appTestTrees = [].concat(
+      this.packageEmberCliInternalFiles(),
+      this.packageTestApplicationConfig(),
+      tree
+    ).filter(Boolean);
+
+    appTestTrees = mergeTrees(appTestTrees, {
+      overwrite: true,
+      annotation: 'TreeMerger (appTestTrees)',
+    });
+
+    return concat(appTestTrees, {
+      inputFiles: [`${this.name}/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,
+      annotation: 'Concat: App Tests',
+    });
+  }
+
+  /*
+   * Concatenates all test support files into one, as follows:
+   *
+   * Given an input tree that looks like:
+   *
+   * ```
+   * addon-tree-output/
+   * bower_components/
+   * the-best-app-ever/
+   * tests/
+   * ├── acceptance/
+   * ├── helpers/
+   * ├── index.html
+   * ├── integration/
+   * ├── test-helper.js
+   * └── unit/
+   * vendor/
+   * ```
+   *
+   * Returns:
+   *
+   * ```
+   * assets/
+   *   test-support.js
+   *   test-support.map (if sourcemaps are enabled)
+   *   test-support.css
+   * ```
+   *
+   * @private
+   * @method packageTestFiles
+   * @return {BroccoliTree}
+  */
+  packageTestFiles(tree, coreTestTree) {
+    let testSupportPath = this.distPaths.testSupportJsFile;
+
+    testSupportPath = testSupportPath.testSupport || testSupportPath;
+
+    let emberCLITree = this.packageEmberCliInternalFiles();
+
+    let headerFiles = [].concat(
+      'vendor/ember-cli/test-support-prefix.js',
+      this.legacyTestFilesToAppend
+    );
+
+    let inputFiles = ['tests/addon-test-support/**/*.js'];
+
+    let footerFiles = ['vendor/ember-cli/test-support-suffix.js'];
+
+    let baseMergedTree = mergeTrees([emberCLITree, tree, coreTestTree], {
+      overwrite: true,
+    });
+    let testJs = concat(baseMergedTree, {
+      headerFiles,
+      inputFiles,
+      footerFiles,
+      outputFile: testSupportPath,
+      annotation: 'Concat: Test Support JS',
+      allowNone: true,
+    });
+
+    let testemPath = path.join(__dirname, 'testem');
+    testemPath = path.dirname(testemPath);
+
+    let testemTree = new Funnel(new UnwatchedDir(testemPath), {
+      files: ['testem.js'],
+      annotation: 'Funnel (testem)',
+    });
+
+    if (this.fingerprint && this.fingerprint.exclude) {
+      this.fingerprint.exclude.push('testem');
+    }
+
+    let sourceTrees = [
+      testemTree,
+      testJs,
+    ];
+
+    if (this.vendorTestStaticStyles.length > 0) {
+      sourceTrees.push(
+        concat(tree, {
+          headerFiles: this.vendorTestStaticStyles,
+          outputFile: this.distPaths.testSupportCssFile,
+          annotation: 'Concat: Test Support CSS',
+        })
+      );
+    }
+
+    return mergeTrees(sourceTrees, {
+      overwrite: true,
+      annotation: 'TreeMerger (testFiles)',
+    });
+  }
+
   /*
    * Given input trees (both application and add-ons), merges them into one.
    *
@@ -890,8 +1198,6 @@ module.exports = class DefaultPackager {
    * ```
    * @private
    * @method packageConfig
-   * @param {Boolean} testsEnabled Boolean flag to control the inclusion of
-   *                  `test.json` file in the resulting tree.
   */
   packageConfig() {
     let env = this.env;
diff --git a/lib/broccoli/ember-app.js b/lib/broccoli/ember-app.js
index c85b9b25b..39ba59eaa 100644
--- a/lib/broccoli/ember-app.js
+++ b/lib/broccoli/ember-app.js
@@ -37,7 +37,6 @@ const emberAppUtils = require('../utilities/ember-app-utils');
 const addonProcessTree = require('../utilities/addon-process-tree');
 const lintAddonsByType = require('../utilities/lint-addons-by-type');
 const experiments = require('../experiments');
-const processModulesOnly = require('./babel-process-modules-only');
 const semver = require('semver');
 const DefaultPackager = require('./default-packager');
 
@@ -161,16 +160,22 @@ class EmberApp {
       styleOutputFiles: this._styleOutputFiles,
       scriptOutputFiles: this._scriptOutputFiles,
       autoRun: this.options.autoRun,
+      fingerprint: this.options.fingerprint,
       customTransformsMap: this._customTransformsMap,
       storeConfigInMeta: this.options.storeConfigInMeta,
       isModuleUnificationEnabled: experiments.MODULE_UNIFICATION && !!this.trees.src,
       additionalAssetPaths: this.otherAssetPaths,
+      vendorTestStaticStyles: this.vendorTestStaticStyles,
+      legacyTestFilesToAppend: this.legacyTestFilesToAppend,
       distPaths: {
         appJsFile: this.options.outputPaths.app.js,
         appCssFile: this.options.outputPaths.app.css,
+        testJsFile: this.options.outputPaths.tests.js,
         appHtmlFile: this.options.outputPaths.app.html,
         vendorJsFile: this.options.outputPaths.vendor.js,
         vendorCssFile: this.options.outputPaths.vendor.css,
+        testSupportJsFile: this.options.outputPaths.testSupport.js,
+        testSupportCssFile: this.options.outputPaths.testSupport.css,
       },
     });
   }
@@ -1240,78 +1245,43 @@ class EmberApp {
     return this._cachedExternalTree;
   }
 
-  /**
-    @private
-    @method _testAppConfigTree
-    @return
+  /*
+   * Gather all tests under `tests` folder.
+   *
+   * Resulting tree:
+   *
+   * ```
+   * /
+   * └── tests/
+   *     ├── acceptance/
+   *     ├── addon-test-support/
+   *     ├── helpers/
+   *     ├── integration/
+   *     ├── lint/
+   *     ├── unit/
+   *     ├── index.html
+   *     └── test-helper.js
+   * ```
+   *
+   * @private
+   * @method getTests
+   * @return {BroccoliTree}
   */
-  _testAppConfigTree() {
-    if (!this._cachedTestAppConfigTree) {
-      let files = ['app-config.js'];
-      let patterns = configReplacePatterns({
-        addons: this.project.addons,
-        autoRun: this.options.autoRun,
-        storeConfigInMeta: this.options.storeConfigInMeta,
-        isModuleUnification: experiments.MODULE_UNIFICATION && !!this.trees.src,
-      });
-
-      let emberCLITree = new ConfigReplace(new UnwatchedDir(__dirname), this._defaultPackager.packageConfig(this.tests), {
-        configPath: path.join(this.name, 'config', 'environments', `test.json`),
-        files,
-        patterns,
-      });
+  getTests() {
+    let addonTrees = this.addonTreesFor('test-support');
 
-      this._cachedTestAppConfigTree = new Funnel(emberCLITree, {
-        files,
-        srcDir: '/',
-        destDir: '/vendor/ember-cli/',
-        annotation: 'Funnel (test-app-config-tree)',
-      });
+    if (this.hinting) {
+      addonTrees.push(this.getLintTests());
     }
 
-    return this._cachedTestAppConfigTree;
-  }
-
-  test() {
-    let addonTrees = this.addonTreesFor('test-support');
-    let mergedTests = mergeTrees(addonTrees.concat(this.trees.tests), {
+    let addonTestSupportFiles = this.addonTestSupportTree();
+    let allTests = mergeTrees(addonTrees.concat(this.trees.tests, addonTestSupportFiles), {
       overwrite: true,
       annotation: 'TreeMerger (tests)',
     });
 
-    let coreTestTree = this._defaultPackager.packageTests(mergedTests);
-
-    let appTestTree = this.appTests(coreTestTree);
-    let testFilesTree = this.testFiles(coreTestTree);
-
-    return mergeTrees([appTestTree, testFilesTree], {
-      annotation: 'TreeMerger (test)',
-    });
-  }
-
-  /**
-    @private
-    @method appTests
-  */
-  appTests(coreTestTree) {
-    let appTestTrees = [].concat(
-      this.hinting && this.lintTestTrees(),
-      this._defaultPackager.packageEmberCliInternalFiles(),
-      this._testAppConfigTree(),
-      coreTestTree
-    ).filter(Boolean);
-
-    appTestTrees = mergeTrees(appTestTrees, {
-      overwrite: true,
-      annotation: 'TreeMerger (appTestTrees)',
-    });
-
-    return this._concatFiles(appTestTrees, {
-      inputFiles: [`${this.name}/tests/**/*.js`],
-      headerFiles: ['vendor/ember-cli/tests-prefix.js'],
-      footerFiles: ['vendor/ember-cli/app-config.js', 'vendor/ember-cli/tests-suffix.js'],
-      outputFile: this.options.outputPaths.tests.js,
-      annotation: 'Concat: App Tests',
+    return new Funnel(allTests, {
+      destDir: 'tests',
     });
   }
 
@@ -1321,30 +1291,28 @@ class EmberApp {
     Those lint trees are afterwards funneled into the `tests` folder, babel-ified and returned as an array.
 
     @private
-    @method lintTestsTrees
+    @method getLintTests
     @return {Array}
    */
-  lintTestTrees() {
+  getLintTests() {
     let lintTrees = [];
 
     if (this.trees.app) {
       let lintedApp = this.addonLintTree('app', this.trees.app);
-      lintedApp = processModulesOnly(new Funnel(lintedApp, {
-        srcDir: '/',
-        destDir: `${this.name}/tests/`,
+      lintedApp = new Funnel(lintedApp, {
+        destDir: 'lint',
         annotation: 'Funnel (lint app)',
-      }), 'Babel: lintTree(app)');
+      });
 
       lintTrees.push(lintedApp);
     }
 
     if (experiments.MODULE_UNIFICATION && this.trees.src) {
       let lintedSrc = this.addonLintTree('src', this.trees.src);
-      lintedSrc = processModulesOnly(new Funnel(lintedSrc, {
-        srcDir: '/',
-        destDir: `${this.name}/tests/src/`,
+      lintedSrc = new Funnel(lintedSrc, {
+        destDir: 'lint/src/',
         annotation: 'Funnel (lint src)',
-      }), 'Babel: lintTree(src)');
+      });
 
       lintTrees.push(lintedSrc);
     }
@@ -1352,19 +1320,19 @@ class EmberApp {
     let lintedTests = this.addonLintTree('tests', this.trees.tests);
     let lintedTemplates = this.addonLintTree('templates', this._templatesTree());
 
-    lintedTests = processModulesOnly(new Funnel(lintedTests, {
-      srcDir: '/',
-      destDir: `${this.name}/tests/`,
+    lintedTests = new Funnel(lintedTests, {
+      destDir: 'lint',
       annotation: 'Funnel (lint tests)',
-    }), 'Babel: lintTree(tests)');
+    });
 
-    lintedTemplates = processModulesOnly(new Funnel(lintedTemplates, {
-      srcDir: '/',
-      destDir: `${this.name}/tests/`,
+    lintedTemplates = new Funnel(lintedTemplates, {
+      destDir: 'lint',
       annotation: 'Funnel (lint templates)',
-    }), 'Babel: lintTree(templates)');
+    });
 
-    return [lintedTests, lintedTemplates].concat(lintTrees);
+    return mergeTrees([lintedTests, lintedTemplates].concat(lintTrees), {
+      overwrite: true,
+    });
   }
 
   /**
@@ -1377,76 +1345,6 @@ class EmberApp {
     return !!this.registry.availablePlugins[addonName];
   }
 
-  /**
-    Returns the tree for test files
-
-    @private
-    @method testFiles
-    @return {Tree} Merged tree for test files
-   */
-  testFiles(coreTestTree) {
-    let testSupportPath = this.options.outputPaths.testSupport.js;
-
-    testSupportPath = testSupportPath.testSupport || testSupportPath;
-
-    let external = this.getExternalTree();
-    let emberCLITree = this._defaultPackager.packageEmberCliInternalFiles();
-    let addonTestSupportTree = this.addonTestSupportTree();
-
-    let headerFiles = [].concat(
-      'vendor/ember-cli/test-support-prefix.js',
-      this.legacyTestFilesToAppend
-    );
-
-    let inputFiles = ['addon-test-support/**/*.js'];
-
-    let footerFiles = ['vendor/ember-cli/test-support-suffix.js'];
-
-    let baseMergedTree = mergeTrees([emberCLITree, external, coreTestTree, addonTestSupportTree]);
-    let testJs = this._concatFiles(baseMergedTree, {
-      headerFiles,
-      inputFiles,
-      footerFiles,
-      outputFile: testSupportPath,
-      annotation: 'Concat: Test Support JS',
-      allowNone: true,
-    });
-
-    let testemPath = path.join(__dirname, 'testem');
-    testemPath = path.dirname(testemPath);
-
-    let testemTree = new Funnel(new UnwatchedDir(testemPath), {
-      files: ['testem.js'],
-      srcDir: '/',
-      destDir: '/',
-      annotation: 'Funnel (testem)',
-    });
-
-    if (this.options.fingerprint && this.options.fingerprint.exclude) {
-      this.options.fingerprint.exclude.push('testem');
-    }
-
-    let sourceTrees = [
-      testemTree,
-      testJs,
-    ];
-
-    if (this.vendorTestStaticStyles.length > 0) {
-      sourceTrees.push(
-        this._concatFiles(external, {
-          headerFiles: this.vendorTestStaticStyles,
-          outputFile: this.options.outputPaths.testSupport.css,
-          annotation: 'Concat: Test Support CSS',
-        })
-      );
-    }
-
-    return mergeTrees(sourceTrees, {
-      overwrite: true,
-      annotation: 'TreeMerger (testFiles)',
-    });
-  }
-
   /**
     @public
     @method dependencies
@@ -1637,6 +1535,7 @@ class EmberApp {
       this.getAppJavascript(),
       this.getAddonTemplates(),
       this.getAddonStyles(),
+      this.getTests(),
       this.getExternalTree(),
       this.getSrc(),
     ].filter(Boolean);
@@ -1657,8 +1556,8 @@ class EmberApp {
       publicTree,
     ].filter(Boolean);
 
-    if (this.tests && this.trees.tests) {
-      sourceTrees = sourceTrees.concat(this.testIndex(), this.test());
+    if (this.tests) {
+      sourceTrees.push(this._defaultPackager.packageTests(fullTree));
     }
 
     return sourceTrees;


Test patch:
diff --git a/tests/unit/broccoli/default-packager/config-test.js b/tests/unit/broccoli/default-packager/config-test.js
index f84d8906b..6cf20166f 100644
--- a/tests/unit/broccoli/default-packager/config-test.js
+++ b/tests/unit/broccoli/default-packager/config-test.js
@@ -62,9 +62,10 @@ describe('Default Packager: Config', function() {
       name,
       project,
       env,
+      areTestsEnabled: false,
     });
 
-    output = yield buildOutput(defaultPackager.packageConfig(false));
+    output = yield buildOutput(defaultPackager.packageConfig());
 
     let outputFiles = output.read();
 
diff --git a/tests/unit/broccoli/default-packager/tests-test.js b/tests/unit/broccoli/default-packager/tests-test.js
index af6fb7ad5..011e9e96d 100644
--- a/tests/unit/broccoli/default-packager/tests-test.js
+++ b/tests/unit/broccoli/default-packager/tests-test.js
@@ -1,34 +1,109 @@
 'use strict';
 
-const p = require('ember-cli-preprocess-registry/preprocessors');
 const co = require('co');
+const stew = require('broccoli-stew');
+const Funnel = require('broccoli-funnel');
 const expect = require('chai').expect;
 const DefaultPackager = require('../../../../lib/broccoli/default-packager');
 const broccoliTestHelper = require('broccoli-test-helper');
+const defaultPackagerHelpers = require('../../../helpers/default-packager');
 
 const buildOutput = broccoliTestHelper.buildOutput;
 const createTempDir = broccoliTestHelper.createTempDir;
+const setupRegistryFor = defaultPackagerHelpers.setupRegistryFor;
 
 describe('Default Packager: Tests', function() {
   let input, output;
   let name = 'the-best-app-ever';
+  let env = 'development';
+
+  let TESTS = {
+    'addon-tree-output': {},
+    'bower_components': {},
+    '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: {
+      custom: {
+        'a.js': 'a.js',
+        'a.css': 'a.css',
+        'b.js': 'b.js',
+        'b.css': 'b.css',
+      },
+      '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': '',
+      },
+      helpers: {
+        'resolver.js': '',
+        'start-app.js': '',
+      },
+      'index.html': 'index',
+      integration: {
+        components: {
+          'login-form-test.js': '',
+          'user-menu-test.js': '',
+        },
+      },
+      'test-helper.js': '// test-helper.js',
+      unit: {
+        services: {
+          'session-test.js': '',
+        },
+      },
+    },
+  };
 
   let project = {
-    dependencies() {
+    configPath() {
+      return `${input.path()}/the-best-app-ever/config/environment`;
+    },
+
+    config() {
       return {
-        'ember-cli-htmlbars': '^2.0.1',
+        a: 1,
+        modulePrefix: 'the-best-app-ever',
       };
     },
-    addons: [],
-  };
 
-  let TESTS = {
-    acceptance: {},
-    helpers: {},
-    'index.html': 'index',
-    integration: {},
-    'test-helper.js': 'test-helper',
-    unit: {},
+    addons: [{
+      // this lintTree implementation will return the same
+      // files as the input tree, but the contents will be
+      // different
+      lintTree(type, tree) {
+        return stew.map(tree, string => string.toUpperCase());
+      },
+    }],
   };
 
   before(co.wrap(function *() {
@@ -49,7 +124,26 @@ describe('Default Packager: Tests', function() {
     let defaultPackager = new DefaultPackager({
       project,
       name,
-      registry: p.defaultRegistry(project),
+      env,
+      areTestsEnabled: true,
+
+      distPaths: {
+        testJsFile: '/assets/tests.js',
+        testSupportJsFile: {
+          testSupport: '/assets/test-support.js',
+          testLoader: '/assets/test-loader.js',
+        },
+        testSupportCssFile: '/assets/test-support.css',
+      },
+
+      vendorTestStaticStyles: [],
+      legacyTestFilesToAppend: [],
+
+      fingerprint: {
+        exclude: [],
+      },
+
+      registry: setupRegistryFor('js', tree => tree),
     });
 
     expect(defaultPackager._cachedTests).to.equal(null);
@@ -60,21 +154,303 @@ describe('Default Packager: Tests', function() {
     expect(defaultPackager._cachedTests._annotation).to.equal('Packaged Tests');
   }));
 
-  it('packages tests files', co.wrap(function *() {
+  it('packages test files', co.wrap(function *() {
     let defaultPackager = new DefaultPackager({
       project,
       name,
-      registry: p.defaultRegistry(project),
+      env,
+      areTestsEnabled: true,
+
+      distPaths: {
+        testJsFile: '/assets/tests.js',
+        testSupportJsFile: {
+          testSupport: '/assets/test-support.js',
+          testLoader: '/assets/test-loader.js',
+        },
+        testSupportCssFile: '/assets/test-support.css',
+      },
+
+      vendorTestStaticStyles: [],
+      legacyTestFilesToAppend: [],
+
+      fingerprint: {
+        exclude: [],
+      },
+
+      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('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',
+      },
+
+      vendorTestStaticStyles: [],
+      legacyTestFilesToAppend: [],
+
+      fingerprint: {
+        exclude: [],
+      },
+
+      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()));
+
+    let outputFiles = output.read();
+
     expect(outputFiles).to.deep.equal({
       [name]: {
-        tests: TESTS,
+        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': '',
+            },
+          },
+        },
+      },
+    });
+  }));
+
+  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 {}',
+        },
+        'routes': {
+          'application.js': 'export default class {}',
+        },
+        'config': {
+          'environment.js': 'environment.js',
+        },
+        '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',
       },
+    };
+
+    emptyInput.write(emptyTestFolder);
+
+    let project = {
+      configPath() {
+        return `${emptyInput.path()}/the-best-app-ever/config/environment`;
+      },
+
+      config() {
+        return { a: 1 };
+      },
+
+      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',
+      },
+
+      vendorTestStaticStyles: [],
+      legacyTestFilesToAppend: [],
+
+      fingerprint: {
+        exclude: [],
+      },
+
+      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',
+    ]);
+
+    emptyInput.dispose();
+  }));
+
+  it('lintTree results do not "win" over app tests', 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',
+      },
+
+      vendorTestStaticStyles: [],
+      legacyTestFilesToAppend: [],
+
+      fingerprint: {
+        exclude: [],
+      },
+
+      registry: setupRegistryFor('js', tree => tree),
     });
+
+    output = yield buildOutput(defaultPackager.packageTests(input.path()));
+
+    let outputFiles = output.read();
+
+    // confirm this contains the original value
+    // unmodified by the `lintTree` added above
+    expect(outputFiles.assets['tests.js']).to.include('// login-test.js');
+  }));
+
+  it('maintains the concatenation order', 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',
+      },
+
+      vendorTestStaticStyles: [
+        'vendor/custom/a.css',
+        'vendor/custom/b.css',
+      ],
+      legacyTestFilesToAppend: [
+        'vendor/custom/a.js',
+        'vendor/custom/b.js',
+      ],
+
+      fingerprint: {
+        exclude: [],
+      },
+
+      registry: setupRegistryFor('js', tree => tree),
+    });
+
+    output = yield buildOutput(defaultPackager.packageTests(input.path()));
+
+    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');
   }));
 });
diff --git a/tests/unit/broccoli/ember-app-test.js b/tests/unit/broccoli/ember-app-test.js
index 63c02c9b2..ccf782664 100644
--- a/tests/unit/broccoli/ember-app-test.js
+++ b/tests/unit/broccoli/ember-app-test.js
@@ -96,6 +96,130 @@ describe('EmberApp', function() {
     }));
   });
 
+  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': '',
+      });
+
+      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 [];
+      };
+
+      let output = yield 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',
+      });
+
+      yield input.dispose();
+      yield addonFooTestSupport.dispose();
+      yield addonBarTestSupport.dispose();
+      yield addonLint.dispose();
+      yield 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();
+
+      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(),
+          ];
+        }
+
+        return [];
+      };
+
+      let output = yield 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',
+      });
+
+      yield input.dispose();
+      yield addonFooTestSupport.dispose();
+      yield addonBarTestSupport.dispose();
+      yield output.dispose();
+    }));
+  });
+
   describe('constructor', function() {
     it('should override project.configPath if configPath option is specified', function() {
       project.configPath = function() { return 'original value'; };
@@ -879,25 +1003,7 @@ describe('EmberApp', function() {
     });
 
     describe('concat order', function() {
-      let count = 0;
-      let args = [];
-
       beforeEach(function() {
-        count = 0;
-        args = [];
-
-        // we are "spying and mocking here" so to ensure we are testing our own
-        // "wiring" not the implementation of our dependencies e.g. broccoli-concat
-        app._concatFiles = function(tree, options) {
-          count++;
-          args.push(options);
-          return tree;
-        };
-
-        app.appAndDependencies = function() {
-          return 'app-and-dependencies-tree';
-        };
-
         mockTemplateRegistry(app);
       });
 
@@ -930,39 +1036,19 @@ describe('EmberApp', function() {
         app.import('files/d.css', { type: 'test' });
         app.import('files/d.css', { type: 'test' });
 
-        app.testFiles('some-tree'); // run
-
-        expect(count).to.eql(2);
-
-        expect(args[0]).to.deep.eql({
-          allowNone: true,
-          annotation: 'Concat: Test Support JS',
-          footerFiles: [
-            'vendor/ember-cli/test-support-suffix.js',
-          ],
-          headerFiles: [
-            'vendor/ember-cli/test-support-prefix.js',
-            'files/d.js',
-            'files/a.js',
-            'files/b.js',
-            'files/c.js',
-          ],
-          inputFiles: [
-            'addon-test-support/**/*.js',
-          ],
-          outputFile: '/assets/test-support.js',
-        });
+        expect(app.legacyTestFilesToAppend).to.deep.equal([
+          'files/d.js',
+          'files/a.js',
+          'files/b.js',
+          'files/c.js',
+        ]);
 
-        expect(args[1]).to.deep.eql({
-          annotation: 'Concat: Test Support CSS',
-          headerFiles: [
-            'files/a.css',
-            'files/b.css',
-            'files/c.css',
-            'files/d.css',
-          ],
-          outputFile: '/assets/test-support.css',
-        });
+        expect(app.vendorTestStaticStyles).to.deep.equal([
+          'files/a.css',
+          'files/b.css',
+          'files/c.css',
+          'files/d.css',
+        ]);
       });
     });
   });
diff --git a/tests/unit/broccoli/ember-app/test-test.js b/tests/unit/broccoli/ember-app/test-test.js
deleted file mode 100644
index c738a838d..000000000
--- a/tests/unit/broccoli/ember-app/test-test.js
+++ /dev/null
@@ -1,101 +0,0 @@
-'use strict';
-
-const co = require('co');
-const broccoliTestHelper = require('broccoli-test-helper');
-const expect = require('chai').expect;
-
-const EmberApp = require('../../../../lib/broccoli/ember-app');
-const MockCLI = require('../../../helpers/mock-cli');
-const Project = require('../../../../lib/models/project');
-
-const buildOutput = broccoliTestHelper.buildOutput;
-const createTempDir = broccoliTestHelper.createTempDir;
-const walkSync = require('walk-sync');
-const stew = require('broccoli-stew');
-
-describe('EmberApp#test', function() {
-  let input;
-
-  beforeEach(co.wrap(function *() {
-    input = yield createTempDir();
-
-    input.write({
-      'config': {
-        'environment.js': `module.exports = function() { return { modulePrefix: 'test-app' }; };`,
-      },
-    });
-  }));
-
-  afterEach(function() {
-    return input.dispose();
-  });
-
-  function createApp(options) {
-    options = options || {};
-
-    let pkg = { name: 'ember-app-test' };
-
-    let cli = new MockCLI();
-    let project = new Project(input.path(), pkg, cli.ui, cli);
-
-    let app = new EmberApp({
-      project,
-      name: pkg.name,
-      _ignoreMissingLoader: true,
-      sourcemaps: { enabled: false },
-    }, options);
-
-    app._compileAddonTemplates = tree => tree;
-
-    return app;
-  }
-
-  it('emits dist/assets/tests.js by default', co.wrap(function *() {
-    input.write({
-      'tests': {
-        'test-helper.js': '// test-helper.js',
-      },
-    });
-
-    let app = createApp();
-    let output = yield buildOutput(app.test());
-
-    let files = walkSync(output.path(), { directories: false });
-    expect(files).to.deep.equal([
-      'assets/test-support.js',
-      'assets/tests.js',
-      'testem.js',
-    ]);
-  }));
-
-  it('lintTree results do not "win" over app tests', co.wrap(function *() {
-    input.write({
-      'app': { },
-      'tests': {
-        'integration': {
-          'components': {
-            'foo-bar-test.js': '// foo-bar-test.js',
-          },
-        },
-      },
-    });
-
-    let app = createApp();
-
-    // create a fake addon that has a `lintTree`
-    app.project.addons.push({
-      // this lintTree implementation will return the same
-      // files as the input tree, but the contents will be
-      // different
-      lintTree(type, tree) {
-        return stew.map(tree, string => string.toUpperCase());
-      },
-    });
-
-    let output = yield buildOutput(app.test());
-
-    // confirm this contains the original value
-    // unmodified by the `lintTree` added above
-    expect(output.read().assets['tests.js']).to.include('// foo-bar-test.js');
-  }));
-});