blob: 858b27bed2caad0c41bf55b6ba45c7fac9fbe055 [file] [log] [blame]
// Copyright (c) 2016, 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:observable/observable.dart';
import 'package:test/test.dart';
import 'observable_test_utils.dart';
// This file contains code ported from:
// https://github.com/rafaelw/ChangeSummary/blob/master/tests/test.js
main() => listChangeTests();
// TODO(jmesserly): port or write array fuzzer tests
listChangeTests() {
StreamSubscription sub;
var model;
tearDown(() {
sub?.cancel();
model = null;
});
_delta(i, r, a) => new ListChangeRecord(model, i, removed: r, addedCount: a);
test('sequential adds', () {
model = toObservable([]);
model.add(0);
var summary;
sub = model.listChanges.listen((r) => summary = r);
model.add(1);
model.add(2);
expect(summary, null);
return new Future(() {
expectChanges(summary, [_delta(1, [], 2)]);
expect(summary[0].added, [1, 2]);
expect(summary[0].removed, []);
});
});
test('List Splice Truncate And Expand With Length', () {
model = toObservable(['a', 'b', 'c', 'd', 'e']);
var summary;
sub = model.listChanges.listen((r) => summary = r);
model.length = 2;
return new Future(() {
expectChanges(summary, [
_delta(2, ['c', 'd', 'e'], 0)
]);
expect(summary[0].added, []);
expect(summary[0].removed, ['c', 'd', 'e']);
summary = null;
model.length = 5;
}).then(newMicrotask).then((_) {
expectChanges(summary, [_delta(2, [], 3)]);
expect(summary[0].added, [null, null, null]);
expect(summary[0].removed, []);
});
});
group('List deltas can be applied', () {
applyAndCheckDeltas(model, copy, changes) => changes.then((summary) {
// apply deltas to the copy
for (ListChangeRecord delta in summary) {
delta.apply(copy);
}
expect('$copy', '$model', reason: 'summary $summary');
});
test('Contained', () {
var model = toObservable(['a', 'b']);
var copy = model.toList();
var changes = model.listChanges.first;
model.removeAt(1);
model.insertAll(0, ['c', 'd', 'e']);
model.removeRange(1, 3);
model.insert(1, 'f');
return applyAndCheckDeltas(model, copy, changes);
});
test('Delete Empty', () {
var model = toObservable(<dynamic>[1]);
var copy = model.toList();
var changes = model.listChanges.first;
model.removeAt(0);
model.insertAll(0, ['a', 'b', 'c']);
return applyAndCheckDeltas(model, copy, changes);
});
test('Right Non Overlap', () {
var model = toObservable(['a', 'b', 'c', 'd']);
var copy = model.toList();
var changes = model.listChanges.first;
model.removeRange(0, 1);
model.insert(0, 'e');
model.removeRange(2, 3);
model.insertAll(2, ['f', 'g']);
return applyAndCheckDeltas(model, copy, changes);
});
test('Left Non Overlap', () {
var model = toObservable(['a', 'b', 'c', 'd']);
var copy = model.toList();
var changes = model.listChanges.first;
model.removeRange(3, 4);
model.insertAll(3, ['f', 'g']);
model.removeRange(0, 1);
model.insert(0, 'e');
return applyAndCheckDeltas(model, copy, changes);
});
test('Right Adjacent', () {
var model = toObservable(['a', 'b', 'c', 'd']);
var copy = model.toList();
var changes = model.listChanges.first;
model.removeRange(1, 2);
model.insert(3, 'e');
model.removeRange(2, 3);
model.insertAll(0, ['f', 'g']);
return applyAndCheckDeltas(model, copy, changes);
});
test('Left Adjacent', () {
var model = toObservable(['a', 'b', 'c', 'd']);
var copy = model.toList();
var changes = model.listChanges.first;
model.removeRange(2, 4);
model.insert(2, 'e');
model.removeAt(1);
model.insertAll(1, ['f', 'g']);
return applyAndCheckDeltas(model, copy, changes);
});
test('Right Overlap', () {
var model = toObservable(['a', 'b', 'c', 'd']);
var copy = model.toList();
var changes = model.listChanges.first;
model.removeAt(1);
model.insert(1, 'e');
model.removeAt(1);
model.insertAll(1, ['f', 'g']);
return applyAndCheckDeltas(model, copy, changes);
});
test('Left Overlap', () {
var model = toObservable(['a', 'b', 'c', 'd']);
var copy = model.toList();
var changes = model.listChanges.first;
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
return applyAndCheckDeltas(model, copy, changes);
});
test('Prefix And Suffix One In', () {
var model = toObservable(['a', 'b', 'c', 'd']);
var copy = model.toList();
var changes = model.listChanges.first;
model.insert(0, 'z');
model.add('z');
return applyAndCheckDeltas(model, copy, changes);
});
test('Remove First', () {
var model = toObservable([16, 15, 15]);
var copy = model.toList();
var changes = model.listChanges.first;
model.removeAt(0);
return applyAndCheckDeltas(model, copy, changes);
});
test('Update Remove', () {
var model = toObservable(['a', 'b', 'c', 'd']);
var copy = model.toList();
var changes = model.listChanges.first;
model.removeAt(2);
model.insertAll(2, ['e', 'f', 'g']); // a b [e f g] d
model[0] = 'h';
model.removeAt(1);
return applyAndCheckDeltas(model, copy, changes);
});
test('Remove Mid List', () {
var model = toObservable(['a', 'b', 'c', 'd']);
var copy = model.toList();
var changes = model.listChanges.first;
model.removeAt(2);
return applyAndCheckDeltas(model, copy, changes);
});
});
group('edit distance', () {
assertEditDistance(orig, changes, expectedDist) => changes.then((summary) {
var actualDistance = 0;
for (var delta in summary) {
actualDistance += delta.addedCount + delta.removed.length;
}
expect(actualDistance, expectedDist);
});
test('add items', () {
var model = toObservable([]);
var changes = model.listChanges.first;
model.addAll([1, 2, 3]);
return assertEditDistance(model, changes, 3);
});
test('trunacte and add, sharing a contiguous block', () {
var model = toObservable(['x', 'x', 'x', 'x', '1', '2', '3']);
var changes = model.listChanges.first;
model.length = 0;
model.addAll(['1', '2', '3', 'y', 'y', 'y', 'y']);
return assertEditDistance(model, changes, 8);
});
test('truncate and add, sharing a discontiguous block', () {
var model = toObservable(['1', '2', '3', '4', '5']);
var changes = model.listChanges.first;
model.length = 0;
model.addAll(['a', '2', 'y', 'y', '4', '5', 'z', 'z']);
return assertEditDistance(model, changes, 7);
});
test('insert at beginning and end', () {
var model = toObservable([2, 3, 4]);
var changes = model.listChanges.first;
model.insert(0, 5);
model[2] = 6;
model.add(7);
return assertEditDistance(model, changes, 4);
});
});
}