Skip to content

Commit 14f95ce

Browse files
NullVoxPopuliclaude
andcommitted
[BUGFIX beta] Add failing tests for dynamic modifier teardown
A dynamic modifier whose value changes from undefined to a modifier after the first render never runs its destructor when its block is removed or when the render result is destroyed. UpdateDynamicModifierOpcode attaches the new instance's destroyable to itself with associateDestroyableChild(this, destroyable), but the opcode is never registered as a destroyable child of its block. Only block opcodes go through didEnter, which calls associateDestroyable. vm.updateWith only pushes to the updating list. Reported through universal-ember/ember-primitives#805, where the Popover content element leaks @floating-ui/dom observers. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fvnp457DHuZC4fBa2KCiqB
1 parent 46b9b1e commit 14f95ce

2 files changed

Lines changed: 79 additions & 0 deletions

File tree

‎packages/@ember/-internals/glimmer/tests/integration/custom-modifier-manager-test.js‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,35 @@ class ModifierManagerTest extends RenderingTestCase {
105105
runTask(() => set(this.context, 'truthy', true));
106106
}
107107

108+
'@test destroys a dynamic modifier that was set after the initial render'(assert) {
109+
let ModifierClass = setModifierManager(
110+
(owner) => {
111+
return new this.CustomModifierManager(owner);
112+
},
113+
class extends EmberObject {
114+
didInsertElement() {
115+
assert.step('didInsertElement');
116+
}
117+
didUpdate() {}
118+
willDestroyElement() {
119+
assert.step('willDestroyElement');
120+
}
121+
}
122+
);
123+
124+
this.render('{{#if this.show}}<div {{this.dyn}}></div>{{/if}}', {
125+
show: true,
126+
dyn: undefined,
127+
});
128+
assert.verifySteps([]);
129+
130+
runTask(() => set(this.context, 'dyn', ModifierClass));
131+
assert.verifySteps(['didInsertElement']);
132+
133+
runTask(() => set(this.context, 'show', false));
134+
assert.verifySteps(['willDestroyElement']);
135+
}
136+
108137
'@test associates manager even through an inheritance structure'(assert) {
109138
assert.expect(5);
110139
let ModifierClass = setModifierManager(

‎packages/@glimmer-workspace/integration-tests/test/modifiers/dynamic-modifiers-test.ts‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
RenderTest,
88
syntaxErrorFor,
99
test,
10+
trackedObj,
1011
} from '@glimmer-workspace/integration-tests';
1112

1213
class DynamicModifiersResolutionModeTest extends RenderTest {
@@ -199,6 +200,55 @@ class DynamicModifiersResolutionModeTest extends RenderTest {
199200
this.assertHTML('<div>Hello, world!</div>');
200201
this.assertStableRerender();
201202
}
203+
204+
@test
205+
'Destroys a dynamic modifier that was set after the initial render when its block is removed'(
206+
assert: Assert
207+
) {
208+
const foo = defineSimpleModifier(() => {
209+
assert.step('install');
210+
211+
return () => assert.step('destroy');
212+
});
213+
214+
const Bar = defineComponent({}, '{{#if @show}}<div {{@foo}}></div>{{/if}}');
215+
const args = trackedObj({ show: true, foo: undefined });
216+
217+
this.renderComponent(Bar, args);
218+
assert.verifySteps([]);
219+
220+
args['foo'] = foo;
221+
this.rerender();
222+
assert.verifySteps(['install']);
223+
224+
args['show'] = false;
225+
this.rerender();
226+
assert.verifySteps(['destroy']);
227+
}
228+
229+
@test
230+
'Destroys a dynamic modifier that was set after the initial render when the render result is destroyed'(
231+
assert: Assert
232+
) {
233+
const foo = defineSimpleModifier(() => {
234+
assert.step('install');
235+
236+
return () => assert.step('destroy');
237+
});
238+
239+
const Bar = defineComponent({}, '<div {{@foo}}></div>');
240+
const args = trackedObj({ foo: undefined });
241+
242+
this.renderComponent(Bar, args);
243+
assert.verifySteps([]);
244+
245+
args['foo'] = foo;
246+
this.rerender();
247+
assert.verifySteps(['install']);
248+
249+
this.destroy();
250+
assert.verifySteps(['destroy']);
251+
}
202252
}
203253

204254
jitSuite(DynamicModifiersResolutionModeTest);

0 commit comments

Comments
 (0)