From e35843a09d36d7cd35228e8735d9ac88d25dc3e2 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Fri, 25 Sep 2026 10:17:00 -0400 Subject: [PATCH 1/2] [BUGFIX release] Add failing test for captureRenderTree with renderComponent Components rendered with `renderComponent` are missing from `captureRenderTree(owner)`. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../application/debug-render-tree-test.ts | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/packages/@ember/-internals/glimmer/tests/integration/application/debug-render-tree-test.ts b/packages/@ember/-internals/glimmer/tests/integration/application/debug-render-tree-test.ts index ebec9f08f2d..dcf6e563c09 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/application/debug-render-tree-test.ts +++ b/packages/@ember/-internals/glimmer/tests/integration/application/debug-render-tree-test.ts @@ -1,7 +1,7 @@ import { ApplicationTestCase, ModuleBasedTestResolver, moduleFor } from 'internal-test-helpers'; import { ENV } from '@ember/-internals/environment'; -import { Component, setComponentManager } from '@ember/-internals/glimmer'; +import { Component, renderComponent, setComponentManager } from '@ember/-internals/glimmer'; import type { InternalOwner } from '@ember/-internals/owner'; import Route from '@ember/routing/route'; import Controller from '@ember/controller'; @@ -1127,6 +1127,53 @@ if (ENV._DEBUG_RENDER_TREE) { ]); } + async '@test components rendered with renderComponent'() { + await this.visit('/'); + + let HelloWorld = setComponentTemplate( + precompileTemplate('Hello {{@name}}', { strictMode: true }), + templateOnly() + ); + let Root = setComponentTemplate( + precompileTemplate('', { + strictMode: true, + scope: () => ({ HelloWorld }), + }), + templateOnly() + ); + let into = document.createElement('div'); + + let result = runTask(() => renderComponent(Root, { owner: this.owner, into })); + + let roots = captureRenderTree(this.owner).filter((node) => node.type === 'component'); + + this.assertRenderNodes( + roots, + [ + { + type: 'component', + name: '{ROOT}', + args: { positional: [], named: {} }, + instance: null, + bounds: this.elementBounds(into), + children: [ + { + type: 'component', + name: 'HelloWorld', + args: { positional: [], named: { name: 'world' } }, + instance: null, + bounds: this.elementBounds(into), + children: [], + }, + ], + }, + ], + 'root' + ); + + runTask(() => result.destroy()); + } + async '@test components'() { this.add( 'template:application', From f6aff5d3d583adddf7449ab65c12a214cf25e9d1 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Fri, 25 Sep 2026 10:17:00 -0400 Subject: [PATCH 2/2] [BUGFIX release] Include renderComponent roots in captureRenderTree `renderComponent` renders with its own renderer for each owner. `captureRenderTree` only read the owner's `renderer:-dom`, so those roots never showed up. `@ember/test-helpers` 5.5.0 renders through `renderComponent` on Ember 6.8 and newer. Since its release, `captureRenderTree` in a rendering test returns nothing, which broke the nightly smoke tests on `beta` and `release`. The renderer now registers itself with `@ember/debug`, and `captureRenderTree` includes its roots. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../-internals/glimmer/lib/base-renderer.ts | 2 ++ .../@ember/debug/lib/capture-render-tree.ts | 30 ++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/@ember/-internals/glimmer/lib/base-renderer.ts b/packages/@ember/-internals/glimmer/lib/base-renderer.ts index 955511c4389..d4069d23cf4 100644 --- a/packages/@ember/-internals/glimmer/lib/base-renderer.ts +++ b/packages/@ember/-internals/glimmer/lib/base-renderer.ts @@ -1,6 +1,7 @@ import { ENV } from '@ember/-internals/environment/lib/env'; import type { InternalOwner } from '@ember/-internals/owner'; import { assert } from '@ember/debug'; +import { registerComponentRenderer } from '@ember/debug/lib/capture-render-tree'; import { _backburner, _getCurrentRunLoop } from '@ember/runloop'; import { associateDestroyableChild, @@ -516,6 +517,7 @@ export function renderComponent( hasDOM: env && 'hasDOM' in env ? Boolean(env?.['hasDOM']) : true, }); RENDERER_CACHE.set(owner, renderer); + registerComponentRenderer(owner, renderer); } /** diff --git a/packages/@ember/debug/lib/capture-render-tree.ts b/packages/@ember/debug/lib/capture-render-tree.ts index 03c5cd4b3fa..13285172bc3 100644 --- a/packages/@ember/debug/lib/capture-render-tree.ts +++ b/packages/@ember/debug/lib/capture-render-tree.ts @@ -1,7 +1,21 @@ import type { Renderer } from '@ember/-internals/glimmer/lib/renderer'; +import type { BaseRenderer } from '@ember/-internals/glimmer/lib/base-renderer'; import type Owner from '@ember/owner'; import type { CapturedRenderNode } from '@glimmer/interfaces'; +/** + * `renderComponent` renders with its own renderer per owner, + * separate from the owner's `renderer:-dom`. + * + * The renderer registers itself here, + * because importing it would be a circular import. + */ +const COMPONENT_RENDERERS = new WeakMap(); + +export function registerComponentRenderer(owner: object, renderer: BaseRenderer): void { + COMPONENT_RENDERERS.set(owner, renderer); +} + /** @module @ember/debug */ @@ -19,14 +33,22 @@ import type { CapturedRenderNode } from '@glimmer/interfaces'; @since 3.14.0 */ export default function captureRenderTree(app: Owner): CapturedRenderNode[] { - let domRenderer = app.lookup('renderer:-dom') as Renderer; + let domRenderer = app.lookup('renderer:-dom') as Renderer | undefined; + let componentRenderer = COMPONENT_RENDERERS.get(app); - if (!domRenderer) { + if (!domRenderer && !componentRenderer) { throw new Error(`BUG: owner is missing renderer`); } // SAFETY: Ideally we'd assert here but that causes awkward circular requires since this is also in @ember/debug. // This is only for debug stuff so not very risky. - let renderer = domRenderer; - return renderer.debugRenderTree.capture(); + let nodes: CapturedRenderNode[] = domRenderer ? domRenderer.debugRenderTree.capture() : []; + + if (componentRenderer) { + for (let node of componentRenderer.debugRenderTree.capture()) { + nodes.push(node); + } + } + + return nodes; }