Files
plezy/test/mixins/event_aware_test.dart

261 lines
8.0 KiB
Dart

import 'package:plezy/media/ids.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/mixins/event_aware.dart';
import 'package:plezy/utils/base_notifier.dart';
import 'package:plezy/utils/global_key_utils.dart';
import 'package:plezy/utils/hierarchical_event_mixin.dart';
class _FakeEvent with HierarchicalEventMixin {
_FakeEvent({required this.serverId, required this.itemId, this.parentChain = const []});
@override
final ServerId serverId;
@override
final String itemId;
@override
final List<String> parentChain;
@override
String get globalKey => buildGlobalKey(serverId, itemId);
}
class _FakeNotifier extends BaseNotifier<_FakeEvent> {}
/// Helper to drain the broadcast microtasks before reading received events.
Future<void> _settle() => Future<void>.delayed(Duration.zero);
void main() {
group('subscribeToHierarchicalEvents', () {
late _FakeNotifier notifier;
late List<_FakeEvent> received;
setUp(() {
notifier = _FakeNotifier();
received = <_FakeEvent>[];
});
tearDown(() => notifier.dispose());
test('drops events when not mounted', () async {
var mounted = false;
final sub = subscribeToHierarchicalEvents<_FakeEvent>(
notifier: notifier,
mounted: () => mounted,
serverId: () => null,
globalKeys: () => null,
itemIds: () => null,
onEvent: received.add,
);
notifier.notify(_FakeEvent(serverId: ServerId('s1'), itemId: '42'));
await _settle();
expect(received, isEmpty);
// Once mounted, future events flow.
mounted = true;
final ev = _FakeEvent(serverId: ServerId('s1'), itemId: '99');
notifier.notify(ev);
await _settle();
expect(received, [ev]);
await sub.cancel();
});
test('filters by serverId when provided', () async {
final sub = subscribeToHierarchicalEvents<_FakeEvent>(
notifier: notifier,
mounted: () => true,
serverId: () => 's1',
globalKeys: () => null,
itemIds: () => null,
onEvent: received.add,
);
final keep = _FakeEvent(serverId: ServerId('s1'), itemId: '1');
final drop = _FakeEvent(serverId: ServerId('s2'), itemId: '1');
notifier.notify(drop);
notifier.notify(keep);
await _settle();
expect(received, [keep]);
await sub.cancel();
});
test('globalKeys filter delivers events matching any global key', () async {
final keys = {buildGlobalKey(ServerId('s1'), '42'), buildGlobalKey(ServerId('s1'), '7')};
final sub = subscribeToHierarchicalEvents<_FakeEvent>(
notifier: notifier,
mounted: () => true,
serverId: () => null,
globalKeys: () => keys,
itemIds: () => null,
onEvent: received.add,
);
final hit = _FakeEvent(serverId: ServerId('s1'), itemId: '42');
final miss = _FakeEvent(serverId: ServerId('s1'), itemId: '9999');
notifier.notify(hit);
notifier.notify(miss);
await _settle();
expect(received, [hit]);
await sub.cancel();
});
test('globalKeys filter takes precedence over itemIds', () async {
// Even though itemIds would match '5', globalKeys path returns early
// and short-circuits the itemIds check.
final globalKeys = {buildGlobalKey(ServerId('s1'), '99')};
final itemIds = {'5'};
final sub = subscribeToHierarchicalEvents<_FakeEvent>(
notifier: notifier,
mounted: () => true,
serverId: () => null,
globalKeys: () => globalKeys,
itemIds: () => itemIds,
onEvent: received.add,
);
// itemId 5 matches the itemIds set but not the globalKeys set.
notifier.notify(_FakeEvent(serverId: ServerId('s1'), itemId: '5'));
await _settle();
expect(received, isEmpty);
// Now an event matching the globalKeys set comes through.
final hit = _FakeEvent(serverId: ServerId('s1'), itemId: '99');
notifier.notify(hit);
await _settle();
expect(received, [hit]);
await sub.cancel();
});
test('null itemIds delivers all events (when no other filters)', () async {
final sub = subscribeToHierarchicalEvents<_FakeEvent>(
notifier: notifier,
mounted: () => true,
serverId: () => null,
globalKeys: () => null,
itemIds: () => null,
onEvent: received.add,
);
final a = _FakeEvent(serverId: ServerId('s1'), itemId: '1');
final b = _FakeEvent(serverId: ServerId('s2'), itemId: '2');
notifier.notify(a);
notifier.notify(b);
await _settle();
expect(received, [a, b]);
await sub.cancel();
});
test('empty itemIds delivers nothing', () async {
final sub = subscribeToHierarchicalEvents<_FakeEvent>(
notifier: notifier,
mounted: () => true,
serverId: () => null,
globalKeys: () => null,
itemIds: () => <String>{},
onEvent: received.add,
);
notifier.notify(_FakeEvent(serverId: ServerId('s1'), itemId: '1'));
notifier.notify(_FakeEvent(serverId: ServerId('s2'), itemId: '2'));
await _settle();
expect(received, isEmpty);
await sub.cancel();
});
test('itemIds filter delivers direct hits', () async {
final sub = subscribeToHierarchicalEvents<_FakeEvent>(
notifier: notifier,
mounted: () => true,
serverId: () => null,
globalKeys: () => null,
itemIds: () => {'42'},
onEvent: received.add,
);
final hit = _FakeEvent(serverId: ServerId('s1'), itemId: '42');
final miss = _FakeEvent(serverId: ServerId('s1'), itemId: '99');
notifier.notify(hit);
notifier.notify(miss);
await _settle();
expect(received, [hit]);
await sub.cancel();
});
test('itemIds filter delivers parent-chain hits', () async {
// Event for an episode whose parent chain includes the show id.
// The screen tracks the show id, so it should receive the event.
final sub = subscribeToHierarchicalEvents<_FakeEvent>(
notifier: notifier,
mounted: () => true,
serverId: () => null,
globalKeys: () => null,
itemIds: () => {'show123'},
onEvent: received.add,
);
final episode = _FakeEvent(serverId: ServerId('s1'), itemId: 'episode456', parentChain: ['season789', 'show123']);
notifier.notify(episode);
await _settle();
expect(received, [episode]);
await sub.cancel();
});
test('filters re-evaluate on each event (dynamic getters)', () async {
var ids = <String>{'1'};
final sub = subscribeToHierarchicalEvents<_FakeEvent>(
notifier: notifier,
mounted: () => true,
serverId: () => null,
globalKeys: () => null,
itemIds: () => ids,
onEvent: received.add,
);
notifier.notify(_FakeEvent(serverId: ServerId('s1'), itemId: '1'));
notifier.notify(_FakeEvent(serverId: ServerId('s1'), itemId: '2'));
await _settle();
expect(received.map((e) => e.itemId).toList(), ['1']);
// Change the filter set; the next event should be evaluated against it.
ids = {'2'};
notifier.notify(_FakeEvent(serverId: ServerId('s1'), itemId: '1'));
notifier.notify(_FakeEvent(serverId: ServerId('s1'), itemId: '2'));
await _settle();
expect(received.map((e) => e.itemId).toList(), ['1', '2']);
await sub.cancel();
});
test('cancel stops further deliveries', () async {
final sub = subscribeToHierarchicalEvents<_FakeEvent>(
notifier: notifier,
mounted: () => true,
serverId: () => null,
globalKeys: () => null,
itemIds: () => null,
onEvent: received.add,
);
notifier.notify(_FakeEvent(serverId: ServerId('s1'), itemId: '1'));
await _settle();
expect(received, hasLength(1));
await sub.cancel();
notifier.notify(_FakeEvent(serverId: ServerId('s1'), itemId: '2'));
await _settle();
expect(received, hasLength(1));
});
});
}