From d41abad8827b624b9e4821c34c1991d92bb22619 Mon Sep 17 00:00:00 2001 From: Dan Freeman Date: Fri, 22 Mar 2024 17:00:25 +0100 Subject: [PATCH] Use `dependentKeyCompat` for bindings (#64) * Use `dependentKeyCompat` for bindings * Bump version --- ember-exclaim/package.json | 2 +- ember-exclaim/src/-private/env/tracked.js | 19 +++++++++-- .../integration/exclaim-ui-tracked-test.js | 33 +++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/ember-exclaim/package.json b/ember-exclaim/package.json index c11cad5..723d063 100644 --- a/ember-exclaim/package.json +++ b/ember-exclaim/package.json @@ -1,6 +1,6 @@ { "name": "ember-exclaim", - "version": "2.0.0", + "version": "2.1.0", "description": "An addon allowing apps to expose declarative, JSON-configurable custom UIs backed by Ember components", "keywords": [ "ember-addon" diff --git a/ember-exclaim/src/-private/env/tracked.js b/ember-exclaim/src/-private/env/tracked.js index 4066d68..3dff078 100644 --- a/ember-exclaim/src/-private/env/tracked.js +++ b/ember-exclaim/src/-private/env/tracked.js @@ -1,4 +1,5 @@ import { TrackedObject } from 'tracked-built-ins'; +import { dependentKeyCompat } from '@ember/object/compat'; import { HelperSpec, Binding } from '../ui-spec.js'; import { recordCanonicalPath } from '../paths.js'; import { triggerChange } from './index.js'; @@ -63,7 +64,7 @@ function bindKey(host, key, value, env) { const bindingPath = value.path.join('.'); recordCanonicalPath(host, key, env, bindingPath); - Object.defineProperty(host, key, { + defineProperty(host, key, { enumerable: true, get() { return value.path.reduce((object, key) => object[key], env); @@ -76,7 +77,7 @@ function bindKey(host, key, value, env) { }, }); } else if (value instanceof HelperSpec) { - Object.defineProperty(host, key, { + defineProperty(host, key, { enumerable: true, get() { return value.invoke(env); @@ -86,3 +87,17 @@ function bindKey(host, key, value, env) { host[key] = bind(value, env); } } + +function defineProperty(object, key, descriptor) { + // Using `dependentKeyCompat` ensures that any computed properties + // in component implementations will still work correctly. This allows + // shared libraries of Exclaim components to use `@computed` internally + // to be compatible with an environment + `ExclaimUi` using classic + // reactivity OR modern reactivity, providing their consumers an easier + // migration path. + Object.defineProperty( + object, + key, + dependentKeyCompat(object, String(key), descriptor) ?? descriptor, + ); +} diff --git a/test-app/tests/integration/exclaim-ui-tracked-test.js b/test-app/tests/integration/exclaim-ui-tracked-test.js index 7dc190e..699c339 100644 --- a/test-app/tests/integration/exclaim-ui-tracked-test.js +++ b/test-app/tests/integration/exclaim-ui-tracked-test.js @@ -1,4 +1,5 @@ import { setComponentTemplate } from '@ember/component'; +import { computed } from '@ember/object'; import Component from '@glimmer/component'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; @@ -140,6 +141,38 @@ module('Integration | Component | ExclaimUi | tracked env', function (hooks) { assert.dom('[data-value]').hasText('goodbye'); }); + test('it exposes bindings that can be consumed by legacy computed properties', async function (assert) { + const implementationMap = { + shout: { + component: setComponentTemplate( + hbs`{{this.shoutedValue}}`, + class extends Component { + @computed('args.config.value') + get shoutedValue() { + return this.args.config.value.toUpperCase(); + } + }, + ), + }, + }; + + const ui = { + $component: 'shout', + value: { $bind: 'envValue' }, + }; + + const env = new TrackedObject({ envValue: 'hi' }); + + await this.renderUI({ implementationMap, ui, env }); + + assert.dom().hasText('HI'); + + env.envValue = 'hello'; + await settled(); + + assert.dom().hasText('HELLO'); + }); + test('it writes bound data back to the env', async function (assert) { const implementationMap = { 'simple-component': {