Skip to content

Commit a5c2af3

Browse files
patricklxchancancode
authored andcommitted
add modifiers to debug render tree
1 parent ed002cc commit a5c2af3

File tree

5 files changed

+269
-7
lines changed

5 files changed

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

@@ -67,6 +67,21 @@ class DebugRenderTreeDelegate extends JitRenderDelegate {
6767

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

7287
class DebugRenderTreeTest extends RenderTest {
@@ -327,6 +342,196 @@ class DebugRenderTreeTest extends RenderTest {
327342
],
328343
},
329344
],
345+
}
346+
]);
347+
}
348+
349+
@test modifiers() {
350+
this.registerComponent('Glimmer', 'HelloWorld', 'Hello World');
351+
const didInsert = () => null;
352+
this.registerModifier(
353+
'did-insert',
354+
class {
355+
element?: SimpleElement;
356+
didInsertElement() {}
357+
didUpdate() {}
358+
willDestroyElement() {}
359+
}
360+
);
361+
const modifier = this.defineModifier('did-update');
362+
363+
this.render(
364+
`<div {{on 'click' this.didInsert}} {{did-insert this.didInsert}} {{did-update this.didInsert}} {{this.modifier this.didInsert}}
365+
><HelloWorld />
366+
{{~#if this.more~}}
367+
<div {{on 'click' this.didInsert}}></div>
368+
{{~/if~}}
369+
</div>`,
370+
{
371+
didInsert: didInsert,
372+
modifier: modifier,
373+
more: false,
374+
}
375+
);
376+
377+
this.assertRenderTree([
378+
{
379+
type: 'modifier',
380+
name: 'did-update',
381+
args: { positional: [didInsert], named: {} },
382+
instance: (instance: any) => typeof instance.installModifier === 'function',
383+
template: null,
384+
bounds: this.nodeBounds(this.element.firstChild),
385+
children: [],
386+
},
387+
{
388+
type: 'modifier',
389+
name: 'on',
390+
args: { positional: ['click', didInsert], named: {} },
391+
instance: (instance: any) => typeof instance === 'object',
392+
template: null,
393+
bounds: this.nodeBounds(this.element.firstChild),
394+
children: [],
395+
},
396+
{
397+
type: 'modifier',
398+
name: 'DidInsertModifier',
399+
args: { positional: [didInsert], named: {} },
400+
instance: (instance: any) => typeof instance.installModifier === 'function',
401+
template: null,
402+
bounds: this.nodeBounds(this.element.firstChild),
403+
children: [],
404+
},
405+
{
406+
type: 'modifier',
407+
name: 'did-insert',
408+
args: { positional: [didInsert], named: {} },
409+
instance: (instance: any) => typeof instance.install === 'function',
410+
template: null,
411+
bounds: this.nodeBounds(this.element.firstChild),
412+
children: [],
413+
},
414+
{
415+
type: 'component',
416+
name: 'HelloWorld',
417+
args: { positional: [], named: {} },
418+
instance: (instance: any) => instance !== undefined,
419+
template: '(unknown template module)',
420+
bounds: this.nodeBounds(this.element.firstChild!.firstChild),
421+
children: [],
422+
},
423+
]);
424+
425+
this.rerender({
426+
more: true,
427+
});
428+
429+
this.assertRenderTree([
430+
{
431+
type: 'modifier',
432+
name: 'on',
433+
args: { positional: ['click', didInsert], named: {} },
434+
instance: (instance: any) => typeof instance === 'object',
435+
template: null,
436+
bounds: this.nodeBounds(this.element.firstChild),
437+
children: [],
438+
},
439+
{
440+
type: 'modifier',
441+
name: 'did-update',
442+
args: { positional: [didInsert], named: {} },
443+
instance: (instance: any) => typeof instance.installModifier === 'function',
444+
template: null,
445+
bounds: this.nodeBounds(this.element.firstChild),
446+
children: [],
447+
},
448+
{
449+
type: 'modifier',
450+
name: 'DidInsertModifier',
451+
args: { positional: [didInsert], named: {} },
452+
instance: (instance: any) => typeof instance.installModifier === 'function',
453+
template: null,
454+
bounds: this.nodeBounds(this.element.firstChild),
455+
children: [],
456+
},
457+
{
458+
type: 'modifier',
459+
name: 'did-insert',
460+
args: { positional: [didInsert], named: {} },
461+
instance: (instance: any) => typeof instance.install === 'function',
462+
template: null,
463+
bounds: this.nodeBounds(this.element.firstChild),
464+
children: [],
465+
},
466+
{
467+
type: 'component',
468+
name: 'HelloWorld',
469+
args: { positional: [], named: {} },
470+
instance: (instance: any) => instance !== undefined,
471+
template: '(unknown template module)',
472+
bounds: this.nodeBounds(this.element.firstChild!.firstChild),
473+
children: [],
474+
},
475+
{
476+
type: 'modifier',
477+
name: 'on',
478+
args: { positional: ['click', didInsert], named: {} },
479+
instance: (instance: any) => typeof instance === 'object',
480+
template: null,
481+
bounds: this.nodeBounds(this.element.firstChild!.lastChild),
482+
children: [],
483+
},
484+
]);
485+
486+
this.rerender({
487+
more: false,
488+
});
489+
490+
this.assertRenderTree([
491+
{
492+
type: 'modifier',
493+
name: 'did-update',
494+
args: { positional: [didInsert], named: {} },
495+
instance: (instance: any) => typeof instance.installModifier === 'function',
496+
template: null,
497+
bounds: this.nodeBounds(this.element.firstChild),
498+
children: [],
499+
},
500+
{
501+
type: 'modifier',
502+
name: 'on',
503+
args: { positional: ['click', didInsert], named: {} },
504+
instance: (instance: any) => typeof instance === 'object',
505+
template: null,
506+
bounds: this.nodeBounds(this.element.firstChild),
507+
children: [],
508+
},
509+
{
510+
type: 'modifier',
511+
name: 'DidInsertModifier',
512+
args: { positional: [didInsert], named: {} },
513+
instance: (instance: any) => typeof instance.installModifier === 'function',
514+
template: null,
515+
bounds: this.nodeBounds(this.element.firstChild),
516+
children: [],
517+
},
518+
{
519+
type: 'modifier',
520+
name: 'did-insert',
521+
args: { positional: [didInsert], named: {} },
522+
instance: (instance: any) => typeof instance.install === 'function',
523+
template: null,
524+
bounds: this.nodeBounds(this.element.firstChild),
525+
children: [],
526+
},
527+
{
528+
type: 'component',
529+
name: 'HelloWorld',
530+
args: { positional: [], named: {} },
531+
instance: (instance: any) => instance !== undefined,
532+
template: '(unknown template module)',
533+
bounds: this.nodeBounds(this.element.firstChild!.firstChild),
534+
children: [],
330535
},
331536
]);
332537
}
@@ -519,6 +724,10 @@ class DebugRenderTreeTest extends RenderTest {
519724
assert.deepEqual(this.delegate.getCapturedRenderTree(), [], 'there was no output');
520725
}
521726

