Skip to content

Commit ef26b1f

Browse files
author
Chris Garrett
committed
[BUGFIX lts] Entangles custom EmberArray implementations when accessed
Restores the previous logic that existed for entangling custom EmberArray implementations when they are accessed, along with a test for the functionality.
1 parent 984e69c commit ef26b1f

File tree

7 files changed

+87
-15
lines changed

7 files changed

+87
-15
lines changed

packages/@ember/-internals/glimmer/tests/integration/helpers/tracked-test.js

+63-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
import { Object as EmberObject, A } from '@ember/-internals/runtime';
2-
import { get, set, tracked, nativeDescDecorator as descriptor } from '@ember/-internals/metal';
1+
import { Object as EmberObject, A, MutableArray } from '@ember/-internals/runtime';
2+
import {
3+
get,
4+
set,
5+
tracked,
6+
nativeDescDecorator as descriptor,
7+
notifyPropertyChange,
8+
} from '@ember/-internals/metal';
39
import Service, { inject } from '@ember/service';
410
import { moduleFor, RenderingTestCase, strip, runTask } from 'internal-test-helpers';
511

@@ -169,6 +175,61 @@ moduleFor(
169175
this.assertText('1, 2, 3, 4');
170176
}
171177

178+
'@test custom ember array properties rerender when updated'() {
179+
let CustomArray = EmberObject.extend(MutableArray, {
180+
init() {
181+
this._super(...arguments);
182+
this._vals = [1, 2, 3];
183+
},
184+
185+
objectAt(index) {
186+
return this._vals[index];
187+
},
188+
189+
replace(start, deleteCount, items = []) {
190+
this._vals.splice(start, deleteCount, ...items);
191+
notifyPropertyChange(this, '[]');
192+
},
193+
194+
join() {
195+
return this._vals.join(...arguments);
196+
},
197+
198+
get length() {
199+
return this._vals.length;
200+
},
201+
});
202+
203+
let NumListComponent = Component.extend({
204+
numbers: tracked({ initializer: () => CustomArray.create() }),
205+
206+
addNumber() {
207+
this.numbers.pushObject(4);
208+
},
209+
});
210+
211+
this.registerComponent('num-list', {
212+
ComponentClass: NumListComponent,
213+
template: strip`
214+
<button {{action this.addNumber}}>
215+
{{join this.numbers}}
216+
</button>
217+
`,
218+
});
219+
220+
this.registerHelper('join', ([value]) => {
221+
return value.join(', ');
222+
});
223+
224+
this.render('<NumList />');
225+
226+
this.assertText('1, 2, 3');
227+
228+
runTask(() => this.$('button').click());
229+
230+
this.assertText('1, 2, 3, 4');
231+
}
232+
172233
'@test nested getters update when dependent properties are invalidated'(assert) {
173234
let computeCount = 0;
174235

packages/@ember/-internals/metal/lib/property_get.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
@module @ember/object
33
*/
4-
import { HAS_NATIVE_PROXY, setProxy, symbol } from '@ember/-internals/utils';
4+
import { HAS_NATIVE_PROXY, isEmberArray, setProxy, symbol } from '@ember/-internals/utils';
55
import { assert, deprecate } from '@ember/debug';
66
import { DEBUG } from '@glimmer/env';
77
import {
@@ -130,7 +130,7 @@ export function _getProp(obj: object, keyName: string) {
130130
if (isTracking()) {
131131
consumeTag(tagFor(obj, keyName));
132132

133-
if (Array.isArray(value)) {
133+
if (Array.isArray(value) || isEmberArray(value)) {
134134
// Add the tag of the returned value if it is an array, since arrays
135135
// should always cause updates if they are consumed and then changed
136136
consumeTag(tagFor(value, '[]'));

packages/@ember/-internals/metal/lib/tracked.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { isEmberArray } from '@ember/-internals/utils';
12
import { assert } from '@ember/debug';
23
import { DEBUG } from '@glimmer/env';
34
import { consumeTag, dirtyTagFor, tagFor, trackedData } from '@glimmer/validator';
@@ -162,7 +163,7 @@ function descriptorForField([_target, key, desc]: [
162163

163164
// Add the tag of the returned value if it is an array, since arrays
164165
// should always cause updates if they are consumed and then changed
165-
if (Array.isArray(value)) {
166+
if (Array.isArray(value) || isEmberArray(value)) {
166167
consumeTag(tagFor(value, '[]'));
167168
}
168169

packages/@ember/-internals/runtime/lib/mixins/array.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44
import { DEBUG } from '@glimmer/env';
55
import { PROXY_CONTENT } from '@ember/-internals/metal';
6-
import { EMBER_ARRAY, HAS_NATIVE_PROXY, tryInvoke } from '@ember/-internals/utils';
6+
import { setEmberArray, HAS_NATIVE_PROXY, tryInvoke } from '@ember/-internals/utils';
77
import {
88
get,
99
set,
@@ -216,7 +216,10 @@ function mapBy(key) {
216216
@public
217217
*/
218218
const ArrayMixin = Mixin.create(Enumerable, {
219-
[EMBER_ARRAY]: true,
219+
init() {
220+
this._super(...arguments);
221+
setEmberArray(this);
222+
},
220223

221224
/**
222225
__Required.__ You must implement this method to apply this mixin.

packages/@ember/-internals/utils/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export { HAS_NATIVE_SYMBOL } from './lib/symbol-utils';
3232
export { HAS_NATIVE_PROXY } from './lib/proxy-utils';
3333
export { isProxy, setProxy } from './lib/is_proxy';
3434
export { default as Cache } from './lib/cache';
35-
export { EMBER_ARRAY, EmberArray, isEmberArray } from './lib/ember-array';
35+
export { EmberArray, setEmberArray, isEmberArray } from './lib/ember-array';
3636
export {
3737
setupMandatorySetter,
3838
teardownMandatorySetter,
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { symbol } from './symbol';
1+
import { _WeakSet } from '@glimmer/util';
22

3-
export const EMBER_ARRAY = symbol('EMBER_ARRAY');
3+
const EMBER_ARRAYS = new _WeakSet();
44

55
export interface EmberArray<T> {
66
length: number;
@@ -10,6 +10,10 @@ export interface EmberArray<T> {
1010
splice(start: number, deleteCount: number, ...items: T[]): void;
1111
}
1212

13-
export function isEmberArray(obj: any): obj is EmberArray<unknown> {
14-
return obj && obj[EMBER_ARRAY];
13+
export function setEmberArray(obj: object) {
14+
EMBER_ARRAYS.add(obj);
15+
}
16+
17+
export function isEmberArray(obj: unknown): obj is EmberArray<unknown> {
18+
return EMBER_ARRAYS.has(obj as object);
1519
}

packages/@ember/-internals/utils/tests/trackable-object-test.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
import { EMBER_ARRAY, isEmberArray } from '..';
1+
import { setEmberArray, isEmberArray } from '..';
22
import { moduleFor, AbstractTestCase } from 'internal-test-helpers';
33

44
moduleFor(
55
'@ember/-internals/utils Trackable Object',
66
class extends AbstractTestCase {
77
['@test classes'](assert) {
8-
class Test {}
9-
Test.prototype[EMBER_ARRAY] = true;
8+
class Test {
9+
constructor() {
10+
setEmberArray(this);
11+
}
12+
}
1013

1114
let instance = new Test();
1215

0 commit comments

Comments
 (0)