diff --git a/packages/ember-runtime/lib/system/array_proxy.js b/packages/ember-runtime/lib/system/array_proxy.js index ca266436a6c..e1bc27ef5d7 100644 --- a/packages/ember-runtime/lib/system/array_proxy.js +++ b/packages/ember-runtime/lib/system/array_proxy.js @@ -4,11 +4,13 @@ import { get, + set, objectAt, alias, PROPERTY_DID_CHANGE, addArrayObserver, removeArrayObserver, + replace, } from 'ember-metal'; import EmberObject from './object'; import { isArray, MutableArray } from '../mixins/array'; @@ -193,6 +195,26 @@ export default class ArrayProxy extends EmberObject { return this._length; } + set length(value) { + let length = this.length; + let removedCount = length - value; + + if (removedCount === 0) { + return; + } else if (removedCount < 0) { + throw new Error(`Cannot set array proxy length to a larger value (in ${this})`); + } + + let content = get(this, 'content'); + if (content) { + replace(content, value, removedCount); + + this._lengthDirty = true; + this._objectsDirtyIndex = 0; + this._objects = null; + } + } + [PROPERTY_DID_CHANGE](key) { if (key === 'arrangedContent') { let oldLength = this._objects === null ? 0 : this._objects.length; diff --git a/packages/ember-runtime/tests/system/array_proxy/content_change_test.js b/packages/ember-runtime/tests/system/array_proxy/content_change_test.js index e9c41511fe2..1c60e309d6c 100644 --- a/packages/ember-runtime/tests/system/array_proxy/content_change_test.js +++ b/packages/ember-runtime/tests/system/array_proxy/content_change_test.js @@ -1,6 +1,5 @@ import { run } from '@ember/runloop'; -import { get, set, changeProperties } from 'ember-metal'; -import { not } from '@ember/object/computed'; +import { set, changeProperties } from 'ember-metal'; import ArrayProxy from '../../../lib/system/array_proxy'; import { A as emberA } from '../../../lib/mixins/array'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; @@ -8,38 +7,6 @@ import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; moduleFor( 'ArrayProxy - content change', class extends AbstractTestCase { - ['@test should update length for null content'](assert) { - let proxy = ArrayProxy.create({ - content: emberA([1, 2, 3]), - }); - - assert.equal(proxy.get('length'), 3, 'precond - length is 3'); - - proxy.set('content', null); - - assert.equal(proxy.get('length'), 0, 'length updates'); - } - - ['@test should update length for null content when there is a computed property watching length']( - assert - ) { - let proxy = ArrayProxy.extend({ - isEmpty: not('length'), - }).create({ - content: emberA([1, 2, 3]), - }); - - assert.equal(proxy.get('length'), 3, 'precond - length is 3'); - - // Consume computed property that depends on length - proxy.get('isEmpty'); - - // update content - proxy.set('content', null); - - assert.equal(proxy.get('length'), 0, 'length updates'); - } - ["@test The ArrayProxy doesn't explode when assigned a destroyed object"](assert) { let proxy1 = ArrayProxy.create(); let proxy2 = ArrayProxy.create(); @@ -92,32 +59,6 @@ moduleFor( assert.deepEqual(indexes, [2, 3, 4]); } - ['@test getting length does not recompute the object cache'](assert) { - let indexes = []; - - let proxy = ArrayProxy.extend({ - objectAtContent(index) { - indexes.push(index); - return this.content[index]; - }, - }).create({ - content: emberA([1, 2, 3, 4, 5]), - }); - - assert.equal(get(proxy, 'length'), 5); - assert.deepEqual(indexes, []); - - indexes.length = 0; - proxy.set('content', emberA([6, 7, 8])); - assert.equal(get(proxy, 'length'), 3); - assert.deepEqual(indexes, []); - - indexes.length = 0; - proxy.content.replace(1, 0, [1, 2, 3]); - assert.equal(get(proxy, 'length'), 6); - assert.deepEqual(indexes, []); - } - ['@test negative indexes are handled correctly'](assert) { let indexes = []; diff --git a/packages/ember-runtime/tests/system/array_proxy/length_test.js b/packages/ember-runtime/tests/system/array_proxy/length_test.js index cf3fe267c5e..3be327341b0 100644 --- a/packages/ember-runtime/tests/system/array_proxy/length_test.js +++ b/packages/ember-runtime/tests/system/array_proxy/length_test.js @@ -1,13 +1,138 @@ import ArrayProxy from '../../../lib/system/array_proxy'; import EmberObject from '../../../lib/system/object'; import { observer } from 'ember-metal'; -import { oneWay as reads } from '@ember/object/computed'; +import { oneWay as reads, not } from '@ember/object/computed'; import { A as a } from '../../../lib/mixins/array'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; +import { set, get } from 'ember-metal'; moduleFor( 'Ember.ArrayProxy - content change (length)', class extends AbstractTestCase { + ['@test should update length for null content'](assert) { + let proxy = ArrayProxy.create({ + content: a([1, 2, 3]), + }); + + assert.equal(proxy.get('length'), 3, 'precond - length is 3'); + + proxy.set('content', null); + + assert.equal(proxy.get('length'), 0, 'length updates'); + } + + ['@test should update length for null content when there is a computed property watching length']( + assert + ) { + let proxy = ArrayProxy.extend({ + isEmpty: not('length'), + }).create({ + content: a([1, 2, 3]), + }); + + assert.equal(proxy.get('length'), 3, 'precond - length is 3'); + + // Consume computed property that depends on length + proxy.get('isEmpty'); + + // update content + proxy.set('content', null); + + assert.equal(proxy.get('length'), 0, 'length updates'); + } + + ['@test getting length does not recompute the object cache'](assert) { + let indexes = []; + + let proxy = ArrayProxy.extend({ + objectAtContent(index) { + indexes.push(index); + return this.content[index]; + }, + }).create({ + content: a([1, 2, 3, 4, 5]), + }); + + assert.equal(get(proxy, 'length'), 5); + assert.deepEqual(indexes, []); + + indexes.length = 0; + proxy.set('content', a([6, 7, 8])); + assert.equal(get(proxy, 'length'), 3); + assert.deepEqual(indexes, []); + + indexes.length = 0; + proxy.content.replace(1, 0, [1, 2, 3]); + assert.equal(get(proxy, 'length'), 6); + assert.deepEqual(indexes, []); + } + + '@test accessing length after content set to null'(assert) { + let obj = ArrayProxy.create({ content: ['foo', 'bar'] }); + + assert.equal(obj.length, 2, 'precond'); + + set(obj, 'content', null); + + assert.equal(obj.length, 0, 'length is 0 without content'); + assert.deepEqual(obj.content, null, 'content was updated'); + } + + '@test setting length to 0'(assert) { + let obj = ArrayProxy.create({ content: ['foo', 'bar'] }); + + assert.equal(obj.length, 2, 'precond'); + + set(obj, 'length', 0); + + assert.equal(obj.length, 0, 'length was updated'); + assert.deepEqual(obj.content, [], 'content length was truncated'); + } + + '@test setting length to smaller value'(assert) { + let obj = ArrayProxy.create({ content: ['foo', 'bar'] }); + + assert.equal(obj.length, 2, 'precond'); + + set(obj, 'length', 1); + + assert.equal(obj.length, 1, 'length was updated'); + assert.deepEqual(obj.content, ['foo'], 'content length was truncated'); + } + + '@test setting length to larger value'(assert) { + let obj = ArrayProxy.create({ content: ['foo', 'bar'] }); + + assert.equal(obj.length, 2, 'precond'); + + assert.throws(function() { + set(obj, 'length', 3); + }, /Cannot set array proxy length to a larger value/); + } + + '@test setting length after content set to null'(assert) { + let obj = ArrayProxy.create({ content: ['foo', 'bar'] }); + + assert.equal(obj.length, 2, 'precond'); + + set(obj, 'content', null); + assert.equal(obj.length, 0, 'length was updated'); + + set(obj, 'length', 0); + assert.equal(obj.length, 0, 'length is still updated'); + } + + '@test setting length to greater than zero'(assert) { + let obj = ArrayProxy.create({ content: ['foo', 'bar'] }); + + assert.equal(obj.length, 2, 'precond'); + + set(obj, 'length', 1); + + assert.equal(obj.length, 1, 'length was updated'); + assert.deepEqual(obj.content, ['foo'], 'content length was truncated'); + } + ['@test array proxy + aliasedProperty complex test'](assert) { let aCalled, bCalled, cCalled, dCalled, eCalled;