diff --git a/packages/@glimmer/integration-tests/test/attributes-test.ts b/packages/@glimmer/integration-tests/test/attributes-test.ts index 20408169aa..279ae2d081 100644 --- a/packages/@glimmer/integration-tests/test/attributes-test.ts +++ b/packages/@glimmer/integration-tests/test/attributes-test.ts @@ -1,6 +1,7 @@ import { normalizeProperty } from '@glimmer/runtime'; -import { assertElement, hasAttribute, jitSuite, RenderTest, test } from '..'; +import { assertElement, hasAttribute, jitSuite, RenderTest, test, tracked } from '..'; import { Namespace, SimpleElement } from '@simple-dom/interface'; +import { castToBrowser, expect } from '@glimmer/util'; export class AttributesTests extends RenderTest { static suiteName = 'Attributes'; @@ -239,6 +240,40 @@ export class AttributesTests extends RenderTest { this.assertStableNodes(); } + @test + 'handles successive updates to the same value'() { + class Model { + @tracked value = ''; + } + + let model = new Model(); + + this.render('', { model }); + this.assert.equal(this.readDOMAttr('value'), ''); + this.assertStableRerender(); + + let inputElement = castToBrowser( + expect(this.element.firstChild, 'expected input to exist'), + 'input' + ); + + inputElement.value = 'bar'; + this.assert.equal(this.readDOMAttr('value'), 'bar'); + + model.value = 'foo'; + this.rerender(); + this.assert.equal(this.readDOMAttr('value'), 'foo'); + this.assertStableNodes(); + + inputElement.value = 'bar'; + this.assert.equal(this.readDOMAttr('value'), 'bar'); + + model.value = 'foo'; + this.rerender(); + this.assert.equal(this.readDOMAttr('value'), 'foo'); + this.assertStableNodes(); + } + @test 'input[checked] prop updates when set to undefined'() { this.registerHelper('if', (params) => { diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts index 48c2205afb..4c9bb5c9c5 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts @@ -544,7 +544,7 @@ function setDeferredAttr( .elements() .setDynamicAttribute(name, valueForRef(value), trusting, namespace); if (!isConstRef(value)) { - vm.updateWith(new UpdateDynamicAttributeOpcode(value, attribute)); + vm.updateWith(new UpdateDynamicAttributeOpcode(value, attribute, vm.env)); } } } diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts index 73f879f2fd..4b6b9fe896 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts @@ -1,4 +1,4 @@ -import { Reference, valueForRef, isConstRef } from '@glimmer/reference'; +import { Reference, valueForRef, isConstRef, createComputeRef } from '@glimmer/reference'; import { Revision, Tag, valueForTag, validateTag, consumeTag } from '@glimmer/validator'; import { check, @@ -17,6 +17,7 @@ import { Owner, CurriedType, ModifierDefinitionState, + Environment, } from '@glimmer/interfaces'; import { $t0 } from '@glimmer/vm'; import { APPEND_OPCODES, UpdatingOpcode } from '../../opcodes'; @@ -237,27 +238,34 @@ APPEND_OPCODES.add(Op.DynamicAttr, (vm, { op1: _name, op2: _trusting, op3: _name let attribute = vm.elements().setDynamicAttribute(name, value, trusting, namespace); if (!isConstRef(reference)) { - vm.updateWith(new UpdateDynamicAttributeOpcode(reference, attribute)); + vm.updateWith(new UpdateDynamicAttributeOpcode(reference, attribute, vm.env)); } }); export class UpdateDynamicAttributeOpcode extends UpdatingOpcode { public type = 'patch-element'; - public lastValue: unknown; + private updateRef: Reference; - constructor(private reference: Reference, private attribute: DynamicAttribute) { + constructor(reference: Reference, attribute: DynamicAttribute, env: Environment) { super(); - this.lastValue = valueForRef(reference); - } - evaluate(vm: UpdatingVM) { - let { attribute, reference, lastValue } = this; - let currentValue = valueForRef(reference); + let initialized = false; - if (currentValue !== lastValue) { - attribute.update(currentValue, vm.env); - this.lastValue = currentValue; - } + this.updateRef = createComputeRef(() => { + let value = valueForRef(reference); + + if (initialized === true) { + attribute.update(value, env); + } else { + initialized = true; + } + }); + + valueForRef(this.updateRef); + } + + evaluate() { + valueForRef(this.updateRef); } }