Skip to content

Commit 9bfc4dd

Browse files
Merge pull request #1559 from patricklx/add-modifiers-to-debug-render-tree
add modifiers to debug render tree
2 parents 974fdaa + 1b47565 commit 9bfc4dd

File tree

11 files changed

+277
-17
lines changed

11 files changed

+277
-17
lines changed

packages/@glimmer-workspace/benchmark-env/lib/benchmark/on-modifier.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ class OnModifierManager implements InternalModifierManager<OnModifierState, obje
2929
}
3030

3131
getDebugName() {
32-
return 'on-modifier';
32+
return 'on';
33+
}
34+
35+
getDebugInstance() {
36+
return null;
3337
}
3438

3539
install(state: OnModifierState) {

packages/@glimmer-workspace/integration-tests/lib/modifiers.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,12 @@ export class TestModifierManager
4444
return tag;
4545
}
4646

47-
getDebugName() {
48-
return '<unknown>';
47+
getDebugName({ Klass }: TestModifierDefinitionState) {
48+
return Klass?.name || '<unknown>';
49+
}
50+
51+
getDebugInstance({ instance }: TestModifier) {
52+
return instance;
4953
}
5054

5155
install({ element, args, instance }: TestModifier) {

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

+183-1
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

@@ -331,6 +331,188 @@ class DebugRenderTreeTest extends RenderTest {
331331
]);
332332
}
333333

