Skip to content

Commit d066dba

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

File tree

5 files changed

+269
-8
lines changed

5 files changed

+269
-8
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

+210-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,196 @@ 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}}
290+
><HelloWorld />
291+
{{~#if this.more~}}
292+
<div {{on 'click' this.didInsert}}></div>
293+
{{~/if~}}
294+
</div>`,
295+
{
296+
didInsert: didInsert,
297+
modifier: modifier,
298+
more: false,
299+
}
300+
);
301+
302+
this.assertRenderTree([
303+
{
304+
type: 'modifier',
305+
name: 'did-update',
306+
args: { positional: [didInsert], named: {} },
307+
instance: (instance: any) => typeof instance.installModifier === 'function',
308+
template: null,
309+
bounds: this.nodeBounds(this.element.firstChild),
310+
children: [],
311+
},
312+
{
313+
type: 'modifier',
314+
name: 'on',
315+
args: { positional: ['click', didInsert], named: {} },
316+
instance: (instance: any) => typeof instance === 'object',
317+
template: null,
318+
bounds: this.nodeBounds(this.element.firstChild),
319+
children: [],
320+
},
321+
{
322+
type: 'modifier',
323+
name: 'DidInsertModifier',
324+
args: { positional: [didInsert], named: {} },
325+
instance: (instance: any) => typeof instance.installModifier === 'function',
326+
template: null,
327+
bounds: this.nodeBounds(this.element.firstChild),
328+
children: [],
329+
},
330+
{
331+
type: 'modifier',
332+
name: 'did-insert',
333+
args: { positional: [didInsert], named: {} },
334+
instance: (instance: any) => typeof instance.install === 'function',
335+
template: null,
336+
bounds: this.nodeBounds(this.element.firstChild),
337+
children: [],
338+
},
339+
{
340+
type: 'component',
341+
name: 'HelloWorld',
342+
args: { positional: [], named: {} },
343+
instance: (instance: any) => instance !== undefined,
344+
template: '(unknown template module)',
345+
bounds: this.nodeBounds(this.element.firstChild!.firstChild),
346+
children: [],
347+
},
348+
]);
349+
350+
this.rerender({
351+
more: true,
352+
});
353+
354+
this.assertRenderTree([
355+
{
356+
type: 'modifier',
357+
name: 'on',
358+
args: { positional: ['click', didInsert], named: {} },
359+
instance: (instance: any) => typeof instance === 'object',
360+
template: null,
361+
bounds: this.nodeBounds(this.element.firstChild),
362+
children: [],
363+
},
364+
{
365+
type: 'modifier',
366+
name: 'did-update',
367+
args: { positional: [didInsert], named: {} },
368+
instance: (instance: any) => typeof instance.installModifier === 'function',
369+
template: null,
370+
bounds: this.nodeBounds(this.element.firstChild),
371+
children: [],
372+
},
373+
{
374+
type: 'modifier',
375+
name: 'DidInsertModifier',
376+
args: { positional: [didInsert], named: {} },
377+
instance: (instance: any) => typeof instance.installModifier === 'function',
378+
template: null,
379+
bounds: this.nodeBounds(this.element.firstChild),
380+
children: [],
381+
},
382+
{
383+
type: 'modifier',
384+
name: 'did-insert',
385+
args: { positional: [didInsert], named: {} },
386+
instance: (instance: any) => typeof instance.install === 'function',
387+
template: null,
388+
bounds: this.nodeBounds(this.element.firstChild),
389+
children: [],
390+
},
391+
{
392+
type: 'component',
393+
name: 'HelloWorld',
394+
args: { positional: [], named: {} },
395+
instance: (instance: any) => instance !== undefined,
396+
template: '(unknown template module)',
397+
bounds: this.nodeBounds(this.element.firstChild!.firstChild),
398+
children: [],
399+
},
400+
{
401+
type: 'modifier',
402+
name: 'on',
403+
args: { positional: ['click', didInsert], named: {} },
404+
instance: (instance: any) => typeof instance === 'object',
405+
template: null,
406+
bounds: this.nodeBounds(this.element.firstChild!.lastChild),
407+
children: [],
408+
},
409+
]);
410+
411+
this.rerender({
412+
more: false,
413+
});
414+
415+
this.assertRenderTree([
416+
{
417+
type: 'modifier',
418+
name: 'did-update',
419+
args: { positional: [didInsert], named: {} },
420+
instance: (instance: any) => typeof instance.installModifier === 'function',
421+
template: null,
422+
bounds: this.nodeBounds(this.element.firstChild),
423+
children: [],
424+
},
425+
{
426+
type: 'modifier',
427+
name: 'on',
428+
args: { positional: ['click', didInsert], named: {} },
429+
instance: (instance: any) => typeof instance === 'object',
430+
template: null,
431+
bounds: this.nodeBounds(this.element.firstChild),
432+
children: [],
433+
},
434+
{
435+
type: 'modifier',
436+
name: 'DidInsertModifier',
437+
args: { positional: [didInsert], named: {} },
438+
instance: (instance: any) => typeof instance.installModifier === 'function',
439+
template: null,
440+
bounds: this.nodeBounds(this.element.firstChild),
441+
children: [],
442+
},
443+
{
444+
type: 'modifier',
445+
name: 'did-insert',
446+
args: { positional: [didInsert], named: {} },
447+
instance: (instance: any) => typeof instance.install === 'function',
448+
template: null,
449+
bounds: this.nodeBounds(this.element.firstChild),
450+
children: [],
451+
},
452+
{
453+
type: 'component',
454+
name: 'HelloWorld',
455+
args: { positional: [], named: {} },
456+
instance: (instance: any) => instance !== undefined,
457+
template: '(unknown template module)',
458+
bounds: this.nodeBounds(this.element.firstChild!.firstChild),
459+
children: [],
460+
},
461+
]);
462+
}
463+
256464
@test 'getDebugCustomRenderTree works'() {
257465
let bucket1 = {};
258466
let instance1 = {};
@@ -502,7 +710,7 @@ class DebugRenderTreeTest extends RenderTest {
502710
this.assertRenderNode(actualNode, expected, `${actualNode.type}:${actualNode.name}`);
503711
});
504712
} else {
505-
this.assert.deepEqual(actual, [], path);
713+
this.assert.deepEqual(actual, expectedNodes, path);
506714
}
507715
}
508716

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/compiled/opcodes/component.ts

+41-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import {
3737
} from '@glimmer/debug';
3838
import { registerDestructor } from '@glimmer/destroyable';
3939
import { managerHasCapability } from '@glimmer/manager';
40-
import { isConstRef, valueForRef } from '@glimmer/reference';
40+
import { createUnboundRef, isConstRef, REFERENCE, valueForRef } from '@glimmer/reference';
4141
import {
4242
assert,
4343
assign,
@@ -489,8 +489,47 @@ export class ComponentElementOperations implements ElementOperations {
489489
this.attributes[name] = deferred;
490490
}
491491

492-
addModifier(modifier: ModifierInstance): void {
492+
addModifier(vm: InternalVM, modifier: ModifierInstance, capturedArgs: CapturedArguments): void {
493493
this.modifiers.push(modifier);
494+
const env = vm.env;
495+
if (env.debugRenderTree) {
496+
const element = vm.elements().constructing!;
497+
const name =
498+
modifier.definition.resolvedName ||
499+
(modifier.state as any).debugName ||
500+
(modifier.definition.state as any).name ||
501+
'unknown-modifier';
502+
const instance = (modifier.state as any).delegate || modifier.manager;
503+
const args: any = {
504+
positional: [],
505+
named: {},
506+
};
507+
for (const value of capturedArgs.positional) {
508+
if (value && value[REFERENCE]) {
509+
args.positional.push(value);
510+
} else {
511+
args.positional.push(createUnboundRef(value, false));
512+
}
513+
}
514+
for (const [key, value] of Object.entries(capturedArgs.named)) {
515+
args.named[key] = createUnboundRef(value, false);
516+
}
517+
env.debugRenderTree.create(modifier.state as any, {
518+
type: 'modifier',
519+
name,
520+
args,
521+
instance,
522+
});
523+
env.debugRenderTree?.didRender(modifier.state as any, {
524+
parentElement: () => (element as any).parentElement,
525+
firstNode: () => element,
526+
lastNode: () => element,
527+
});
528+
registerDestructor(
529+
modifier.state as any,
530+
() => vm.env.debugRenderTree?.willDestroy(modifier.state as any)
531+
);
532+
}
494533
}
495534

496535
flush(vm: InternalVM): ModifierInstance[] {

packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,12 @@ APPEND_OPCODES.add(Op.Modifier, (vm, { op1: handle }) => {
117117

118118
let { constructing } = vm.elements();
119119

120+
let capturedArgs = args.capture();
120121
let state = manager.create(
121122
owner,
122123
expect(constructing, 'BUG: ElementModifier could not find the element it applies to'),
123124
definition.state,
124-
args.capture()
125+
capturedArgs
125126
);
126127

127128
let instance: ModifierInstance = {
@@ -135,7 +136,7 @@ APPEND_OPCODES.add(Op.Modifier, (vm, { op1: handle }) => {
135136
'BUG: ElementModifier could not find operations to append to'
136137
);
137138

138-
operations.addModifier(instance);
139+
operations.addModifier(vm, instance, capturedArgs);
139140

140141
let tag = manager.getTag(state);
141142

@@ -234,7 +235,7 @@ APPEND_OPCODES.add(Op.DynamicModifier, (vm) => {
234235
'BUG: ElementModifier could not find operations to append to'
235236
);
236237

237-
operations.addModifier(instance);
238+
operations.addModifier(vm, instance, args);
238239

239240
tag = instance.manager.getTag(instance.state);
240241

0 commit comments

Comments
 (0)