diff --git a/packages/compat/src/compat-app-builder.ts b/packages/compat/src/compat-app-builder.ts index fa101ade7..67b5a4118 100644 --- a/packages/compat/src/compat-app-builder.ts +++ b/packages/compat/src/compat-app-builder.ts @@ -361,32 +361,6 @@ export class CompatAppBuilder { }); } - if (type === 'implicit-test-scripts') { - // this is the traditional test-support-suffix.js - result.push({ - kind: 'in-memory', - relativePath: '_testing_suffix_.js', - source: ` - var runningTests=true; - if (typeof Testem !== 'undefined' && (typeof QUnit !== 'undefined' || typeof Mocha !== 'undefined')) { - Testem.hookIntoTestFramework(); - }`, - }); - - // whether or not anybody was actually using @embroider/macros - // explicitly as an addon, we ensure its test-support file is always - // present. - if (!result.find(s => s.kind === 'on-disk' && s.sourcePath.endsWith('embroider-macros-test-support.js'))) { - result.unshift({ - kind: 'on-disk', - sourcePath: require.resolve('@embroider/macros/src/vendor/embroider-macros-test-support'), - mtime: 0, - size: 0, - relativePath: 'embroider-macros-test-support.js', - }); - } - } - return result; } @@ -556,10 +530,8 @@ export class CompatAppBuilder { let testJS = this.testJSEntrypoint(appFiles, prepared); html.insertScriptTag(html.testJavascript, testJS.relativePath, { type: 'module' }); - let implicitTestScriptsAsset = this.implicitTestScriptsAsset(prepared, parentEngine); - if (implicitTestScriptsAsset) { - html.insertScriptTag(html.implicitTestScripts, implicitTestScriptsAsset.relativePath); - } + // virtual test-support.js + html.insertScriptTag(html.implicitTestScripts, '@embroider/core/test-support.js'); let implicitTestStylesAsset = this.implicitTestStylesAsset(prepared, parentEngine); if (implicitTestStylesAsset) { @@ -596,25 +568,6 @@ export class CompatAppBuilder { return asset; } - private implicitTestScriptsAsset( - prepared: Map, - application: AppFiles - ): InternalAsset | undefined { - let testSupportJS = prepared.get('assets/test-support.js'); - if (!testSupportJS) { - let implicitTestScripts = this.impliedAssets('implicit-test-scripts', application); - if (implicitTestScripts.length > 0) { - testSupportJS = new ConcatenatedAsset( - 'assets/test-support.js', - implicitTestScripts, - this.resolvableExtensionsPattern - ); - prepared.set(testSupportJS.relativePath, testSupportJS); - } - } - return testSupportJS; - } - private implicitTestStylesAsset( prepared: Map, application: AppFiles diff --git a/packages/core/src/module-resolver.ts b/packages/core/src/module-resolver.ts index 65a51d5c2..d10debe36 100644 --- a/packages/core/src/module-resolver.ts +++ b/packages/core/src/module-resolver.ts @@ -193,6 +193,7 @@ export class Resolver { request = this.handleFastbootSwitch(request); request = await this.handleGlobalsCompat(request); request = this.handleImplicitModules(request); + request = this.handleImplicitTestScripts(request); request = this.handleRenaming(request); // we expect the specifier to be app relative at this point - must be after handleRenaming request = this.generateFastbootSwitch(request); @@ -421,6 +422,28 @@ export class Resolver { } } + private handleImplicitTestScripts(request: R): R { + //TODO move the extra forwardslash handling out into the vite plugin + const candidates = [ + '@embroider/core/test-support.js', + '/@embroider/core/test-support.js', + './@embroider/core/test-support.js', + ]; + + if (!candidates.includes(request.specifier)) { + return request; + } + + let pkg = this.packageCache.ownerOfFile(request.fromFile); + if (pkg?.root !== this.options.engines[0].root) { + throw new Error( + `bug: found an import of ${request.specifier} in ${request.fromFile}, but this is not the top-level Ember app. The top-level Ember app is the only one that has support for @embroider/core/test-support.js. If you think something should be fixed in Embroider, please open an issue on https://github.com/embroider-build/embroider/issues.` + ); + } + + return logTransition('test-support', request, request.virtualize(resolve(pkg.root, '-embroider-test-support.js'))); + } + private async handleGlobalsCompat(request: R): Promise { if (isTerminal(request)) { return request; diff --git a/packages/core/src/virtual-content.ts b/packages/core/src/virtual-content.ts index 55ac3a903..9e51dc9cf 100644 --- a/packages/core/src/virtual-content.ts +++ b/packages/core/src/virtual-content.ts @@ -2,6 +2,7 @@ import { dirname, basename, resolve, posix, sep, join } from 'path'; import type { Resolver, AddonPackage, Package } from '.'; import { explicitRelative, extensionsPattern } from '.'; import { compile } from './js-handlebars'; +import { decodeImplicitTestScripts, renderImplicitTestScripts } from './virtual-test-support'; const externalESPrefix = '/@embroider/ext-es/'; const externalCJSPrefix = '/@embroider/ext-cjs/'; @@ -40,6 +41,11 @@ export function virtualContent(filename: string, resolver: Resolver): VirtualCon return renderImplicitModules(im, resolver); } + let isImplicitTestScripts = decodeImplicitTestScripts(filename); + if (isImplicitTestScripts) { + return renderImplicitTestScripts(filename, resolver); + } + throw new Error(`not an @embroider/core virtual file: ${filename}`); } diff --git a/packages/core/src/virtual-test-support.ts b/packages/core/src/virtual-test-support.ts new file mode 100644 index 000000000..ff8a6a912 --- /dev/null +++ b/packages/core/src/virtual-test-support.ts @@ -0,0 +1,78 @@ +import type { Package } from '@embroider/shared-internals'; +import type { V2AddonPackage } from '@embroider/shared-internals/src/package'; +import { readFileSync } from 'fs'; +import resolve from 'resolve'; +import type { Engine } from './app-files'; +import type { Resolver } from './module-resolver'; +import type { VirtualContentResult } from './virtual-content'; + +export function decodeImplicitTestScripts(filename: string): boolean { + return filename.endsWith('-embroider-test-support.js'); +} + +export function renderImplicitTestScripts(filename: string, resolver: Resolver): VirtualContentResult { + const owner = resolver.packageCache.ownerOfFile(filename); + if (!owner) { + throw new Error(`Failed to find a valid owner for ${filename}`); + } + return { src: getTestSupport(owner, resolver), watches: [] }; +} + +function getTestSupport(owner: Package, resolver: Resolver): string { + let engineConfig = resolver.owningEngine(owner); + let engine: Engine = { + package: owner, + addons: new Map( + engineConfig.activeAddons.map(addon => [ + resolver.packageCache.get(addon.root) as V2AddonPackage, + addon.canResolveFromFile, + ]) + ), + isApp: true, + modulePrefix: resolver.options.modulePrefix, + appRelativePath: 'NOT_USED_DELETE_ME', + }; + + return generateTestSupport(engine); +} + +function generateTestSupport(engine: Engine): string { + // Add classic addons test-support + let result: string[] = impliedAddonTestSupport(engine); + let hasEmbroiderMacrosTestSupport = result.find(sourcePath => + sourcePath.endsWith('embroider-macros-test-support.js') + ); + result = result.map((sourcePath: string): string => { + let source = readFileSync(sourcePath); + return `${source}`; + }); + + // Add _testing_suffix_.js + result.push(` +var runningTests=true; +if (typeof Testem !== 'undefined' && (typeof QUnit !== 'undefined' || typeof Mocha !== 'undefined')) { + Testem.hookIntoTestFramework(); +}`); + + // whether or not anybody was actually using @embroider/macros explicitly + // as an addon, we ensure its test-support file is always present. + if (!hasEmbroiderMacrosTestSupport) { + result.unshift(`${readFileSync(require.resolve('@embroider/macros/src/vendor/embroider-macros-test-support'))}`); + } + + return result.join('') as string; +} + +function impliedAddonTestSupport(engine: Engine): string[] { + let result: Array = []; + for (let addon of Array.from(engine.addons.keys())) { + let implicitScripts = addon.meta['implicit-test-scripts']; + if (implicitScripts) { + let options = { basedir: addon.root }; + for (let mod of implicitScripts) { + result.push(resolve.sync(mod, options)); + } + } + } + return result; +} diff --git a/packages/vite/src/resolver.ts b/packages/vite/src/resolver.ts index d5ccc34eb..1f9a46d56 100644 --- a/packages/vite/src/resolver.ts +++ b/packages/vite/src/resolver.ts @@ -3,6 +3,7 @@ import { virtualContent, ResolverLoader } from '@embroider/core'; import { RollupModuleRequest, virtualPrefix } from './request'; import assertNever from 'assert-never'; import makeDebug from 'debug'; +import { resolve } from 'path'; const debug = makeDebug('embroider:vite'); @@ -59,5 +60,15 @@ export function resolver(): Plugin { return src; } }, + buildEnd() { + this.emitFile({ + type: 'asset', + fileName: '@embroider/core/test-support.js', + source: virtualContent( + resolve(resolverLoader.resolver.options.engines[0].root, '-embroider-test-support.js'), + resolverLoader.resolver + ).src, + }); + }, }; }