diff --git a/packages/ember-runtime/lib/main.js b/packages/ember-runtime/lib/main.js index 5b275b7e609..ae7006613d7 100644 --- a/packages/ember-runtime/lib/main.js +++ b/packages/ember-runtime/lib/main.js @@ -12,8 +12,6 @@ import inject from 'ember-runtime/inject'; import Namespace from 'ember-runtime/system/namespace'; import EmberObject from 'ember-runtime/system/object'; -import TrackedArray from 'ember-runtime/system/tracked_array'; -import SubArray from 'ember-runtime/system/subarray'; import { Container, Registry } from 'ember-runtime/system/container'; import ArrayProxy from 'ember-runtime/system/array_proxy'; import ObjectProxy from 'ember-runtime/system/object_proxy'; @@ -117,8 +115,6 @@ EmComputed.intersect = intersect; Ember.String = EmberStringUtils; Ember.Object = EmberObject; -Ember.TrackedArray = TrackedArray; -Ember.SubArray = SubArray; Ember.Container = Container; Ember.Registry = Registry; Ember.Namespace = Namespace; diff --git a/packages/ember-runtime/lib/system/subarray.js b/packages/ember-runtime/lib/system/subarray.js deleted file mode 100644 index 930f6c93186..00000000000 --- a/packages/ember-runtime/lib/system/subarray.js +++ /dev/null @@ -1,178 +0,0 @@ -import EmberError from 'ember-metal/error'; - -var RETAIN = 'r'; -var FILTER = 'f'; - -function Operation(type, count) { - this.type = type; - this.count = count; -} - -export default SubArray; - -/** - An `Ember.SubArray` tracks an array in a way similar to, but more specialized - than, `Ember.TrackedArray`. It is useful for keeping track of the indexes of - items within a filtered array. - - @class SubArray - @namespace Ember - @private -*/ -function SubArray(length) { - if (arguments.length < 1) { length = 0; } - - if (length > 0) { - this._operations = [new Operation(RETAIN, length)]; - } else { - this._operations = []; - } -} - - -SubArray.prototype = { - /** - Track that an item was added to the tracked array. - - @method addItem - - @param {Number} index The index of the item in the tracked array. - @param {Boolean} match `true` iff the item is included in the subarray. - - @return {number} The index of the item in the subarray. - @private - */ - addItem(index, match) { - var returnValue = -1; - var itemType = match ? RETAIN : FILTER; - var self = this; - - this._findOperation(index, function(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) { - var newOperation, splitOperation; - - if (itemType === operation.type) { - ++operation.count; - } else if (index === rangeStart) { - // insert to the left of `operation` - self._operations.splice(operationIndex, 0, new Operation(itemType, 1)); - } else { - newOperation = new Operation(itemType, 1); - splitOperation = new Operation(operation.type, rangeEnd - index + 1); - operation.count = index - rangeStart; - - self._operations.splice(operationIndex + 1, 0, newOperation, splitOperation); - } - - if (match) { - if (operation.type === RETAIN) { - returnValue = seenInSubArray + (index - rangeStart); - } else { - returnValue = seenInSubArray; - } - } - - self._composeAt(operationIndex); - }, function(seenInSubArray) { - self._operations.push(new Operation(itemType, 1)); - - if (match) { - returnValue = seenInSubArray; - } - - self._composeAt(self._operations.length - 1); - }); - - return returnValue; - }, - - /** - Track that an item was removed from the tracked array. - - @method removeItem - - @param {Number} index The index of the item in the tracked array. - - @return {number} The index of the item in the subarray, or `-1` if the item - was not in the subarray. - @private - */ - removeItem(index) { - var returnValue = -1; - var self = this; - - this._findOperation(index, function (operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) { - if (operation.type === RETAIN) { - returnValue = seenInSubArray + (index - rangeStart); - } - - if (operation.count > 1) { - --operation.count; - } else { - self._operations.splice(operationIndex, 1); - self._composeAt(operationIndex); - } - }, function() { - throw new EmberError('Can\'t remove an item that has never been added.'); - }); - - return returnValue; - }, - - - _findOperation(index, foundCallback, notFoundCallback) { - var seenInSubArray = 0; - var operationIndex, len, operation, rangeStart, rangeEnd; - - // OPTIMIZE: change to balanced tree - // find leftmost operation to the right of `index` - for (operationIndex = rangeStart = 0, len = this._operations.length; operationIndex < len; rangeStart = rangeEnd + 1, ++operationIndex) { - operation = this._operations[operationIndex]; - rangeEnd = rangeStart + operation.count - 1; - - if (index >= rangeStart && index <= rangeEnd) { - foundCallback(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray); - return; - } else if (operation.type === RETAIN) { - seenInSubArray += operation.count; - } - } - - notFoundCallback(seenInSubArray); - }, - - _composeAt(index) { - var op = this._operations[index]; - var otherOp; - - if (!op) { - // Composing out of bounds is a no-op, as when removing the last operation - // in the list. - return; - } - - if (index > 0) { - otherOp = this._operations[index - 1]; - if (otherOp.type === op.type) { - op.count += otherOp.count; - this._operations.splice(index - 1, 1); - --index; - } - } - - if (index < this._operations.length - 1) { - otherOp = this._operations[index + 1]; - if (otherOp.type === op.type) { - op.count += otherOp.count; - this._operations.splice(index + 1, 1); - } - } - }, - - toString() { - var str = ''; - this._operations.forEach((operation) => { - str += ' ' + operation.type + ':' + operation.count; - }); - return str.substring(1); - } -}; diff --git a/packages/ember-runtime/lib/system/tracked_array.js b/packages/ember-runtime/lib/system/tracked_array.js deleted file mode 100644 index 1fdcf905b5d..00000000000 --- a/packages/ember-runtime/lib/system/tracked_array.js +++ /dev/null @@ -1,331 +0,0 @@ -import { get } from 'ember-metal/property_get'; - -var RETAIN = 'r'; -var INSERT = 'i'; -var DELETE = 'd'; - -export default TrackedArray; - -/** - An `Ember.TrackedArray` tracks array operations. It's useful when you want to - lazily compute the indexes of items in an array after they've been shifted by - subsequent operations. - - @class TrackedArray - @namespace Ember - @param {Array} [items=[]] The array to be tracked. This is used just to get - the initial items for the starting state of retain:n. - @private -*/ -function TrackedArray(items) { - if (arguments.length < 1) { items = []; } - - var length = get(items, 'length'); - - if (length) { - this._operations = [new ArrayOperation(RETAIN, length, items)]; - } else { - this._operations = []; - } -} - -TrackedArray.RETAIN = RETAIN; -TrackedArray.INSERT = INSERT; -TrackedArray.DELETE = DELETE; - -TrackedArray.prototype = { - - /** - Track that `newItems` were added to the tracked array at `index`. - - @method addItems - @param index - @param newItems - @private - */ - addItems(index, newItems) { - var count = get(newItems, 'length'); - if (count < 1) { return; } - - var match = this._findArrayOperation(index); - var arrayOperation = match.operation; - var arrayOperationIndex = match.index; - var arrayOperationRangeStart = match.rangeStart; - var composeIndex, newArrayOperation; - - newArrayOperation = new ArrayOperation(INSERT, count, newItems); - - if (arrayOperation) { - if (!match.split) { - // insert left of arrayOperation - this._operations.splice(arrayOperationIndex, 0, newArrayOperation); - composeIndex = arrayOperationIndex; - } else { - this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation); - composeIndex = arrayOperationIndex + 1; - } - } else { - // insert at end - this._operations.push(newArrayOperation); - composeIndex = arrayOperationIndex; - } - - this._composeInsert(composeIndex); - }, - - /** - Track that `count` items were removed at `index`. - - @method removeItems - @param index - @param count - @private - */ - removeItems(index, count) { - if (count < 1) { return; } - - var match = this._findArrayOperation(index); - var arrayOperationIndex = match.index; - var arrayOperationRangeStart = match.rangeStart; - var newArrayOperation, composeIndex; - - newArrayOperation = new ArrayOperation(DELETE, count); - if (!match.split) { - // insert left of arrayOperation - this._operations.splice(arrayOperationIndex, 0, newArrayOperation); - composeIndex = arrayOperationIndex; - } else { - this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation); - composeIndex = arrayOperationIndex + 1; - } - - return this._composeDelete(composeIndex); - }, - - /** - Apply all operations, reducing them to retain:n, for `n`, the number of - items in the array. - - `callback` will be called for each operation and will be passed the following arguments: - - * {array} items The items for the given operation - * {number} offset The computed offset of the items, ie the index in the - array of the first item for this operation. - * {string} operation The type of the operation. One of - `Ember.TrackedArray.{RETAIN, DELETE, INSERT}` - - @method apply - @param {Function} callback - @private - */ - apply(callback) { - var items = []; - var offset = 0; - - this._operations.forEach((arrayOperation, operationIndex) => { - callback(arrayOperation.items, offset, arrayOperation.type, operationIndex); - - if (arrayOperation.type !== DELETE) { - offset += arrayOperation.count; - items = items.concat(arrayOperation.items); - } - }); - - this._operations = [new ArrayOperation(RETAIN, items.length, items)]; - }, - - /** - Return an `ArrayOperationMatch` for the operation that contains the item at `index`. - - @method _findArrayOperation - - @param {Number} index the index of the item whose operation information - should be returned. - @private - */ - _findArrayOperation(index) { - var split = false; - var arrayOperationIndex, arrayOperation, - arrayOperationRangeStart, arrayOperationRangeEnd, - len; - - // OPTIMIZE: we could search these faster if we kept a balanced tree. - // find leftmost arrayOperation to the right of `index` - for (arrayOperationIndex = arrayOperationRangeStart = 0, len = this._operations.length; arrayOperationIndex < len; ++arrayOperationIndex) { - arrayOperation = this._operations[arrayOperationIndex]; - - if (arrayOperation.type === DELETE) { continue; } - - arrayOperationRangeEnd = arrayOperationRangeStart + arrayOperation.count - 1; - - if (index === arrayOperationRangeStart) { - break; - } else if (index > arrayOperationRangeStart && index <= arrayOperationRangeEnd) { - split = true; - break; - } else { - arrayOperationRangeStart = arrayOperationRangeEnd + 1; - } - } - - return new ArrayOperationMatch(arrayOperation, arrayOperationIndex, split, arrayOperationRangeStart); - }, - - _split(arrayOperationIndex, splitIndex, newArrayOperation) { - var arrayOperation = this._operations[arrayOperationIndex]; - var splitItems = arrayOperation.items.slice(splitIndex); - var splitArrayOperation = new ArrayOperation(arrayOperation.type, splitItems.length, splitItems); - - // truncate LHS - arrayOperation.count = splitIndex; - arrayOperation.items = arrayOperation.items.slice(0, splitIndex); - - this._operations.splice(arrayOperationIndex + 1, 0, newArrayOperation, splitArrayOperation); - }, - - // see SubArray for a better implementation. - _composeInsert(index) { - var newArrayOperation = this._operations[index]; - var leftArrayOperation = this._operations[index - 1]; // may be undefined - var rightArrayOperation = this._operations[index + 1]; // may be undefined - var leftOp = leftArrayOperation && leftArrayOperation.type; - var rightOp = rightArrayOperation && rightArrayOperation.type; - - if (leftOp === INSERT) { - // merge left - leftArrayOperation.count += newArrayOperation.count; - leftArrayOperation.items = leftArrayOperation.items.concat(newArrayOperation.items); - - if (rightOp === INSERT) { - // also merge right (we have split an insert with an insert) - leftArrayOperation.count += rightArrayOperation.count; - leftArrayOperation.items = leftArrayOperation.items.concat(rightArrayOperation.items); - this._operations.splice(index, 2); - } else { - // only merge left - this._operations.splice(index, 1); - } - } else if (rightOp === INSERT) { - // merge right - newArrayOperation.count += rightArrayOperation.count; - newArrayOperation.items = newArrayOperation.items.concat(rightArrayOperation.items); - this._operations.splice(index + 1, 1); - } - }, - - _composeDelete(index) { - var arrayOperation = this._operations[index]; - var deletesToGo = arrayOperation.count; - var leftArrayOperation = this._operations[index - 1]; // may be undefined - var leftOp = leftArrayOperation && leftArrayOperation.type; - var nextArrayOperation; - var nextOp; - var nextCount; - var removeNewAndNextOp = false; - var removedItems = []; - - if (leftOp === DELETE) { - arrayOperation = leftArrayOperation; - index -= 1; - } - - for (var i = index + 1; deletesToGo > 0; ++i) { - nextArrayOperation = this._operations[i]; - nextOp = nextArrayOperation.type; - nextCount = nextArrayOperation.count; - - if (nextOp === DELETE) { - arrayOperation.count += nextCount; - continue; - } - - if (nextCount > deletesToGo) { - // d:2 {r,i}:5 we reduce the retain or insert, but it stays - removedItems = removedItems.concat(nextArrayOperation.items.splice(0, deletesToGo)); - nextArrayOperation.count -= deletesToGo; - - // In the case where we truncate the last arrayOperation, we don't need to - // remove it; also the deletesToGo reduction is not the entirety of - // nextCount - i -= 1; - nextCount = deletesToGo; - - deletesToGo = 0; - } else { - if (nextCount === deletesToGo) { - // Handle edge case of d:2 i:2 in which case both operations go away - // during composition. - removeNewAndNextOp = true; - } - removedItems = removedItems.concat(nextArrayOperation.items); - deletesToGo -= nextCount; - } - - if (nextOp === INSERT) { - // d:2 i:3 will result in delete going away - arrayOperation.count -= nextCount; - } - } - - if (arrayOperation.count > 0) { - // compose our new delete with possibly several operations to the right of - // disparate types - this._operations.splice(index + 1, i - 1 - index); - } else { - // The delete operation can go away; it has merely reduced some other - // operation, as in d:3 i:4; it may also have eliminated that operation, - // as in d:3 i:3. - this._operations.splice(index, removeNewAndNextOp ? 2 : 1); - } - - return removedItems; - }, - - toString() { - var str = ''; - this._operations.forEach((operation) => { - str += ' ' + operation.type + ':' + operation.count; - }); - return str.substring(1); - } -}; - -/** - Internal data structure to represent an array operation. - - @method ArrayOperation - @private - @param {String} operation The type of the operation. One of - `Ember.TrackedArray.{RETAIN, INSERT, DELETE}` - @param {Number} count The number of items in this operation. - @param {Array} items The items of the operation, if included. RETAIN and - INSERT include their items, DELETE does not. - @private -*/ -function ArrayOperation(operation, count, items) { - this.type = operation; // RETAIN | INSERT | DELETE - this.count = count; - this.items = items; -} - -/** - Internal data structure used to include information when looking up operations - by item index. - - @method ArrayOperationMatch - @private - @param {ArrayOperation} operation - @param {Number} index The index of `operation` in the array of operations. - @param {Boolean} split Whether or not the item index searched for would - require a split for a new operation type. - @param {Number} rangeStart The index of the first item in the operation, - with respect to the tracked array. The index of the last item can be computed - from `rangeStart` and `operation.count`. - @private -*/ -function ArrayOperationMatch(operation, index, split, rangeStart) { - this.operation = operation; - this.index = index; - this.split = split; - this.rangeStart = rangeStart; -} diff --git a/packages/ember-runtime/tests/system/subarray_test.js b/packages/ember-runtime/tests/system/subarray_test.js deleted file mode 100644 index ebbf00c023c..00000000000 --- a/packages/ember-runtime/tests/system/subarray_test.js +++ /dev/null @@ -1,124 +0,0 @@ -import SubArray from 'ember-runtime/system/subarray'; - -var subarray; - -QUnit.module('SubArray', { - setup() { - subarray = new SubArray(); - } -}); - -function operationsString() { - var str = ''; - subarray._operations.forEach((operation) => { - str += ' ' + operation.type + ':' + operation.count; - }); - return str.substring(1); -} - -QUnit.test('Subarray operations are initially retain:n', function() { - subarray = new SubArray(10); - - equal(operationsString(), 'r:10', 'subarray operations are initially retain n'); -}); - -QUnit.test('Retains compose with retains on insert', function() { - subarray.addItem(0, true); - subarray.addItem(1, true); - subarray.addItem(2, true); - - equal(operationsString(), 'r:3', 'Retains compose with retains on insert.'); -}); - -QUnit.test('Retains compose with retains on removal', function() { - subarray.addItem(0, true); - subarray.addItem(1, false); - subarray.addItem(2, true); - - equal(operationsString(), 'r:1 f:1 r:1', 'precond - operations are initially correct.'); - - subarray.removeItem(1); - - equal(operationsString(), 'r:2', 'Retains compose with retains on removal.'); -}); - -QUnit.test('Filters compose with filters on insert', function() { - subarray.addItem(0, false); - subarray.addItem(1, false); - subarray.addItem(2, false); - - equal(operationsString(), 'f:3', 'Retains compose with retains on insert.'); -}); - -QUnit.test('Filters compose with filters on removal', function() { - subarray.addItem(0, false); - subarray.addItem(1, true); - subarray.addItem(2, false); - - equal(operationsString(), 'f:1 r:1 f:1', 'precond - operations are initially correct.'); - - subarray.removeItem(1); - - equal(operationsString(), 'f:2', 'Filters compose with filters on removal.'); -}); - -QUnit.test('Filters split retains', function() { - subarray.addItem(0, true); - subarray.addItem(1, true); - subarray.addItem(1, false); - - equal(operationsString(), 'r:1 f:1 r:1', 'Filters split retains.'); -}); - -QUnit.test('Retains split filters', function() { - subarray.addItem(0, false); - subarray.addItem(1, false); - subarray.addItem(1, true); - - equal(operationsString(), 'f:1 r:1 f:1', 'Retains split filters.'); -}); - -QUnit.test('`addItem` returns the index of the item in the subarray', function() { - equal(subarray.addItem(0, true), 0, '`addItem` returns the index of the item in the subarray'); - subarray.addItem(1, false); - equal(subarray.addItem(2, true), 1, '`addItem` returns the index of the item in the subarray'); - - equal(operationsString(), 'r:1 f:1 r:1', 'Operations are correct.'); -}); - -QUnit.test('`addItem` returns -1 if the new item is not in the subarray', function() { - equal(subarray.addItem(0, false), -1, '`addItem` returns -1 if the item is not in the subarray'); -}); - -QUnit.test('`removeItem` returns the index of the item in the subarray', function() { - subarray.addItem(0, true); - subarray.addItem(1, false); - subarray.addItem(2, true); - - equal(subarray.removeItem(2), 1, '`removeItem` returns the index of the item in the subarray'); - equal(subarray.removeItem(0), 0, '`removeItem` returns the index of the item in the subarray'); -}); - -QUnit.test('`removeItem` returns -1 if the item was not in the subarray', function() { - subarray.addItem(0, true); - subarray.addItem(1, false); - - equal(subarray.removeItem(1), -1, '`removeItem` returns -1 if the item is not in the subarray'); -}); - -QUnit.test('`removeItem` raises a sensible exception when there are no operations in the subarray', function() { - var subarrayExploder = function() { - subarray.removeItem(9); - }; - throws(subarrayExploder, /never\ been\ added/, '`removeItem` raises a sensible exception when there are no operations in the subarray'); -}); - -QUnit.test('left composition does not confuse a subsequent right non-composition', function() { - subarray.addItem(0, true); - subarray.addItem(1, false); - subarray.addItem(2, true); - equal(operationsString(), 'r:1 f:1 r:1', 'precond - initial state of subarray is as expected'); - - subarray.addItem(1, true); - equal(operationsString(), 'r:2 f:1 r:1', 'left-composition does not confuse right non-composition'); -}); diff --git a/packages/ember-runtime/tests/system/tracked_array_test.js b/packages/ember-runtime/tests/system/tracked_array_test.js deleted file mode 100644 index 30d7bcf9fe1..00000000000 --- a/packages/ember-runtime/tests/system/tracked_array_test.js +++ /dev/null @@ -1,255 +0,0 @@ -import TrackedArray from 'ember-runtime/system/tracked_array'; - -var trackedArray; -var RETAIN = TrackedArray.RETAIN; -var INSERT = TrackedArray.INSERT; -var DELETE = TrackedArray.DELETE; - -QUnit.module('Ember.TrackedArray'); - -QUnit.test('operations for a tracked array of length n are initially retain:n', function() { - trackedArray = new TrackedArray([1, 2, 3, 4]); - - equal('r:4', trackedArray.toString(), 'initial mutation is retain n'); -}); - -QUnit.test('insert zero items is a no-op', function() { - trackedArray = new TrackedArray([1, 2, 3, 4]); - - trackedArray.addItems(2, []); - - equal(trackedArray.toString(), 'r:4', 'insert:0 is a no-op'); - - deepEqual(trackedArray._operations[0].items, [1, 2, 3, 4], 'after a no-op, existing operation has right items'); -}); - -QUnit.test('inserts can split retains', function() { - trackedArray = new TrackedArray([1, 2, 3, 4]); - - trackedArray.addItems(2, ['a']); - - equal(trackedArray.toString(), 'r:2 i:1 r:2', 'inserts can split retains'); - - deepEqual(trackedArray._operations[0].items, [1, 2], 'split retains have the right items'); - deepEqual(trackedArray._operations[1].items, ['a'], 'inserts have the right items'); - deepEqual(trackedArray._operations[2].items, [3, 4], 'split retains have the right items'); -}); - -QUnit.test('inserts can expand (split/compose) inserts', function() { - trackedArray = new TrackedArray([]); - - trackedArray.addItems(0, [1, 2, 3, 4]); - trackedArray.addItems(2, ['a']); - - equal(trackedArray.toString(), 'i:5', 'inserts can expand inserts'); - - deepEqual(trackedArray._operations[0].items, [1, 2, 'a', 3, 4], 'expanded inserts have the right items'); -}); - -QUnit.test('inserts left of inserts compose', function() { - trackedArray = new TrackedArray([1, 2, 3, 4]); - - trackedArray.addItems(2, ['b']); - trackedArray.addItems(2, ['a']); - - equal(trackedArray.toString(), 'r:2 i:2 r:2', 'inserts left of inserts compose'); - - deepEqual(trackedArray._operations[0].items, [1, 2], 'split retains have the right items'); - deepEqual(trackedArray._operations[1].items, ['a', 'b'], 'composed inserts have the right items'); - deepEqual(trackedArray._operations[2].items, [3, 4], 'split retains have the right items'); -}); - -QUnit.test('inserts right of inserts compose', function() { - trackedArray = new TrackedArray([1, 2, 3, 4]); - - trackedArray.addItems(2, ['a']); - trackedArray.addItems(3, ['b']); - - equal(trackedArray.toString(), 'r:2 i:2 r:2', 'inserts right of inserts compose'); - - deepEqual(trackedArray._operations[0].items, [1, 2], 'split retains have the right items'); - deepEqual(trackedArray._operations[1].items, ['a', 'b'], 'composed inserts have the right items'); - deepEqual(trackedArray._operations[2].items, [3, 4], 'split retains have the right items'); -}); - -QUnit.test('delete zero items is a no-op', function() { - trackedArray = new TrackedArray([1, 2, 3, 4]); - - trackedArray.addItems(2, []); - - equal(trackedArray.toString(), 'r:4', 'insert:0 is a no-op'); - - deepEqual(trackedArray._operations[0].items, [1, 2, 3, 4], 'after a no-op, existing operation has right items'); -}); - -QUnit.test('deletes compose with several inserts and retains', function() { - trackedArray = new TrackedArray([1, 2, 3, 4]); - - trackedArray.addItems(4, ['e']); - trackedArray.addItems(3, ['d']); - trackedArray.addItems(2, ['c']); - trackedArray.addItems(1, ['b']); - trackedArray.addItems(0, ['a']); // a1b2c3d4e i1r1i1r1i1r1i1r1i1 - - trackedArray.removeItems(0, 9); - equal(trackedArray.toString(), 'd:4', 'deletes compose with several inserts and retains'); -}); - -QUnit.test('deletes compose with several inserts and retains and an adjacent delete', function() { - trackedArray = new TrackedArray([1, 2, 3, 4, 5]); - - trackedArray.removeItems(0, 1); - trackedArray.addItems(4, ['e']); - trackedArray.addItems(3, ['d']); - trackedArray.addItems(2, ['c']); - trackedArray.addItems(1, ['b']); - trackedArray.addItems(0, ['a']); // a2b3c4d5e d1i1r1i1r1i1r1i1r1i1 - - trackedArray.removeItems(0, 9); - equal(trackedArray.toString(), 'd:5', 'deletes compose with several inserts, retains, and a single prior delete'); -}); - -QUnit.test('deletes compose with several inserts and retains and can reduce the last one', function() { - trackedArray = new TrackedArray([1, 2, 3, 4]); - - trackedArray.addItems(4, ['e', 'f']); - trackedArray.addItems(3, ['d']); - trackedArray.addItems(2, ['c']); - trackedArray.addItems(1, ['b']); - trackedArray.addItems(0, ['a']); // a1b2c3d4e i1r1i1r1i1r1i1r1i2 - - trackedArray.removeItems(0, 9); - equal(trackedArray.toString(), 'd:4 i:1', 'deletes compose with several inserts and retains, reducing the last one'); - deepEqual(trackedArray._operations[1].items, ['f'], 'last mutation\'s items is correct'); -}); - -QUnit.test('deletes can split retains', function() { - trackedArray = new TrackedArray([1, 2, 3, 4]); - trackedArray.removeItems(0, 2); - - equal(trackedArray.toString(), 'd:2 r:2', 'deletes can split retains'); - deepEqual(trackedArray._operations[1].items, [3, 4], 'retains reduced by delete have the right items'); -}); - -QUnit.test('deletes can trim retains on the right', function() { - trackedArray = new TrackedArray([1, 2, 3]); - trackedArray.removeItems(2, 1); - - equal(trackedArray.toString(), 'r:2 d:1', 'deletes can trim retains on the right'); - deepEqual(trackedArray._operations[0].items, [1, 2], 'retains reduced by delete have the right items'); -}); - -QUnit.test('deletes can trim retains on the left', function() { - trackedArray = new TrackedArray([1, 2, 3]); - trackedArray.removeItems(0, 1); - - equal(trackedArray.toString(), 'd:1 r:2', 'deletes can trim retains on the left'); - deepEqual(trackedArray._operations[1].items, [2, 3], 'retains reduced by delete have the right items'); -}); - -QUnit.test('deletes can split inserts', function() { - trackedArray = new TrackedArray([]); - trackedArray.addItems(0, ['a', 'b', 'c']); - trackedArray.removeItems(0, 1); - - equal(trackedArray.toString(), 'i:2', 'deletes can split inserts'); - deepEqual(trackedArray._operations[0].items, ['b', 'c'], 'inserts reduced by delete have the right items'); -}); - -QUnit.test('deletes can trim inserts on the right', function() { - trackedArray = new TrackedArray([]); - trackedArray.addItems(0, ['a', 'b', 'c']); - trackedArray.removeItems(2, 1); - - equal(trackedArray.toString(), 'i:2', 'deletes can trim inserts on the right'); - deepEqual(trackedArray._operations[0].items, ['a', 'b'], 'inserts reduced by delete have the right items'); -}); - -QUnit.test('deletes can trim inserts on the left', function() { - trackedArray = new TrackedArray([]); - trackedArray.addItems(0, ['a', 'b', 'c']); - trackedArray.removeItems(0, 1); - - equal(trackedArray.toString(), 'i:2', 'deletes can trim inserts on the right'); - deepEqual(trackedArray._operations[0].items, ['b', 'c'], 'inserts reduced by delete have the right items'); -}); - -QUnit.test('deletes can trim inserts on the left while composing with a delete on the left', function() { - trackedArray = new TrackedArray(['a']); - trackedArray.removeItems(0, 1); - trackedArray.addItems(0, ['b', 'c']); - trackedArray.removeItems(0, 1); - - equal(trackedArray.toString(), 'd:1 i:1', 'deletes can trim inserts and compose with a delete on the left'); - deepEqual(trackedArray._operations[1].items, ['c'], 'inserts reduced by delete have the right items'); -}); - -QUnit.test('deletes can reduce an insert or retain, compose with several mutations of different types and reduce the last mutation if it is non-delete', function() { - trackedArray = new TrackedArray([1, 2, 3, 4]); - - trackedArray.addItems(4, ['e', 'f']); // 1234ef - trackedArray.addItems(3, ['d']); // 123d4ef - trackedArray.addItems(2, ['c']); // 12c3d4ef - trackedArray.addItems(1, ['b']); // 1b2c3d4ef - trackedArray.addItems(0, ['a', 'a', 'a']); // aaa1b2c3d4ef i3r1i1r1i1r1i1r1i2 - - trackedArray.removeItems(1, 10); - equal(trackedArray.toString(), 'i:1 d:4 i:1', 'deletes reduce an insert, compose with several inserts and retains, reducing the last one'); - deepEqual(trackedArray._operations[0].items, ['a'], 'first reduced mutation\'s items is correct'); - deepEqual(trackedArray._operations[2].items, ['f'], 'last reduced mutation\'s items is correct'); -}); - -QUnit.test('removeItems returns the removed items', function() { - trackedArray = new TrackedArray([1, 2, 3, 4]); - deepEqual(trackedArray.removeItems(1, 2), [2, 3], '`removeItems` returns the removed items'); -}); - -QUnit.test('apply invokes the callback with each group of items and the mutation\'s calculated offset', function() { - var i = 0; - trackedArray = new TrackedArray([1, 2, 3, 4]); - - trackedArray.addItems(2, ['a', 'b', 'c']); // 12abc34 - trackedArray.removeItems(4, 2); // 12ab4 - trackedArray.addItems(1, ['d']); // 1d2ab4 r1 i1 r1 i2 d1 r1 - - equal(trackedArray.toString(), 'r:1 i:1 r:1 i:2 d:1 r:1', 'precond - trackedArray is in expected state'); - - trackedArray.apply(function (items, offset, operation) { - switch (i++) { - case 0: - deepEqual(items, [1], 'callback passed right items'); - equal(offset, 0, 'callback passed right offset'); - equal(operation, RETAIN, 'callback passed right operation'); - break; - case 1: - deepEqual(items, ['d'], 'callback passed right items'); - equal(offset, 1, 'callback passed right offset'); - equal(operation, INSERT, 'callback passed right operation'); - break; - case 2: - deepEqual(items, [2], 'callback passed right items'); - equal(offset, 2, 'callback passed right offset'); - equal(operation, RETAIN, 'callback passed right operation'); - break; - case 3: - deepEqual(items, ['a', 'b'], 'callback passed right items'); - equal(offset, 3, 'callback passed right offset'); - equal(operation, INSERT, 'callback passed right operation'); - break; - case 4: - // deletes not passed items at the moment; that might need to be added - // if TrackedArray is used more widely - equal(offset, 5, 'callback passed right offset'); - equal(operation, DELETE, 'callback passed right operation'); - break; - case 5: - deepEqual(items, [4], 'callback passed right items'); - equal(offset, 5, 'callback passed right offset'); - equal(operation, RETAIN, 'callback passed right operation'); - break; - } - }); - equal(i, 6, '`apply` invoked callback right number of times'); - - equal(trackedArray.toString(), 'r:6', 'after `apply` operations become retain:n'); -});