Skip to content

Commit

Permalink
[BUGFIX] Allow setting length on ArrayProxy.
Browse files Browse the repository at this point in the history
  • Loading branch information
rwjblue committed Jun 27, 2018
1 parent 721735c commit 7896004
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 60 deletions.
22 changes: 22 additions & 0 deletions packages/ember-runtime/lib/system/array_proxy.js
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down
@@ -1,45 +1,12 @@
import { run } from '@ember/runloop';
import { get, set, changeProperties } from 'ember-metal';
import { not } from '@ember/object/computed';
import ArrayProxy from '../../../lib/system/array_proxy';
import { A as emberA } from '../../../lib/mixins/array';
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();
Expand Down Expand Up @@ -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 = [];

Expand Down
127 changes: 126 additions & 1 deletion 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;

Expand Down

0 comments on commit 7896004

Please sign in to comment.