| // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'package:observe/observe.dart'; |
| import 'package:unittest/unittest.dart'; |
| import 'observe_test_utils.dart'; |
| |
| // This file contains code ported from: |
| // https://github.com/rafaelw/ChangeSummary/blob/master/tests/test.js |
| |
| main() { |
| // TODO(jmesserly): rename this? Is summarizeListChanges coming back? |
| group('summarizeListChanges', listChangeTests); |
| } |
| |
| // TODO(jmesserly): port or write array fuzzer tests |
| listChangeTests() { |
| StreamSubscription sub; |
| |
| tearDown(() { sub.cancel(); }); |
| |
| observeTest('sequential adds', () { |
| var model = toObservable([]); |
| model.add(0); |
| |
| var summary; |
| sub = model.changes.listen((r) { summary = _filter(r); }); |
| |
| model.add(1); |
| model.add(2); |
| |
| expect(summary, null); |
| performMicrotaskCheckpoint(); |
| |
| expectChanges(summary, [_delta(1, 0, 2)]); |
| }); |
| |
| observeTest('List Splice Truncate And Expand With Length', () { |
| var model = toObservable(['a', 'b', 'c', 'd', 'e']); |
| |
| var summary; |
| sub = model.changes.listen((r) { summary = _filter(r); }); |
| |
| model.length = 2; |
| |
| performMicrotaskCheckpoint(); |
| expectChanges(summary, [_delta(2, 3, 0)]); |
| summary = null; |
| |
| model.length = 5; |
| |
| performMicrotaskCheckpoint(); |
| expectChanges(summary, [_delta(2, 0, 3)]); |
| }); |
| |
| group('List deltas can be applied', () { |
| |
| var summary = null; |
| |
| observeArray(model) { |
| sub = model.changes.listen((records) { summary = _filter(records); }); |
| } |
| |
| applyAndCheckDeltas(model, copy) { |
| summary = null; |
| performMicrotaskCheckpoint(); |
| |
| // apply deltas to the copy |
| for (var delta in summary) { |
| copy.removeRange(delta.index, delta.index + delta.removedCount); |
| for (int i = delta.addedCount - 1; i >= 0; i--) { |
| copy.insert(delta.index, model[delta.index + i]); |
| } |
| } |
| |
| // Note: compare strings for easier debugging. |
| expect('$copy', '$model', reason: 'summary $summary'); |
| } |
| |
| observeTest('Contained', () { |
| var model = toObservable(['a', 'b']); |
| var copy = model.toList(); |
| observeArray(model); |
| |
| model.removeAt(1); |
| model.insertAll(0, ['c', 'd', 'e']); |
| model.removeRange(1, 3); |
| model.insert(1, 'f'); |
| |
| applyAndCheckDeltas(model, copy); |
| }); |
| |
| observeTest('Delete Empty', () { |
| var model = toObservable([1]); |
| var copy = model.toList(); |
| observeArray(model); |
| |
| model.removeAt(0); |
| model.insertAll(0, ['a', 'b', 'c']); |
| |
| applyAndCheckDeltas(model, copy); |
| }); |
| |
| observeTest('Right Non Overlap', () { |
| var model = toObservable(['a', 'b', 'c', 'd']); |
| var copy = model.toList(); |
| observeArray(model); |
| |
| model.removeRange(0, 1); |
| model.insert(0, 'e'); |
| model.removeRange(2, 3); |
| model.insertAll(2, ['f', 'g']); |
| |
| applyAndCheckDeltas(model, copy); |
| }); |
| |
| observeTest('Left Non Overlap', () { |
| var model = toObservable(['a', 'b', 'c', 'd']); |
| var copy = model.toList(); |
| observeArray(model); |
| |
| model.removeRange(3, 4); |
| model.insertAll(3, ['f', 'g']); |
| model.removeRange(0, 1); |
| model.insert(0, 'e'); |
| |
| applyAndCheckDeltas(model, copy); |
| }); |
| |
| observeTest('Right Adjacent', () { |
| var model = toObservable(['a', 'b', 'c', 'd']); |
| var copy = model.toList(); |
| observeArray(model); |
| |
| model.removeRange(1, 2); |
| model.insert(3, 'e'); |
| model.removeRange(2, 3); |
| model.insertAll(0, ['f', 'g']); |
| |
| applyAndCheckDeltas(model, copy); |
| }); |
| |
| observeTest('Left Adjacent', () { |
| var model = toObservable(['a', 'b', 'c', 'd']); |
| var copy = model.toList(); |
| observeArray(model); |
| |
| model.removeRange(2, 4); |
| model.insert(2, 'e'); |
| |
| model.removeAt(1); |
| model.insertAll(1, ['f', 'g']); |
| |
| applyAndCheckDeltas(model, copy); |
| }); |
| |
| observeTest('Right Overlap', () { |
| var model = toObservable(['a', 'b', 'c', 'd']); |
| var copy = model.toList(); |
| observeArray(model); |
| |
| model.removeAt(1); |
| model.insert(1, 'e'); |
| model.removeAt(1); |
| model.insertAll(1, ['f', 'g']); |
| |
| applyAndCheckDeltas(model, copy); |
| }); |
| |
| observeTest('Left Overlap', () { |
| var model = toObservable(['a', 'b', 'c', 'd']); |
| var copy = model.toList(); |
| observeArray(model); |
| |
| model.removeAt(2); |
| model.insertAll(2, ['e', 'f', 'g']); |
| // a b [e f g] d |
| model.removeRange(1, 3); |
| model.insertAll(1, ['h', 'i', 'j']); |
| // a [h i j] f g d |
| |
| applyAndCheckDeltas(model, copy); |
| }); |
| |
| observeTest('Prefix And Suffix One In', () { |
| var model = toObservable(['a', 'b', 'c', 'd']); |
| var copy = model.toList(); |
| observeArray(model); |
| |
| model.insert(0, 'z'); |
| model.add('z'); |
| |
| applyAndCheckDeltas(model, copy); |
| }); |
| |
| observeTest('Remove First', () { |
| var model = toObservable([16, 15, 15]); |
| var copy = model.toList(); |
| observeArray(model); |
| |
| model.removeAt(0); |
| |
| applyAndCheckDeltas(model, copy); |
| }); |
| |
| observeTest('Update Remove', () { |
| var model = toObservable(['a', 'b', 'c', 'd']); |
| var copy = model.toList(); |
| observeArray(model); |
| |
| model.removeAt(2); |
| model.insertAll(2, ['e', 'f', 'g']); // a b [e f g] d |
| model[0] = 'h'; |
| model.removeAt(1); |
| |
| applyAndCheckDeltas(model, copy); |
| }); |
| |
| observeTest('Remove Mid List', () { |
| var model = toObservable(['a', 'b', 'c', 'd']); |
| var copy = model.toList(); |
| observeArray(model); |
| |
| model.removeAt(2); |
| |
| applyAndCheckDeltas(model, copy); |
| }); |
| }); |
| |
| group('edit distance', () { |
| var summary = null; |
| |
| observeArray(model) { |
| sub = model.changes.listen((records) { summary = _filter(records); }); |
| } |
| |
| assertEditDistance(orig, expectDistance) { |
| summary = null; |
| performMicrotaskCheckpoint(); |
| var actualDistance = 0; |
| |
| if (summary != null) { |
| for (var delta in summary) { |
| actualDistance += delta.addedCount + delta.removedCount; |
| } |
| } |
| |
| expect(actualDistance, expectDistance); |
| } |
| |
| observeTest('add items', () { |
| var model = toObservable([]); |
| observeArray(model); |
| model.addAll([1, 2, 3]); |
| assertEditDistance(model, 3); |
| }); |
| |
| observeTest('trunacte and add, sharing a contiguous block', () { |
| var model = toObservable(['x', 'x', 'x', 'x', '1', '2', '3']); |
| observeArray(model); |
| model.length = 0; |
| model.addAll(['1', '2', '3', 'y', 'y', 'y', 'y']); |
| // Note: unlike the JS implementation, we don't perform a full diff. |
| // The change records are computed with no regards to the *contents* of |
| // the array. Thus, we get 14 instead of 8. |
| assertEditDistance(model, 14); |
| }); |
| |
| observeTest('truncate and add, sharing a discontiguous block', () { |
| var model = toObservable(['1', '2', '3', '4', '5']); |
| observeArray(model); |
| model.length = 0; |
| model.addAll(['a', '2', 'y', 'y', '4', '5', 'z', 'z']); |
| // Note: unlike the JS implementation, we don't perform a full diff. |
| // The change records are computed with no regards to the *contents* of |
| // the array. Thus, we get 13 instead of 7. |
| assertEditDistance(model, 13); |
| }); |
| |
| observeTest('insert at beginning and end', () { |
| var model = toObservable([2, 3, 4]); |
| observeArray(model); |
| model.insert(0, 5); |
| model[2] = 6; |
| model.add(7); |
| assertEditDistance(model, 4); |
| }); |
| }); |
| } |
| |
| _delta(i, r, a) => new ListChangeRecord(i, removedCount: r, addedCount: a); |
| _filter(records) => records.where((r) => r is ListChangeRecord).toList(); |