diff --git a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts index 430a6d5b39..b5a962d0a7 100644 --- a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts @@ -253,6 +253,54 @@ class DebugRenderTreeTest extends RenderTest { ]); } + @test 'in-element in tree'() { + this.registerComponent('Glimmer', 'HiWorld', 'Hi World'); + this.registerComponent( + 'Glimmer', + 'HelloWorld', + '{{#in-element this.destinationElement}}{{/in-element}}', + class extends GlimmerishComponent { + get destinationElement() { + return document.getElementById('target'); + } + } + ); + + this.render(`
`); + + this.assertRenderTree([ + { + type: 'component', + name: 'HelloWorld', + args: { positional: [], named: { arg: 'first' } }, + instance: (instance: GlimmerishComponent) => instance.args['arg'] === 'first', + template: '(unknown template module)', + bounds: this.nodeBounds(this.element.firstChild!.nextSibling), + children: [ + { + type: 'keyword', + name: 'in-element', + args: { positional: [this.element.firstChild], named: {} }, + instance: (instance: GlimmerishComponent) => instance === null, + template: null, + bounds: this.nodeBounds(this.element.firstChild!.firstChild, this.element), + children: [ + { + type: 'component', + name: 'HiWorld', + args: { positional: [], named: {} }, + instance: (instance: GlimmerishComponent) => instance, + template: '(unknown template module)', + bounds: this.nodeBounds(this.element.firstChild!.firstChild), + children: [], + }, + ], + }, + ], + }, + ]); + } + @test 'getDebugCustomRenderTree works'() { let bucket1 = {}; let instance1 = {}; @@ -441,12 +489,12 @@ class DebugRenderTreeTest extends RenderTest { assert.deepEqual(this.delegate.getCapturedRenderTree(), [], 'there was no output'); } - nodeBounds(_node: SimpleNode | null): CapturedBounds { + nodeBounds(_node: SimpleNode | null, parent?: SimpleNode): CapturedBounds { let node = expect(_node, 'BUG: Expected node'); return { parentElement: expect( - node.parentNode, + parent || node.parentNode, 'BUG: detached node' ) as unknown as SimpleNode as SimpleElement, firstNode: node as unknown as SimpleNode, @@ -502,7 +550,7 @@ class DebugRenderTreeTest extends RenderTest { this.assertRenderNode(actualNode, expected, `${actualNode.type}:${actualNode.name}`); }); } else { - this.assert.deepEqual(actual, [], path); + this.assert.deepEqual(actual, expectedNodes, path); } } diff --git a/packages/@glimmer/interfaces/lib/runtime/debug-render-tree.d.ts b/packages/@glimmer/interfaces/lib/runtime/debug-render-tree.d.ts index f9f8ae5943..a2edcedce5 100644 --- a/packages/@glimmer/interfaces/lib/runtime/debug-render-tree.d.ts +++ b/packages/@glimmer/interfaces/lib/runtime/debug-render-tree.d.ts @@ -3,7 +3,7 @@ import type { SimpleElement, SimpleNode } from '@simple-dom/interface'; import type { Bounds } from '../dom/bounds.js'; import type { Arguments, CapturedArguments } from './arguments.js'; -export type RenderNodeType = 'outlet' | 'engine' | 'route-template' | 'component'; +export type RenderNodeType = 'outlet' | 'engine' | 'route-template' | 'component' | 'keyword'; export interface RenderNode { type: RenderNodeType; diff --git a/packages/@glimmer/node/lib/serialize-builder.ts b/packages/@glimmer/node/lib/serialize-builder.ts index e161956c36..baa8c1dacc 100644 --- a/packages/@glimmer/node/lib/serialize-builder.ts +++ b/packages/@glimmer/node/lib/serialize-builder.ts @@ -129,7 +129,7 @@ class SerializeBuilder extends NewElementBuilder implements ElementBuilder { element: SimpleElement, cursorId: string, insertBefore: Maybe = null - ): Nullable { + ): RemoteLiveBlock { let { dom } = this; let script = dom.createElement('script'); script.setAttribute('glmr', cursorId); diff --git a/packages/@glimmer/runtime/lib/vm/element-builder.ts b/packages/@glimmer/runtime/lib/vm/element-builder.ts index e4e6d30012..6fb49b907a 100644 --- a/packages/@glimmer/runtime/lib/vm/element-builder.ts +++ b/packages/@glimmer/runtime/lib/vm/element-builder.ts @@ -1,6 +1,7 @@ import type { AttrNamespace, Bounds, + CapturedArguments, Cursor, CursorStackSymbol, ElementBuilder, @@ -12,6 +13,7 @@ import type { Maybe, ModifierInstance, Nullable, + Reference, SimpleComment, SimpleDocumentFragment, SimpleElement, @@ -20,6 +22,7 @@ import type { UpdatableBlock, } from '@glimmer/interfaces'; import { destroy, registerDestructor } from '@glimmer/destroyable'; +import { createConstRef } from '@glimmer/reference'; import { assert, expect, Stack } from '@glimmer/util'; import type { DynamicAttribute } from './attributes/dynamic'; @@ -100,7 +103,6 @@ export class NewElementBuilder implements ElementBuilder { constructor(env: Environment, parentNode: SimpleElement, nextSibling: Nullable) { this.pushElement(parentNode, nextSibling); - this.env = env; this.dom = env.getAppendOperations(); this.updateOperations = env.getDOM(); @@ -214,15 +216,31 @@ export class NewElementBuilder implements ElementBuilder { element: SimpleElement, guid: string, insertBefore: Maybe - ): Nullable { - return this.__pushRemoteElement(element, guid, insertBefore); + ): RemoteLiveBlock { + const block = this.__pushRemoteElement(element, guid, insertBefore); + if (this.env.debugRenderTree) { + const namedArgs: Record = {}; + if (insertBefore) { + namedArgs['insertBefore'] = createConstRef(insertBefore, false); + } + this.env.debugRenderTree.create(block, { + type: 'keyword', + name: 'in-element', + args: { + named: namedArgs, + positional: [createConstRef(element, false)], + } as CapturedArguments, + instance: null, + }); + } + return block; } __pushRemoteElement( element: SimpleElement, _guid: string, insertBefore: Maybe - ): Nullable { + ): RemoteLiveBlock { this.pushElement(element, insertBefore); if (insertBefore === undefined) { @@ -236,12 +254,20 @@ export class NewElementBuilder implements ElementBuilder { return this.pushLiveBlock(block, true); } - popRemoteElement() { - this.popBlock(); + popRemoteElement(): void { + const block = this.popBlock(); this.popElement(); + const parentElement = this.element; + if (this.env.debugRenderTree) { + this.env.debugRenderTree?.didRender(block, { + parentElement: () => parentElement, + firstNode: () => block.firstNode(), + lastNode: () => block.lastNode(), + }); + } } - protected pushElement(element: SimpleElement, nextSibling: Maybe = null) { + protected pushElement(element: SimpleElement, nextSibling: Maybe = null): void { this[CURSOR_STACK].push(new CursorImpl(element, nextSibling)); } @@ -268,7 +294,7 @@ export class NewElementBuilder implements ElementBuilder { return element; } - willCloseElement() { + willCloseElement(): void { this.block().closeElement(); } diff --git a/packages/@glimmer/runtime/lib/vm/rehydrate-builder.ts b/packages/@glimmer/runtime/lib/vm/rehydrate-builder.ts index dbb91bd300..66501924e4 100644 --- a/packages/@glimmer/runtime/lib/vm/rehydrate-builder.ts +++ b/packages/@glimmer/runtime/lib/vm/rehydrate-builder.ts @@ -455,7 +455,7 @@ export class RehydrateBuilder extends NewElementBuilder implements ElementBuilde element: SimpleElement, cursorId: string, insertBefore: Maybe - ): Nullable { + ): RemoteLiveBlock { const marker = this.getMarker(castToBrowser(element, 'HTML'), cursorId); assert(