appflowy-editor/test/editor/util/property_notifier_test.dart
Lucas 41335c54ae
chore: merge flutter 3_27_4 into main branch (#1228)
* chore: merge flutter/3_27_4 to main branch

* chore: code format

* fix: unit tests
2026-07-27 15:37:27 +08:00

113 lines
3.6 KiB
Dart

import 'package:appflowy_editor/src/editor/util/property_notifier.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('PropertyValueNotifier', () {
test('notifies listeners on assignment even when value is unchanged', () {
final notifier = PropertyValueNotifier<int>(1);
var calls = 0;
notifier.addListener(() => calls++);
notifier.value = 1;
notifier.value = 1;
expect(calls, 2);
});
test('notifies listeners in registration order', () {
final notifier = PropertyValueNotifier<int>(0);
final order = <int>[];
notifier.addListener(() => order.add(1));
notifier.addListener(() => order.add(2));
notifier.addListener(() => order.add(3));
notifier.value = 1;
expect(order, [1, 2, 3]);
});
test('removeListener stops further notifications', () {
final notifier = PropertyValueNotifier<int>(0);
var calls = 0;
void listener() => calls++;
notifier.addListener(listener);
notifier.value = 1;
notifier.removeListener(listener);
notifier.value = 2;
expect(calls, 1);
});
test('listener removed during dispatch is not called this round', () {
final notifier = PropertyValueNotifier<int>(0);
final order = <String>[];
late VoidCallback bListener;
void aListener() {
order.add('a');
notifier.removeListener(bListener);
}
bListener = () => order.add('b');
notifier.addListener(aListener);
notifier.addListener(bListener);
notifier.value = 1;
expect(order, ['a']);
// After dispatch, b is fully removed.
notifier.value = 2;
expect(order, ['a', 'a']);
});
test('listener added during dispatch is deferred until next notify', () {
final notifier = PropertyValueNotifier<int>(0);
final order = <String>[];
void late() => order.add('late');
notifier.addListener(() {
order.add('early');
notifier.addListener(late);
});
notifier.value = 1;
expect(order, ['early']);
notifier.value = 2;
expect(order, ['early', 'early', 'late']);
});
test('hasListeners reflects current registration', () {
final notifier = PropertyValueNotifier<int>(0);
void listener() {}
expect(notifier.hasListeners, isFalse);
notifier.addListener(listener);
expect(notifier.hasListeners, isTrue);
notifier.removeListener(listener);
expect(notifier.hasListeners, isFalse);
});
test('removeListener is O(1) even at large listener counts', () {
// Regression test for the original O(n²) dispose cost. We just make sure
// a large registration / deregistration pass completes well within a
// second; the ChangeNotifier-backed implementation took multiple seconds
// for this workload (10k * 10k linear scans + array shifts).
final notifier = PropertyValueNotifier<int>(0);
const n = 20000;
final listeners = <void Function()>[];
for (var i = 0; i < n; i++) {
// Each listener must be a distinct closure so Set semantics hold.
void l() {}
listeners.add(l);
notifier.addListener(l);
}
final stopwatch = Stopwatch()..start();
for (final l in listeners) {
notifier.removeListener(l);
}
stopwatch.stop();
expect(notifier.hasListeners, isFalse);
expect(
stopwatch.elapsedMilliseconds,
lessThan(1000),
reason: 'Removing $n listeners should be O(n), not O(n^2). Took '
'${stopwatch.elapsedMilliseconds} ms.',
);
});
});
}