334+
@test modifiers() {
335+
this.registerComponent('Glimmer', 'HelloWorld', 'Hello World');
336+
const didInsert = () => null;
337+
338+
class DidInsertModifier {
339+
element?: SimpleElement;
340+
didInsertElement() {}
341+
didUpdate() {}
342+
willDestroyElement() {}
343+
}
344+
345+
this.registerModifier('did-insert', DidInsertModifier);
346+
347+
class MyCustomModifier {}
348+
349+
setModifierManager(
350+
() => ({
351+
capabilities: modifierCapabilities('3.22'),
352+
createModifier() {
353+
return new MyCustomModifier();
354+
},
355+
installModifier() {},
356+
updateModifier() {},
357+
destroyModifier() {},
358+
}),
359+
MyCustomModifier
360+
);
361+
362+
const foo = Symbol('foo');
363+
const bar = Symbol('bar');
364+
365+
this.render(
366+
`<div {{on 'click' this.didInsert}} {{did-insert this.foo bar=this.bar}} {{this.modifier this.bar foo=this.foo}}
367+
><HelloWorld />
368+
{{~#if this.more~}}
369+
<div {{on 'click' this.didInsert passive=true}}></div>
370+
{{~/if~}}
371+
</div>`,
372+
{
373+
didInsert: didInsert,
374+
modifier: MyCustomModifier,
375+
foo,
376+
bar,
377+
more: false,
378+
}
379+
);
380+
381+
this.assertRenderTree([
382+
{
383+
type: 'modifier',
384+
name: 'on',
385+
args: { positional: ['click', didInsert], named: {} },
386+
instance: null,
387+
template: null,
388+
bounds: this.nodeBounds(this.element.firstChild),
389+
children: [],
390+
},
391+
{
392+
type: 'modifier',
393+
name: 'DidInsertModifier',
394+
args: { positional: [foo], named: { bar } },
395+
instance: (instance: unknown) => instance instanceof DidInsertModifier,
396+
template: null,
397+
bounds: this.nodeBounds(this.element.firstChild),
398+
children: [],
399+
},
400+
{
401+
type: 'modifier',
402+
name: 'MyCustomModifier',
403+
args: { positional: [bar], named: { foo } },
404+
instance: (instance: unknown) => instance instanceof MyCustomModifier,
405+
template: null,
406+
bounds: this.nodeBounds(this.element.firstChild),
407+
children: [],
408+
},
409+
{
410+
type: 'component',
411+
name: 'HelloWorld',
412+
args: { positional: [], named: {} },
413+
instance: (instance: any) => instance !== undefined,
414+
template: '(unknown template module)',
415+
bounds: this.nodeBounds(this.element.firstChild!.firstChild),
416+
children: [],
417+
},
418+
]);
419+
420+
this.rerender({
421+
more: true,
422+
});
423+
424+
this.assertRenderTree([
425+
{
426+
type: 'modifier',
427+
name: 'on',
428+
args: { positional: ['click', didInsert], named: {} },
429+
instance: null,
430+
template: null,
431+
bounds: this.nodeBounds(this.element.firstChild),
432+
children: [],
433+
},
434+
{
435+
type: 'modifier',
436+
name: 'DidInsertModifier',
437+
args: { positional: [foo], named: { bar } },
438+
instance: (instance: unknown) => instance instanceof DidInsertModifier,
439+
template: null,
440+
bounds: this.nodeBounds(this.element.firstChild),
441+
children: [],
442+
},
443+
{
444+
type: 'modifier',
445+
name: 'MyCustomModifier',
446+
args: { positional: [bar], named: { foo } },
447+
instance: (instance: unknown) => instance instanceof MyCustomModifier,
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+
type: 'modifier',
463+
name: 'on',
464+
args: { positional: ['click', didInsert], named: { passive: true } },
465+
instance: null,
466+
template: null,
467+
bounds: this.nodeBounds(this.element.firstChild!.lastChild),
468+
children: [],
469+
},
470+
]);
471+
472+
this.rerender({
473+
more: false,
474+
});
475+
476+
this.assertRenderTree([
477+
{
478+
type: 'modifier',
479+
name: 'on',
480+
args: { positional: ['click', didInsert], named: {} },
481+
instance: null,
482+
template: null,
483+
bounds: this.nodeBounds(this.element.firstChild),
484+
children: [],
485+
},
486+
{
487+
type: 'modifier',
488+
name: 'DidInsertModifier',
489+
args: { positional: [foo], named: { bar } },
490+
instance: (instance: unknown) => instance instanceof DidInsertModifier,
491+
template: null,
492+
bounds: this.nodeBounds(this.element.firstChild),
493+
children: [],
494+
},
495+
{
496+
type: 'modifier',
497+
name: 'MyCustomModifier',
498+
args: { positional: [bar], named: { foo } },
499+
instance: (instance: unknown) => instance instanceof MyCustomModifier,
500+
template: null,
501+
bounds: this.nodeBounds(this.element.firstChild),
502+
children: [],
503+
},
504+
{
505+
type: 'component',
506+
name: 'HelloWorld',
507+
args: { positional: [], named: {} },
508+
instance: (instance: any) => instance !== undefined,
509+
template: '(unknown template module)',
510+
bounds: this.nodeBounds(this.element.firstChild!.firstChild),
511+
children: [],
512+
},
513+
]);
514+
}
515+
334516
@test 'getDebugCustomRenderTree works'() {
335517
let bucket1 = {};
336518
let instance1 = {};

packages/@glimmer/interfaces/lib/managers/internal/modifier.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface InternalModifierManager<
2323
getTag(modifier: TModifierInstanceState): UpdatableTag | null;
2424

2525
getDebugName(Modifier: TModifierDefinitionState): string;
26+
getDebugInstance(Modifier: TModifierInstanceState): unknown;
2627

2728
// At initial render, the modifier gets a chance to install itself on the
2829
// element it is managing. It can also return a bucket of state that

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ 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' | 'keyword';
6+
export type RenderNodeType =
7+
| 'outlet'
8+
| 'engine'
9+
| 'route-template'
10+
| 'component'
11+
| 'modifier'
12+
| 'keyword';
713

814
export interface RenderNode {
915
type: RenderNodeType;

packages/@glimmer/manager/lib/public/modifier.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -112,17 +112,21 @@ export class CustomModifierManager<O extends Owner, ModifierInstance>
112112
modifier: instance,
113113
};
114114

115-
if (import.meta.env.DEV) {
116-
state.debugName = typeof definition === 'function' ? definition.name : definition.toString();
117-
}
118-
119115
registerDestructor(state, () => delegate.destroyModifier(instance, args));
120116

121117
return state;
122118
}
123119

