diff --git a/packages/compat/tests/stage1.test.ts b/packages/compat/tests/stage1.test.ts deleted file mode 100644 index b1023cfc4..000000000 --- a/packages/compat/tests/stage1.test.ts +++ /dev/null @@ -1,489 +0,0 @@ -import { Project, BuildResult, ExpectFile, expectFilesAt } from '@embroider/test-support'; -import resolve from 'resolve'; -import { dirname } from 'path'; -import merge from 'lodash/merge'; - -describe('stage1 build', function () { - jest.setTimeout(120000); - - describe('max compatibility', function () { - let build: BuildResult; - let expectFile: ExpectFile; - - beforeAll(async function () { - process.env.THROW_UNLESS_PARALLELIZABLE = '1'; // see https://github.com/embroider-build/embroider/pull/924 - // A simple ember app with no tests - let app = Project.emberNew(); - - // We create an addon. - // Our addon is configured to use ember-auto-import's dynamic import. - let addon = app.addAddon( - 'my-addon', - ` - options: { - babel: { - plugins: [ require.resolve('ember-auto-import/babel-plugin') ] - } - }, - ` - ); - - merge(addon.files, { - addon: { - components: { - 'hello-world.js': ` - import Component from '@ember/component'; - import layout from '../templates/components/hello-world'; - import { getOwnConfig } from '@embroider/macros'; - export default Component.extend({ - message: 'embroider-sample-transforms-target', - config: getOwnConfig(), - layout - }); - `, - 'has-inline-template.js': ` - import Component from '@ember/component'; - import { hbs } from 'ember-cli-htmlbars'; - export default Component.extend({ - // tagged template form: - layout: ${"hbs`
Inline
{{macroDependencySatisfies 'ember-source' '>3'}}`"}, - // call expression form: - extra: hbs("
Extra
") - }); - `, - 'does-dynamic-import.js': ` - export default function() { - return import('some-library'); - } - `, - }, - templates: { - components: { - 'hello-world.hbs': ` -
hello world
- {{macroDependencySatisfies "ember-source" ">3"}} - `, - 'module-name.hbs': `
hello world
`, - }, - }, - }, - app: { - components: { - 'hello-world.js': `export { default } from 'my-addon/components/hello-world'`, - }, - }, - }); - - // Our addon will use @embroider/sample-transforms as examples of custom - // AST and babel transforms. - addon.linkPackage('@embroider/sample-transforms'); - addon.linkPackage('@embroider/macros'); - addon.linkPackage('ember-auto-import'); - - app.linkPackage('ember-auto-import'); - app.linkPackage('webpack'); - // our app will include an in-repo addon - app.pkg['ember-addon'] = { - paths: ['lib/in-repo-addon'], - }; - app.files.lib = { - 'in-repo-addon': { - 'package.json': JSON.stringify( - { - name: 'in-repo-addon', - keywords: ['ember-addon'], - }, - null, - 2 - ), - 'index.js': `module.exports = { name: 'in-repo-addon' };`, - addon: { - helpers: { - 'helper-from-in-repo-addon.js': '', - }, - }, - }, - }; - - build = await BuildResult.build(app, { stage: 1 }); - expectFile = expectFilesAt(build.outputPath); - }); - - afterAll(async function () { - delete process.env.THROW_UNLESS_PARALLELIZABLE; - await build.cleanup(); - }); - - test('component in app tree', function () { - expectFile('node_modules/my-addon/_app_/components/hello-world.js').exists(); - }); - - test('addon metadata', function () { - let assertMeta = expectFile('node_modules/my-addon/package.json').json('ember-addon'); - assertMeta.get('app-js').deepEquals({ './components/hello-world.js': './_app_/components/hello-world.js' }); // should have app-js metadata - assertMeta - .get('implicit-modules') - .includes('./components/hello-world', 'staticAddonTrees is off so we should include the component implicitly'); - assertMeta - .get('implicit-modules') - .includes( - './templates/components/hello-world.hbs', - 'staticAddonTrees is off so we should include the template implicitly' - ); - assertMeta.get('version').equals(2); - }); - - test('component in addon tree', function () { - let assertFile = expectFile('node_modules/my-addon/components/hello-world.js'); - assertFile.matches(`getOwnConfig()`, `JS macros have not run yet`); - assertFile.matches(`embroider-sample-transforms-result`, `custom babel plugins have run`); - }); - - test('component template in addon tree', function () { - let assertFile = expectFile('node_modules/my-addon/templates/components/hello-world.hbs'); - assertFile.matches( - '
hello world
', - 'template is still hbs and custom transforms have run' - ); - assertFile.matches( - '{{macroDependencySatisfies "ember-source" ">3"}}', - 'template macros have not run' - ); - }); - - test('test module name added', function () { - let assertFile = expectFile('node_modules/my-addon/templates/components/module-name.hbs'); - assertFile.matches( - '
hello world
', - 'template is still hbs and module name transforms have run' - ); - }); - - test('component with inline template', function () { - let assertFile = expectFile('node_modules/my-addon/components/has-inline-template.js'); - assertFile.matches( - 'hbs`
Inline
', - 'tagged template is still hbs and custom transforms have run' - ); - assertFile.matches( - /hbs\(["']
Extra<\/div>["']\)/, - 'called template is still hbs and custom transforms have run' - ); - assertFile.matches( - /{{macroDependencySatisfies ['"]ember-source['"] ['"]>3['"]}}<\/span>/, - 'template macros have not run' - ); - }); - - test('in-repo-addon is available', function () { - resolve.sync('in-repo-addon/helpers/helper-from-in-repo-addon', { basedir: build.outputPath }); - }); - - test('dynamic import is preserved', function () { - expectFile('node_modules/my-addon/components/does-dynamic-import.js').matches( - /return import\(['"]some-library['"]\)/ - ); - }); - }); - - describe('inline hbs, ember-cli-htmlbars@3', function () { - let build: BuildResult; - let expectFile: ExpectFile; - - beforeAll(async function () { - // A simple ember app with no tests - let app = Project.emberNew(); - - // We create an addon - let addon = app.addAddon('my-addon'); - addon.files.addon = { - components: { - 'has-inline-template.js': ` - import Component from '@ember/component'; - import hbs from 'htmlbars-inline-precompile'; - export default Component.extend({ - // tagged template form: - layout: ${"hbs`
Inline
{{macroDependencySatisfies 'ember-source' '>3'}}`"}, - // call expression form: - extra: hbs("
Extra
") - }); - `, - }, - }; - - // Our addon will use @embroider/sample-transforms as examples of custom - // AST and babel transforms. - addon.linkPackage('@embroider/sample-transforms'); - addon.linkPackage('ember-cli-htmlbars-inline-precompile'); - addon.linkPackage('ember-cli-htmlbars', dirname(require.resolve('ember-cli-htmlbars-3/package.json'))); - addon.linkPackage('@embroider/macros'); - - build = await BuildResult.build(app, { stage: 1 }); - expectFile = expectFilesAt(build.outputPath); - }); - - afterAll(async function () { - await build.cleanup(); - }); - - test('component with inline template', function () { - let assertFile = expectFile('node_modules/my-addon/components/has-inline-template.js'); - assertFile.matches( - 'hbs`
Inline
', - 'tagged template is still hbs and custom transforms have run' - ); - assertFile.matches( - /hbs\(["']
Extra<\/div>["']\)/, - 'called template is still hbs and custom transforms have run' - ); - assertFile.matches( - /{{macroDependencySatisfies ['"]ember-source['"] ['"]>3['"]}}<\/span>/, - 'template macros have not run' - ); - }); - }); - - describe('addon dummy app', function () { - let build: BuildResult; - - beforeAll(async function () { - let app = Project.addonNew(); - (app.files.addon as Project['files']).components = { - 'hello-world.js': '', - }; - - build = await BuildResult.build(app, { stage: 1, type: 'addon' }); - }); - - afterAll(async function () { - await build.cleanup(); - }); - - test('dummy app can resolve own addon', function () { - resolve.sync('my-addon/components/hello-world.js', { basedir: build.outputPath }); - }); - }); - - describe('problematic addon zoo', function () { - let build: BuildResult; - let expectFile: ExpectFile; - - beforeAll(async function () { - let app = Project.emberNew(); - - // an addon that emits a package.json file from its treeForAddon - let addon = app.addAddon( - 'alpha', - ` - treeForAddon() { - return require('path').join(__dirname, 'alpha-addon-tree'); - } - ` - ); - addon.files['alpha-addon-tree'] = { - 'package.json': '{}', - }; - - // an addon that manually extends the Addon base class - let hasCustomBase = app.addAddon('has-custom-base'); - hasCustomBase.files['index.js'] = ` - const { join } = require('path'); - const Addon = require('ember-cli/lib/models/addon'); - module.exports = Addon.extend({ - name: 'has-custom-base', - treeForAddon() { - return join(__dirname, 'weird-addon-path'); - } - }); - `; - hasCustomBase.files['weird-addon-path'] = { - 'has-custom-base': { - 'file.js': '// weird-addon-path/file.js', - }, - }; - - // an addon that nullifies its custom fastboot tree with a custom fastboot hook - let undefinedFastboot = app.addAddon('undefined-fastboot'); - merge(undefinedFastboot.files, { - 'index.js': `module.exports = { - name: 'undefined-fastboot', - treeForFastBoot() {} - }`, - fastboot: { - 'something.js': '', - }, - }); - - // Use one addon to patch the hook on another (yes, this happens in the - // wild...), and ensure we still detect the customized hook - app.addAddon('externally-customized'); - let troubleMaker = app.addAddon('trouble-maker'); - merge(troubleMaker.files, { - injected: { - hello: { 'world.js': '// hello' }, - }, - 'index.js': ` - const { join } = require('path'); - module.exports = { - name: 'trouble-maker', - included() { - let instance = this.project.addons.find(a => a.name === "externally-customized"); - let root = this.root; - instance.treeForPublic = function() { - return join(root, 'injected'); - } - } - }`, - }); - - // an addon that customizes packageJSON['ember-addon'].main and then uses - // stock trees. Setting the main actually changes the root for *all* stock - // trees. - let movedMain = app.addAddon('moved-main'); - merge(movedMain.files, { - custom: { - 'index.js': `module.exports = { name: 'moved-main'};`, - addon: { helpers: { 'hello.js': '// hello-world' } }, - }, - }); - merge(movedMain.pkg, { 'ember-addon': { main: 'custom/index.js' } }); - - // an addon that uses treeFor() to sometimes suppress its stock trees - let suppressed = app.addAddon( - 'suppressed', - ` - treeFor(name) { - if (name !== 'app') { - return this._super.treeFor(name); - } else { - return undefined; - } - } - ` - ); - merge(suppressed.files, { - addon: { - 'addon-example.js': '// example', - }, - app: { - 'app-example.js': '// example', - }, - }); - - // an addon that uses treeFor() to sometimes suppress its custom trees - let suppressedCustom = app.addAddon( - 'suppressed-custom', - ` - treeFor(name) { - if (name !== 'app') { - return this._super(name); - } else { - return undefined; - } - }, - treeForApp() { - return require('path').join(__dirname, 'app-custom'); - }, - treeForAddon() { - return require('path').join(__dirname, 'addon-custom'); - }, - ` - ); - merge(suppressedCustom.files, { - 'addon-custom': { - 'suppressed-custom': { - 'addon-example.js': '// example', - }, - }, - 'app-custom': { - 'app-example.js': '// example', - }, - }); - - let blacklistedInRepoAddon = app.addInRepoAddon('blacklisted-in-repo-addon'); - merge(blacklistedInRepoAddon.files, { - addon: { - 'example.js': '', - }, - }); - - let disabledInRepoAddon = app.addInRepoAddon( - 'disabled-in-repo-addon', - ` - isEnabled() { return false } - ` - ); - merge(disabledInRepoAddon.files, { - addon: { - 'example.js': '', - }, - }); - - build = await BuildResult.build(app, { - stage: 1, - type: 'app', - emberAppOptions: { - addons: { - blacklist: ['blacklisted-in-repo-addon'], - }, - }, - }); - expectFile = expectFilesAt(build.outputPath); - }); - - afterAll(async function () { - await build.cleanup(); - }); - - test('real package.json wins', function () { - expectFile('node_modules/alpha/package.json').matches(`alpha`); - }); - - test('custom tree hooks are detected in addons that manually extend from Addon', function () { - let assertFile = expectFile('node_modules/has-custom-base/file.js'); - assertFile.matches(/weird-addon-path\/file\.js/); - }); - - test('no fastboot-js is emitted', function () { - expectFile('node_modules/undefined-fastboot/package.json') - .json() - .get('ember-addon.fastboot-js') - .equals(undefined); - }); - - test('custom tree hooks are detected when they have been patched into the addon instance', function () { - let assertFile = expectFile('node_modules/externally-customized/public/hello/world.js'); - assertFile.exists(); - }); - - test('addon with customized ember-addon.main can still use stock trees', function () { - expectFile('node_modules/moved-main/helpers/hello.js').matches(/hello-world/); - }); - - test('addon with customized treeFor can suppress a stock tree', function () { - expectFile('node_modules/suppressed/_app_/app-example.js').doesNotExist(); - }); - - test('addon with customized treeFor can pass through a stock tree', function () { - expectFile('node_modules/suppressed/addon-example.js').exists(); - }); - - test('addon with customized treeFor can suppress a customized tree', function () { - expectFile('node_modules/suppressed-custom/_app_/app-example.js').doesNotExist(); - }); - - test('addon with customized treeFor can pass through a customized tree', function () { - expectFile('node_modules/suppressed-custom/addon-example.js').exists(); - }); - - test('blacklisted in-repo addon is present but empty', function () { - expectFile('lib/blacklisted-in-repo-addon/package.json').exists(); - expectFile('lib/blacklisted-in-repo-addon/example.js').doesNotExist(); - }); - - test('disabled in-repo addon is present but empty', function () { - expectFile('lib/disabled-in-repo-addon/package.json').exists(); - expectFile('lib/disabled-in-repo-addon/example.js').doesNotExist(); - }); - }); -}); diff --git a/tests/fixtures/basic-in-repo-addon/lib/in-repo-addon/addon/helpers/helper-from-in-repo-addon.js b/tests/fixtures/basic-in-repo-addon/lib/in-repo-addon/addon/helpers/helper-from-in-repo-addon.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/basic-in-repo-addon/lib/in-repo-addon/index.js b/tests/fixtures/basic-in-repo-addon/lib/in-repo-addon/index.js new file mode 100644 index 000000000..1d5db98b1 --- /dev/null +++ b/tests/fixtures/basic-in-repo-addon/lib/in-repo-addon/index.js @@ -0,0 +1 @@ +module.exports = { name: 'in-repo-addon' }; diff --git a/tests/fixtures/basic-in-repo-addon/lib/in-repo-addon/package.json b/tests/fixtures/basic-in-repo-addon/lib/in-repo-addon/package.json new file mode 100644 index 000000000..2fbfb00f5 --- /dev/null +++ b/tests/fixtures/basic-in-repo-addon/lib/in-repo-addon/package.json @@ -0,0 +1,4 @@ +{ + "name": "in-repo-addon", + "keywords": ["ember-addon"] +} \ No newline at end of file diff --git a/tests/fixtures/blacklisted-addon-build-options/ember-cli-build.js b/tests/fixtures/blacklisted-addon-build-options/ember-cli-build.js new file mode 100644 index 000000000..674c7e024 --- /dev/null +++ b/tests/fixtures/blacklisted-addon-build-options/ember-cli-build.js @@ -0,0 +1,21 @@ +'use strict'; + +const EmberApp = require('ember-cli/lib/broccoli/ember-app'); + +module.exports = function (defaults) { + let app = new EmberApp(defaults, { + // Add options here + addons: { + blacklist: ['blacklisted-in-repo-addon'], + }, + }); + + const { Webpack } = require('@embroider/webpack'); + return require('@embroider/compat').compatBuild(app, Webpack, { + skipBabel: [ + { + package: 'qunit', + }, + ], + }); +}; diff --git a/tests/fixtures/blacklisted-addon-build-options/lib/blacklisted-in-repo-addon/addon/example.js b/tests/fixtures/blacklisted-addon-build-options/lib/blacklisted-in-repo-addon/addon/example.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/blacklisted-addon-build-options/lib/blacklisted-in-repo-addon/index.js b/tests/fixtures/blacklisted-addon-build-options/lib/blacklisted-in-repo-addon/index.js new file mode 100644 index 000000000..3a77c77d6 --- /dev/null +++ b/tests/fixtures/blacklisted-addon-build-options/lib/blacklisted-in-repo-addon/index.js @@ -0,0 +1,3 @@ +module.exports = { + name: require('./package').name, +}; diff --git a/tests/fixtures/blacklisted-addon-build-options/lib/blacklisted-in-repo-addon/package.json b/tests/fixtures/blacklisted-addon-build-options/lib/blacklisted-in-repo-addon/package.json new file mode 100644 index 000000000..f9d40ca62 --- /dev/null +++ b/tests/fixtures/blacklisted-addon-build-options/lib/blacklisted-in-repo-addon/package.json @@ -0,0 +1,4 @@ +{ + "name": "blacklisted-in-repo-addon", + "keywords": ["ember-addon"] +} \ No newline at end of file diff --git a/tests/fixtures/blacklisted-addon-build-options/lib/disabled-in-repo-addon/addon/example.js b/tests/fixtures/blacklisted-addon-build-options/lib/disabled-in-repo-addon/addon/example.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/blacklisted-addon-build-options/lib/disabled-in-repo-addon/index.js b/tests/fixtures/blacklisted-addon-build-options/lib/disabled-in-repo-addon/index.js new file mode 100644 index 000000000..b5b01410d --- /dev/null +++ b/tests/fixtures/blacklisted-addon-build-options/lib/disabled-in-repo-addon/index.js @@ -0,0 +1,6 @@ +module.exports = { + name: require('./package').name, + isEnabled() { + return false; + }, +}; diff --git a/tests/fixtures/blacklisted-addon-build-options/lib/disabled-in-repo-addon/package.json b/tests/fixtures/blacklisted-addon-build-options/lib/disabled-in-repo-addon/package.json new file mode 100644 index 000000000..44410c325 --- /dev/null +++ b/tests/fixtures/blacklisted-addon-build-options/lib/disabled-in-repo-addon/package.json @@ -0,0 +1,4 @@ +{ + "name": "disabled-in-repo-addon", + "keywords": ["ember-addon"] +} \ No newline at end of file diff --git a/tests/fixtures/hello-world-addon/addon/components/does-dynamic-import.js b/tests/fixtures/hello-world-addon/addon/components/does-dynamic-import.js new file mode 100644 index 000000000..228c0a8fc --- /dev/null +++ b/tests/fixtures/hello-world-addon/addon/components/does-dynamic-import.js @@ -0,0 +1,3 @@ +export default function () { + return import('some-library'); +} diff --git a/tests/fixtures/hello-world-addon/addon/components/has-inline-template.js b/tests/fixtures/hello-world-addon/addon/components/has-inline-template.js new file mode 100644 index 000000000..84ce0394d --- /dev/null +++ b/tests/fixtures/hello-world-addon/addon/components/has-inline-template.js @@ -0,0 +1,8 @@ +import Component from '@ember/component'; +import { hbs } from 'ember-cli-htmlbars'; +export default Component.extend({ + // tagged template form: + layout: hbs`
Inline
{{macroDependencySatisfies 'ember-source' '>3'}}`, + // call expression form: + extra: hbs('
Extra
'), +}); diff --git a/tests/fixtures/hello-world-addon/addon/components/hello-world.js b/tests/fixtures/hello-world-addon/addon/components/hello-world.js new file mode 100644 index 000000000..674075603 --- /dev/null +++ b/tests/fixtures/hello-world-addon/addon/components/hello-world.js @@ -0,0 +1,8 @@ +import Component from '@ember/component'; +import layout from '../templates/components/hello-world'; +import { getOwnConfig } from '@embroider/macros'; +export default Component.extend({ + message: 'embroider-sample-transforms-target', + config: getOwnConfig(), + layout, +}); diff --git a/tests/fixtures/hello-world-addon/addon/templates/components/hello-world.hbs b/tests/fixtures/hello-world-addon/addon/templates/components/hello-world.hbs new file mode 100644 index 000000000..180916ab4 --- /dev/null +++ b/tests/fixtures/hello-world-addon/addon/templates/components/hello-world.hbs @@ -0,0 +1,2 @@ +
hello world
+{{macroDependencySatisfies "ember-source" ">3"}} \ No newline at end of file diff --git a/tests/fixtures/hello-world-addon/addon/templates/components/module-name.hbs b/tests/fixtures/hello-world-addon/addon/templates/components/module-name.hbs new file mode 100644 index 000000000..17aaa1f8d --- /dev/null +++ b/tests/fixtures/hello-world-addon/addon/templates/components/module-name.hbs @@ -0,0 +1 @@ +
hello world
\ No newline at end of file diff --git a/tests/fixtures/hello-world-addon/app/components/hello-world.js b/tests/fixtures/hello-world-addon/app/components/hello-world.js new file mode 100644 index 000000000..0f8e0e70a --- /dev/null +++ b/tests/fixtures/hello-world-addon/app/components/hello-world.js @@ -0,0 +1 @@ +export { default } from 'my-addon/components/hello-world'; diff --git a/tests/fixtures/hello-world-addon/index.js b/tests/fixtures/hello-world-addon/index.js new file mode 100644 index 000000000..791da73d0 --- /dev/null +++ b/tests/fixtures/hello-world-addon/index.js @@ -0,0 +1,8 @@ +module.exports = { + name: require('./package').name, + options: { + babel: { + plugins: [require.resolve('ember-auto-import/babel-plugin')], + }, + }, +}; diff --git a/tests/scenarios/package.json b/tests/scenarios/package.json index 76aeb30ab..929aa4360 100644 --- a/tests/scenarios/package.json +++ b/tests/scenarios/package.json @@ -9,6 +9,7 @@ "jsdom": "^16.2.2", "lodash": "^4.17.20", "qunit": "^2.16.0", + "resolve": "^1.20.0", "scenario-tester": "^2.0.1", "ts-node": "^9.1.1" }, @@ -30,6 +31,8 @@ "ember-cli-3.20": "npm:ember-cli@~3.20.0", "ember-cli-3.24": "npm:ember-cli@~3.24.0", "ember-cli-beta": "npm:ember-cli@beta", + "ember-cli-htmlbars-inline-precompile": "^2.1.0", + "ember-cli-htmlbars-3": "npm:ember-cli-htmlbars@3", "ember-cli-latest": "npm:ember-cli@latest", "ember-cli-fastboot": "^2.2.3", "ember-composable-helpers": "^4.4.1", diff --git a/tests/scenarios/stage1-test.ts b/tests/scenarios/stage1-test.ts new file mode 100644 index 000000000..b1042a855 --- /dev/null +++ b/tests/scenarios/stage1-test.ts @@ -0,0 +1,444 @@ +import resolve from 'resolve'; +import { join } from 'path'; +import merge from 'lodash/merge'; +import fs from 'fs-extra'; +import { loadFromFixtureData } from './helpers'; +import { appReleaseScenario, dummyAppScenarios, baseAddon } from './scenarios'; +import { PreparedApp } from 'scenario-tester'; +import QUnit from 'qunit'; +const { module: Qmodule, test } = QUnit; + +appReleaseScenario + .map('stage-1', project => { + let addon = baseAddon(); + + merge(addon.files, loadFromFixtureData('hello-world-addon')); + addon.pkg.name = 'my-addon'; + + addon.linkDependency('@embroider/sample-transforms', { baseDir: __dirname }); + addon.linkDependency('@embroider/macros', { baseDir: __dirname }); + project.addDependency(addon); + + // our app will include an in-repo addon + project.pkg['ember-addon'] = { paths: ['lib/in-repo-addon'] }; + merge(project.files, loadFromFixtureData('basic-in-repo-addon')); + }) + .forEachScenario(async scenario => { + Qmodule(`${scenario.name} max compatibility`, function (hooks) { + let app: PreparedApp; + let workspaceDir: string; + + hooks.before(async () => { + process.env.THROW_UNLESS_PARALLELIZABLE = '1'; // see https://github.com/embroider-build/embroider/pull/924 + app = await scenario.prepare(); + await app.execute('cross-env STAGE1_ONLY=true node ./node_modules/ember-cli/bin/ember b'); + workspaceDir = fs.readFileSync(join(app.dir, 'dist', '.stage1-output'), 'utf8'); + }); + + hooks.after(async () => { + delete process.env.THROW_UNLESS_PARALLELIZABLE; + }); + + test('component in app tree', function (assert) { + assert.ok(fs.existsSync(join(workspaceDir, 'node_modules/my-addon/_app_/components/hello-world.js'))); + }); + + test('addon metadata', function (assert) { + let assertMeta = fs.readJsonSync(join(workspaceDir, 'node_modules/my-addon/package.json'))['ember-addon']; + assert.deepEqual(assertMeta['app-js'], { './components/hello-world.js': './_app_/components/hello-world.js' }); + assert.ok( + JSON.stringify(assertMeta['implicit-modules']).includes('./components/hello-world'), + 'staticAddonTrees is off so we should include the component implicitly' + ); + assert.ok( + JSON.stringify(assertMeta['implicit-modules']).includes('./templates/components/hello-world.hbs'), + 'staticAddonTrees is off so we should include the template implicitly' + ); + + assert.equal(assertMeta.version, 2); + }); + + test('component in addon tree', function (assert) { + let fileContents = fs.readFileSync(join(workspaceDir, 'node_modules/my-addon/components/hello-world.js')); + + assert.ok(fileContents.includes('getOwnConfig()'), 'JS macros have not run yet'); + assert.ok(fileContents.includes('embroider-sample-transforms-result'), 'custom babel plugins have run'); + }); + + test('component template in addon tree', function (assert) { + let fileContents = fs.readFileSync( + join(workspaceDir, 'node_modules/my-addon/templates/components/hello-world.hbs') + ); + assert.ok( + fileContents.includes('
hello world
'), + 'template is still hbs and custom transforms have run' + ); + assert.ok( + fileContents.includes('{{macroDependencySatisfies "ember-source" ">3"}}'), + 'template macros have not run' + ); + }); + + test('test module name added', function (assert) { + let fileContents = fs.readFileSync( + join(workspaceDir, 'node_modules/my-addon/templates/components/module-name.hbs') + ); + let searchRegExp = /\\/gi; + let replaceWith = '\\\\'; + assert.ok( + fileContents.includes( + `
hello world
` + ), + 'template is still hbs and module name transforms have run' + ); + }); + + test('component with inline template', function (assert) { + let fileContents = fs.readFileSync( + join(workspaceDir, 'node_modules/my-addon/components/has-inline-template.js') + ); + assert.ok( + fileContents.includes('hbs`
Inline
'), + 'tagged template is still hbs and custom transforms have run' + ); + assert.ok( + /hbs\(["']
Extra<\/div>["']\)/.test(fileContents.toString()), + 'called template is still hbs and custom transforms have run' + ); + assert.ok( + /{{macroDependencySatisfies ['"]ember-source['"] ['"]>3['"]}}<\/span>/.test(fileContents.toString()), + 'template macros have not run' + ); + }); + + test('in-repo-addon is available', function (assert) { + assert.ok(resolve.sync('in-repo-addon/helpers/helper-from-in-repo-addon', { basedir: workspaceDir })); + }); + + test('dynamic import is preserved', function (assert) { + let fileContents = fs.readFileSync( + join(workspaceDir, 'node_modules/my-addon/components/does-dynamic-import.js') + ); + assert.ok(/return import\(['"]some-library['"]\)/.test(fileContents.toString())); + }); + }); + }); + +appReleaseScenario + .map('stage-1-inline-hbs', project => { + let addon = baseAddon(); + + merge(addon.files, { + addon: { + components: { + 'has-inline-template.js': ` + import Component from '@ember/component'; + import hbs from 'htmlbars-inline-precompile'; + export default Component.extend({ + // tagged template form: + layout: ${"hbs`
Inline
{{macroDependencySatisfies 'ember-source' '>3'}}`"}, + // call expression form: + extra: hbs("
Extra
") + }); + `, + }, + }, + }); + addon.pkg.name = 'my-addon'; + + addon.linkDependency('@embroider/sample-transforms', { baseDir: __dirname }); + addon.linkDependency('@embroider/macros', { baseDir: __dirname }); + addon.linkDependency('ember-cli-htmlbars-inline-precompile', { baseDir: __dirname }); + addon.linkDependency('ember-cli-htmlbars-3', { baseDir: __dirname, resolveName: 'ember-cli-htmlbars' }); + project.addDependency(addon); + }) + .forEachScenario(async scenario => { + Qmodule(`${scenario.name} inline hbs, ember-cli-htmlbars@3`, function (hooks) { + let app: PreparedApp; + let workspaceDir: string; + + hooks.before(async () => { + app = await scenario.prepare(); + await app.execute('cross-env STAGE1_ONLY=true node ./node_modules/ember-cli/bin/ember b'); + workspaceDir = fs.readFileSync(join(app.dir, 'dist', '.stage1-output'), 'utf8'); + }); + + test('component with inline template', function (assert) { + let fileContents = fs.readFileSync( + join(workspaceDir, 'node_modules/my-addon/components/has-inline-template.js') + ); + assert.ok( + fileContents.includes('hbs`
Inline
'), + 'tagged template is still hbs and custom transforms have run' + ); + assert.ok( + /hbs\(["']
Extra<\/div>["']\)/.test(fileContents.toString()), + 'called template is still hbs and custom transforms have run' + ); + assert.ok( + /{{macroDependencySatisfies ['"]ember-source['"] ['"]>3['"]}}<\/span>/.test(fileContents.toString()), + 'template macros have not run' + ); + }); + }); + }); + +appReleaseScenario + .map('stage-1-problematic-addon-zoo', project => { + let addon = baseAddon(); + + // an addon that emits a package.json file from its treeForAddon + merge(addon.files, { + index: `module.exports = { + name: require('./package').name, + treeForAddon() { + return require('path').join(__dirname, 'alpha-addon-tree'); + } + };`, + 'alpha-addon-tree': { + 'package.json': '{}', + }, + }); + + addon.pkg.name = 'alpha'; + + let hasCustomBase = baseAddon(); + + // an addon that manually extends the Addon base class + merge(hasCustomBase.files, { + 'index.js': ` + const { join } = require('path'); + const Addon = require('ember-cli/lib/models/addon'); + module.exports = Addon.extend({ + name: 'has-custom-base', + treeForAddon() { + return join(__dirname, 'weird-addon-path'); + } + });`, + 'weird-addon-path': { + 'has-custom-base': { + 'file.js': '// weird-addon-path/file.js', + }, + }, + }); + + hasCustomBase.pkg.name = 'has-custom-base'; + + let undefinedFastboot = baseAddon(); + + // an addon that nullifies its custom fastboot tree with a custom fastboot hook + merge(undefinedFastboot.files, { + 'index.js': `module.exports = { + name: 'undefined-fastboot', + treeForFastBoot() {} + }`, + fastboot: { + 'something.js': '', + }, + }); + + undefinedFastboot.pkg.name = 'undefined-fastboot'; + + // Use one addon to patch the hook on another (yes, this happens in the + // wild...), and ensure we still detect the customized hook + let externallyCustomized = baseAddon(); + let troubleMaker = baseAddon(); + merge(troubleMaker.files, { + injected: { + hello: { 'world.js': '// hello' }, + }, + 'index.js': ` + const { join } = require('path'); + module.exports = { + name: 'trouble-maker', + included() { + let instance = this.project.addons.find(a => a.name === "externally-customized"); + let root = this.root; + instance.treeForPublic = function() { + return join(root, 'injected'); + } + } + }`, + }); + + externallyCustomized.pkg.name = 'externally-customized'; + troubleMaker.pkg.name = 'trouble-maker'; + + // an addon that customizes packageJSON['ember-addon'].main and then uses + // stock trees. Setting the main actually changes the root for *all* stock + // trees. + let movedMain = baseAddon(); + merge(movedMain.files, { + custom: { + 'index.js': `module.exports = { name: 'moved-main'};`, + addon: { helpers: { 'hello.js': '// hello-world' } }, + }, + }); + merge(movedMain.pkg, { 'ember-addon': { main: 'custom/index.js' }, name: 'moved-main' }); + + // an addon that uses treeFor() to sometimes suppress its stock trees + let suppressed = baseAddon(); + merge(suppressed.files, { + 'index.js': `module.exports = { + name: require('./package').name, + treeFor(name) { + if (name !== 'app') { + return this._super.treeFor(name); + } else { + return undefined; + } + } + }`, + addon: { + 'addon-example.js': '// example', + }, + app: { + 'app-example.js': '// example', + }, + }); + + suppressed.pkg.name = 'suppressed'; + + // an addon that uses treeFor() to sometimes suppress its custom trees + let suppressedCustom = baseAddon(); + merge(suppressedCustom.files, { + 'index.js': `module.exports = { + name: require('./package').name, + treeFor(name) { + if (name !== 'app') { + return this._super(name); + } else { + return undefined; + } + }, + treeForApp() { + return require('path').join(__dirname, 'app-custom'); + }, + treeForAddon() { + return require('path').join(__dirname, 'addon-custom'); + }, + }`, + 'addon-custom': { + 'suppressed-custom': { + 'addon-example.js': '// example', + }, + }, + 'app-custom': { + 'app-example.js': '// example', + }, + }); + + suppressedCustom.pkg.name = 'suppressed-custom'; + + project.addDependency(addon); + project.addDependency(hasCustomBase); + project.addDependency(undefinedFastboot); + project.addDependency(externallyCustomized); + project.addDependency(troubleMaker); + project.addDependency(movedMain); + project.addDependency(suppressed); + project.addDependency(suppressedCustom); + + project.pkg['ember-addon'] = { paths: ['lib/disabled-in-repo-addon', 'lib/blacklisted-in-repo-addon'] }; + merge(project.files, loadFromFixtureData('blacklisted-addon-build-options')); + }) + .forEachScenario(async scenario => { + Qmodule(`${scenario.name} problematic addon zoo`, function (hooks) { + let app: PreparedApp; + let workspaceDir: string; + + hooks.before(async () => { + app = await scenario.prepare(); + await app.execute('cross-env STAGE1_ONLY=true node ./node_modules/ember-cli/bin/ember b'); + workspaceDir = fs.readFileSync(join(app.dir, 'dist', '.stage1-output'), 'utf8'); + }); + + test('real package.json wins', function (assert) { + let fileContents = fs.readFileSync(join(workspaceDir, 'node_modules/alpha/package.json')); + assert.ok(fileContents.includes('alpha')); + }); + + test('custom tree hooks are detected in addons that manually extend from Addon', function (assert) { + let fileContents = fs.readFileSync(join(workspaceDir, 'node_modules/has-custom-base/file.js')); + assert.ok(/weird-addon-path\/file\.js/.test(fileContents.toString())); + }); + + test('no fastboot-js is emitted', function (assert) { + let fileContents = fs.readJsonSync(join(workspaceDir, 'node_modules/undefined-fastboot/package.json')); + assert.equal(fileContents['ember-addon']['fastboot-js'], null); + }); + + test('custom tree hooks are detected when they have been patched into the addon instance', function (assert) { + assert.ok(fs.existsSync(join(workspaceDir, 'node_modules/externally-customized/public/hello/world.js'))); + }); + + test('addon with customized ember-addon.main can still use stock trees', function (assert) { + let fileContents = fs.readFileSync(join(workspaceDir, 'node_modules/moved-main/helpers/hello.js')); + assert.ok(/hello-world/.test(fileContents.toString())); + }); + + test('addon with customized treeFor can suppress a stock tree', function (assert) { + assert.notOk(fs.existsSync(join(workspaceDir, 'node_modules/suppressed/_app_/app-example.js'))); + }); + + test('addon with customized treeFor can pass through a stock tree', function (assert) { + assert.ok(fs.existsSync(join(workspaceDir, 'node_modules/suppressed/addon-example.js'))); + }); + + test('addon with customized treeFor can suppress a customized tree', function (assert) { + assert.notOk(fs.existsSync(join(workspaceDir, 'node_modules/suppressed-custom/_app_/app-example.js'))); + }); + + test('addon with customized treeFor can pass through a customized tree', function (assert) { + assert.ok(fs.existsSync(join(workspaceDir, 'node_modules/suppressed-custom/addon-example.js'))); + }); + + test('blacklisted in-repo addon is present but empty', function (assert) { + assert.ok(fs.existsSync(join(workspaceDir, 'lib/blacklisted-in-repo-addon/package.json'))); + assert.notOk(fs.existsSync(join(workspaceDir, 'lib/blacklisted-in-repo-addon/example.js'))); + }); + + test('disabled in-repo addon is present but empty', function (assert) { + assert.ok(fs.existsSync(join(workspaceDir, 'lib/disabled-in-repo-addon/package.json'))); + assert.notOk(fs.existsSync(join(workspaceDir, 'lib/disabled-in-repo-addon/example.js'))); + }); + }); + }); + +dummyAppScenarios + .map('stage-1-dummy-addon', project => { + project.pkg.name = 'my-addon'; + + project.linkDependency('@embroider/webpack', { baseDir: __dirname }); + project.linkDependency('@embroider/core', { baseDir: __dirname }); + project.linkDependency('@embroider/compat', { baseDir: __dirname }); + + merge(project.files, { + addon: { + components: { + 'hello-world.js': '', + }, + }, + }); + }) + .forEachScenario(async scenario => { + Qmodule(`${scenario.name} addon dummy app`, function (hooks) { + let app: PreparedApp; + let workspaceDir: string; + + hooks.before(async () => { + app = await scenario.prepare(); + await app.execute('cross-env STAGE1_ONLY=true node ./node_modules/ember-cli/bin/ember b'); + workspaceDir = fs.readFileSync(join(app.dir, 'dist', '.stage1-output'), 'utf8'); + }); + + test('dummy app can resolve own addon', function (assert) { + assert.ok(resolve.sync('my-addon/components/hello-world.js', { basedir: workspaceDir })); + }); + }); + });