From a6ab7655b3af05284aafdc9141b9d4971bddca2c Mon Sep 17 00:00:00 2001 From: marcojakob Date: Mon, 8 Sep 2014 15:21:32 +0200 Subject: [PATCH] Simplify the EventBus API: Allow any Dart object as an event (see issue #11) --- CHANGELOG.md | 18 +++ README.md | 70 +++++++--- example/events.dart | 24 ++-- example/example.css | 36 +---- example/example.dart | 28 ++-- example/index.html | 132 +++++++++--------- lib/event_bus.dart | 101 +++++++++----- lib/src/logging_event_bus.dart | 14 -- lib/src/simple_event_bus.dart | 41 ------ pubspec.yaml | 8 +- test/all_tests.dart | 6 +- test/event_bus_test.dart | 106 ++++++++++++++ test/hierarchical_event_bus_test.dart | 70 ++++++++++ test/src/simple_event_bus_test.dart | 191 -------------------------- 14 files changed, 419 insertions(+), 426 deletions(-) delete mode 100644 lib/src/logging_event_bus.dart delete mode 100644 lib/src/simple_event_bus.dart create mode 100644 test/event_bus_test.dart create mode 100644 test/hierarchical_event_bus_test.dart delete mode 100644 test/src/simple_event_bus_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index f4b354f..74cbbb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## Version 0.3.0 (2014-09-08) + +* BREAKING CHANGE: Changed and simplified the EventBus API. We can now dispatch + any Dart object as an event. Before, we had to create an EventType for every + type of event we wanted to fire. Now we can use any class as an event. + Listeners can (optionally) filter events by that class. +* Added a way to create a **hierarchical event bus** that filters events by + class and their subclasses. This currently only works with classes + **extending** other classes and not with **implementing** an interface. + We might have to wait for + https://code.google.com/p/dart/issues/detail?id=20756 to enable interfaces. +* BREAKING CHANGE: Default to async mode instead of sync. This now matches the + of the Dart streams. +* BREAKING CHANGE: Removed LoggingEventBus. Reason is that logging can easily + be implemented with a event listener that listens for all events and logs + them. + + ## Version 0.2.5 (2014-09-03) * Update example. diff --git a/README.md b/README.md index 1598f08..ee3a554 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -Event Bus -================ +# Event Bus A simple Event Bus using Dart [Streams](https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart:async.Stream) for decoupling applications. @@ -7,9 +6,9 @@ for decoupling applications. ## Demo -See [Dart Event Bus in action](http://code.makery.ch/dart/event-bus/). +See [Dart Event Bus in action](http://code.makery.ch/library/dart-event-bus/). -All examples are also available in the `example` directory on GitHub. +All examples are also available in the [example directory on GitHub](https://github.com/marcojakob/dart-event-bus/tree/master/example). ## Event Bus Pattern @@ -42,16 +41,25 @@ By communication through an **Event Bus**, the coupling is reduced. ## Usage ### 1. Define Events + +Any Dart class can be used as an event. + ```dart import 'package:event_bus/event_bus.dart'; -final EventType userLoggedInEvent = new EventType(); -final EventType newOrderEvent = new EventType(); +class UserLoggedInEvent { + User user; + + UserLoggedInEvent(this.user); +} + +class NewOrderEvent { + Order order; + + NewOrderEvent(this.text); +} ``` -Note: The generic type of the event (`User` and `Order` in this case) is the -type of data that will be provided when the event is fired. - ### 2. Create Event Bus @@ -64,40 +72,58 @@ set up to group a specific set of events. EventBus eventBus = new EventBus(); ``` -This will instantiate the default implementation of `EventBus` which is -`SimpleEventBus`. You may provide your own `EventBus` by either extending -`SimpleEventBus` or implementing `EventBus`. +You can alternatively use the `HierarchicalEventBus` that filters events by +event class **including** its subclasses. + +```dart +EventBus eventBus = new EventBus.hierarchical(); +``` ### 3. Register Listeners -Register listeners that will be called whenever the event is fired. +Register listeners for a **specific events**: + ```dart -eventBus.on(userLoggedInEvent).listen((User user) { - print(user.name); +eventBus.on(UserLoggedInEvent).listen((UserLoggedInEvent event) { + print(event.user); }); ``` -`EventBus` uses Dart [Streams](http://api.dartlang.org/docs/releases/latest/dart_async/Stream.html) +Register listeners for **all events**: + +```dart +eventBus.on().listen((event) { + // Print the runtime type. Such a set up could be used for logging. + print(event.runtimeType); +}); +``` + + +#### About Dart Streams + +`EventBus` uses Dart [Streams](https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart:async.Stream) as its underlying mechanism to keep track of listeners. You may use all -functionality available by the [Stream](http://api.dartlang.org/docs/releases/latest/dart_async/Stream.html) -API. One example is the use of [StreamSubscriptions](http://api.dartlang.org/docs/releases/latest/dart_async/StreamSubscription.html) +functionality available by the [Stream](https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart:async.Stream) +API. One example is the use of [StreamSubscriptions](https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart:async.StreamSubscription) to later unsubscribe from the events. ```dart -StreamSubscription subscription = eventBus.on(userLoggedInEvent).listen((User user) { - print(user.name); +StreamSubscription loginSubscription = eventBus.on(UserLoggedInEvent).listen((UserLoggedInEvent event) { + print(event.user); }); -subscription.cancel(); +loginSubscription.cancel(); ``` + ### 4. Fire Events Finally, we need to fire an event. ```dart -eventBus.fire(userLoggedInEvent, new User('Mickey')); +User myUser = new User('Mickey'); +eventBus.fire(new UserLoggedInEvent(myUser)); ``` diff --git a/example/events.dart b/example/events.dart index 25398ec..07cce78 100644 --- a/example/events.dart +++ b/example/events.dart @@ -1,20 +1,24 @@ /** - * This is an example of how to set up events used by the event bus. + * This is an example of how to set up the [EventBus] and its events. */ library events; import 'package:event_bus/event_bus.dart'; -export 'package:event_bus/event_bus.dart'; - -EventBus _eventBus; /// The global [EventBus] object. -EventBus get eventBus => _eventBus; +EventBus eventBus = new EventBus(); + +/// Event A. +class MyEventA { + String text; + + MyEventA(this.text); +} -/// Initializes the global [EventBus] object. Should only be called once! -void init(EventBus eventBus) { - _eventBus = eventBus; +/// Event B. +class MyEventB { + String text; + + MyEventB(this.text); } -final EventType textUpdateA = new EventType('textUpdateA'); -final EventType textUpdateB = new EventType('textUpdateB'); \ No newline at end of file diff --git a/example/example.css b/example/example.css index 23c5f9d..3ff1fc7 100644 --- a/example/example.css +++ b/example/example.css @@ -1,46 +1,16 @@ - -body { - background-color: #F8F8F8; - font-family: 'Open Sans', sans-serif; - font-size: 14px; - font-weight: normal; - line-height: 1.2em; - margin: 15px; -} - -h1, p { - color: #333; -} - -#example-container { - width: 620px; - margin-bottom: 20px; -} - #example-container .listener { width: 300px; margin: 5px 5px 20px 5px; padding: 10px; float: left; - position: relative; border: 1px solid #ccc; - background-color: #fff; - -webkit-box-sizing:border-box; - -khtml-box-sizing:border-box; - -moz-box-sizing:border-box; - box-sizing:border-box; } #example-container .listener * { - margin-top: 5px; + margin-top: 5px; } #example-container .listener textarea { - display: block; - width: 270px; - height: 100px; -} - -#example-container .event { - display: block; + width: 270px; + height: 100px; } \ No newline at end of file diff --git a/example/example.dart b/example/example.dart index a59907a..e978df7 100644 --- a/example/example.dart +++ b/example/example.dart @@ -1,19 +1,21 @@ import 'dart:html'; import 'dart:async'; -import 'events.dart' as events; -import 'package:intl/intl.dart'; +import 'events.dart'; import 'package:logging/logging.dart'; int counterA = 1; int counterB = 1; +final _log = new Logger('event_bus_example'); + void main() { // Init logging. initLogging(); - // Initialize the global event bus. - events.init(new events.LoggingEventBus()); + // Log all events. + eventBus.on().listen((event) => _log.finest('event fired: ${event.runtimeType}')); + // Initialize the listener boxes. Listener listener1 = new Listener(querySelector('#listener-1')); @@ -29,28 +31,24 @@ void main() { // ------------------------------------------------- // Fire Event A // ------------------------------------------------- - events.eventBus.fire(events.textUpdateA, 'Received Event A [$counterA]'); + eventBus.fire(new MyEventA('Received Event A [$counterA]')); fireLabelA.text = '--> fired [$counterA]'; counterA++; - fireButtonA.text = 'Fire Event A [$counterA]'; }); fireButtonB.onClick.listen((_) { // ------------------------------------------------- // Fire Event B // ------------------------------------------------- - events.eventBus.fire(events.textUpdateB, 'Received Event B [$counterB]'); + eventBus.fire(new MyEventB('Received Event B [$counterB]')); fireLabelB.text = '--> fired [$counterB]'; counterB++; - fireButtonB.text = 'Fire Event B [$counterB]'; }); } initLogging() { - DateFormat dateFormat = new DateFormat('yyyy.mm.dd HH:mm:ss.SSS'); - // Print output to console. Logger.root.onRecord.listen((LogRecord r) { - print('${dateFormat.format(r.time)}\t${r.loggerName}\t[${r.level.name}]:\t${r.message}'); + print('${r.time}\t${r.loggerName}\t[${r.level.name}]:\t${r.message}'); }); // Root logger level. @@ -81,8 +79,8 @@ class Listener { // ------------------------------------------------- // Listen for Event A // ------------------------------------------------- - subscription = events.eventBus.on(events.textUpdateA).listen((String text) { - appendOuput(text); + subscription = eventBus.on(MyEventA).listen((event) { + appendOuput(event.text); }); appendOuput('---'); appendOuput('Listening for Event A'); @@ -97,8 +95,8 @@ class Listener { // ------------------------------------------------- // Listen for Event B // ------------------------------------------------- - subscription = events.eventBus.on(events.textUpdateB).listen((String text) { - appendOuput(text); + subscription = eventBus.on(MyEventB).listen((MyEventB event) { + appendOuput(event.text); }); appendOuput('---'); appendOuput('Listening for Event B'); diff --git a/example/index.html b/example/index.html index 62fbe3b..0095bb2 100644 --- a/example/index.html +++ b/example/index.html @@ -1,70 +1,78 @@ - - - Event Bus - - - - - - - - -
- - + +Event Bus + + + + + + + + +
+ + Example Source on GitHub - -
-
-

Listener 1

-

- -

-

- - -

-

- - - -

-
-
-

Listener 2

-

- -

-

- - -

-

- - - -

-
+ +
+
+

Listener 1

+

+ +

+

+ + +

+

+ + + +

+
+
+

Listener 2

+

+ +

+

+ + +

+

+ + + +

+
+
+ +

Fire Events

+
+
+

+ + +

- -

Fire Events

-
-
-

- - -

-
-
-

- - -

-
+
+

+ + +

- +
+ diff --git a/lib/event_bus.dart b/lib/event_bus.dart index 4465cfe..b3cef36 100644 --- a/lib/event_bus.dart +++ b/lib/event_bus.dart @@ -1,42 +1,59 @@ library event_bus; -import 'package:logging/logging.dart'; import 'dart:async'; -part 'src/simple_event_bus.dart'; -part 'src/logging_event_bus.dart'; +@MirrorsUsed(symbols: '*') // Do not keep any names. +import 'dart:mirrors'; /** * Dispatches events to listeners using the Dart [Stream] API. The [EventBus] * enables decoupled applications. It allows objects to interact without * requiring to explicitly define listeners and keeping track of them. * - * Usually there is just one [EventBus] per application, but more than one - * may be set up to group a specific set of events. - * * Not all events should be broadcasted through the [EventBus] but only those of * general interest. * - * **Note:** Make sure that listeners on the stream handle the same type as - * the generic type argument of [EventType]. Currently, this can't be expressed in - * Dart - see [Issue 254](https://code.google.com/p/dart/issues/detail?id=254) + * Events are normal Dart objects. By specifying a class, listeners can + * filter events. Such a filter will return + * specifying a class. */ -abstract class EventBus { +class EventBus { + + /// Controller for the event bus stream. + StreamController _streamController; + + /** + * Creates an [EventBus]. + * + * If [sync] is true, events are passed directly to the stream's listeners + * during an [fire] call. If false (the default), the event will be passed to + * the listeners at a later time, after the code creating the event has + * completed. + */ + EventBus({bool sync: false}) { + _streamController = new StreamController.broadcast(sync: sync); + } /** - * Creates [SimpleEventBus], the default implementation of [EventBus]. + * Creats a [HierarchicalEventBus]. + * + * It filters events by an event class **including** its subclasses. + * + * Note: This currently only works with classes **extending** other classes + * and not with **implementing** an interface. We might have to wait for + * https://code.google.com/p/dart/issues/detail?id=20756 to enable interfaces. * * If [sync] is true, events are passed directly to the stream's listeners - * during an add, addError or close call. If [sync] is false, the event - * will be passed to the listeners at a later time, after the code creating - * the event has returned. + * during an [fire] call. If false (the default), the event will be passed to + * the listeners at a later time, after the code creating the event has + * completed. */ - factory EventBus({bool sync: true}) { - return new SimpleEventBus(sync: sync); + factory EventBus.hierarchical({bool sync: false}) { + return new HierarchicalEventBus(sync: sync); } /** - * Returns the [Stream] to listen for events of type [eventType]. + * Listens for events of [eventType]. * * The returned [Stream] is a broadcast stream so multiple subscriptions are * allowed. @@ -46,32 +63,54 @@ abstract class EventBus { * unpaused or canceled. So it's usually better to just cancel and later * subscribe again (avoids memory leak). */ - Stream/**/ on(EventType/**/ eventType); + Stream on([Type eventType]) { + return _streamController.stream.where((event) => eventType == null || + event.runtimeType == eventType); + } /** - * Fires a new event on the event bus with the specified [data]. + * Fires a new event on the event bus with the specified [event]. */ - void fire(EventType/**/ eventType, /**/ data); + void fire(event) { + _streamController.add(event); + } + + /** + * Destroy this [EventBus]. This is generally only in a testing context. + */ + void destroy() { + _streamController.close(); + } } /** - * Type class used to publish events with an [EventBus]. - * [T] is the type of data that is provided when an event is fired. + * A [HierarchicalEventBus] that filters events by event class **including** + * its subclasses. */ -class EventType { - - String name; +class HierarchicalEventBus extends EventBus { /** - * Constructor with an optional [name] for logging purposes. + * Creates a [HierarchicalEventBus]. + * + * If [sync] is true, events are passed directly to the stream's listeners + * during an [fire] call. If false (the default), the event will be passed to + * the listeners at a later time, after the code creating the event has completed. */ - EventType([this.name]); + HierarchicalEventBus({bool sync: false}) : super(sync: sync); /** - * Returns true if the provided data is of type [T]. + * Listens for events of [eventType] and of all subclasses of [eventType]. * - * This method is needed to provide type safety to the [EventBus] as long as - * Dart does not support generic types for methods. + * The returned [Stream] is a broadcast stream so multiple subscriptions are + * allowed. + * + * Each listener is handled independently, and if they pause, only the pausing + * listener is affected. A paused listener will buffer events internally until + * unpaused or canceled. So it's usually better to just cancel and later + * subscribe again (avoids memory leak). */ - bool isTypeT(data) => data is T; + Stream on([Type eventType]) { + return _streamController.stream.where((event) => eventType == null || + reflect(event).type.isSubclassOf(reflectClass(eventType))); + } } \ No newline at end of file diff --git a/lib/src/logging_event_bus.dart b/lib/src/logging_event_bus.dart deleted file mode 100644 index f6a7cc1..0000000 --- a/lib/src/logging_event_bus.dart +++ /dev/null @@ -1,14 +0,0 @@ -part of event_bus; - -final _logger = new Logger("event_bus"); - -/** - * A [SimpleEventBus] that adds logging. - */ -class LoggingEventBus extends SimpleEventBus { - - void fire(EventType/**/ eventType, /**/ data) { - super.fire(eventType, data); - _logger.finest('event fired: ${eventType.name}'); - } -} \ No newline at end of file diff --git a/lib/src/simple_event_bus.dart b/lib/src/simple_event_bus.dart deleted file mode 100644 index aad2a88..0000000 --- a/lib/src/simple_event_bus.dart +++ /dev/null @@ -1,41 +0,0 @@ -part of event_bus; - -/** - * Basic implementation of [EventBus] that provides a broadcast [StreamController] - * for each [EventType]. - */ -class SimpleEventBus implements EventBus { - bool sync; - - /** - * Constructor. - * - * If [sync] is true, events are passed directly to the stream's listeners - * during an add, addError or close call. If [sync] is false, the event - * will be passed to the listeners at a later time, after the code creating - * the event has returned. - */ - SimpleEventBus({this.sync: true}); - - /// Map containing a stream controller for each [EventType] - Map streamControllers = - new Map(); - - Stream/**/ on(EventType/**/ eventType) { - return streamControllers.putIfAbsent(eventType, () { - return new StreamController.broadcast(sync: sync); - } - ).stream; - } - - void fire(EventType/**/ eventType, /**/ data) { - if (data != null && !eventType.isTypeT(data)) { - throw new ArgumentError('Provided data is not of same type as T of EventType.'); - } - - var controller = streamControllers[eventType]; - if (controller != null) { - controller.add(data); - } - } -} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 48eef46..53bd99c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,12 +1,10 @@ name: event_bus -version: 0.2.4 +version: 0.3.0 author: Marco Jakob description: A simple Event Bus using Dart Streams for decoupling applications homepage: https://github.com/marcojakob/dart-event-bus -documentation: http://edu.makery.ch/projects/dart-event-bus -dependencies: - logging: '>=0.9.0 <0.10.0' +documentation: http://code.makery.ch/library/dart-event-bus dev_dependencies: browser: '>=0.10.0 <0.11.0' - intl: '>=0.11.0 <0.12.0' + logging: '>=0.9.0 <0.10.0' unittest: '>=0.11.0 <0.12.0' diff --git a/test/all_tests.dart b/test/all_tests.dart index 9ca78a3..a4ee06a 100644 --- a/test/all_tests.dart +++ b/test/all_tests.dart @@ -2,11 +2,13 @@ library all_tests; import 'package:unittest/html_enhanced_config.dart'; -import 'src/simple_event_bus_test.dart' as simpleEventbus; +import 'event_bus_test.dart' as eventBusTest; +import 'hierarchical_event_bus_test.dart' as hierarchicalEventBusTest; main() { useHtmlEnhancedConfiguration(); - simpleEventbus.main(); + eventBusTest.main(); + hierarchicalEventBusTest.main(); } diff --git a/test/event_bus_test.dart b/test/event_bus_test.dart new file mode 100644 index 0000000..02b09d9 --- /dev/null +++ b/test/event_bus_test.dart @@ -0,0 +1,106 @@ +library event_bus_test; + +import 'dart:async'; +import 'package:unittest/unittest.dart'; + +import 'package:event_bus/event_bus.dart'; + +class EventA { + String text; + + EventA(this.text); +} + +class EventB { + String text; + + EventB(this.text); +} + +main() { + +group('[EventBus]', () { + + test('Fire one event', () { + // given + EventBus eventBus = new EventBus(); + Future f = eventBus.on(EventA).toList(); + + // when + eventBus.fire(new EventA('a1')); + eventBus.destroy(); + + // then + return f.then((List events) { + expect(events.length, 1); + }); + }); + + test('Fire two events of same type', () { + // given + EventBus eventBus = new EventBus(); + Future f = eventBus.on(EventA).toList(); + + // when + eventBus.fire(new EventA('a1')); + eventBus.fire(new EventA('a2')); + eventBus.destroy(); + + // then + return f.then((List events) { + expect(events.length, 2); + }); + }); + + test('Fire events of different type', () { + // given + EventBus eventBus = new EventBus(); + Future f1 = eventBus.on(EventA).toList(); + Future f2 = eventBus.on(EventB).toList(); + + // when + eventBus.fire(new EventA('a1')); + eventBus.fire(new EventB('b1')); + eventBus.destroy(); + + // then + return Future.wait( + [ + f1.then((List events) { + expect(events.length, 1); + }), + f2.then((List events) { + expect(events.length, 1); + }) + ]); + }); + + test('Fire events of different type, receive all types', () { + // given + EventBus eventBus = new EventBus(); + Future f = eventBus.on().toList(); + + // when + eventBus.fire(new EventA('a1')); + eventBus.fire(new EventB('b1')); + eventBus.fire(new EventB('b2')); + eventBus.destroy(); + + // then + return f.then((List events) { + expect(events.length, 3); + }); + }); +}); +} + + + + + + + + + + + diff --git a/test/hierarchical_event_bus_test.dart b/test/hierarchical_event_bus_test.dart new file mode 100644 index 0000000..f223a19 --- /dev/null +++ b/test/hierarchical_event_bus_test.dart @@ -0,0 +1,70 @@ +library hierarchical_event_bus_test; + +import 'dart:async'; +import 'package:unittest/unittest.dart'; + +import 'package:event_bus/event_bus.dart'; + +class EventA extends SuperEvent { + String text; + + EventA(this.text); +} + +class EventB extends SuperEvent { + String text; + + EventB(this.text); +} + +class SuperEvent { +} + +main() { + +group('[HierarchicalEventBus]', () { + + test('Listen on same class', () { + // given + EventBus eventBus = new EventBus.hierarchical(); + Future f = eventBus.on(EventA).toList(); + + // when + eventBus.fire(new EventA('a1')); + eventBus.fire(new EventB('b1')); + eventBus.destroy(); + + // then + return f.then((List events) { + expect(events.length, 1); + }); + }); + + test('Listen on superclass', () { + // given + EventBus eventBus = new EventBus.hierarchical(); + Future f = eventBus.on(SuperEvent).toList(); + + // when + eventBus.fire(new EventA('a1')); + eventBus.fire(new EventB('b1')); + eventBus.destroy(); + + // then + return f.then((List events) { + expect(events.length, 2); + }); + }); +}); +} + + + + + + + + + + + diff --git a/test/src/simple_event_bus_test.dart b/test/src/simple_event_bus_test.dart deleted file mode 100644 index dab4e4c..0000000 --- a/test/src/simple_event_bus_test.dart +++ /dev/null @@ -1,191 +0,0 @@ -library simple_event_bus_test; - -import 'dart:async'; -import 'package:unittest/unittest.dart'; - -import 'package:event_bus/event_bus.dart'; - -final EventType stringEvent1 = new EventType(); -final EventType stringEvent2 = new EventType(); - -final EventType intEvent1 = new EventType(); -final EventType intEvent2 = new EventType(); - -main() { - -group('[SimpleEventBus]', () { - - SimpleEventBus eventBus; - - setUp(() { - eventBus = new SimpleEventBus(sync: false); - }); - - test('on_ListenersForMultipleEvents_HasMultipleStreamControllers', () { - // when - eventBus.on(stringEvent1); - eventBus.on(stringEvent2); - eventBus.on(intEvent1); - eventBus.on(intEvent2); - - // then - expect(eventBus.streamControllers, hasLength(4)); - }); - - test('on_MultipleListenersForOneEvent_HasOneStreamController', () { - // when - eventBus.on(stringEvent1); - eventBus.on(stringEvent1); - - // then - expect(eventBus.streamControllers, hasLength(1)); - }); - - test('on_AddMultipleListenersForOneEvent_IsBroadcastStreamAndNoError', () { - // when - eventBus.on(stringEvent1).listen((_) => null); - eventBus.on(stringEvent1).listen((_) => null); - - // then - expect(eventBus.streamControllers[stringEvent1].stream.isBroadcast, isTrue); - }); - - test('on_CancelSubscriptionForEvent_StreamControllerStillAcceptsEvents', () { - // given - StreamSubscription subscription = eventBus.on(stringEvent1).listen((_) => null); - - // when - subscription.cancel(); - eventBus.fire(stringEvent1, 'aaaaa'); - - // then - expect(eventBus.streamControllers[stringEvent1], isNotNull); - expect(eventBus.streamControllers[stringEvent1].hasListener, isFalse); - expect(eventBus.streamControllers[stringEvent1].isClosed, isFalse); - }); - - test('on_CancelSubscriptionAndAddNewListener_ReceivesEvents', () { - // then - Function callback = expectAsync((arg) => expect(arg, equals('aaaaa'))); - - // given - StreamSubscription subscription = eventBus.on(stringEvent1).listen((_) => null); - - // when - subscription.cancel(); - eventBus.on(stringEvent1).listen(callback); - eventBus.fire(stringEvent1, 'aaaaa'); - }); - - test('on_CancelOneSubscriptionForEventWithTwoListeners_OtherListenerReceivesEvents', () { - // then - Function callback = expectAsync((arg) => expect(arg, equals('aaaaa'))); - - // given - StreamSubscription subscription1 = eventBus.on(stringEvent1).listen((_) => null); - StreamSubscription subscription2 = eventBus.on(stringEvent1).listen(callback); - - // when - subscription1.cancel(); - eventBus.fire(stringEvent1, 'aaaaa'); - }); - - test('fire_OneListener_ReceivesEvent', () { - // then - Function callback = expectAsync((arg) =>expect(arg, equals('Hello Event'))); - - // given - eventBus.on(stringEvent1).listen(callback); - - // when - eventBus.fire(stringEvent1, 'Hello Event'); - }); - - test('fire_MultipleListeners_AllListenersReceiveTheEvent', () { - // then - Function callback1 = expectAsync((arg) => expect(arg, equals('Hello Event'))); - Function callback2 = expectAsync((arg) => expect(arg, equals('Hello Event'))); - - // given - eventBus.on(stringEvent1).listen(callback1); - eventBus.on(stringEvent1).listen(callback2); - - // when - eventBus.fire(stringEvent1, 'Hello Event'); - }); - - test('fire_TwoTimes_ListenerReceivesTwoEvents', () { - // then - Function callback = expectAsync((_) => null, count: 2); - - // given - eventBus.on(stringEvent1).listen(callback); - - // when - eventBus.fire(stringEvent1, 'Hello Event'); - eventBus.fire(stringEvent1, 'Hello Event2'); - }); - - test('fire_WhenSubscriptionPaused_ListenerDoesNotReceiveEvent', () { - // then - Function callback = expectAsync((_) => null, count: 0); - - // given - StreamSubscription subscription = eventBus.on(stringEvent1).listen(callback); - - // when - subscription.pause(); - eventBus.fire(stringEvent1, 'Hello Event'); - }); - - test('fire_WhenSubscriptionPausedAndResumed_ListenerReceivesCachedEvent', () { - // then - Function callback = expectAsync((arg) => expect(arg, equals('Hello Event'))); - - // given - StreamSubscription subscription = eventBus.on(stringEvent1).listen(callback); - - // when - subscription.pause(); - eventBus.fire(stringEvent1, 'Hello Event'); - subscription.resume(); - }); - - test('fire_TwoListenersOnePaused_OtherListenerReceivesEvents', () { - // then - Function callback1 = expectAsync((_) => null, count: 0); - Function callback2 = expectAsync((arg) => expect(arg, equals('Hello Event'))); - - // given - StreamSubscription subscription1 = eventBus.on(stringEvent1).listen(callback1); - StreamSubscription subscription2 = eventBus.on(stringEvent1).listen(callback2); - - // when - subscription1.pause(); - eventBus.fire(stringEvent1, 'Hello Event'); - }); - - test('fire_WrongDataType_ThrowsError', () { - expect(() => eventBus.fire(stringEvent1, 22), throwsArgumentError); - }); - - test('fire_NullAsData_NoError', () { - try { - eventBus.fire(stringEvent1, null); - } catch (e) { - fail('Should not throw error: $e'); - } - }); -}); -} - - - - - - - - - - -