| // 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'; |
| |
| void main() => observableTests(); |
| |
| void observableTests() { |
| // Track the subscriptions so we can clean them up in tearDown. |
| List subs; |
| |
| setUp(() { |
| subs = []; |
| }); |
| |
| tearDown(() { |
| for (var sub in subs) sub.cancel(); |
| }); |
| |
| test('handle future result', () { |
| var callback = expectAsync(() {}); |
| return new Future(callback); |
| }); |
| |
| test('no observers', () { |
| var t = createModel(123); |
| expect(t.value, 123); |
| t.value = 42; |
| expect(t.value, 42); |
| expect(t.hasObservers, false); |
| }); |
| |
| test('listen adds an observer', () { |
| var t = createModel(123); |
| expect(t.hasObservers, false); |
| |
| subs.add(t.changes.listen((n) {})); |
| expect(t.hasObservers, true); |
| }); |
| |
| test('changes delived async', () { |
| var t = createModel(123); |
| int called = 0; |
| |
| subs.add(t.changes.listen(expectAsync((records) { |
| called++; |
| expectPropertyChanges(records, 2); |
| }))); |
| |
| t.value = 41; |
| t.value = 42; |
| expect(called, 0); |
| }); |
| |
| test('cause changes in handler', () { |
| var t = createModel(123); |
| int called = 0; |
| |
| subs.add(t.changes.listen(expectAsync((records) { |
| called++; |
| expectPropertyChanges(records, 1); |
| if (called == 1) { |
| // Cause another change |
| t.value = 777; |
| } |
| }, count: 2))); |
| |
| t.value = 42; |
| }); |
| |
| test('multiple observers', () { |
| var t = createModel(123); |
| |
| verifyRecords(records) { |
| expectPropertyChanges(records, 2); |
| } |
| |
| subs.add(t.changes.listen(expectAsync(verifyRecords))); |
| subs.add(t.changes.listen(expectAsync(verifyRecords))); |
| |
| t.value = 41; |
| t.value = 42; |
| }); |
| |
| test('async processing model', () { |
| var t = createModel(123); |
| var records = []; |
| subs.add(t.changes.listen((r) { |
| records.addAll(r); |
| })); |
| t.value = 41; |
| t.value = 42; |
| expectChanges(records, [], reason: 'changes delived async'); |
| |
| return new Future(() { |
| expectPropertyChanges(records, 2); |
| records.clear(); |
| |
| t.value = 777; |
| expectChanges(records, [], reason: 'changes delived async'); |
| }).then(newMicrotask).then((_) { |
| expectPropertyChanges(records, 1); |
| }); |
| }); |
| |
| test('cancel listening', () { |
| var t = createModel(123); |
| var sub; |
| sub = t.changes.listen(expectAsync((records) { |
| expectPropertyChanges(records, 1); |
| sub.cancel(); |
| t.value = 777; |
| })); |
| t.value = 42; |
| }); |
| |
| test('cancel and reobserve', () { |
| var t = createModel(123); |
| var sub; |
| sub = t.changes.listen(expectAsync((records) { |
| expectPropertyChanges(records, 1); |
| sub.cancel(); |
| |
| scheduleMicrotask(() { |
| subs.add(t.changes.listen(expectAsync((records) { |
| expectPropertyChanges(records, 1); |
| }))); |
| t.value = 777; |
| }); |
| })); |
| t.value = 42; |
| }); |
| |
| test('cannot modify changes list', () { |
| var t = createModel(123); |
| var records; |
| subs.add(t.changes.listen((r) { |
| records = r; |
| })); |
| t.value = 42; |
| |
| return new Future(() { |
| expectPropertyChanges(records, 1); |
| |
| // Verify that mutation operations on the list fail: |
| |
| expect(() { |
| records[0] = new PropertyChangeRecord(t, #value, 0, 1); |
| }, throwsUnsupportedError); |
| |
| expect(() { |
| records.clear(); |
| }, throwsUnsupportedError); |
| |
| expect(() { |
| records.length = 0; |
| }, throwsUnsupportedError); |
| }); |
| }); |
| |
| test('notifyChange', () { |
| var t = createModel(123); |
| var records = []; |
| subs.add(t.changes.listen((r) { |
| records.addAll(r); |
| })); |
| t.notifyChange(new PropertyChangeRecord(t, #value, 123, 42)); |
| |
| return new Future(() { |
| expectPropertyChanges(records, 1); |
| expect(t.value, 123, reason: 'value did not actually change.'); |
| }); |
| }); |
| |
| test('notifyPropertyChange', () { |
| var t = createModel(123); |
| var records; |
| subs.add(t.changes.listen((r) { |
| records = r; |
| })); |
| expect(t.notifyPropertyChange(#value, t.value, 42), 42, |
| reason: 'notifyPropertyChange returns newValue'); |
| |
| return new Future(() { |
| expectPropertyChanges(records, 1); |
| expect(t.value, 123, reason: 'value did not actually change.'); |
| }); |
| }); |
| } |
| |
| expectPropertyChanges(records, int number) { |
| expect(records.length, number, reason: 'expected $number change records'); |
| for (var record in records) { |
| expect(record is PropertyChangeRecord, true, |
| reason: 'record should be PropertyChangeRecord'); |
| expect((record as PropertyChangeRecord).name, #value, |
| reason: 'record should indicate a change to the "value" property'); |
| } |
| } |
| |
| createModel(int number) => new ObservableSubclass(number); |
| |
| class ObservableSubclass<T> extends PropertyChangeNotifier { |
| ObservableSubclass([T initialValue]) : _value = initialValue; |
| |
| T get value => _value; |
| set value(T newValue) { |
| T oldValue = _value; |
| _value = newValue; |
| notifyPropertyChange(#value, oldValue, newValue); |
| } |
| |
| T _value; |
| |
| @override |
| String toString() => '#<$runtimeType value: $value>'; |
| } |