727+
defineModifier(name: string) {
728+
return this.delegate.registerCustomModifier(name);
729+
}
730+
522731
nodeBounds(_node: SimpleNode | null): CapturedBounds {
523732
let node = expect(_node, 'BUG: Expected node');
524733

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' | 'keyword';
6+
export type RenderNodeType = 'outlet' | 'engine' | 'route-template' | 'component' | 'modifier' | 'keyword';
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
@@ -144,11 +144,12 @@ APPEND_OPCODES.add(Op.Modifier, (vm, { op1: handle }) => {
144144

145145
let { constructing } = vm.elements();
146146

147+
let capturedArgs = args.capture();
147148
let state = manager.create(
148149
owner,
149150
expect(constructing, 'BUG: ElementModifier could not find the element it applies to'),
150151
definition.state,
151-
args.capture()
152+
capturedArgs
152153
);
153154

154155
let instance: ModifierInstance = {
@@ -162,7 +163,7 @@ APPEND_OPCODES.add(Op.Modifier, (vm, { op1: handle }) => {
162163
'BUG: ElementModifier could not find operations to append to'
163164
);
164165

165-
operations.addModifier(instance);
166+
operations.addModifier(vm, instance, capturedArgs);
166167

167168
let tag = manager.getTag(state);
168169

@@ -263,7 +264,7 @@ APPEND_OPCODES.add(Op.DynamicModifier, (vm) => {
263264
'BUG: ElementModifier could not find operations to append to'
264265
);
265266

266-
operations.addModifier(instance);
267+
operations.addModifier(vm, instance, args);
267268

268269
tag = instance.manager.getTag(instance.state);
269270

0 commit comments

Comments
 (0)