Skip to content

Commit c09b319

Browse files
Merge pull request #21591 from NullVoxPopuli-ai-agent/nvp/fix-dynamic-modifier-teardown
[BUGFIX lts] Destroy dynamic modifiers that were set after the initial render
2 parents 46b9b1e + d8a8382 commit c09b319

3 files changed

Lines changed: 85 additions & 1 deletion

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);

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,12 @@ APPEND_OPCODES.add(VM_DYNAMIC_MODIFIER_OP, (vm) => {
296296
}
297297

298298
if (!isConstRef(ref) || tag) {
299-
return vm.updateWith(new UpdateDynamicModifierOpcode(tag, instance, instanceRef));
299+
let updateOpcode = new UpdateDynamicModifierOpcode(tag, instance, instanceRef);
300+
301+
// The block must own this opcode, or instances created on update are never destroyed.
302+
vm.associateDestroyable(updateOpcode);
303+
304+
return vm.updateWith(updateOpcode);
300305
}
301306
});
302307

0 commit comments

Comments
 (0)