From a77fdba9b1a8e1d2e4ed6de8df1b9379ee0c8d21 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Sun, 4 Oct 2026 12:20:00 -0400 Subject: [PATCH 1/3] Add ember-qunit to the test build A test in this repo can now use `setupRenderingTest` from `ember-qunit` and `render` from `@ember/test-helpers`, as a test in an app does. Dependencies: - `ember-qunit` is a devDependency, and `testDependencies` in rollup.config.mjs lets the test build resolve it. - The macro config of `ember-qunit` turns off its theme and its container styles. index.html loads the QUnit styles, and tests render into `#qunit-fixture`. `appEmberSatisfies`: `@ember/test-helpers` renders with `renderComponent` only if `appEmberSatisfies('>=6.8.0-alpha.1')` is true. That macro reads the ember-source version from the dependencies of the app. Here the app is ember-source, which cannot depend on itself, so the macro was false and `render()` took the outlet path, which fails on this version. broccoli/app-ember-satisfies-plugin.cjs answers the macro with the version of this repo. index.html: - `ember-qunit` turns `QUnit.config.autostart` off when it is imported. The page now sets that itself and calls `QUnit.start()` after all tests are loaded. - `ember-qunit` calls `Testem.hookIntoTestFramework()` when it is imported, and testem throws on a second hook. Before, the order of testem.js and the test modules depended on load timing. Now testem.js loads after the tests, and QUnit starts after testem.js. - QUnit is imported by package name, and the dev server pre-bundles it. A test can then `import { module, test } from 'qunit'`, and the page and the tests share one copy. setup-test-helpers.js: - An app gives one `Application` to `setApplication()`. `moduleFor` fails a test that leaves a namespace behind, so here each test gets its own `Application`, which is destroyed with its instance. - `teardownContext()` turns testing mode off, and `setupContext()` leaves the backburner debug mode on. A `QUnit.testStart` callback restores both. Without it, two `Route` tests fail. Co-Authored-By: Claude Opus 5.5 (1M context) --- babel.test.config.mjs | 14 ++++- broccoli/app-ember-satisfies-plugin.cjs | 32 +++++++++++ index.html | 20 +++++-- package.json | 1 + .../lib/ember-dev/setup-test-helpers.js | 55 +++++++++++++++++++ pnpm-lock.yaml | 3 + rollup.config.mjs | 1 + vite.config.mjs | 2 +- 8 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 broccoli/app-ember-satisfies-plugin.cjs create mode 100644 packages/internal-test-helpers/lib/ember-dev/setup-test-helpers.js diff --git a/babel.test.config.mjs b/babel.test.config.mjs index dad707aeaf6..b2705501e39 100644 --- a/babel.test.config.mjs +++ b/babel.test.config.mjs @@ -14,12 +14,17 @@ import { buildMacros } from '@embroider/macros/babel'; // eslint-disable-next-line no-redeclare const require = createRequire(import.meta.url); const buildDebugMacroPlugin = require('./broccoli/build-debug-macro-plugin.cjs'); +const appEmberSatisfiesPlugin = require('./broccoli/app-ember-satisfies-plugin.cjs'); const isProduction = process.env.EMBER_ENV === 'production'; -// @ember/test-helpers and @ember/test-waiters use @embroider/macros. +// @ember/test-helpers, @ember/test-waiters and ember-qunit use @embroider/macros. // // buildMacros() reads NODE_ENV, but our builds select the mode with EMBER_ENV. const macros = buildMacros({ + setConfig: { + // index.html loads the QUnit styles, and tests render into #qunit-fixture. + 'ember-qunit': { theme: 'none', disableContainerStyles: true }, + }, configure(config) { if (!isProduction) { config.enablePackageDevelopment(process.cwd()); @@ -39,5 +44,10 @@ export default { ], ], - plugins: [...baseConfig.plugins, ...macros.babelMacros, ...buildDebugMacroPlugin(!isProduction)], + plugins: [ + ...baseConfig.plugins, + appEmberSatisfiesPlugin, + ...macros.babelMacros, + ...buildDebugMacroPlugin(!isProduction), + ], }; diff --git a/broccoli/app-ember-satisfies-plugin.cjs b/broccoli/app-ember-satisfies-plugin.cjs new file mode 100644 index 00000000000..c1217c19000 --- /dev/null +++ b/broccoli/app-ember-satisfies-plugin.cjs @@ -0,0 +1,32 @@ +const semver = require('semver'); +const { version } = require('../package.json'); + +/* + `appEmberSatisfies` from @embroider/macros reads the ember-source version + from the dependencies of the app. + + In this repo the app is ember-source, and a package cannot depend on itself. + So this plugin answers the macro with the version of this repo, before + @embroider/macros sees it. +*/ +module.exports = function appEmberSatisfiesPlugin({ types: t }) { + return { + name: 'app-ember-satisfies', + visitor: { + Program(program) { + program.traverse({ + CallExpression(call) { + if (!call.get('callee').referencesImport('@embroider/macros', 'appEmberSatisfies')) { + return; + } + + let [range] = call.node.arguments; + let satisfied = semver.satisfies(version, range.value, { includePrerelease: true }); + + call.replaceWith(t.booleanLiteral(satisfied)); + }, + }); + }, + }, + }; +}; diff --git a/index.html b/index.html index ba2b0bcf2fe..d67e07a7834 100644 --- a/index.html +++ b/index.html @@ -7,9 +7,14 @@
diff --git a/package.json b/package.json index 26b3917add8..ee5d718b1b7 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,7 @@ "ember-cli-browserstack": "^4.0.0", "ember-cli-dependency-checker": "^3.3.1", "ember-cli-yuidoc": "^0.9.1", + "ember-qunit": "^9.1.0", "eslint": "^9.21.0", "eslint-import-resolver-node": "^0.3.10", "eslint-plugin-disable-features": "^0.1.3", diff --git a/packages/internal-test-helpers/lib/ember-dev/setup-test-helpers.js b/packages/internal-test-helpers/lib/ember-dev/setup-test-helpers.js new file mode 100644 index 00000000000..da75c506bb1 --- /dev/null +++ b/packages/internal-test-helpers/lib/ember-dev/setup-test-helpers.js @@ -0,0 +1,55 @@ +import Application from '@ember/application'; +import { setTesting } from '@ember/debug'; +import { _backburner } from '@ember/runloop'; +import { setApplication } from '@ember/test-helpers'; +import { registerDestructor } from '@glimmer/destroyable'; +import { ModuleBasedResolver } from '../test-resolver'; + +/* + The setup that the `tests/test-helper.js` of an app does for + `@ember/test-helpers` and `ember-qunit`. + + An app gives one `Application` to `setApplication()`, for all tests. + + This test suite cannot keep an `Application` between tests, because + `moduleFor` fails a test that leaves a namespace behind. So each test + gets its own `Application`, which is destroyed with its instance. + + This file is JavaScript, because the type declarations of + `@ember/test-helpers` do not resolve the `@ember/*` packages in this repo. +*/ +export default function setupTestHelpers() { + let options = { + autoboot: false, + rootElement: '#qunit-fixture', + Resolver: ModuleBasedResolver, + }; + + let applicationPerTest = { + Resolver: ModuleBasedResolver, + + boot() { + return Promise.resolve(this); + }, + + buildInstance() { + let application = Application.create(options); + let instance = application.buildInstance(); + + registerDestructor(instance, () => application.destroy()); + + return { + boot: () => application.boot().then(() => instance.boot()), + }; + }, + }; + + setApplication(applicationPerTest); + + // `teardownContext()` turns testing mode off, and `setupContext()` leaves + // the backburner debug mode on. + QUnit.testStart(() => { + setTesting(true); + _backburner.DEBUG = false; + }); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f53d3c07d5..2fef94be9d6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -169,6 +169,9 @@ importers: ember-cli-yuidoc: specifier: ^0.9.1 version: 0.9.1 + ember-qunit: + specifier: ^9.1.0 + version: 9.1.0(@ember/test-helpers@5.5.0(@babel/core@7.29.7))(qunit@2.26.0) eslint: specifier: ^9.21.0 version: 9.39.4 diff --git a/rollup.config.mjs b/rollup.config.mjs index 95266380d4d..1162c7291b1 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -19,6 +19,7 @@ const testDependencies = [ '@ember/test-helpers', '@ember/test-waiters', 'dom-element-descriptors', + 'ember-qunit', 'qunit', 'vite', 'js-reporters', diff --git a/vite.config.mjs b/vite.config.mjs index d610bfe1619..768765fc057 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -67,7 +67,7 @@ export default defineConfig(({ mode }) => { }, ], }, - optimizeDeps: { noDiscovery: true, include: ['expect-type'] }, + optimizeDeps: { noDiscovery: true, include: ['expect-type', 'qunit'] }, publicDir: 'tests/public', build, From 55670ee4759ea92fad5bac0bcab6e288c27de31f Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Sun, 4 Oct 2026 12:20:17 -0400 Subject: [PATCH 2/3] Rename element-test.js to element-test.gjs The content is the same. The next commit rewrites the file, and a separate rename keeps that diff readable. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../integration/helpers/{element-test.js => element-test.gjs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/@ember/-internals/glimmer/tests/integration/helpers/{element-test.js => element-test.gjs} (100%) diff --git a/packages/@ember/-internals/glimmer/tests/integration/helpers/element-test.js b/packages/@ember/-internals/glimmer/tests/integration/helpers/element-test.gjs similarity index 100% rename from packages/@ember/-internals/glimmer/tests/integration/helpers/element-test.js rename to packages/@ember/-internals/glimmer/tests/integration/helpers/element-test.gjs From cab58a29a1fb5ce71e9cfdb2727b7c20f2938107 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Sun, 4 Oct 2026 12:20:17 -0400 Subject: [PATCH 3/3] Migrate the {{element}} helper tests to a plain .gjs test The file is now a test as in an app: `module` and `test` from `qunit`, `setupRenderingTest` from `ember-qunit`, `render` and `settled` from `@ember/test-helpers`, and `