import 'package:clide/kernel/kernel.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('DaemonBus', () { late DaemonBus bus; setUp(() => bus = DaemonBus()); tearDown(() => bus.dispose()); test('emit delivers to stream subscribers', () async { final events = []; final sub = bus.stream.listen(events.add); bus.emit(const ThemeChanged(themeName: 'summer-night')); await Future.delayed(Duration.zero); expect(events, hasLength(1)); expect(events.first.event, isA()); await sub.cancel(); }); test('on() filters by runtime type', () async { final themes = []; final extensions = []; final s1 = bus.on().listen(themes.add); final s2 = bus.on().listen(extensions.add); bus.emit(const ThemeChanged(themeName: 'a')); bus.emit(const ExtensionActivated(id: 'builtin.git')); bus.emit(const ThemeChanged(themeName: 'b')); await Future.delayed(Duration.zero); expect(themes.map((e) => e.themeName), ['a', 'b']); expect(extensions.map((e) => e.id), ['builtin.git']); await s1.cancel(); await s2.cancel(); }); test('broadcasts to multiple listeners independently', () async { final a = []; final b = []; final s1 = bus.stream.listen((e) => a.add(e.event)); final s2 = bus.stream.listen((e) => b.add(e.event)); bus.emit(const ThemeChanged(themeName: 'x')); await Future.delayed(Duration.zero); expect(a, hasLength(1)); expect(b, hasLength(1)); await s1.cancel(); await s2.cancel(); }); test('emit after dispose is a silent no-op', () async { await bus.dispose(); // must not throw bus.emit(const ThemeChanged(themeName: 'nope')); }); test('envelope stamps a timestamp', () async { final capture = []; final sub = bus.stream.listen(capture.add); final before = DateTime.now().toUtc(); bus.emit(const ThemeChanged(themeName: 'n')); await Future.delayed(Duration.zero); final after = DateTime.now().toUtc(); expect(capture, hasLength(1)); final ts = capture.first.timestamp; expect(ts.isAfter(before) || ts.isAtSameMomentAs(before), isTrue); expect(ts.isBefore(after) || ts.isAtSameMomentAs(after), isTrue); await sub.cancel(); }); }); }