diff --git a/packages/@glimmer-workspace/integration-tests/test/helpers/if-inline-test.ts b/packages/@glimmer-workspace/integration-tests/test/helpers/if-inline-test.ts new file mode 100644 index 000000000..0d48fd49c --- /dev/null +++ b/packages/@glimmer-workspace/integration-tests/test/helpers/if-inline-test.ts @@ -0,0 +1,197 @@ +import { + defineComponent, + jitSuite, + RenderTest, + strip, + test, + tracked, +} from '@glimmer-workspace/integration-tests'; + +class IfInlineTest extends RenderTest { + static suiteName = 'Helpers test: inline {{if}}'; + + @test + 'inline if helper updates when tracked property changes'() { + class Person { + @tracked isActive = false; + + toggle() { + this.isActive = !this.isActive; + } + } + + const person = new Person(); + + this.render(strip`
{{if this.person.isActive "Active" "Inactive"}}
`, { person }); + + this.assertHTML('
Inactive
', 'Initial render shows inactive state'); + this.assertStableRerender(); + + person.toggle(); + this.rerender(); + this.assertHTML('
Active
', 'Updates when tracked property changes to true'); + + person.toggle(); + this.rerender(); + this.assertHTML('
Inactive
', 'Updates when tracked property changes to false'); + } + + @test + 'inline if helper with only truthy value updates when tracked property changes'() { + class Person { + @tracked isActive = false; + + toggle() { + this.isActive = !this.isActive; + } + } + + const person = new Person(); + + this.render(strip`
{{if this.person.isActive "Active"}}
`, { person }); + + this.assertHTML('
', 'Initial render shows empty when false'); + this.assertStableRerender(); + + person.toggle(); + this.rerender(); + this.assertHTML('
Active
', 'Updates when tracked property changes to true'); + + person.toggle(); + this.rerender(); + this.assertHTML('
', 'Updates when tracked property changes to false'); + } + + @test + 'inline if helper updates when component argument changes'() { + const TestComponent = defineComponent({}, '{{if @isActive "Active" "Inactive"}}'); + + this.render('', { + isActive: false, + TestComponent, + }); + + this.assertHTML('Inactive', 'Initial render shows inactive state'); + this.assertStableRerender(); + + this.rerender({ isActive: true, TestComponent }); + this.assertHTML('Active', 'Updates when argument changes to true'); + + this.rerender({ isActive: false, TestComponent }); + this.assertHTML('Inactive', 'Updates when argument changes to false'); + } + + @test + 'inline if helper with components updates when tracked property changes'() { + class Person { + @tracked isActive = false; + + toggle() { + this.isActive = !this.isActive; + } + } + + const person = new Person(); + + const Ok = defineComponent({}, '
OK Component
'); + const Ko = defineComponent({}, '
KO Component
'); + + // Create a component with Ok and Ko in scope + const TestContainer = defineComponent({ Ok, Ko }, '
{{if @isActive Ok Ko}}
'); + + this.render('', { person, TestContainer }); + + this.assertHTML('
KO Component
', 'Initial render shows KO component'); + this.assertStableRerender(); + + person.toggle(); + this.rerender(); + this.assertHTML( + '
OK Component
', + 'Updates to OK component when tracked property changes to true' + ); + + person.toggle(); + this.rerender(); + this.assertHTML( + '
KO Component
', + 'Updates to KO component when tracked property changes to false' + ); + } + + @test + 'inline if helper with components updates when component argument changes'() { + const Ok = defineComponent({}, '
OK Component
'); + const Ko = defineComponent({}, '
KO Component
'); + + const TestComponent = defineComponent({ Ok, Ko }, '{{if @isOk Ok Ko}}'); + + this.render('', { isOk: false, TestComponent }); + + this.assertHTML('
KO Component
', 'Initial render shows KO component'); + this.assertStableRerender(); + + this.rerender({ isOk: true, TestComponent }); + this.assertHTML( + '
OK Component
', + 'Updates to OK component when argument changes to true' + ); + + this.rerender({ isOk: false, TestComponent }); + this.assertHTML( + '
KO Component
', + 'Updates to KO component when argument changes to false' + ); + } + + @test + 'comparison with block form if helper using components'() { + class Person { + @tracked isActive = false; + + toggle() { + this.isActive = !this.isActive; + } + } + + const person = new Person(); + + const Ok = defineComponent({}, '
OK Component
'); + const Ko = defineComponent({}, '
KO Component
'); + + // Create a component with Ok and Ko in scope for both inline and block forms + const TestContainer = defineComponent( + { Ok, Ko }, + ` +
+ Inline: {{if @isActive Ok Ko}} + Block: {{#if @isActive}}{{else}}{{/if}} +
+ ` + ); + + this.render('', { person, TestContainer }); + + this.assertHTML( + '
Inline:
KO Component
Block:
KO Component
', + 'Initial render both show KO component' + ); + this.assertStableRerender(); + + person.toggle(); + this.rerender(); + this.assertHTML( + '
Inline:
OK Component
Block:
OK Component
', + 'Both update to OK component when tracked property changes to true' + ); + + person.toggle(); + this.rerender(); + this.assertHTML( + '
Inline:
KO Component
Block:
KO Component
', + 'Both update to KO component when tracked property changes to false' + ); + } +} + +jitSuite(IfInlineTest); diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts index c27b0ca51..0e6b7aa48 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts @@ -285,10 +285,18 @@ APPEND_OPCODES.add(VM_IF_INLINE_OP, (vm) => { vm.stack.push( createComputeRef(() => { - if (toBool(valueForRef(condition))) { - return valueForRef(truthy); + // Explicitly consume the condition reference to ensure tracking + let conditionValue = valueForRef(condition); + + // Also explicitly consume truthy and falsy references to ensure proper tracking + // when they are component references + let truthyValue = valueForRef(truthy); + let falsyValue = valueForRef(falsy); + + if (toBool(conditionValue)) { + return truthyValue; } else { - return valueForRef(falsy); + return falsyValue; } }) );