124-
getDebugName({ debugName }: CustomModifierState<ModifierInstance>) {
125-
return debugName!;
120+
getDebugName(definition: object) {
121+
if (typeof definition === 'function') {
122+
return definition.name || definition.toString();
123+
} else {
124+
return '<unknown>';
125+
}
126+
}
127+
128+
getDebugInstance({ modifier }: CustomModifierState<ModifierInstance>) {
129+
return modifier;
126130
}
127131

128132
getTag({ tag }: CustomModifierState<ModifierInstance>) {

packages/@glimmer/manager/test/managers-test.ts

+4
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,10 @@ module('Managers', () => {
289289
return 'internal';
290290
}
291291

292+
getDebugInstance() {
293+
return null;
294+
}
295+
292296
getDestroyable() {
293297
return null;
294298
}

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

+40-3
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import type { UpdatingVM } from '../../vm';
5656
import type { InternalVM } from '../../vm/append';
5757
import type { BlockArgumentsImpl } from '../../vm/arguments';
5858

59+
import { ConcreteBounds } from '../../bounds';
5960
import { hasCustomDebugRenderTreeLifecycle } from '../../component/interfaces';
6061
import { resolveComponent } from '../../component/resolve';
6162
import { isCurriedType, isCurriedValue, resolveCurriedValue } from '../../curried-value';
@@ -489,8 +490,46 @@ export class ComponentElementOperations implements ElementOperations {
489490
this.attributes[name] = deferred;
490491
}
491492

492-
addModifier(modifier: ModifierInstance): void {
493+
addModifier(vm: InternalVM, modifier: ModifierInstance, capturedArgs: CapturedArguments): void {
493494
this.modifiers.push(modifier);
495+
496+
if (vm.env.debugRenderTree !== undefined) {
497+
const { manager, definition, state } = modifier;
498+
499+
// TODO: we need a stable object for the debugRenderTree as the key, add support for
500+
// the case where the state is a primitive, or if in practice we always have/require
501+
// an object, then change the internal types to reflect that
502+
if (state === null || (typeof state !== 'object' && typeof state !== 'function')) {
503+
return;
504+
}
505+
506+
let { element, constructing } = vm.elements();
507+
let name = manager.getDebugName(definition.state);
508+
let instance = manager.getDebugInstance(state);
509+
510+
assert(constructing, `Expected a constructing element in addModifier`);
511+
512+
let bounds = new ConcreteBounds(element, constructing, constructing);
513+
514+
vm.env.debugRenderTree.create(state, {
515+
type: 'modifier',
516+
name,
517+
args: capturedArgs,
518+
instance,
519+
});
520+
521+
vm.env.debugRenderTree.didRender(state, bounds);
522+
523+
// For tearing down the debugRenderTree
524+
vm.associateDestroyable(state);
525+
526+
vm.updateWith(new DebugRenderTreeUpdateOpcode(state));
527+
vm.updateWith(new DebugRenderTreeDidRenderOpcode(state, bounds));
528+
529+
registerDestructor(state, () => {
530+
vm.env.debugRenderTree?.willDestroy(state);
531+
});
532+
}
494533
}
495534

496535
flush(vm: InternalVM): ModifierInstance[] {
@@ -645,8 +684,6 @@ APPEND_OPCODES.add(Op.GetComponentSelf, (vm, { op1: _state, op2: _names }) => {
645684
instance: valueForRef(selfRef),
646685
});
647686

648-
vm.associateDestroyable(instance);
649-
650687
registerDestructor(instance, () => {
651688
vm.env.debugRenderTree?.willDestroy(instance);
652689
});

0 commit comments

Comments
 (0)