Skip to content

Migrate the {{element}} helper tests to a plain .gjs test with setupRenderingTest - #21659

Open
NullVoxPopuli-ai-agent wants to merge 4 commits into
emberjs:mainfrom
NullVoxPopuli-ai-agent:nvp/vanilla-gjs-element-test
Open

NullVoxPopuli-ai-agent wants to merge 4 commits into
emberjs:mainfrom
NullVoxPopuli-ai-agent:nvp/vanilla-gjs-element-test

Conversation

@NullVoxPopuli-ai-agent

Copy link
Copy Markdown
Contributor

element-test.gjs is now a test as in an app. It uses setupRenderingTest from ember-qunit, render and settled from @ember/test-helpers, and <template>. It uses nothing from internal-test-helpers.

module('Helpers test: {{element}}', function (hooks) {
  setupRenderingTest(hooks);

  test('it renders a tag with the given tag name', async function (assert) {
    await render(
      <template>{{#let (element "h1") as |Tag|}}<Tag id="content">hello world!</Tag>{{/let}}</template>
    );

    assert.strictEqual(this.element.innerHTML, '<h1 id="content">hello world!</h1>');
  });
});

How to read this PR

Read it commit by commit. GitHub shows the test as one deleted file and one new file in the full diff.

  1. Add @ember/test-helpers to the test build: the same config changes as in Use settled from @ember/test-helpers in the RouterService transitionTo tests #21658.
  2. Add ember-qunit to the test build: the setup that the real setupRenderingTest needs.
  3. Rename element-test.js to element-test.gjs: no content change.
  4. Migrate the {{element}} helper tests to a plain .gjs test: the one file change.

The test file

The 14 test names, the 15 templates and the error patterns are the same.

Before After
moduleFor + RenderingTestCase module + setupRenderingTest(hooks)
template('...', { scope }) <template>...</template>
this.renderComponent(X, { expect }) await render(X) + this.element.innerHTML
runTask(() => (state.x = y)) state.x = y; await settled();
this.assertHTML(html) assert.strictEqual(this.element.innerHTML, html)
this.assertText(text) assert.strictEqual(this.element.textContent, text)
assert.throws(() => render) await assert.rejects(render(...))

Three things are not the same:

  • this.renderComponent() also ran assertStableRerender(). A plain test has no equivalent, so that check is gone.
  • assertHTML ignored the order of attributes, and innerHTML does not. One expected string now has id before type.
  • The build compiles <template>. template() compiled in the browser.

The setup

  • ember-qunit is a devDependency. Its macro config turns off its theme and its container styles, because index.html loads the QUnit styles and tests render into #qunit-fixture.
  • broccoli/app-ember-satisfies-plugin.cjs answers appEmberSatisfies(...) with the version of this repo. @embroider/macros reads that version from the dependencies of the app, and ember-source cannot depend on itself. Without the plugin, render() takes the outlet path and all 14 tests fail.
  • index.html starts QUnit itself, after the tests and testem.js are loaded. ember-qunit turns autostart off on import. It also calls the testem hook on import, and testem throws on a second hook, so testem.js now loads after the tests.
  • index.html imports QUnit by package name, and the dev server pre-bundles it. A test can then import { module, test } from 'qunit'.
  • setup-test-helpers.js gives each test its own Application. An app passes one Application to setApplication(), but moduleFor fails a test that leaves a namespace behind.
  • The same file restores testing mode and the backburner debug flag before each test, because teardownContext() and setupContext() change them. Without that, two Route tests fail.

Two small effects on the whole suite, both from the import of ember-qunit: QUnit.config.testTimeout is 60 seconds, and the QUnit toolbar gets the "Hide container" and "Development mode" checkboxes.

Verification

  • The 14 element tests pass on five builds (development with and without NODE_ENV, production, stable decorators, TypeScript emit) and on five runtime variants (deprecations and optional features).
  • Full suite, 0 failures in each build:
    • development: 9513 tests, 9495 pass, 18 skipped
    • production: 9351 tests, 9286 pass, 65 skipped
    • stable decorators: 9513 tests, 9495 pass, 18 skipped
    • stable decorators with TypeScript emit: 9513 tests, 9495 pass, 18 skipped
  • With the await settled() lines removed, "it renders when the tag name changes" fails.
  • pnpm start (the vite dev server) in headless Chrome: the 14 tests pass, and a moduleFor module passes too.
  • The rename commit passes alone.
  • pnpm type-check:internals, eslint and prettier pass. pnpm build passes and leaves no diff. This repo does not lint or format .gjs files.

Related

🤖 Generated with Claude Code

NullVoxPopuli and others added 4 commits October 4, 2026 12:00
`@ember/test-helpers` was only available in the smoke tests.

- `@ember/test-helpers` is a devDependency of the root package.
- `testDependencies` in rollup.config.mjs lets the test build resolve
  `@ember/test-helpers`, `@ember/test-waiters` and
  `dom-element-descriptors` from node_modules.
- babel.test.config.mjs runs the `@embroider/macros` plugin, because
  those two addons use macros.

`buildMacros()` reads NODE_ENV, but the test build selects its mode with
EMBER_ENV. The `configure` hook enables development mode from EMBER_ENV,
so `isDevelopingApp()` is the same with and without NODE_ENV.

The published build does not use babel.test.config.mjs.

These are the same changes as in emberjs#21658.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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 `<template>`.

The 14 test names, the 15 templates and the error patterns are the same.

| Before | After |
| --- | --- |
| `moduleFor` + `RenderingTestCase` | `module` + `setupRenderingTest(hooks)` |
| `template('...', { scope })` | `<template>...</template>` |
| `this.renderComponent(X, { expect })` | `await render(X)` + `this.element.innerHTML` |
| `runTask(() => (state.x = y))` | `state.x = y; await settled();` |
| `this.assertHTML(html)` | `assert.strictEqual(this.element.innerHTML, html)` |
| `this.assertText(text)` | `assert.strictEqual(this.element.textContent, text)` |
| `assert.throws(() => render)` | `await assert.rejects(render(...))` |

Three things are not the same:

- `this.renderComponent()` also ran `assertStableRerender()`. A plain test
  has no equivalent, so that check is gone.
- `assertHTML` ignored the order of attributes, and `innerHTML` does not.
  One expected string now has `id` before `type`.
- The build compiles `<template>`. `template()` compiled in the browser.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants