Skip to content

Commit 8b98f28

Browse files
committed
add modifiers to debug render tree
1 parent dc76897 commit 8b98f28

File tree

4 files changed

+143
-4
lines changed

4 files changed

+143
-4
lines changed

packages/@glimmer-workspace/integration-tests/lib/setup-harness.ts

+13
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,19 @@ export async function setupQunit() {
6363
qunit.moduleDone(pause);
6464
}
6565

66+
// @ts-expect-error missing in types, does exist: https://api.qunitjs.com/callbacks/QUnit.on/#the-testend-event
67+
QUnit.on('testEnd', (testEnd) => {
68+
if (testEnd.status === 'failed') {
69+
testEnd.errors.forEach((assertion: any) => {
70+
console.error(assertion.stack);
71+
// message: speedometer
72+
// actual: 75
73+
// expected: 88
74+
// stack: at dmc.test.js:12
75+
});
76+
}
77+
});
78+
6679
qunit.done(({ failed }) => {
6780
if (failed > 0) {
6881
console.log('[HARNESS] fail');

packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts

+91-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type {
99
SimpleNode,
1010
} from '@glimmer/interfaces';
1111
import type { TemplateOnlyComponent } from '@glimmer/runtime';
12-
import { setComponentTemplate } from '@glimmer/manager';
12+
import { modifierCapabilities, setComponentTemplate, setModifierManager } from '@glimmer/manager';
1313
import { EMPTY_ARGS, templateOnlyComponent, TemplateOnlyComponentManager } from '@glimmer/runtime';
1414
import { assign, expect } from '@glimmer/util';
1515

@@ -66,9 +66,27 @@ class DebugRenderTreeDelegate extends JitRenderDelegate {
6666

6767
this.registry.register('component', name, definition);
6868
}
69+
70+
registerCustomModifier(name: string) {
71+
const r = setModifierManager(
72+
() => ({
73+
capabilities: modifierCapabilities('3.22'),
74+
createModifier() {},
75+
installModifier() {},
76+
updateModifier() {},
77+
destroyModifier() {},
78+
}),
79+
class DidInsertModifier {}
80+
);
81+
this.registry.register('modifier', name, r);
82+
return r;
83+
}
6984
}
7085

7186
class DebugRenderTreeTest extends RenderTest {
87+
defineModifier(name: string) {
88+
return this.delegate.registerCustomModifier(name);
89+
}
7290
static suiteName = 'Application test: debug render tree';
7391

7492
declare delegate: DebugRenderTreeDelegate;
@@ -253,6 +271,77 @@ class DebugRenderTreeTest extends RenderTest {
253271
]);
254272
}
255273

274+
@test modifiers() {
275+
this.registerComponent('Glimmer', 'HelloWorld', 'Hello World');
276+
const didInsert = () => null;
277+
this.registerModifier(
278+
'did-insert',
279+
class {
280+
element?: SimpleElement;
281+
didInsertElement() {}
282+
didUpdate() {}
283+
willDestroyElement() {}
284+
}
285+
);
286+
const modifier = this.defineModifier('did-update');
287+
288+
this.render(
289+
`<div {{on 'click' this.didInsert}} {{did-insert this.didInsert}} {{did-update this.didInsert}} {{this.modifier this.didInsert}}><HelloWorld /></div>`,
290+
{
291+
didInsert: didInsert,
292+
modifier: modifier,
293+
}
294+
);
295+
296+
this.assertRenderTree([
297+
{
298+
type: 'modifier',
299+
name: 'did-insert',
300+
args: { positional: [didInsert], named: {} },
301+
instance: (instance: any) => typeof instance.didInsertElement === 'function',
302+
template: null,
303+
bounds: this.nodeBounds(this.element.firstChild),
304+
children: [],
305+
},
306+
{
307+
type: 'modifier',
308+
name: 'did-update',
309+
args: { positional: [didInsert], named: {} },
310+
instance: (instance: any) => typeof instance.installModifier === 'function',
311+
template: null,
312+
bounds: this.nodeBounds(this.element.firstChild),
313+
children: [],
314+
},
315+
{
316+
type: 'modifier',
317+
name: 'DidInsertModifier',
318+
args: { positional: [didInsert], named: {} },
319+
instance: (instance: any) => typeof instance.installModifier === 'function',
320+
template: null,
321+
bounds: this.nodeBounds(this.element.firstChild),
322+
children: [],
323+
},
324+
{
325+
type: 'modifier',
326+
name: 'on',
327+
args: { positional: ['click', didInsert], named: {} },
328+
instance: (instance: any) => instance === undefined,
329+
template: null,
330+
bounds: this.nodeBounds(this.element.firstChild),
331+
children: [],
332+
},
333+
{
334+
type: 'component',
335+
name: 'HelloWorld',
336+
args: { positional: [], named: {} },
337+
instance: (instance: any) => instance !== undefined,
338+
template: '(unknown template module)',
339+
bounds: this.nodeBounds(this.element.firstChild!.firstChild),
340+
children: [],
341+
},
342+
]);
343+
}
344+
256345
@test 'getDebugCustomRenderTree works'() {
257346
let bucket1 = {};
258347
let instance1 = {};
@@ -502,7 +591,7 @@ class DebugRenderTreeTest extends RenderTest {
502591
this.assertRenderNode(actualNode, expected, `${actualNode.type}:${actualNode.name}`);
503592
});
504593
} else {
505-
this.assert.deepEqual(actual, [], path);
594+
this.assert.deepEqual(actual, expectedNodes, path);
506595
}
507596
}
508597

packages/@glimmer/interfaces/lib/runtime/debug-render-tree.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { SimpleElement, SimpleNode } from '@simple-dom/interface';
33
import type { Bounds } from '../dom/bounds.js';
44
import type { Arguments, CapturedArguments } from './arguments.js';
55

6-
export type RenderNodeType = 'outlet' | 'engine' | 'route-template' | 'component';
6+
export type RenderNodeType = 'outlet' | 'engine' | 'route-template' | 'component' | 'modifier';
77

88
export interface RenderNode {
99
type: RenderNodeType;

packages/@glimmer/runtime/lib/vm/element-builder.ts

+38-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type {
2020
UpdatableBlock,
2121
} from '@glimmer/interfaces';
2222
import { destroy, registerDestructor } from '@glimmer/destroyable';
23+
import { createUnboundRef, REFERENCE } from '@glimmer/reference';
2324
import { assert, expect, Stack } from '@glimmer/util';
2425

2526
import type { DynamicAttribute } from './attributes/dynamic';
@@ -195,8 +196,8 @@ export class NewElementBuilder implements ElementBuilder {
195196
this.constructing = null;
196197
this.operations = null;
197198

198-
this.pushModifiers(modifiers);
199199
this.pushElement(element, null);
200+
this.pushModifiers(modifiers);
200201
this.didOpenElement(element);
201202
}
202203

@@ -247,6 +248,42 @@ export class NewElementBuilder implements ElementBuilder {
247248

248249
private pushModifiers(modifiers: Nullable<ModifierInstance[]>): void {
249250
this.modifierStack.push(modifiers);
251+
if (this.env.debugRenderTree && modifiers?.length) {
252+
for (const modifier of modifiers) {
253+
const state = {};
254+
const name =
255+
modifier.definition.resolvedName ||
256+
(modifier.state as any).debugName ||
257+
'unknown-modifier';
258+
const instance = (modifier.state as any).instance || (modifier.state as any).delegate;
259+
const element = this.element;
260+
const args: any = {
261+
positional: [],
262+
named: {},
263+
};
264+
for (const value of (modifier.state as any).args.positional) {
265+
if (value && value[REFERENCE]) {
266+
args.positional.push(value);
267+
} else {
268+
args.positional.push(createUnboundRef(value, false));
269+
}
270+
}
271+
for (const [key, value] of Object.entries((modifier.state as any)?.args.named)) {
272+
args.named[key] = createUnboundRef(value, false);
273+
}
274+
this.env.debugRenderTree?.create(state, {
275+
type: 'modifier',
276+
name,
277+
args: args,
278+
instance,
279+
});
280+
this.env.debugRenderTree?.didRender(state, {
281+
parentElement: () => (element as any).parentElement,
282+
firstNode: () => element,
283+
lastNode: () => element,
284+
});
285+
}
286+
}
250287
}
251288

252289
private popModifiers(): Nullable<ModifierInstance[]> {

0 commit comments

Comments
 (0)