diff --git a/packages/@glimmer-workspace/integration-tests/test/strict-mode-test.ts b/packages/@glimmer-workspace/integration-tests/test/strict-mode-test.ts index f00d6096ea1..6aacd407591 100644 --- a/packages/@glimmer-workspace/integration-tests/test/strict-mode-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/strict-mode-test.ts @@ -636,6 +636,91 @@ class DynamicStrictModeTest extends RenderTest { this.assertStableRerender(); } + @test + 'Can use a dynamic component with a changing definition (append position)'(assert: Assert) { + const Foo = defineComponent({}, 'Hello, world!', { + definition: class extends GlimmerishComponent { + override willDestroy() { + assert.step('willDestroy 1 called'); + } + }, + }); + + const Bar = defineComponent({}, 'Hello, earth!', { + definition: class extends GlimmerishComponent { + override willDestroy() { + assert.step('willDestroy 2 called'); + } + }, + }); + + const Baz = defineComponent({}, '{{@Foo}}'); + + let args = trackedObj({ Foo }); + + this.renderComponent(Baz, args); + this.assertHTML('Hello, world!'); + this.assertStableRerender(); + + args['Foo'] = Bar; + + this.rerender(); + this.assertHTML('Hello, earth!'); + this.assertStableRerender(); + assert.verifySteps(['willDestroy 1 called']); + + args['Foo'] = undefined; + + this.rerender(); + this.assertHTML(''); + this.assertStableRerender(); + assert.verifySteps(['willDestroy 2 called']); + } + + @test + 'Can use a dynamic component with a changing definition (append position, with args)'() { + const Foo = defineComponent({}, 'Hello, {{@value}}!'); + const Bar = defineComponent({}, 'Goodbye, {{@value}}!'); + const Baz = defineComponent({}, '{{@Foo value="world"}}'); + + let args = trackedObj({ Foo }); + + this.renderComponent(Baz, args); + this.assertHTML('Hello, world!'); + this.assertStableRerender(); + + args['Foo'] = Bar; + + this.rerender(); + this.assertHTML('Goodbye, world!'); + this.assertStableRerender(); + } + + @test + 'Can use an inline if to swap components in append position'() { + const Ok = defineComponent({}, 'Ok'); + const Ko = defineComponent({}, 'Ko'); + const Foo = defineComponent({ Ok, Ko }, '{{if @isOk Ok Ko}}'); + + let args = trackedObj({ isOk: true }); + + this.renderComponent(Foo, args); + this.assertHTML('Ok'); + this.assertStableRerender(); + + args['isOk'] = false; + + this.rerender(); + this.assertHTML('Ko'); + this.assertStableRerender(); + + args['isOk'] = true; + + this.rerender(); + this.assertHTML('Ok'); + this.assertStableRerender(); + } + @test 'Can use a dynamic component in block position'() { const Foo = defineComponent({}, 'Hello, {{yield}}'); diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/stdlib.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/stdlib.ts index 466add49cc9..4f4b29746ea 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/stdlib.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/stdlib.ts @@ -56,6 +56,7 @@ export function StdAppend( if (typeof nonDynamicAppend === 'number') { when(ContentType.Component, () => { + op(VM_ASSERT_SAME_OP); op(VM_RESOLVE_CURRIED_COMPONENT_OP); op(VM_PUSH_DYNAMIC_COMPONENT_INSTANCE_OP); InvokeBareComponent(op); diff --git a/packages/@glimmer/opcode-compiler/lib/syntax/statements.ts b/packages/@glimmer/opcode-compiler/lib/syntax/statements.ts index 4a57b28a48c..cfafe8dd329 100644 --- a/packages/@glimmer/opcode-compiler/lib/syntax/statements.ts +++ b/packages/@glimmer/opcode-compiler/lib/syntax/statements.ts @@ -6,6 +6,7 @@ import type { WireFormat, } from '@glimmer/interfaces'; import { + VM_ASSERT_SAME_OP, VM_CLOSE_ELEMENT_OP, VM_COMMENT_OP, VM_COMPONENT_ATTR_OP, @@ -218,6 +219,7 @@ STATEMENTS.add(SexpOpcodes.Append, (op, [, value]) => { }, (when) => { when(ContentType.Component, () => { + op(VM_ASSERT_SAME_OP); op(VM_RESOLVE_CURRIED_COMPONENT_OP); op(VM_PUSH_DYNAMIC_COMPONENT_INSTANCE_OP); InvokeNonStaticComponent(op, {