blob: 67e258765885f5e47dc8a3de5dc603dbb58b4789 [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';
main() {
group('$ObservableList', () {
group('list api', _runListTests);
_runObservableListTests();
_runDeprecatedTests();
});
}
bool _hasListChanges(List<ChangeRecord> c) {
return c.any((r) => r is ListChangeRecord);
}
bool _hasPropChanges(List<ChangeRecord> c) {
return c.any((r) => r is PropertyChangeRecord);
}
bool _onlyListChanges(ChangeRecord c) => c is ListChangeRecord;
bool _onlyPropRecords(ChangeRecord c) => c is PropertyChangeRecord;
_runListTests() {
// TODO(matanl): Can we run the List-API tests from the SDK?
// Any methods actually implemented by ObservableList are below, otherwise I
// am relying on the test suite for DelegatingList.
test('index set operator', () {
final list = new ObservableList<String>(1)..[0] = 'value';
expect(list, ['value']);
});
test('add', () {
final list = new ObservableList<String>()..add('value');
expect(list, ['value']);
});
test('addAll', () {
final list = new ObservableList<String>()..addAll(['a', 'b', 'c']);
expect(list, ['a', 'b', 'c']);
});
test('clear', () {
final list = new ObservableList<String>.from(['a', 'b', 'c'])..clear();
expect(list, isEmpty);
});
test('fillRange', () {
final list = new ObservableList<String>(5)..fillRange(0, 5, 'a');
expect(list, ['a', 'a', 'a', 'a', 'a']);
});
test('insert', () {
final list = new ObservableList<String>.from(['a', 'c'])..insert(1, 'b');
expect(list, ['a', 'b', 'c']);
});
test('insertAll', () {
final list = new ObservableList<String>.from(['c']);
list.insertAll(0, ['a', 'b']);
expect(list, ['a', 'b', 'c']);
});
test('length', () {
final list = new ObservableList<String>()..length = 3;
expect(list, [null, null, null]);
list.length = 1;
expect(list, [null]);
list.length = 0;
expect(list, isEmpty);
});
test('remove', () {
final list = new ObservableList<String>.from(['a', 'b', 'c']);
expect(list.remove('b'), isTrue);
expect(list, ['a', 'c']);
});
test('removeAt', () {
final list = new ObservableList<String>.from(['a', 'b', 'c']);
expect(list.removeAt(1), 'b');
expect(list, ['a', 'c']);
});
test('removeLast', () {
final list = new ObservableList<String>.from(['a', 'b', 'c']);
expect(list.removeLast(), 'c');
});
test('removeRange', () {
final list = new ObservableList<String>.from(['a', 'b', 'c']);
list.removeRange(0, 2);
expect(list, ['c']);
});
test('removeWhere', () {
final list = new ObservableList<String>.from(['a', 'b', 'a', 'b']);
list.removeWhere((v) => v == 'a');
expect(list, ['b', 'b']);
});
test('retainWhere', () {
final list = new ObservableList<String>.from(['a', 'b', 'a', 'b']);
list.retainWhere((v) => v == 'a');
expect(list, ['a', 'a']);
});
test('setAll', () {
final list = new ObservableList<String>.from(['a', 'b', 'c']);
list.setAll(1, ['d', 'f']);
expect(list, ['a', 'd', 'f']);
});
test('setRange', () {
final list = new ObservableList<String>.from(['a', 'b', 'c']);
list.setRange(0, 2, ['d', 'e']);
expect(list, ['d', 'e', 'c']);
list.setRange(1, 3, ['f', 'g', 'h'], 1);
expect(list, ['d', 'g', 'h']);
});
}
// These are the tests we will keep after deprecations occur.
_runObservableListTests() {
group('content changes', () {
Completer<List<ListChangeRecord>> completer;
List<String> previousState;
ObservableList<String> list;
Stream<List<ChangeRecord>> stream;
StreamSubscription sub;
Future next() {
completer = new Completer<List<ListChangeRecord>>.sync();
return completer.future;
}
Future<Null> expectChanges(List<ListChangeRecord> changes) {
// Applying these change records in order should make the new list.
for (final change in changes) {
change.apply(previousState);
}
expect(list, previousState);
// If these fail, it might be safe to update if optimized/changed.
return next().then((actualChanges) {
expect(actualChanges, changes);
});
}
setUp(() {
previousState = ['a', 'b', 'c'];
list = new ObservableList<String>.from(previousState);
stream = list.changes.where(_hasListChanges);
sub = stream.listen((c) {
if (completer?.isCompleted == false) {
completer.complete(c.where(_onlyListChanges).toList());
}
previousState = list.toList();
});
});
tearDown(() => sub.cancel());
ListChangeRecord _delta(
int index, {
List removed: const [],
int addedCount: 0,
}) {
return new ListChangeRecord(
list,
index,
removed: removed,
addedCount: addedCount,
);
}
test('operator[]=', () async {
list[0] = 'd';
await expectChanges([
_delta(0, removed: ['a'], addedCount: 1),
]);
});
test('add', () async {
list.add('d');
await expectChanges([
_delta(3, addedCount: 1),
]);
});
test('addAll', () async {
list.addAll(['d', 'e']);
await expectChanges([
_delta(3, addedCount: 2),
]);
});
test('clear', () async {
list.clear();
await expectChanges([
_delta(0, removed: ['a', 'b', 'c']),
]);
});
test('fillRange', () async {
list.fillRange(1, 3, 'd');
await expectChanges([
_delta(1, removed: ['b', 'c'], addedCount: 2),
]);
});
test('insert', () async {
list.insert(1, 'd');
await expectChanges([
_delta(1, addedCount: 1),
]);
});
test('insertAll', () async {
list.insertAll(1, ['d', 'e']);
await expectChanges([
_delta(1, addedCount: 2),
]);
});
test('length', () async {
list.length = 5;
await expectChanges([
_delta(3, addedCount: 2),
]);
list.length = 1;
await expectChanges([
_delta(1, removed: ['b', 'c', null, null])
]);
});
test('remove', () async {
list.remove('b');
await expectChanges([
_delta(1, removed: ['b'])
]);
});
test('removeAt', () async {
list.removeAt(1);
await expectChanges([
_delta(1, removed: ['b'])
]);
});
test('removeRange', () async {
list.removeRange(0, 2);
await expectChanges([
_delta(0, removed: ['a', 'b'])
]);
});
test('removeWhere', () async {
list.removeWhere((s) => s == 'b');
await expectChanges([
_delta(1, removed: ['b'])
]);
});
test('replaceRange', () async {
list.replaceRange(0, 2, ['d', 'e']);
await expectChanges([
// Normally would be
// _delta(0, removed: ['a', 'b']),
// _delta(0, addedCount: 2),
// But projectListSplices(...) optimizes to single record
_delta(0, removed: ['a', 'b'], addedCount: 2),
]);
});
test('retainWhere', () async {
list.retainWhere((s) => s == 'b');
await expectChanges([
_delta(0, removed: ['a']),
_delta(1, removed: ['c']),
]);
});
test('setAll', () async {
list.setAll(1, ['d', 'e']);
await expectChanges([
_delta(1, removed: ['b', 'c'], addedCount: 2),
]);
});
test('setRange', () async {
list.setRange(0, 2, ['d', 'e']);
await expectChanges([
_delta(0, removed: ['a', 'b'], addedCount: 2),
]);
list.setRange(1, 3, ['f', 'g', 'h'], 1);
await expectChanges([
_delta(1, removed: ['e', 'c'], addedCount: 2),
]);
});
});
}
// These are tests we will remove after deprecations occur.
_runDeprecatedTests() {
group('listChanges stream', () {
Completer<List<ListChangeRecord>> completer;
ObservableList<String> list;
List<String> previousState;
StreamSubscription sub;
Future next() {
completer = new Completer<List<ListChangeRecord>>.sync();
return completer.future;
}
Future<Null> expectChanges(List<ListChangeRecord> changes) {
// Applying these change records in order should make the new list.
for (final change in changes) {
change.apply(previousState);
}
expect(list, previousState);
// If these fail, it might be safe to update if optimized/changed.
return next().then((actualChanges) {
expect(actualChanges, changes);
});
}
setUp(() {
previousState = ['a', 'b', 'c'];
list = new ObservableList<String>.from(previousState);
sub = list.listChanges.listen((c) {
if (completer?.isCompleted == false) {
completer.complete(c.toList());
}
previousState = list.toList();
});
});
tearDown(() => sub.cancel());
test('updates when an index changes', () async {
list[0] = 'd';
await expectChanges([
new ListChangeRecord.replace(list, 0, ['a']),
]);
});
test('should not emit an empty list', () async {
// This is optimized into a no-op.
list[0] = 'd';
list[0] = 'a';
next().then((_) {
fail('Should not emit change records');
});
await new Future.value();
});
});
group('length changes', () {
Completer<List<ListChangeRecord>> completer;
ObservableList<String> list;
Stream<List<ChangeRecord>> stream;
StreamSubscription sub;
Future next() {
completer = new Completer<List<ListChangeRecord>>.sync();
return completer.future;
}
setUp(() {
list = new ObservableList<String>.from(['a', 'b', 'c']);
stream = list.changes.where(_hasPropChanges);
sub = stream.listen((c) {
if (completer?.isCompleted == false) {
completer.complete(c.where(_onlyPropRecords).toList());
}
});
});
tearDown(() => sub.cancel());
PropertyChangeRecord _length(int oldLength, int newLength) {
return new PropertyChangeRecord(list, #length, oldLength, newLength);
}
PropertyChangeRecord _isEmpty(bool oldValue, bool newValue) {
return new PropertyChangeRecord(list, #isEmpty, oldValue, newValue);
}
PropertyChangeRecord _isNotEmpty(bool oldValue, bool newValue) {
return new PropertyChangeRecord(list, #isNotEmpty, oldValue, newValue);
}
test('add', () async {
list.add('d');
expect(await next(), [
_length(3, 4),
]);
});
test('addAll', () async {
list.addAll(['d', 'e']);
expect(await next(), [
_length(3, 5),
]);
});
test('clear', () async {
list.clear();
expect(await next(), [
_length(3, 0),
_isEmpty(false, true),
_isNotEmpty(true, false),
]);
});
test('insert', () async {
list.insert(0, 'd');
expect(await next(), [
_length(3, 4),
]);
});
test('insertAll', () async {
list.insertAll(0, ['d', 'e']);
expect(await next(), [
_length(3, 5),
]);
});
test('length', () async {
list.length = 5;
expect(await next(), [
_length(3, 5),
]);
list.length = 0;
expect(await next(), [
_length(5, 0),
_isEmpty(false, true),
_isNotEmpty(true, false),
]);
list.length = 1;
expect(await next(), [
_length(0, 1),
_isEmpty(true, false),
_isNotEmpty(false, true),
]);
});
test('remove', () async {
list.remove('a');
expect(await next(), [
_length(3, 2),
]);
});
test('removeAt', () async {
list.removeAt(0);
expect(await next(), [
_length(3, 2),
]);
});
test('removeLast', () async {
list.removeLast();
expect(await next(), [
_length(3, 2),
]);
});
test('removeRange', () async {
list.removeRange(0, 2);
expect(await next(), [
_length(3, 1),
]);
});
test('removeWhere', () async {
list.removeWhere((s) => s == 'a');
expect(await next(), [
_length(3, 2),
]);
});
test('retainWhere', () async {
list.retainWhere((s) => s == 'a');
expect(await next(), [
_length(3, 1),
]);
});
});
}