diff --git a/.github/workflows/hydrated_bloc.yaml b/.github/workflows/hydrated_bloc.yaml
index 66b69f6ade1..7055b38a6d7 100644
--- a/.github/workflows/hydrated_bloc.yaml
+++ b/.github/workflows/hydrated_bloc.yaml
@@ -33,7 +33,7 @@ jobs:
run: flutter analyze lib test example
- name: Run tests
- run: flutter test -j 1 --no-pub --coverage --test-randomize-ordering-seed random
+ run: flutter test --no-pub --coverage --test-randomize-ordering-seed random
- name: Check Code Coverage
uses: VeryGoodOpenSource/very_good_coverage@v1.2.0
diff --git a/README.md b/README.md
index 8883d4d25bd..18a6bfe70a1 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,10 @@ Our top sponsors are shown below! [[Become a Sponsor](https://github.com/sponsor
Try the Flutter Chat Tutorial  💬
- |
+
+
+
+ |
diff --git a/docs/_coverpage.md b/docs/_coverpage.md
index 0c47a490f6c..54dd9a14489 100644
--- a/docs/_coverpage.md
+++ b/docs/_coverpage.md
@@ -22,17 +22,16 @@
Get Started
-Sponsored with 💖 by
+Sponsored with 💖 by
-
+
diff --git a/docs/_snippets/core_concepts/simple_bloc_observer_on_change_usage.dart.md b/docs/_snippets/core_concepts/simple_bloc_observer_on_change_usage.dart.md
index 173a413d091..3d70eb54eff 100644
--- a/docs/_snippets/core_concepts/simple_bloc_observer_on_change_usage.dart.md
+++ b/docs/_snippets/core_concepts/simple_bloc_observer_on_change_usage.dart.md
@@ -1,8 +1,12 @@
```dart
void main() {
- Bloc.observer = SimpleBlocObserver();
- CounterCubit()
- ..increment()
- ..close();
+ BlocOverrides.runZoned(
+ () {
+ CounterCubit()
+ ..increment()
+ ..close();
+ },
+ blocObserver: SimpleBlocObserver(),
+ );
}
-```
\ No newline at end of file
+```
diff --git a/docs/_snippets/core_concepts/simple_bloc_observer_on_transition_usage.dart.md b/docs/_snippets/core_concepts/simple_bloc_observer_on_transition_usage.dart.md
index 74e8cd604a6..bd2db81040a 100644
--- a/docs/_snippets/core_concepts/simple_bloc_observer_on_transition_usage.dart.md
+++ b/docs/_snippets/core_concepts/simple_bloc_observer_on_transition_usage.dart.md
@@ -1,8 +1,12 @@
```dart
void main() {
- Bloc.observer = SimpleBlocObserver();
- CounterBloc()
- ..add(Increment())
- ..close();
+ BlocOverrides.runZoned(
+ () {
+ CounterBloc()
+ ..add(Increment())
+ ..close();
+ },
+ blocObserver: SimpleBlocObserver(),
+ );
}
-```
\ No newline at end of file
+```
diff --git a/docs/assets/style.css b/docs/assets/style.css
index 433bb61f586..bf63272d92d 100644
--- a/docs/assets/style.css
+++ b/docs/assets/style.css
@@ -54,3 +54,15 @@ section.cover blockquote > .buttons > a:hover {
#TOPBANNER p {
padding: 1.25rem 0;
}
+
+#cover-sponsors-grid {
+ display: grid;
+ grid-auto-rows: 1fr;
+ grid-template-columns: 1fr 1fr 1fr;
+ gap: 0px 1em;
+ max-width: 600px;
+ justify-content: center;
+ align-items: center;
+ margin: 0 auto;
+ font-size: 0.8em;
+}
diff --git a/docs/migration.md b/docs/migration.md
index d64a614cf0e..6df6a2d53ad 100644
--- a/docs/migration.md
+++ b/docs/migration.md
@@ -2,6 +2,243 @@
?> 💡 **Tip**: Please refer to the [release log](https://github.com/felangel/bloc/releases) for more information regarding what changed in each release.
+## v8.0.0
+
+### package:bloc
+
+#### ❗✨ Introduce new `BlocOverrides` API
+
+!> In bloc v8.0.0, `Bloc.observer` and `Bloc.transformer` were removed in favor of the `BlocOverrides` API.
+
+##### Rationale
+
+The previous API used to override the default `BlocObserver` and `EventTransformer` relied on a global singleton for both the `BlocObserver` and `EventTransformer`.
+
+As a result, it was not possible to:
+
+- Have multiple `BlocObserver` or `EventTransformer` implementations scoped to different parts of the application
+- Have `BlocObserver` or `EventTransformer` overrides be scoped to a package
+ - If a package were to depend on `package:bloc` and registered its own `BlocObserver`, any consumer of the package would either have to overwrite the package's `BlocObserver` or report to the package's `BlocObserver`.
+
+It was also more difficult to test because of the shared global state across tests.
+
+Bloc v8.0.0 introduces a `BlocOverrides` class which allows developers to override `BlocObserver` and/or `EventTransformer` for a specific `Zone` rather than relying on a global mutable singleton.
+
+**v7.x.x**
+
+```dart
+void main() {
+ Bloc.observer = CustomBlocObserver();
+ Bloc.transformer = customEventTransformer();
+
+ // ...
+}
+```
+
+**v8.0.0**
+
+```dart
+void main() {
+ BlocOverrides.runZoned(
+ () {
+ // ...
+ },
+ blocObserver: CustomBlocObserver(),
+ eventTransformer: customEventTransformer(),
+ );
+}
+```
+
+`Bloc` instances will use the `BlocObserver` and/or `EventTransformer` for the current `Zone` via `BlocOverrides.current`. If there are no `BlocOverrides` for the zone, they will use the existing internal defaults (no change in behavior/functionality).
+
+This allows allow each `Zone` to function independently with its own `BlocOverrides`.
+
+```dart
+BlocOverrides.runZoned(
+ () {
+ // BlocObserverA and eventTransformerA
+ final overrides = BlocOverrides.current;
+
+ // Blocs in this zone report to BlocObserverA
+ // and use eventTransformerA as the default transformer.
+ // ...
+
+ // Later...
+ BlocOverrides.runZoned(
+ () {
+ // BlocObserverB and eventTransformerB
+ final overrides = BlocOverrides.current;
+
+ // Blocs in this zone report to BlocObserverB
+ // and use eventTransformerB as the default transformer.
+ // ...
+ },
+ blocObserver: BlocObserverB(),
+ eventTransformer: eventTransformerB(),
+ );
+ },
+ blocObserver: BlocObserverA(),
+ eventTransformer: eventTransformerA(),
+);
+```
+
+#### ❗✨ Improve Error Handling and Reporting
+
+!> In bloc v8.0.0, `BlocUnhandledErrorException` is removed. In addition, any uncaught exceptions are always reported to `onError` and rethrown (regardless of debug or release mode). The `addError` API reports errors to `onError`, but does not treat reported errors as uncaught exceptions.
+
+##### Rationale
+
+The goal of these changes is:
+
+- make internal unhandled exceptions extremely obvious while still preserving bloc functionality
+- support `addError` without disrupting control flow
+
+Previously, error handling and reporting varied depending on whether the application was running in debug or release mode. In addition, errors reported via `addError` were treated as uncaught exceptions in debug mode which led to a poor developer experience when using the `addError` API (specifically when writing unit tests).
+
+In v8.0.0, `addError` can be safely used to report errors and `blocTest` can be used to verify that errors are reported. All errors are still reported to `onError`, however, only uncaught exceptions are rethrown (regardless of debug or release mode).
+
+#### ❗🧹 Make `BlocObserver` abstract
+
+!> In bloc v8.0.0, `BlocObserver` was converted into an `abstract` class which means an instance of `BlocObserver` cannot be instantiated.
+
+##### Rationale
+
+`BlocObserver` was intended to be an interface. Since the default API implementation are no-ops, `BlocObserver` is now an `abstract` class to clearly communicate that the class is meant to be extended and not directly instantiated.
+
+**v7.x.x**
+
+```dart
+void main() {
+ // It was possible to create an instance of the base class.
+ final observer = BlocObserver();
+}
+```
+
+**v8.0.0**
+
+```dart
+class MyBlocObserver extends BlocObserver {...}
+
+void main() {
+ // Cannot instantiate the base class.
+ final observer = BlocObserver(); // ERROR
+
+ // Extend `BlocObserver` instead.
+ final observer = MyBlocObserver(); // OK
+}
+```
+
+#### ❗✨ `add` throws `StateError` if Bloc is closed
+
+!> In bloc v8.0.0, calling `add` on a closed bloc will result in a `StateError`.
+
+##### Rationale
+
+Previously, it was possible to call `add` on a closed bloc and the internal error would get swallowed, making it difficult to debug why the added event was not being processed. In order to make this scenario more visible, in v8.0.0, calling `add` on a closed bloc will throw a `StateError` which will be reported as an uncaught exception and propagated to `onError`.
+
+#### ❗✨ `emit` throws `StateError` if Bloc is closed
+
+!> In bloc v8.0.0, calling `emit` within a closed bloc will result in a `StateError`.
+
+##### Rationale
+
+Previously, it was possible to call `emit` within a closed bloc and no state change would occur but there would also be no indication of what went wrong, making it difficult to debug. In order to make this scenario more visible, in v8.0.0, calling `emit` within a closed bloc will throw a `StateError` which will be reported as an uncaught exception and propagated to `onError`.
+
+#### ❗🧹 Remove Deprecated APIs
+
+!> In bloc v8.0.0, all previously deprecated APIs were removed.
+
+##### Summary
+
+- `mapEventToState` removed in favor of `on`
+- `transformEvents` removed in favor of `EventTransformer` API
+- `TransitionFunction` typedef removed in favor of `EventTransformer` API
+- `listen` removed in favor of `stream.listen`
+
+### package:bloc_test
+
+#### ✨ `MockBloc` and `MockCubit` no longer require `registerFallbackValue`
+
+!> In bloc_test v9.0.0, developers no longer need to explicitly call `registerFallbackValue` when using `MockBloc` or `MockCubit`.
+
+##### Summary
+
+`registerFallbackValue` is only needed when using the `any()` matcher from `package:mocktail` for a custom type. Previously, `registerFallbackValue` was needed for every `Event` and `State` when using `MockBloc` or `MockCubit`.
+
+**v8.x.x**
+
+```dart
+class FakeMyEvent extends Fake implements MyEvent {}
+class FakeMyState extends Fake implements MyState {}
+class MyMockBloc extends MockBloc implements MyBloc {}
+
+void main() {
+ setUpAll(() {
+ registerFallbackValue(FakeMyEvent());
+ registerFallbackValue(FakeMyState());
+ });
+
+ // Tests...
+}
+```
+
+**v9.0.0**
+
+```dart
+class MyMockBloc extends MockBloc implements MyBloc {}
+
+void main() {
+ // Tests...
+}
+```
+
+### package:hydrated_bloc
+
+#### ❗✨ Introduce new `HydratedBlocOverrides` API
+
+!> In hydrated_bloc v8.0.0, `HydratedBloc.storage` was removed in favor of the `HydratedBlocOverrides` API.
+
+##### Rationale
+
+Previously, a global singleton was used to override the `Storage` implementation.
+
+As a result, it was not possible to have multiple `Storage` implementations scoped to different parts of the application. It was also more difficult to test because of the shared global state across tests.
+
+`HydratedBloc` v8.0.0 introduces a `HydratedBlocOverrides` class which allows developers to override `Storage` for a specific `Zone` rather than relying on a global mutable singleton.
+
+**v7.x.x**
+
+```dart
+void main() async {
+ HydratedBloc.storage = await HydratedStorage.build(
+ storageDirectory: await getApplicationSupportDirectory(),
+ );
+
+ // ...
+}
+```
+
+**v8.0.0**
+
+```dart
+void main() {
+ final storage = await HydratedStorage.build(
+ storageDirectory: await getApplicationSupportDirectory(),
+ );
+
+ HydratedBlocOverrides.runZoned(
+ () {
+ // ...
+ },
+ storage: storage,
+ );
+}
+```
+
+`HydratedBloc` instances will use the `Storage` for the current `Zone` via `HydratedBlocOverrides.current`.
+
+This allows allow each `Zone` to function independently with its own `BlocOverrides`.
+
## v7.2.0
### package:bloc
diff --git a/examples/angular_counter/web/main.dart b/examples/angular_counter/web/main.dart
index dd28c5842f3..095e5afe64c 100644
--- a/examples/angular_counter/web/main.dart
+++ b/examples/angular_counter/web/main.dart
@@ -23,6 +23,8 @@ class SimpleBlocObserver extends BlocObserver {
}
void main() {
- Bloc.observer = SimpleBlocObserver();
- runApp(ng.AppComponentNgFactory);
+ BlocOverrides.runZoned(
+ () => runApp(ng.AppComponentNgFactory),
+ blocObserver: SimpleBlocObserver(),
+ );
}
diff --git a/examples/flutter_bloc_with_stream/test/ticker_page_test.dart b/examples/flutter_bloc_with_stream/test/ticker_page_test.dart
index 0c04f63dc81..f8dc72a4250 100644
--- a/examples/flutter_bloc_with_stream/test/ticker_page_test.dart
+++ b/examples/flutter_bloc_with_stream/test/ticker_page_test.dart
@@ -11,10 +11,6 @@ import 'package:mocktail/mocktail.dart';
class MockTickerBloc extends MockBloc
implements TickerBloc {}
-class FakeTickerEvent extends Fake implements TickerEvent {}
-
-class FakeTickerState extends Fake implements TickerState {}
-
extension on WidgetTester {
Future pumpTickerPage(TickerBloc tickerBloc) {
return pumpWidget(
@@ -28,11 +24,6 @@ extension on WidgetTester {
void main() {
late TickerBloc tickerBloc;
- setUpAll(() {
- registerFallbackValue(FakeTickerEvent());
- registerFallbackValue(FakeTickerState());
- });
-
setUp(() {
tickerBloc = MockTickerBloc();
});
diff --git a/examples/flutter_complex_list/lib/main.dart b/examples/flutter_complex_list/lib/main.dart
index 5883358447b..129d20961fc 100644
--- a/examples/flutter_complex_list/lib/main.dart
+++ b/examples/flutter_complex_list/lib/main.dart
@@ -1,11 +1,13 @@
import 'package:bloc/bloc.dart';
-import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_complex_list/app.dart';
import 'package:flutter_complex_list/repository.dart';
import 'package:flutter_complex_list/simple_bloc_observer.dart';
void main() {
- Bloc.observer = SimpleBlocObserver();
- runApp(App(repository: Repository()));
+ BlocOverrides.runZoned(
+ () => runApp(App(repository: Repository())),
+ blocObserver: SimpleBlocObserver(),
+ );
}
diff --git a/examples/flutter_counter/lib/main.dart b/examples/flutter_counter/lib/main.dart
index 061f098e0e1..87da92815fc 100644
--- a/examples/flutter_counter/lib/main.dart
+++ b/examples/flutter_counter/lib/main.dart
@@ -1,10 +1,12 @@
import 'package:bloc/bloc.dart';
-import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
import 'app.dart';
import 'counter_observer.dart';
void main() {
- Bloc.observer = CounterObserver();
- runApp(const CounterApp());
+ BlocOverrides.runZoned(
+ () => runApp(const CounterApp()),
+ blocObserver: CounterObserver(),
+ );
}
diff --git a/examples/flutter_dynamic_form/test/new_car/view/new_car_page_test.dart b/examples/flutter_dynamic_form/test/new_car/view/new_car_page_test.dart
index c07ab3da315..75915351416 100644
--- a/examples/flutter_dynamic_form/test/new_car/view/new_car_page_test.dart
+++ b/examples/flutter_dynamic_form/test/new_car/view/new_car_page_test.dart
@@ -11,10 +11,6 @@ class MockNewCarRepository extends Mock implements NewCarRepository {}
class MockNewCarBloc extends MockBloc
implements NewCarBloc {}
-class FakeNewCarEvent extends Fake implements NewCarEvent {}
-
-class FakeNewCarState extends Fake implements NewCarState {}
-
extension on WidgetTester {
Future pumpNewCarPage(NewCarRepository newCarRepository) {
return pumpWidget(
@@ -56,8 +52,6 @@ void main() {
final mockYear = mockYears[0];
setUp(() {
- registerFallbackValue(FakeNewCarState());
- registerFallbackValue(FakeNewCarEvent());
newCarRepository = MockNewCarRepository();
newCarBloc = MockNewCarBloc();
});
diff --git a/examples/flutter_firebase_login/lib/main.dart b/examples/flutter_firebase_login/lib/main.dart
index 1dd1ac35875..809123c0106 100644
--- a/examples/flutter_firebase_login/lib/main.dart
+++ b/examples/flutter_firebase_login/lib/main.dart
@@ -5,10 +5,12 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_firebase_login/app/app.dart';
Future main() async {
- Bloc.observer = AppBlocObserver();
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
final authenticationRepository = AuthenticationRepository();
await authenticationRepository.user.first;
- runApp(App(authenticationRepository: authenticationRepository));
+ BlocOverrides.runZoned(
+ () => runApp(App(authenticationRepository: authenticationRepository)),
+ blocObserver: AppBlocObserver(),
+ );
}
diff --git a/examples/flutter_firebase_login/test/app/view/app_test.dart b/examples/flutter_firebase_login/test/app/view/app_test.dart
index 9d386a9f417..fb0dcf0ed0f 100644
--- a/examples/flutter_firebase_login/test/app/view/app_test.dart
+++ b/examples/flutter_firebase_login/test/app/view/app_test.dart
@@ -15,20 +15,11 @@ class MockAuthenticationRepository extends Mock
class MockAppBloc extends MockBloc implements AppBloc {}
-class FakeAppEvent extends Fake implements AppEvent {}
-
-class FakeAppState extends Fake implements AppState {}
-
void main() {
group('App', () {
late AuthenticationRepository authenticationRepository;
late User user;
- setUpAll(() {
- registerFallbackValue(FakeAppEvent());
- registerFallbackValue(FakeAppState());
- });
-
setUp(() {
authenticationRepository = MockAuthenticationRepository();
user = MockUser();
@@ -54,11 +45,6 @@ void main() {
late AuthenticationRepository authenticationRepository;
late AppBloc appBloc;
- setUpAll(() {
- registerFallbackValue(FakeAppEvent());
- registerFallbackValue(FakeAppState());
- });
-
setUp(() {
authenticationRepository = MockAuthenticationRepository();
appBloc = MockAppBloc();
diff --git a/examples/flutter_firebase_login/test/home/view/home_page_test.dart b/examples/flutter_firebase_login/test/home/view/home_page_test.dart
index 808a64bf4e9..d845e56f64e 100644
--- a/examples/flutter_firebase_login/test/home/view/home_page_test.dart
+++ b/examples/flutter_firebase_login/test/home/view/home_page_test.dart
@@ -9,10 +9,6 @@ import 'package:mocktail/mocktail.dart';
class MockAppBloc extends MockBloc implements AppBloc {}
-class FakeAppEvent extends Fake implements AppEvent {}
-
-class FakeAppState extends Fake implements AppState {}
-
class MockUser extends Mock implements User {}
void main() {
@@ -21,11 +17,6 @@ void main() {
late AppBloc appBloc;
late User user;
- setUpAll(() {
- registerFallbackValue(FakeAppEvent());
- registerFallbackValue(FakeAppState());
- });
-
setUp(() {
appBloc = MockAppBloc();
user = MockUser();
diff --git a/examples/flutter_firebase_login/test/login/view/login_form_test.dart b/examples/flutter_firebase_login/test/login/view/login_form_test.dart
index 622e128b40f..f4ce84fc0f1 100644
--- a/examples/flutter_firebase_login/test/login/view/login_form_test.dart
+++ b/examples/flutter_firebase_login/test/login/view/login_form_test.dart
@@ -14,8 +14,6 @@ class MockAuthenticationRepository extends Mock
class MockLoginCubit extends MockCubit implements LoginCubit {}
-class FakeLoginState extends Fake implements LoginState {}
-
class MockEmail extends Mock implements Email {}
class MockPassword extends Mock implements Password {}
@@ -33,10 +31,6 @@ void main() {
group('LoginForm', () {
late LoginCubit loginCubit;
- setUpAll(() {
- registerFallbackValue(FakeLoginState());
- });
-
setUp(() {
loginCubit = MockLoginCubit();
when(() => loginCubit.state).thenReturn(const LoginState());
diff --git a/examples/flutter_firebase_login/test/sign_up/view/sign_up_form_test.dart b/examples/flutter_firebase_login/test/sign_up/view/sign_up_form_test.dart
index 86d980548ee..9b7f6f144f6 100644
--- a/examples/flutter_firebase_login/test/sign_up/view/sign_up_form_test.dart
+++ b/examples/flutter_firebase_login/test/sign_up/view/sign_up_form_test.dart
@@ -13,8 +13,6 @@ class MockAuthenticationRepository extends Mock
class MockSignUpCubit extends MockCubit implements SignUpCubit {}
-class FakeSignUpState extends Fake implements SignUpState {}
-
class MockEmail extends Mock implements Email {}
class MockPassword extends Mock implements Password {}
@@ -35,10 +33,6 @@ void main() {
group('SignUpForm', () {
late SignUpCubit signUpCubit;
- setUpAll(() {
- registerFallbackValue(FakeSignUpState());
- });
-
setUp(() {
signUpCubit = MockSignUpCubit();
when(() => signUpCubit.state).thenReturn(const SignUpState());
diff --git a/examples/flutter_firestore_todos/lib/main.dart b/examples/flutter_firestore_todos/lib/main.dart
index edf7e2716c6..f6a739a324e 100644
--- a/examples/flutter_firestore_todos/lib/main.dart
+++ b/examples/flutter_firestore_todos/lib/main.dart
@@ -9,9 +9,11 @@ import 'package:user_repository/user_repository.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
- Bloc.observer = SimpleBlocObserver();
await Firebase.initializeApp();
- runApp(TodosApp());
+ BlocOverrides.runZoned(
+ () => runApp(TodosApp()),
+ blocObserver: SimpleBlocObserver(),
+ );
}
class TodosApp extends StatelessWidget {
diff --git a/examples/flutter_infinite_list/lib/main.dart b/examples/flutter_infinite_list/lib/main.dart
index 46774e8b0bc..8c4b1c70d4f 100644
--- a/examples/flutter_infinite_list/lib/main.dart
+++ b/examples/flutter_infinite_list/lib/main.dart
@@ -1,10 +1,12 @@
import 'package:bloc/bloc.dart';
-import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_infinite_list/app.dart';
import 'package:flutter_infinite_list/simple_bloc_observer.dart';
void main() {
- Bloc.observer = SimpleBlocObserver();
- runApp(App());
+ BlocOverrides.runZoned(
+ () => runApp(App()),
+ blocObserver: SimpleBlocObserver(),
+ );
}
diff --git a/examples/flutter_infinite_list/test/posts/view/posts_list_test.dart b/examples/flutter_infinite_list/test/posts/view/posts_list_test.dart
index 01cf25da809..6c09cc47eca 100644
--- a/examples/flutter_infinite_list/test/posts/view/posts_list_test.dart
+++ b/examples/flutter_infinite_list/test/posts/view/posts_list_test.dart
@@ -5,10 +5,6 @@ import 'package:flutter_infinite_list/posts/posts.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
-class FakePostState extends Fake implements PostState {}
-
-class FakePostEvent extends Fake implements PostEvent {}
-
class MockPostBloc extends MockBloc implements PostBloc {}
extension on WidgetTester {
@@ -32,11 +28,6 @@ void main() {
late PostBloc postBloc;
- setUpAll(() {
- registerFallbackValue(FakePostState());
- registerFallbackValue(FakePostEvent());
- });
-
setUp(() {
postBloc = MockPostBloc();
});
diff --git a/examples/flutter_login/test/login/view/login_form_test.dart b/examples/flutter_login/test/login/view/login_form_test.dart
index 0e14a3e8bcf..8d5884a4c4e 100644
--- a/examples/flutter_login/test/login/view/login_form_test.dart
+++ b/examples/flutter_login/test/login/view/login_form_test.dart
@@ -6,10 +6,6 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:formz/formz.dart';
import 'package:mocktail/mocktail.dart';
-class FakeLoginEvent extends Fake implements LoginEvent {}
-
-class FakeLoginState extends Fake implements LoginState {}
-
class MockLoginBloc extends MockBloc
implements LoginBloc {}
@@ -17,11 +13,6 @@ void main() {
group('LoginForm', () {
late LoginBloc loginBloc;
- setUpAll(() {
- registerFallbackValue(FakeLoginEvent());
- registerFallbackValue(FakeLoginState());
- });
-
setUp(() {
loginBloc = MockLoginBloc();
});
diff --git a/examples/flutter_shopping_cart/lib/main.dart b/examples/flutter_shopping_cart/lib/main.dart
index 68c8c455c16..499d3a6fe7d 100644
--- a/examples/flutter_shopping_cart/lib/main.dart
+++ b/examples/flutter_shopping_cart/lib/main.dart
@@ -1,10 +1,12 @@
-import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_shopping_cart/app.dart';
import 'package:flutter_shopping_cart/shopping_repository.dart';
import 'package:flutter_shopping_cart/simple_bloc_observer.dart';
void main() {
- Bloc.observer = SimpleBlocObserver();
- runApp(App(shoppingRepository: ShoppingRepository()));
+ BlocOverrides.runZoned(
+ () => runApp(App(shoppingRepository: ShoppingRepository())),
+ blocObserver: SimpleBlocObserver(),
+ );
}
diff --git a/examples/flutter_shopping_cart/test/cart/view/cart_page_test.dart b/examples/flutter_shopping_cart/test/cart/view/cart_page_test.dart
index c4dffc93c8d..41864ceec3d 100644
--- a/examples/flutter_shopping_cart/test/cart/view/cart_page_test.dart
+++ b/examples/flutter_shopping_cart/test/cart/view/cart_page_test.dart
@@ -6,10 +6,6 @@ import 'package:mocktail/mocktail.dart';
import '../../helper.dart';
-class FakeCartState extends Fake implements CartState {}
-
-class FakeCartEvent extends Fake implements CartEvent {}
-
void main() {
late CartBloc cartBloc;
@@ -19,11 +15,6 @@ void main() {
Item(3, 'item #3'),
];
- setUpAll(() {
- registerFallbackValue(FakeCartState());
- registerFallbackValue(FakeCartEvent());
- });
-
setUp(() {
cartBloc = MockCartBloc();
});
diff --git a/examples/flutter_shopping_cart/test/catalog/view/catalog_page_test.dart b/examples/flutter_shopping_cart/test/catalog/view/catalog_page_test.dart
index 7151c135fcf..04534b90b6a 100644
--- a/examples/flutter_shopping_cart/test/catalog/view/catalog_page_test.dart
+++ b/examples/flutter_shopping_cart/test/catalog/view/catalog_page_test.dart
@@ -6,25 +6,10 @@ import 'package:mocktail/mocktail.dart';
import '../../helper.dart';
-class FakeCartState extends Fake implements CartState {}
-
-class FakeCartEvent extends Fake implements CartEvent {}
-
-class FakeCatalogState extends Fake implements CatalogState {}
-
-class FakeCatalogEvent extends Fake implements CatalogEvent {}
-
void main() {
late CartBloc cartBloc;
late CatalogBloc catalogBloc;
- setUpAll(() {
- registerFallbackValue(FakeCartState());
- registerFallbackValue(FakeCartEvent());
- registerFallbackValue(FakeCatalogState());
- registerFallbackValue(FakeCatalogEvent());
- });
-
setUp(() {
catalogBloc = MockCatalogBloc();
cartBloc = MockCartBloc();
diff --git a/examples/flutter_shopping_cart/test/helper.dart b/examples/flutter_shopping_cart/test/helper.dart
index 3cd5c42e7a5..17e7567d2e4 100644
--- a/examples/flutter_shopping_cart/test/helper.dart
+++ b/examples/flutter_shopping_cart/test/helper.dart
@@ -4,15 +4,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_shopping_cart/cart/cart.dart';
import 'package:flutter_shopping_cart/catalog/catalog.dart';
import 'package:flutter_test/flutter_test.dart';
-import 'package:mocktail/mocktail.dart';
-
-class FakeCartState extends Fake implements CartState {}
-
-class FakeCartEvent extends Fake implements CartEvent {}
-
-class FakeCatalogState extends Fake implements CatalogState {}
-
-class FakeCatalogEvent extends Fake implements CatalogEvent {}
class MockCartBloc extends MockBloc implements CartBloc {}
@@ -25,11 +16,6 @@ extension PumpApp on WidgetTester {
CatalogBloc? catalogBloc,
required Widget child,
}) {
- registerFallbackValue(FakeCartState());
- registerFallbackValue(FakeCartEvent());
- registerFallbackValue(FakeCatalogState());
- registerFallbackValue(FakeCatalogEvent());
-
return pumpWidget(
MaterialApp(
home: MultiBlocProvider(
diff --git a/examples/flutter_timer/test/timer/view/timer_page_test.dart b/examples/flutter_timer/test/timer/view/timer_page_test.dart
index 0ac43069dcc..88ce6219408 100644
--- a/examples/flutter_timer/test/timer/view/timer_page_test.dart
+++ b/examples/flutter_timer/test/timer/view/timer_page_test.dart
@@ -8,10 +8,6 @@ import 'package:mocktail/mocktail.dart';
class MockTimerBloc extends MockBloc
implements TimerBloc {}
-class FakeTimerState extends Fake implements TimerState {}
-
-class FakeTimerEvent extends Fake implements TimerEvent {}
-
extension on WidgetTester {
Future pumpTimerView(TimerBloc timerBloc) {
return pumpWidget(
@@ -25,11 +21,6 @@ extension on WidgetTester {
void main() {
late TimerBloc timerBloc;
- setUpAll(() {
- registerFallbackValue(FakeTimerState());
- registerFallbackValue(FakeTimerEvent());
- });
-
setUp(() {
timerBloc = MockTimerBloc();
});
diff --git a/examples/flutter_weather/lib/main.dart b/examples/flutter_weather/lib/main.dart
index 02f98976811..82d1515b06d 100644
--- a/examples/flutter_weather/lib/main.dart
+++ b/examples/flutter_weather/lib/main.dart
@@ -8,11 +8,14 @@ import 'package:weather_repository/weather_repository.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
- Bloc.observer = WeatherBlocObserver();
- HydratedBloc.storage = await HydratedStorage.build(
+ final storage = await HydratedStorage.build(
storageDirectory: kIsWeb
? HydratedStorage.webStorageDirectory
: await getTemporaryDirectory(),
);
- runApp(WeatherApp(weatherRepository: WeatherRepository()));
+ HydratedBlocOverrides.runZoned(
+ () => runApp(WeatherApp(weatherRepository: WeatherRepository())),
+ blocObserver: WeatherBlocObserver(),
+ storage: storage,
+ );
}
diff --git a/examples/flutter_weather/test/app_test.dart b/examples/flutter_weather/test/app_test.dart
index a6c01d31d69..7222e159328 100644
--- a/examples/flutter_weather/test/app_test.dart
+++ b/examples/flutter_weather/test/app_test.dart
@@ -10,18 +10,11 @@ import 'package:weather_repository/weather_repository.dart';
import 'helpers/hydrated_bloc.dart';
-class FakeColor extends Fake implements Color {}
-
class MockThemeCubit extends MockCubit implements ThemeCubit {}
class MockWeatherRepository extends Mock implements WeatherRepository {}
void main() {
- setUpAll(() {
- initHydratedBloc();
- registerFallbackValue(FakeColor());
- });
-
group('WeatherApp', () {
late WeatherRepository weatherRepository;
@@ -30,7 +23,11 @@ void main() {
});
testWidgets('renders WeatherAppView', (tester) async {
- await tester.pumpWidget(WeatherApp(weatherRepository: weatherRepository));
+ await mockHydratedStorage(() async {
+ await tester.pumpWidget(
+ WeatherApp(weatherRepository: weatherRepository),
+ );
+ });
expect(find.byType(WeatherAppView), findsOneWidget);
});
});
@@ -46,24 +43,34 @@ void main() {
testWidgets('renders WeatherPage', (tester) async {
when(() => themeCubit.state).thenReturn(Colors.blue);
- await tester.pumpWidget(
- RepositoryProvider.value(
- value: weatherRepository,
- child: BlocProvider.value(value: themeCubit, child: WeatherAppView()),
- ),
- );
+ await mockHydratedStorage(() async {
+ await tester.pumpWidget(
+ RepositoryProvider.value(
+ value: weatherRepository,
+ child: BlocProvider.value(
+ value: themeCubit,
+ child: WeatherAppView(),
+ ),
+ ),
+ );
+ });
expect(find.byType(WeatherPage), findsOneWidget);
});
testWidgets('has correct theme primary color', (tester) async {
const color = Color(0xFFD2D2D2);
when(() => themeCubit.state).thenReturn(color);
- await tester.pumpWidget(
- RepositoryProvider.value(
- value: weatherRepository,
- child: BlocProvider.value(value: themeCubit, child: WeatherAppView()),
- ),
- );
+ await mockHydratedStorage(() async {
+ await tester.pumpWidget(
+ RepositoryProvider.value(
+ value: weatherRepository,
+ child: BlocProvider.value(
+ value: themeCubit,
+ child: WeatherAppView(),
+ ),
+ ),
+ );
+ });
final materialApp = tester.widget(find.byType(MaterialApp));
expect(materialApp.theme?.primaryColor, color);
});
diff --git a/examples/flutter_weather/test/helpers/hydrated_bloc.dart b/examples/flutter_weather/test/helpers/hydrated_bloc.dart
index 9cadb01149e..faa02eb8b17 100644
--- a/examples/flutter_weather/test/helpers/hydrated_bloc.dart
+++ b/examples/flutter_weather/test/helpers/hydrated_bloc.dart
@@ -4,12 +4,16 @@ import 'package:mocktail/mocktail.dart';
class MockStorage extends Mock implements Storage {}
-late Storage hydratedStorage;
+T mockHydratedStorage(T Function() body, {Storage? storage}) {
+ return HydratedBlocOverrides.runZoned(
+ body,
+ storage: storage ?? _buildMockStorage(),
+ );
+}
-void initHydratedBloc() {
+Storage _buildMockStorage() {
TestWidgetsFlutterBinding.ensureInitialized();
- hydratedStorage = MockStorage();
- when(() => hydratedStorage.write(any(), any()))
- .thenAnswer((_) async {});
- HydratedBloc.storage = hydratedStorage;
+ final storage = MockStorage();
+ when(() => storage.write(any(), any())).thenAnswer((_) async {});
+ return storage;
}
diff --git a/examples/flutter_weather/test/settings/view/settings_page_test.dart b/examples/flutter_weather/test/settings/view/settings_page_test.dart
index 93f0210b506..5bdc20b9f89 100644
--- a/examples/flutter_weather/test/settings/view/settings_page_test.dart
+++ b/examples/flutter_weather/test/settings/view/settings_page_test.dart
@@ -7,8 +7,6 @@ import 'package:flutter_weather/settings/settings.dart';
import 'package:flutter_weather/weather/weather.dart';
import 'package:mocktail/mocktail.dart';
-class FakeWeatherState extends Fake implements WeatherState {}
-
class MockWeatherCubit extends MockCubit implements WeatherCubit {
}
@@ -16,10 +14,6 @@ void main() {
group('SettingsPage', () {
late WeatherCubit weatherCubit;
- setUpAll(() {
- registerFallbackValue(FakeWeatherState());
- });
-
setUp(() {
weatherCubit = MockWeatherCubit();
});
diff --git a/examples/flutter_weather/test/theme/cubit/theme_cubit_test.dart b/examples/flutter_weather/test/theme/cubit/theme_cubit_test.dart
index 04637f39713..7fbd62be780 100644
--- a/examples/flutter_weather/test/theme/cubit/theme_cubit_test.dart
+++ b/examples/flutter_weather/test/theme/cubit/theme_cubit_test.dart
@@ -18,19 +18,22 @@ class MockWeather extends Mock implements Weather {
}
void main() {
- initHydratedBloc();
group('ThemeCubit', () {
test('initial state is correct', () {
- expect(ThemeCubit().state, ThemeCubit.defaultColor);
+ mockHydratedStorage(() {
+ expect(ThemeCubit().state, ThemeCubit.defaultColor);
+ });
});
group('toJson/fromJson', () {
test('work properly', () {
- final themeCubit = ThemeCubit();
- expect(
- themeCubit.fromJson(themeCubit.toJson(themeCubit.state)),
- themeCubit.state,
- );
+ mockHydratedStorage(() {
+ final themeCubit = ThemeCubit();
+ expect(
+ themeCubit.fromJson(themeCubit.toJson(themeCubit.state)),
+ themeCubit.state,
+ );
+ });
});
});
@@ -43,35 +46,35 @@ void main() {
blocTest(
'emits correct color for WeatherCondition.clear',
- build: () => ThemeCubit(),
+ build: () => mockHydratedStorage(() => ThemeCubit()),
act: (cubit) => cubit.updateTheme(clearWeather),
expect: () => [Colors.orangeAccent],
);
blocTest(
'emits correct color for WeatherCondition.snowy',
- build: () => ThemeCubit(),
+ build: () => mockHydratedStorage(() => ThemeCubit()),
act: (cubit) => cubit.updateTheme(snowyWeather),
expect: () => [Colors.lightBlueAccent],
);
blocTest(
'emits correct color for WeatherCondition.cloudy',
- build: () => ThemeCubit(),
+ build: () => mockHydratedStorage(() => ThemeCubit()),
act: (cubit) => cubit.updateTheme(cloudyWeather),
expect: () => [Colors.blueGrey],
);
blocTest(
'emits correct color for WeatherCondition.rainy',
- build: () => ThemeCubit(),
+ build: () => mockHydratedStorage(() => ThemeCubit()),
act: (cubit) => cubit.updateTheme(rainyWeather),
expect: () => [Colors.indigoAccent],
);
blocTest(
'emits correct color for WeatherCondition.unknown',
- build: () => ThemeCubit(),
+ build: () => mockHydratedStorage(() => ThemeCubit()),
act: (cubit) => cubit.updateTheme(unknownWeather),
expect: () => [ThemeCubit.defaultColor],
);
diff --git a/examples/flutter_weather/test/weather/cubit/weather_cubit_test.dart b/examples/flutter_weather/test/weather/cubit/weather_cubit_test.dart
index e198278445f..700630c1b52 100644
--- a/examples/flutter_weather/test/weather/cubit/weather_cubit_test.dart
+++ b/examples/flutter_weather/test/weather/cubit/weather_cubit_test.dart
@@ -22,8 +22,6 @@ void main() {
late weather_repository.Weather weather;
late weather_repository.WeatherRepository weatherRepository;
- setUpAll(initHydratedBloc);
-
setUp(() {
weather = MockWeather();
weatherRepository = MockWeatherRepository();
@@ -36,38 +34,42 @@ void main() {
});
test('initial state is correct', () {
- final weatherCubit = WeatherCubit(weatherRepository);
- expect(weatherCubit.state, WeatherState());
+ mockHydratedStorage(() {
+ final weatherCubit = WeatherCubit(weatherRepository);
+ expect(weatherCubit.state, WeatherState());
+ });
});
group('toJson/fromJson', () {
test('work properly', () {
- final weatherCubit = WeatherCubit(weatherRepository);
- expect(
- weatherCubit.fromJson(weatherCubit.toJson(weatherCubit.state)),
- weatherCubit.state,
- );
+ mockHydratedStorage(() {
+ final weatherCubit = WeatherCubit(weatherRepository);
+ expect(
+ weatherCubit.fromJson(weatherCubit.toJson(weatherCubit.state)),
+ weatherCubit.state,
+ );
+ });
});
});
group('fetchWeather', () {
blocTest(
'emits nothing when city is null',
- build: () => WeatherCubit(weatherRepository),
+ build: () => mockHydratedStorage(() => WeatherCubit(weatherRepository)),
act: (cubit) => cubit.fetchWeather(null),
expect: () => [],
);
blocTest(
'emits nothing when city is empty',
- build: () => WeatherCubit(weatherRepository),
+ build: () => mockHydratedStorage(() => WeatherCubit(weatherRepository)),
act: (cubit) => cubit.fetchWeather(''),
expect: () => [],
);
blocTest(
'calls getWeather with correct city',
- build: () => WeatherCubit(weatherRepository),
+ build: () => mockHydratedStorage(() => WeatherCubit(weatherRepository)),
act: (cubit) => cubit.fetchWeather(weatherLocation),
verify: (_) {
verify(() => weatherRepository.getWeather(weatherLocation)).called(1);
@@ -81,7 +83,7 @@ void main() {
() => weatherRepository.getWeather(any()),
).thenThrow(Exception('oops'));
},
- build: () => WeatherCubit(weatherRepository),
+ build: () => mockHydratedStorage(() => WeatherCubit(weatherRepository)),
act: (cubit) => cubit.fetchWeather(weatherLocation),
expect: () => [
WeatherState(status: WeatherStatus.loading),
@@ -91,7 +93,7 @@ void main() {
blocTest(
'emits [loading, success] when getWeather returns (celsius)',
- build: () => WeatherCubit(weatherRepository),
+ build: () => mockHydratedStorage(() => WeatherCubit(weatherRepository)),
act: (cubit) => cubit.fetchWeather(weatherLocation),
expect: () => [
WeatherState(status: WeatherStatus.loading),
@@ -115,7 +117,7 @@ void main() {
blocTest(
'emits [loading, success] when getWeather returns (fahrenheit)',
- build: () => WeatherCubit(weatherRepository),
+ build: () => mockHydratedStorage(() => WeatherCubit(weatherRepository)),
seed: () => WeatherState(temperatureUnits: TemperatureUnits.fahrenheit),
act: (cubit) => cubit.fetchWeather(weatherLocation),
expect: () => [
@@ -145,7 +147,7 @@ void main() {
group('refreshWeather', () {
blocTest(
'emits nothing when status is not success',
- build: () => WeatherCubit(weatherRepository),
+ build: () => mockHydratedStorage(() => WeatherCubit(weatherRepository)),
act: (cubit) => cubit.refreshWeather(),
expect: () => [],
verify: (_) {
@@ -155,7 +157,7 @@ void main() {
blocTest(
'emits nothing when location is null',
- build: () => WeatherCubit(weatherRepository),
+ build: () => mockHydratedStorage(() => WeatherCubit(weatherRepository)),
seed: () => WeatherState(status: WeatherStatus.success),
act: (cubit) => cubit.refreshWeather(),
expect: () => [],
@@ -166,7 +168,7 @@ void main() {
blocTest(
'invokes getWeather with correct location',
- build: () => WeatherCubit(weatherRepository),
+ build: () => mockHydratedStorage(() => WeatherCubit(weatherRepository)),
seed: () => WeatherState(
status: WeatherStatus.success,
weather: Weather(
@@ -189,7 +191,7 @@ void main() {
() => weatherRepository.getWeather(any()),
).thenThrow(Exception('oops'));
},
- build: () => WeatherCubit(weatherRepository),
+ build: () => mockHydratedStorage(() => WeatherCubit(weatherRepository)),
seed: () => WeatherState(
status: WeatherStatus.success,
weather: Weather(
@@ -205,7 +207,7 @@ void main() {
blocTest(
'emits updated weather (celsius)',
- build: () => WeatherCubit(weatherRepository),
+ build: () => mockHydratedStorage(() => WeatherCubit(weatherRepository)),
seed: () => WeatherState(
status: WeatherStatus.success,
weather: Weather(
@@ -237,7 +239,7 @@ void main() {
blocTest(
'emits updated weather (fahrenheit)',
- build: () => WeatherCubit(weatherRepository),
+ build: () => mockHydratedStorage(() => WeatherCubit(weatherRepository)),
seed: () => WeatherState(
temperatureUnits: TemperatureUnits.fahrenheit,
status: WeatherStatus.success,
@@ -272,7 +274,7 @@ void main() {
group('toggleUnits', () {
blocTest(
'emits updated units when status is not success',
- build: () => WeatherCubit(weatherRepository),
+ build: () => mockHydratedStorage(() => WeatherCubit(weatherRepository)),
act: (cubit) => cubit.toggleUnits(),
expect: () => [
WeatherState(temperatureUnits: TemperatureUnits.fahrenheit),
@@ -282,7 +284,7 @@ void main() {
blocTest(
'emits updated units and temperature '
'when status is success (celsius)',
- build: () => WeatherCubit(weatherRepository),
+ build: () => mockHydratedStorage(() => WeatherCubit(weatherRepository)),
seed: () => WeatherState(
status: WeatherStatus.success,
temperatureUnits: TemperatureUnits.fahrenheit,
@@ -311,7 +313,7 @@ void main() {
blocTest(
'emits updated units and temperature '
'when status is success (fahrenheit)',
- build: () => WeatherCubit(weatherRepository),
+ build: () => mockHydratedStorage(() => WeatherCubit(weatherRepository)),
seed: () => WeatherState(
status: WeatherStatus.success,
temperatureUnits: TemperatureUnits.celsius,
diff --git a/examples/flutter_weather/test/weather/view/weather_page_test.dart b/examples/flutter_weather/test/weather/view/weather_page_test.dart
index f7f2e7e86b8..dfd7802d961 100644
--- a/examples/flutter_weather/test/weather/view/weather_page_test.dart
+++ b/examples/flutter_weather/test/weather/view/weather_page_test.dart
@@ -15,22 +15,12 @@ import '../../helpers/hydrated_bloc.dart';
class MockWeatherRepository extends Mock implements WeatherRepository {}
-class FakeColor extends Fake implements Color {}
-
class MockThemeCubit extends MockCubit implements ThemeCubit {}
-class FakeWeatherState extends Fake implements WeatherState {}
-
class MockWeatherCubit extends MockCubit implements WeatherCubit {
}
void main() {
- setUpAll(() {
- initHydratedBloc();
- registerFallbackValue(FakeColor());
- registerFallbackValue(FakeWeatherState());
- });
-
group('WeatherPage', () {
late WeatherRepository weatherRepository;
@@ -39,10 +29,12 @@ void main() {
});
testWidgets('renders WeatherView', (tester) async {
- await tester.pumpWidget(RepositoryProvider.value(
- value: weatherRepository,
- child: MaterialApp(home: WeatherPage()),
- ));
+ await mockHydratedStorage(() async {
+ await tester.pumpWidget(RepositoryProvider.value(
+ value: weatherRepository,
+ child: MaterialApp(home: WeatherPage()),
+ ));
+ });
expect(find.byType(WeatherView), findsOneWidget);
});
});
@@ -110,17 +102,20 @@ void main() {
});
testWidgets('state is cached', (tester) async {
- when(() => hydratedStorage.read('WeatherCubit')).thenReturn(
+ final storage = MockStorage();
+ when(() => storage.read('WeatherCubit')).thenReturn(
WeatherState(
status: WeatherStatus.success,
weather: weather,
temperatureUnits: TemperatureUnits.fahrenheit,
).toJson(),
);
- await tester.pumpWidget(BlocProvider.value(
- value: WeatherCubit(MockWeatherRepository()),
- child: MaterialApp(home: WeatherView()),
- ));
+ await mockHydratedStorage(() async {
+ await tester.pumpWidget(BlocProvider.value(
+ value: WeatherCubit(MockWeatherRepository()),
+ child: MaterialApp(home: WeatherView()),
+ ));
+ }, storage: storage);
expect(find.byType(WeatherPopulated), findsOneWidget);
});
diff --git a/packages/angular_bloc/CHANGELOG.md b/packages/angular_bloc/CHANGELOG.md
index 1135ddb609b..1265008e1f1 100644
--- a/packages/angular_bloc/CHANGELOG.md
+++ b/packages/angular_bloc/CHANGELOG.md
@@ -1,3 +1,15 @@
+# 8.0.0-dev.3
+
+- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.5`
+
+# 8.0.0-dev.2
+
+- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.3`
+
+# 8.0.0-dev.1
+
+- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.2`
+
# 7.1.0
- feat: upgrade to `bloc ^7.2.0`
diff --git a/packages/angular_bloc/README.md b/packages/angular_bloc/README.md
index a096d19732b..bb345bca9c4 100644
--- a/packages/angular_bloc/README.md
+++ b/packages/angular_bloc/README.md
@@ -175,8 +175,8 @@ At this point we have successfully separated our presentational layer from our b
## Examples
-- [Counter](https://github.com/felangel/Bloc/tree/master/examples/angular_counter) - a complete example of how to create a `CounterBloc` and hook it up to an AngularDart app.
-- [Github Search](https://github.com/felangel/Bloc/tree/master/examples/github_search/angular_github_search) - an example of how to create a Github Search Application using the `bloc` and `angular_bloc` packages.
+- [Counter](https://github.com/felangel/bloc/tree/master/examples/angular_counter) - a complete example of how to create a `CounterBloc` and hook it up to an AngularDart app.
+- [Github Search](https://github.com/felangel/bloc/tree/master/examples/github_search/angular_github_search) - an example of how to create a Github Search Application using the `bloc` and `angular_bloc` packages.
## Maintainers
diff --git a/packages/angular_bloc/lib/angular_bloc.dart b/packages/angular_bloc/lib/angular_bloc.dart
index 76a8aa17cc7..a409859feee 100644
--- a/packages/angular_bloc/lib/angular_bloc.dart
+++ b/packages/angular_bloc/lib/angular_bloc.dart
@@ -5,5 +5,4 @@
library angular_dart;
export 'package:bloc/bloc.dart';
-
export './src/pipes/pipes.dart';
diff --git a/packages/angular_bloc/pubspec.yaml b/packages/angular_bloc/pubspec.yaml
index 89204d14a3f..029990785e3 100644
--- a/packages/angular_bloc/pubspec.yaml
+++ b/packages/angular_bloc/pubspec.yaml
@@ -1,6 +1,6 @@
name: angular_bloc
description: Angular Components that make it easy to implement the BLoC (Business Logic Component) design pattern. Built to be used with the bloc state management package.
-version: 7.1.0
+version: 8.0.0-dev.3
repository: https://github.com/felangel/bloc/tree/master/packages/angular_bloc
issue_tracker: https://github.com/felangel/bloc/issues
homepage: https://bloclibrary.dev
@@ -11,7 +11,7 @@ environment:
dependencies:
angular: ^7.0.0
- bloc: ^7.2.0
+ bloc: ^8.0.0-dev.5
dev_dependencies:
angular_test: ^4.0.0
@@ -19,5 +19,5 @@ dev_dependencies:
build_test: ^2.0.0
build_web_compilers: ^3.0.0
meta: ^1.2.3
- mocktail: ^0.1.4
- test: ^1.14.6
+ mocktail: ^0.2.0
+ test: ^1.16.0
diff --git a/packages/angular_bloc/test/bloc_pipe_test.dart b/packages/angular_bloc/test/bloc_pipe_test.dart
index e88a0b52643..61540cd2c74 100644
--- a/packages/angular_bloc/test/bloc_pipe_test.dart
+++ b/packages/angular_bloc/test/bloc_pipe_test.dart
@@ -38,6 +38,7 @@ void main() {
test('should return initialState when subscribing to an bloc', () {
expect(pipe.transform(bloc), 0);
});
+
test('should return the latest available value', () async {
pipe.transform(bloc);
bloc.add(Increment());
@@ -70,6 +71,7 @@ void main() {
expect(pipe.transform(newBloc), 0);
}));
});
+
test('should not dispose of existing subscription when Streams are equal',
() async {
// See https://github.com/dart-lang/angular2/issues/260
@@ -80,6 +82,7 @@ void main() {
expect(pipe.transform(_bloc), 1);
}));
});
+
test('should request a change detection check upon receiving a new value',
() async {
pipe.transform(bloc);
@@ -89,11 +92,13 @@ void main() {
}));
});
});
+
group('ngOnDestroy', () {
test('should do nothing when no subscription and not throw exception',
() {
pipe.ngOnDestroy();
});
+
test('should dispose of the existing subscription', () async {
pipe
..transform(bloc)
diff --git a/packages/angular_bloc/test/legacy/bloc_pipe_test.dart b/packages/angular_bloc/test/legacy/bloc_pipe_test.dart
deleted file mode 100644
index 7929e45cc90..00000000000
--- a/packages/angular_bloc/test/legacy/bloc_pipe_test.dart
+++ /dev/null
@@ -1,113 +0,0 @@
-@TestOn('browser')
-
-import 'dart:async';
-
-import 'package:angular/angular.dart' show ChangeDetectorRef;
-import 'package:angular_bloc/angular_bloc.dart';
-import 'package:mocktail/mocktail.dart';
-import 'package:test/test.dart';
-
-class MockChangeDetectorRef extends Mock implements ChangeDetectorRef {}
-
-enum CounterEvent { increment, decrement }
-
-class CounterBloc extends Bloc {
- CounterBloc() : super(0);
-
- @override
- Stream mapEventToState(CounterEvent event) async* {
- switch (event) {
- case CounterEvent.decrement:
- yield state - 1;
- break;
- case CounterEvent.increment:
- yield state + 1;
- break;
- }
- }
-}
-
-void main() {
- group('Stream', () {
- late Bloc bloc;
- late BlocPipe pipe;
- late ChangeDetectorRef ref;
-
- setUp(() {
- bloc = CounterBloc();
- ref = MockChangeDetectorRef();
- pipe = BlocPipe(ref);
- });
-
- group('transform', () {
- test('should return initialState when subscribing to an bloc', () {
- expect(pipe.transform(bloc), 0);
- });
- test('should return the latest available value', () async {
- pipe.transform(bloc);
- bloc.add(CounterEvent.increment);
- Timer.run(expectAsync0(() {
- final dynamic res = pipe.transform(bloc);
- expect(res, 1);
- }));
- });
-
- test(
- 'should return same value when nothing has changed '
- 'since the last call', () async {
- pipe.transform(bloc);
- bloc.add(CounterEvent.increment);
- Timer.run(expectAsync0(() {
- pipe.transform(bloc);
- expect(pipe.transform(bloc), 1);
- }));
- });
-
- test(
- 'should dispose of the existing subscription when '
- 'subscribing to a new bloc', () async {
- pipe.transform(bloc);
- var newBloc = CounterBloc();
- expect(pipe.transform(newBloc), 0);
- // this should not affect the pipe
- bloc.add(CounterEvent.increment);
- Timer.run(expectAsync0(() {
- expect(pipe.transform(newBloc), 0);
- }));
- });
- test('should not dispose of existing subscription when Streams are equal',
- () async {
- // See https://github.com/dart-lang/angular2/issues/260
- final _bloc = CounterBloc();
- expect(pipe.transform(_bloc), 0);
- _bloc.add(CounterEvent.increment);
- Timer.run(expectAsync0(() {
- expect(pipe.transform(_bloc), 1);
- }));
- });
- test('should request a change detection check upon receiving a new value',
- () async {
- pipe.transform(bloc);
- bloc.add(CounterEvent.increment);
- Timer(const Duration(milliseconds: 10), expectAsync0(() {
- verify(() => ref.markForCheck()).called(1);
- }));
- });
- });
- group('ngOnDestroy', () {
- test('should do nothing when no subscription and not throw exception',
- () {
- pipe.ngOnDestroy();
- });
- test('should dispose of the existing subscription', () async {
- pipe
- ..transform(bloc)
- ..ngOnDestroy();
- bloc.add(CounterEvent.increment);
- Timer.run(expectAsync0(() {
- expect(pipe.transform(bloc), 1);
- }));
- });
- });
- });
-}
diff --git a/packages/bloc/CHANGELOG.md b/packages/bloc/CHANGELOG.md
index 4ccac0d4608..ca9f9b73cc5 100644
--- a/packages/bloc/CHANGELOG.md
+++ b/packages/bloc/CHANGELOG.md
@@ -1,3 +1,37 @@
+# 8.0.0-dev.5
+
+- **BREAKING**: feat: introduce `BlocOverrides` API ([#2932](https://github.com/felangel/bloc/pull/2932))
+ - `Bloc.observer` removed in favor of `BlocOverrides.runZoned` and `BlocOverrides.current.blocObserver`
+ - `Bloc.transformer` removed in favor of `BlocOverrides.runZoned` and `BlocOverrides.current.eventTransformer`
+- **BREAKING**: refactor: make `BlocObserver` an abstract class
+- **BREAKING**: feat: `add` throws `StateError` when bloc is closed ([#2912](https://github.com/felangel/bloc/pull/2912))
+- **BREAKING**: feat: `emit` throws `StateError` when bloc is closed ([#2913](https://github.com/felangel/bloc/pull/2913))
+
+# 8.0.0-dev.4
+
+- **BREAKING**: feat: improve error handling/reporting
+ - `BlocUnhandledErrorException` is removed
+ - Uncaught exceptions are always reported to `onError` and rethrown
+ - `addError` reports error to `onError` but does not propagate as an uncaught exception
+
+# 8.0.0-dev.3
+
+- **BREAKING**: feat: restrict scope of `emit` in `Bloc` and `Cubit`
+ - In `Cubit`, `emit` is `protected` so it can only be used within the `Cubit` instance.
+ - In `Bloc`, `emit` is `internal` so it cannot be used outside of the internal package implementation.
+
+# 8.0.0-dev.2
+
+- **BREAKING**: refactor: remove deprecated `listen` on `BlocBase`
+
+# 8.0.0-dev.1
+
+- **BREAKING**: refactor: remove deprecated `TransitionFunction`
+- **BREAKING**: refactor: remove deprecated `transformEvents`
+- **BREAKING**: refactor: remove deprecated `mapEventToState`
+- **BREAKING**: refactor: remove deprecated `transformTransitions`
+- feat: throw `StateError` if an event is added without a registered event handler
+
# 7.2.1
- fix: `on` should have an `EventTransformer` instead of `EventTransformer`
@@ -24,7 +58,7 @@
# 7.2.0-dev.3
-- **BREAKING**: refactor!: require `emit.forEach` `onData` to be synchronous
+- **BREAKING**: refactor: require `emit.forEach` `onData` to be synchronous
- refactor: minor internal optimizations in `on` implementation
# 7.2.0-dev.2
diff --git a/packages/bloc/README.md b/packages/bloc/README.md
index f636fba540d..18760d58134 100644
--- a/packages/bloc/README.md
+++ b/packages/bloc/README.md
@@ -166,8 +166,12 @@ class MyBlocObserver extends BlocObserver {
```dart
void main() {
- Bloc.observer = MyBlocObserver();
- // Use cubits...
+ BlocOverrides.runZoned(
+ () {
+ // Use cubits...
+ },
+ blocObserver: MyBlocObserver(),
+ );
}
```
@@ -317,8 +321,12 @@ class MyBlocObserver extends BlocObserver {
```dart
void main() {
- Bloc.observer = MyBlocObserver();
- // Use blocs...
+ BlocOverrides.runZoned(
+ () {
+ // Use blocs...
+ },
+ blocObserver: MyBlocObserver(),
+ );
}
```
@@ -328,7 +336,7 @@ void main() {
## Examples
-- [Counter](https://github.com/felangel/Bloc/tree/master/packages/bloc/example) - an example of how to create a `CounterBloc` in a pure Dart app.
+- [Counter](https://github.com/felangel/bloc/tree/master/packages/bloc/example) - an example of how to create a `CounterBloc` in a pure Dart app.
## Maintainers
diff --git a/packages/bloc/example/main.dart b/packages/bloc/example/main.dart
index 8423c6fcbc1..2a3566c75e6 100644
--- a/packages/bloc/example/main.dart
+++ b/packages/bloc/example/main.dart
@@ -41,10 +41,10 @@ class SimpleBlocObserver extends BlocObserver {
}
void main() {
- Bloc.observer = SimpleBlocObserver();
-
- cubitMain();
- blocMain();
+ BlocOverrides.runZoned(() {
+ cubitMain();
+ blocMain();
+ }, blocObserver: SimpleBlocObserver());
}
void cubitMain() {
diff --git a/packages/bloc/lib/src/bloc.dart b/packages/bloc/lib/src/bloc.dart
index 12b03fa38e9..ee1ba916c0e 100644
--- a/packages/bloc/lib/src/bloc.dart
+++ b/packages/bloc/lib/src/bloc.dart
@@ -1,9 +1,104 @@
-// ignore_for_file: deprecated_member_use_from_same_package
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
+const _asyncRunZoned = runZoned;
+
+/// This class facilitates overriding [BlocObserver] and [EventTransformer].
+/// It should be extended by another class in client code with overrides
+/// that construct a custom implementation. The implementation in this class
+/// defaults to the base [blocObserver] and [eventTransformer] implementation.
+/// For example:
+///
+/// ```dart
+/// class MyBlocObserver extends BlocObserver {
+/// ...
+/// // A custom BlocObserver implementation.
+/// ...
+/// }
+///
+/// void main() {
+/// BlocOverrides.runZoned(() {
+/// ...
+/// // Bloc instances will use MyBlocObserver instead of the default BlocObserver.
+/// ...
+/// }, blocObserver: MyBlocObserver());
+/// }
+/// ```
+abstract class BlocOverrides {
+ static final _token = Object();
+
+ /// Returns the current [BlocOverrides] instance.
+ ///
+ /// This will return `null` if the current [Zone] does not contain
+ /// any [BlocOverrides].
+ ///
+ /// See also:
+ /// * [BlocOverrides.runZoned] to provide [BlocOverrides] in a fresh [Zone].
+ ///
+ static BlocOverrides? get current => Zone.current[_token] as BlocOverrides?;
+
+ /// Runs [body] in a fresh [Zone] using the provided overrides.
+ static R runZoned(
+ R Function() body, {
+ BlocObserver? blocObserver,
+ EventTransformer? eventTransformer,
+ }) {
+ final overrides = _BlocOverridesScope(blocObserver, eventTransformer);
+ return _asyncRunZoned(body, zoneValues: {_token: overrides});
+ }
+
+ /// The [BlocObserver] that will be used within the current [Zone].
+ ///
+ /// By default, a base [BlocObserver] implementation is used.
+ BlocObserver get blocObserver => _defaultBlocObserver;
+
+ /// The [EventTransformer] that will be used within the current [Zone].
+ ///
+ /// By default, all events are processed concurrently.
+ ///
+ /// If a custom transformer is specified for a particular event handler,
+ /// it will take precendence over the global transformer.
+ ///
+ /// See also:
+ ///
+ /// * [package:bloc_concurrency](https://pub.dev/packages/bloc_concurrency) for an
+ /// opinionated set of event transformers.
+ ///
+ EventTransformer get eventTransformer => _defaultEventTransformer;
+}
+
+class _BlocOverridesScope extends BlocOverrides {
+ _BlocOverridesScope(this._blocObserver, this._eventTransformer);
+
+ final BlocOverrides? _previous = BlocOverrides.current;
+ final BlocObserver? _blocObserver;
+ final EventTransformer? _eventTransformer;
+
+ @override
+ BlocObserver get blocObserver {
+ final blocObserver = _blocObserver;
+ if (blocObserver != null) return blocObserver;
+
+ final previous = _previous;
+ if (previous != null) return previous.blocObserver;
+
+ return super.blocObserver;
+ }
+
+ @override
+ EventTransformer get eventTransformer {
+ final eventTransformer = _eventTransformer;
+ if (eventTransformer != null) return eventTransformer;
+
+ final previous = _previous;
+ if (previous != null) return previous.eventTransformer;
+
+ return super.eventTransformer;
+ }
+}
+
/// {@template emitter}
/// An [Emitter] is a class which is capable of emitting new states.
///
@@ -87,7 +182,7 @@ typedef EventTransformer = Stream Function(
class _Emitter implements Emitter {
_Emitter(this._emit);
- final void Function(State) _emit;
+ final void Function(State state) _emit;
final _completer = Completer();
final _disposables = Function()>[];
@@ -97,7 +192,7 @@ class _Emitter implements Emitter {
@override
Future onEach(
Stream stream, {
- required void Function(T) onData,
+ required void Function(T data) onData,
void Function(Object error, StackTrace stackTrace)? onError,
}) async {
final completer = Completer();
@@ -117,7 +212,7 @@ class _Emitter implements Emitter {
@override
Future forEach(
Stream stream, {
- required State Function(T) onData,
+ required State Function(T data) onData,
State Function(Object error, StackTrace stackTrace)? onError,
}) {
return onEach(
@@ -207,46 +302,10 @@ Please make sure to await all asynchronous operations within event handlers.
Future get future => _completer.future;
}
-/// **@Deprecated - Use `on` with an `EventTransformer` instead.
-/// Will be removed in v8.0.0**
-///
-/// Signature for a mapper function which takes an [Event] as input
-/// and outputs a [Stream] of [Transition] objects.
-@Deprecated(
- 'Use `on` with an `EventTransformer` instead. '
- 'Will be removed in v8.0.0',
-)
-typedef TransitionFunction = Stream>
- Function(Event);
-
-/// {@template bloc_unhandled_error_exception}
-/// Exception thrown when an unhandled error occurs within a bloc.
-///
-/// _Note: thrown in debug mode only_
-/// {@endtemplate}
-class BlocUnhandledErrorException implements Exception {
- /// {@macro bloc_unhandled_error_exception}
- BlocUnhandledErrorException(
- this.bloc,
- this.error, [
- this.stackTrace = StackTrace.empty,
- ]);
-
- /// The bloc in which the unhandled error occurred.
- final BlocBase bloc;
-
- /// The unhandled [error] object.
- final Object error;
-
- /// Stack trace which accompanied the error.
- /// May be [StackTrace.empty] if no stack trace was provided.
- final StackTrace stackTrace;
-
- @override
- String toString() {
- return 'Unhandled error $error occurred in $bloc.\n'
- '$stackTrace';
- }
+class _Handler {
+ const _Handler({required this.isType, required this.type});
+ final bool Function(dynamic value) isType;
+ final Type type;
}
/// {@template bloc}
@@ -255,48 +314,41 @@ class BlocUnhandledErrorException implements Exception {
/// {@endtemplate}
abstract class Bloc extends BlocBase {
/// {@macro bloc}
- Bloc(State initialState) : super(initialState) {
- _bindEventsToStates();
- }
-
- /// The current [BlocObserver] instance.
- static BlocObserver observer = BlocObserver();
-
- /// The default [EventTransformer] used for all event handlers.
- /// By default all events are processed concurrently.
- ///
- /// If a custom transformer is specified for a particular event handler,
- /// it will take precendence over the global transformer.
- ///
- /// See also:
- ///
- /// * [package:bloc_concurrency](https://pub.dev/packages/bloc_concurrency) for an
- /// opinionated set of event transformers.
- ///
- static EventTransformer transformer = (events, mapper) {
- return events
- .map(mapper)
- .transform(const _FlatMapStreamTransformer());
- };
-
- StreamSubscription>? _transitionSubscription;
+ Bloc(State initialState) : super(initialState);
final _eventController = StreamController.broadcast();
final _subscriptions = >[];
- final _handlerTypes = [];
+ final _handlers = <_Handler>[];
final _emitters = <_Emitter>[];
+ final _eventTransformer =
+ BlocOverrides.current?.eventTransformer ?? _defaultEventTransformer;
/// Notifies the [Bloc] of a new [event] which triggers
/// all corresponding [EventHandler] instances.
- /// If [close] has already been called, any subsequent calls to [add] will
- /// be ignored and will not result in any subsequent state changes.
+ ///
+ /// * A [StateError] will be thrown if there is no event handler
+ /// registered for the incoming [event].
+ ///
+ /// * A [StateError] will be thrown if the bloc is closed and the
+ /// [event] will not be processed.
void add(Event event) {
- if (_eventController.isClosed) return;
+ assert(() {
+ final handlerExists = _handlers.any((handler) => handler.isType(event));
+ if (!handlerExists) {
+ final eventType = event.runtimeType;
+ throw StateError(
+ '''add($eventType) was called without a registered event handler.\n'''
+ '''Make sure to register a handler via on<$eventType>((event, emit) {...})''',
+ );
+ }
+ return true;
+ }());
try {
onEvent(event);
_eventController.add(event);
} catch (error, stackTrace) {
onError(error, stackTrace);
+ rethrow;
}
}
@@ -322,63 +374,30 @@ abstract class Bloc extends BlocBase {
@mustCallSuper
void onEvent(Event event) {
// ignore: invalid_use_of_protected_member
- observer.onEvent(this, event);
+ _blocObserver?.onEvent(this, event);
}
- /// **@Deprecated - Use `on` with an `EventTransformer` instead.
- /// Will be removed in v8.0.0**
- ///
- /// Transforms the [events] stream along with a [transitionFn] function into
- /// a `Stream`.
- /// Events that should be processed by [mapEventToState] need to be passed to
- /// [transitionFn].
- /// By default `asyncExpand` is used to ensure all [events] are processed in
- /// the order in which they are received.
- /// You can override [transformEvents] for advanced usage in order to
- /// manipulate the frequency and specificity with which [mapEventToState] is
- /// called as well as which [events] are processed.
- ///
- /// For example, if you only want [mapEventToState] to be called on the most
- /// recent [Event] you can use `switchMap` instead of `asyncExpand`.
- ///
- /// ```dart
- /// @override
- /// Stream> transformEvents(events, transitionFn) {
- /// return events.switchMap(transitionFn);
- /// }
- /// ```
- ///
- /// Alternatively, if you only want [mapEventToState] to be called for
- /// distinct [events]:
+ /// {@template emit}
+ /// **[emit] is only for internal use and should never be called directly.
+ /// The [Emitter] instance provided to each [EventHandler]
+ /// should be used instead.**
///
/// ```dart
- /// @override
- /// Stream> transformEvents(events, transitionFn) {
- /// return super.transformEvents(
- /// events.distinct(),
- /// transitionFn,
- /// );
+ /// class MyBloc extends Bloc {
+ /// MyBloc() : super(MyInitialState()) {
+ /// on((event, emit) {
+ /// // use `emit` to update the state.
+ /// emit(MyOtherState());
+ /// });
+ /// }
/// }
/// ```
- @Deprecated(
- 'Use `on` with an `EventTransformer` instead. '
- 'Will be removed in v8.0.0',
- )
- Stream> transformEvents(
- Stream events,
- TransitionFunction transitionFn,
- ) {
- return events.asyncExpand(transitionFn);
- }
-
- /// {@template emit}
- /// **[emit] should never be used outside of tests.**
///
/// Updates the state of the bloc to the provided [state].
/// A bloc's state should only be updated by `emitting` a new `state`
/// from an [EventHandler] in response to an incoming event.
/// {@endtemplate}
- @visibleForTesting
+ @internal
@override
void emit(State state) => super.emit(state);
@@ -412,18 +431,18 @@ abstract class Bloc extends BlocBase {
EventTransformer? transformer,
}) {
assert(() {
- final handlerExists = _handlerTypes.any((type) => type == E);
+ final handlerExists = _handlers.any((handler) => handler.type == E);
if (handlerExists) {
throw StateError(
'on<$E> was called multiple times. '
'There should only be a single event handler per event type.',
);
}
- _handlerTypes.add(E);
+ _handlers.add(_Handler(isType: (dynamic e) => e is E, type: E));
return true;
}());
- final _transformer = transformer ?? Bloc.transformer;
+ final _transformer = transformer ?? _eventTransformer;
final subscription = _transformer(
_eventController.stream.where((event) => event is E).cast(),
(dynamic event) {
@@ -456,6 +475,7 @@ abstract class Bloc extends BlocBase {
await handler(event as E, emitter);
} catch (error, stackTrace) {
onError(error, stackTrace);
+ rethrow;
} finally {
onDone();
}
@@ -468,15 +488,6 @@ abstract class Bloc extends BlocBase {
_subscriptions.add(subscription);
}
- /// **@Deprecated - Use on instead. Will be removed in v8.0.0**
- ///
- /// Must be implemented when a class extends [Bloc].
- /// [mapEventToState] is called whenever an [event] is [add]ed
- /// and is responsible for converting that [event] into a new [state].
- /// [mapEventToState] can `yield` zero, one, or multiple states for an event.
- @Deprecated('Use on instead. Will be removed in v8.0.0')
- Stream mapEventToState(Event event) async* {}
-
/// Called whenever a [transition] occurs with the given [transition].
/// A [transition] occurs when a new `event` is added
/// and a new state is `emitted` from a corresponding [EventHandler].
@@ -503,36 +514,7 @@ abstract class Bloc extends BlocBase {
@mustCallSuper
void onTransition(Transition transition) {
// ignore: invalid_use_of_protected_member
- Bloc.observer.onTransition(this, transition);
- }
-
- /// **@Deprecated - Override `Stream get stream` instead.
- /// Will be removed in v8.0.0**
- ///
- /// Transforms the `Stream` into a new `Stream`.
- /// By default [transformTransitions] returns
- /// the incoming `Stream`.
- /// You can override [transformTransitions] for advanced usage in order to
- /// manipulate the frequency and specificity at which `transitions`
- /// (state changes) occur.
- ///
- /// For example, if you want to debounce outgoing state changes:
- ///
- /// ```dart
- /// @override
- /// Stream> transformTransitions(
- /// Stream> transitions,
- /// ) {
- /// return transitions.debounceTime(Duration(seconds: 1));
- /// }
- /// ```
- @Deprecated(
- 'Override `Stream get stream` instead. Will be removed in v8.0.0',
- )
- Stream> transformTransitions(
- Stream> transitions,
- ) {
- return transitions;
+ _blocObserver?.onTransition(this, transition);
}
/// Closes the `event` and `state` `Streams`.
@@ -548,48 +530,8 @@ abstract class Bloc extends BlocBase {
for (final emitter in _emitters) emitter.cancel();
await Future.wait(_emitters.map((e) => e.future));
await Future.wait(_subscriptions.map((s) => s.cancel()));
- await _transitionSubscription?.cancel();
return super.close();
}
-
- void _bindEventsToStates() {
- void assertNoMixedUsage() {
- assert(() {
- if (_handlerTypes.isNotEmpty) {
- throw StateError(
- 'mapEventToState cannot be overridden in '
- 'conjunction with on.',
- );
- }
- return true;
- }());
- }
-
- _transitionSubscription = transformTransitions(
- transformEvents(
- _eventController.stream,
- (event) => mapEventToState(event).map(
- (nextState) => Transition(
- currentState: state,
- event: event,
- nextState: nextState,
- ),
- ),
- ),
- ).listen(
- (transition) {
- if (transition.nextState == state && _emitted) return;
- try {
- assertNoMixedUsage();
- onTransition(transition);
- emit(transition.nextState);
- } catch (error, stackTrace) {
- onError(error, stackTrace);
- }
- },
- onError: onError,
- );
- }
}
/// {@template cubit}
@@ -623,9 +565,11 @@ abstract class BlocBase {
/// {@macro bloc_stream}
BlocBase(this._state) {
// ignore: invalid_use_of_protected_member
- Bloc.observer.onCreate(this);
+ _blocObserver?.onCreate(this);
}
+ final _blocObserver = BlocOverrides.current?.blocObserver;
+
StreamController? __stateController;
StreamController get _stateController {
return __stateController ??= StreamController.broadcast();
@@ -647,41 +591,30 @@ abstract class BlocBase {
/// Subsequent state changes cannot occur within a closed bloc.
bool get isClosed => _stateController.isClosed;
- /// Adds a subscription to the `Stream`.
- /// Returns a [StreamSubscription] which handles events from
- /// the `Stream` using the provided [onData], [onError] and [onDone]
- /// handlers.
- @Deprecated(
- 'Use stream.listen instead. Will be removed in v8.0.0',
- )
- StreamSubscription listen(
- void Function(State)? onData, {
- Function? onError,
- void Function()? onDone,
- bool? cancelOnError,
- }) {
- return stream.listen(
- onData,
- onError: onError,
- onDone: onDone,
- cancelOnError: cancelOnError,
- );
- }
-
/// Updates the [state] to the provided [state].
- /// [emit] does nothing if the instance has been closed or if the
- /// [state] being emitted is equal to the current [state].
+ /// [emit] does nothing if the [state] being emitted
+ /// is equal to the current [state].
///
/// To allow for the possibility of notifying listeners of the initial state,
/// emitting a state which is equal to the initial state is allowed as long
/// as it is the first thing emitted by the instance.
+ ///
+ /// * Throws a [StateError] if the bloc is closed.
+ @protected
void emit(State state) {
- if (_stateController.isClosed) return;
- if (state == _state && _emitted) return;
- onChange(Change(currentState: this.state, nextState: state));
- _state = state;
- _stateController.add(_state);
- _emitted = true;
+ try {
+ if (isClosed) {
+ throw StateError('Cannot emit new states after calling close');
+ }
+ if (state == _state && _emitted) return;
+ onChange(Change(currentState: this.state, nextState: state));
+ _state = state;
+ _stateController.add(_state);
+ _emitted = true;
+ } catch (error, stackTrace) {
+ onError(error, stackTrace);
+ rethrow;
+ }
}
/// Called whenever a [change] occurs with the given [change].
@@ -707,7 +640,7 @@ abstract class BlocBase {
@mustCallSuper
void onChange(Change change) {
// ignore: invalid_use_of_protected_member
- Bloc.observer.onChange(this, change);
+ _blocObserver?.onChange(this, change);
}
/// Reports an [error] which triggers [onError] with an optional [StackTrace].
@@ -718,13 +651,8 @@ abstract class BlocBase {
/// Called whenever an [error] occurs and notifies [BlocObserver.onError].
///
- /// In debug mode, [onError] throws a [BlocUnhandledErrorException] for
- /// improved visibility.
- ///
- /// In release mode, [onError] does not throw and will instead only report
- /// the error to [BlocObserver.onError].
- ///
/// **Note: `super.onError` should always be called last.**
+ ///
/// ```dart
/// @override
/// void onError(Object error, StackTrace stackTrace) {
@@ -738,10 +666,7 @@ abstract class BlocBase {
@mustCallSuper
void onError(Object error, StackTrace stackTrace) {
// ignore: invalid_use_of_protected_member
- Bloc.observer.onError(this, error, stackTrace);
- assert(() {
- throw BlocUnhandledErrorException(this, error, stackTrace);
- }());
+ _blocObserver?.onError(this, error, stackTrace);
}
/// Closes the instance.
@@ -750,11 +675,20 @@ abstract class BlocBase {
@mustCallSuper
Future close() async {
// ignore: invalid_use_of_protected_member
- Bloc.observer.onClose(this);
+ _blocObserver?.onClose(this);
await _stateController.close();
}
}
+late final _defaultBlocObserver = _DefaultBlocObserver();
+late final _defaultEventTransformer = (Stream events, EventMapper mapper) {
+ return events
+ .map(mapper)
+ .transform(const _FlatMapStreamTransformer());
+};
+
+class _DefaultBlocObserver extends BlocObserver {}
+
class _FlatMapStreamTransformer extends StreamTransformerBase, T> {
const _FlatMapStreamTransformer();
diff --git a/packages/bloc/lib/src/bloc_observer.dart b/packages/bloc/lib/src/bloc_observer.dart
index 53c9871cb15..05689c44523 100644
--- a/packages/bloc/lib/src/bloc_observer.dart
+++ b/packages/bloc/lib/src/bloc_observer.dart
@@ -2,7 +2,7 @@ import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
/// An interface for observing the behavior of [Bloc] instances.
-class BlocObserver {
+abstract class BlocObserver {
/// Called whenever a [Bloc] is instantiated.
/// In many cases, a cubit may be lazily instantiated and
/// [onCreate] can be used to observe exactly when the cubit
diff --git a/packages/bloc/pubspec.yaml b/packages/bloc/pubspec.yaml
index 575ef4b36f8..36724fd7f16 100644
--- a/packages/bloc/pubspec.yaml
+++ b/packages/bloc/pubspec.yaml
@@ -1,6 +1,6 @@
name: bloc
description: A predictable state management library that helps implement the BLoC (Business Logic Component) design pattern.
-version: 7.2.1
+version: 8.0.0-dev.5
repository: https://github.com/felangel/bloc/tree/master/packages/bloc
issue_tracker: https://github.com/felangel/bloc/issues
homepage: https://github.com/felangel/bloc
@@ -13,6 +13,6 @@ dependencies:
meta: ^1.3.0
dev_dependencies:
- mocktail: ^0.1.0
+ mocktail: ^0.2.0
stream_transform: ^2.0.0
- test: ^1.16.0
+ test: ^1.18.2
diff --git a/packages/bloc/test/bloc_event_transformer_test.dart b/packages/bloc/test/bloc_event_transformer_test.dart
index f850aa42e4d..7ad003cc799 100644
--- a/packages/bloc/test/bloc_event_transformer_test.dart
+++ b/packages/bloc/test/bloc_event_transformer_test.dart
@@ -39,16 +39,6 @@ class CounterBloc extends Bloc {
}
void main() {
- late EventTransformer transformer;
-
- setUp(() {
- transformer = Bloc.transformer;
- });
-
- tearDown(() {
- Bloc.transformer = transformer;
- });
-
test('processes events concurrently by default', () async {
final states = [];
final bloc = CounterBloc()
@@ -196,77 +186,81 @@ void main() {
test(
'processes events sequentially when '
'Bloc.transformer is overridden.', () async {
- Bloc.transformer = (events, mapper) => events.asyncExpand(mapper);
- final states = [];
- final bloc = CounterBloc()
- ..stream.listen(states.add)
- ..add(Increment())
- ..add(Increment())
- ..add(Increment());
+ await BlocOverrides.runZoned(
+ () async {
+ final states = [];
+ final bloc = CounterBloc()
+ ..stream.listen(states.add)
+ ..add(Increment())
+ ..add(Increment())
+ ..add(Increment());
- await tick();
+ await tick();
- expect(
- bloc.onCalls,
- equals([Increment()]),
- );
+ expect(
+ bloc.onCalls,
+ equals([Increment()]),
+ );
- await wait();
+ await wait();
- expect(
- bloc.onEmitCalls,
- equals([Increment()]),
- );
- expect(states, equals([1]));
+ expect(
+ bloc.onEmitCalls,
+ equals([Increment()]),
+ );
+ expect(states, equals([1]));
- await tick();
+ await tick();
- expect(
- bloc.onCalls,
- equals([Increment(), Increment()]),
- );
+ expect(
+ bloc.onCalls,
+ equals([Increment(), Increment()]),
+ );
- await wait();
+ await wait();
- expect(
- bloc.onEmitCalls,
- equals([Increment(), Increment()]),
- );
+ expect(
+ bloc.onEmitCalls,
+ equals([Increment(), Increment()]),
+ );
- expect(states, equals([1, 2]));
+ expect(states, equals([1, 2]));
- await tick();
+ await tick();
- expect(
- bloc.onCalls,
- equals([
- Increment(),
- Increment(),
- Increment(),
- ]),
- );
+ expect(
+ bloc.onCalls,
+ equals([
+ Increment(),
+ Increment(),
+ Increment(),
+ ]),
+ );
- await wait();
+ await wait();
- expect(
- bloc.onEmitCalls,
- equals([Increment(), Increment(), Increment()]),
- );
+ expect(
+ bloc.onEmitCalls,
+ equals([Increment(), Increment(), Increment()]),
+ );
- expect(states, equals([1, 2, 3]));
+ expect(states, equals([1, 2, 3]));
- await bloc.close();
+ await bloc.close();
- expect(
- bloc.onCalls,
- equals([Increment(), Increment(), Increment()]),
- );
+ expect(
+ bloc.onCalls,
+ equals([Increment(), Increment(), Increment()]),
+ );
- expect(
- bloc.onEmitCalls,
- equals([Increment(), Increment(), Increment()]),
- );
+ expect(
+ bloc.onEmitCalls,
+ equals([Increment(), Increment(), Increment()]),
+ );
- expect(states, equals([1, 2, 3]));
+ expect(states, equals([1, 2, 3]));
+ },
+ eventTransformer: (events, mapper) => events.asyncExpand(mapper),
+ );
});
}
diff --git a/packages/bloc/test/bloc_observer_test.dart b/packages/bloc/test/bloc_observer_test.dart
index 4ccf8dd6ae0..70f644154d9 100644
--- a/packages/bloc/test/bloc_observer_test.dart
+++ b/packages/bloc/test/bloc_observer_test.dart
@@ -3,6 +3,8 @@ import 'package:test/test.dart';
import 'blocs/blocs.dart';
+class DefaultBlocObserver extends BlocObserver {}
+
void main() {
final bloc = CounterBloc();
final error = Exception();
@@ -18,42 +20,42 @@ void main() {
group('onCreate', () {
test('does nothing by default', () {
// ignore: invalid_use_of_protected_member
- BlocObserver().onCreate(bloc);
+ DefaultBlocObserver().onCreate(bloc);
});
});
group('onEvent', () {
test('does nothing by default', () {
// ignore: invalid_use_of_protected_member
- BlocObserver().onEvent(bloc, event);
+ DefaultBlocObserver().onEvent(bloc, event);
});
});
group('onChange', () {
test('does nothing by default', () {
// ignore: invalid_use_of_protected_member
- BlocObserver().onChange(bloc, change);
+ DefaultBlocObserver().onChange(bloc, change);
});
});
group('onTransition', () {
test('does nothing by default', () {
// ignore: invalid_use_of_protected_member
- BlocObserver().onTransition(bloc, transition);
+ DefaultBlocObserver().onTransition(bloc, transition);
});
});
group('onError', () {
test('does nothing by default', () {
// ignore: invalid_use_of_protected_member
- BlocObserver().onError(bloc, error, stackTrace);
+ DefaultBlocObserver().onError(bloc, error, stackTrace);
});
});
group('onClose', () {
test('does nothing by default', () {
// ignore: invalid_use_of_protected_member
- BlocObserver().onClose(bloc);
+ DefaultBlocObserver().onClose(bloc);
});
});
});
diff --git a/packages/bloc/test/bloc_on_test.dart b/packages/bloc/test/bloc_on_test.dart
index 6652b5044e5..51e8e7169cd 100644
--- a/packages/bloc/test/bloc_on_test.dart
+++ b/packages/bloc/test/bloc_on_test.dart
@@ -53,7 +53,7 @@ class MissingHandlerBloc extends Bloc {
}
void main() {
- group('on', () {
+ group('on', () {
test('throws StateError when handler is registered more than once', () {
const expectedMessage = 'on was called multiple times. '
'There should only be a single event handler per event type.';
@@ -62,6 +62,16 @@ void main() {
expect(() => DuplicateHandlerBloc(), expected);
});
+ test('throws StateError when handler is missing', () {
+ const expectedMessage =
+ '''add(TestEventA) was called without a registered event handler.\n'''
+ '''Make sure to register a handler via on((event, emit) {...})''';
+ final expected = throwsA(
+ isA().having((e) => e.message, 'message', expectedMessage),
+ );
+ expect(() => MissingHandlerBloc().add(TestEventA()), expected);
+ });
+
test('invokes all on when event E is added where E is T', () async {
var onEventCallCount = 0;
var onACallCount = 0;
diff --git a/packages/bloc/test/bloc_overrides_test.dart b/packages/bloc/test/bloc_overrides_test.dart
new file mode 100644
index 00000000000..dfce6fa3dc0
--- /dev/null
+++ b/packages/bloc/test/bloc_overrides_test.dart
@@ -0,0 +1,116 @@
+import 'package:bloc/bloc.dart';
+import 'package:bloc/src/bloc.dart';
+import 'package:mocktail/mocktail.dart';
+import 'package:test/test.dart';
+
+class FakeBlocObserver extends Fake implements BlocObserver {}
+
+void main() {
+ group('BlocOverrides', () {
+ group('runZoned', () {
+ test('uses default BlocObserver when not specified', () {
+ BlocOverrides.runZoned(() {
+ final overrides = BlocOverrides.current;
+ expect(overrides!.blocObserver, isA());
+ });
+ });
+
+ test('uses default EventTransformer when not specified', () {
+ BlocOverrides.runZoned(() {
+ final overrides = BlocOverrides.current;
+ expect(overrides!.eventTransformer, isA());
+ });
+ });
+
+ test('uses custom BlocObserver when specified', () {
+ final blocObserver = FakeBlocObserver();
+ BlocOverrides.runZoned(() {
+ final overrides = BlocOverrides.current;
+ expect(overrides!.blocObserver, equals(blocObserver));
+ }, blocObserver: blocObserver);
+ });
+
+ test('uses custom EventTransformer when specified', () {
+ final eventTransformer = (Stream events, EventMapper mapper) {
+ return events.asyncExpand(mapper);
+ };
+ BlocOverrides.runZoned(() {
+ final overrides = BlocOverrides.current;
+ expect(overrides!.eventTransformer, equals(eventTransformer));
+ }, eventTransformer: eventTransformer);
+ });
+
+ test(
+ 'uses current BlocObserver when not specified '
+ 'and zone already contains a BlocObserver', () {
+ final blocObserver = FakeBlocObserver();
+ BlocOverrides.runZoned(() {
+ BlocOverrides.runZoned(() {
+ final overrides = BlocOverrides.current;
+ expect(overrides!.blocObserver, equals(blocObserver));
+ });
+ }, blocObserver: blocObserver);
+ });
+
+ test(
+ 'uses current EventTransformer when not specified '
+ 'and zone already contains an EventTransformer', () {
+ final eventTransformer = (Stream events, EventMapper mapper) {
+ return events.asyncExpand(mapper);
+ };
+ BlocOverrides.runZoned(() {
+ BlocOverrides.runZoned(() {
+ final overrides = BlocOverrides.current;
+ expect(overrides!.eventTransformer, equals(eventTransformer));
+ });
+ }, eventTransformer: eventTransformer);
+ });
+
+ test(
+ 'uses nested BlocObserver when specified '
+ 'and zone already contains a BlocObserver', () {
+ final rootBlocObserver = FakeBlocObserver();
+ BlocOverrides.runZoned(() {
+ final nestedBlocObserver = FakeBlocObserver();
+ final overrides = BlocOverrides.current;
+ expect(overrides!.blocObserver, equals(rootBlocObserver));
+ BlocOverrides.runZoned(() {
+ final overrides = BlocOverrides.current;
+ expect(overrides!.blocObserver, equals(nestedBlocObserver));
+ }, blocObserver: nestedBlocObserver);
+ }, blocObserver: rootBlocObserver);
+ });
+
+ test(
+ 'uses nested EventTransformer when specified '
+ 'and zone already contains an EventTransformer', () {
+ final rootEventTransformer = (Stream events, EventMapper mapper) {
+ return events.asyncExpand(mapper);
+ };
+ BlocOverrides.runZoned(() {
+ final nestedEventTransformer = (Stream events, EventMapper mapper) {
+ return events.asyncExpand(mapper);
+ };
+ final overrides = BlocOverrides.current;
+ expect(overrides!.eventTransformer, equals(rootEventTransformer));
+ BlocOverrides.runZoned(() {
+ final overrides = BlocOverrides.current;
+ expect(overrides!.eventTransformer, equals(nestedEventTransformer));
+ }, eventTransformer: nestedEventTransformer);
+ }, eventTransformer: rootEventTransformer);
+ });
+
+ test('overrides cannot be mutated after zone is created', () {
+ final originalBlocObserver = FakeBlocObserver();
+ final otherBlocObserver = FakeBlocObserver();
+ var blocObserver = originalBlocObserver;
+ BlocOverrides.runZoned(() {
+ blocObserver = otherBlocObserver;
+ final overrides = BlocOverrides.current!;
+ expect(overrides.blocObserver, equals(originalBlocObserver));
+ expect(overrides.blocObserver, isNot(equals(otherBlocObserver)));
+ }, blocObserver: blocObserver);
+ });
+ });
+ });
+}
diff --git a/packages/bloc/test/bloc_test.dart b/packages/bloc/test/bloc_test.dart
index de6b4423a85..df0d7dabaab 100644
--- a/packages/bloc/test/bloc_test.dart
+++ b/packages/bloc/test/bloc_test.dart
@@ -21,20 +21,23 @@ void main() {
setUp(() {
simpleBloc = SimpleBloc();
observer = MockBlocObserver();
- Bloc.observer = observer;
});
test('triggers onCreate on observer when instantiated', () {
- final bloc = SimpleBloc();
- // ignore: invalid_use_of_protected_member
- verify(() => observer.onCreate(bloc)).called(1);
+ BlocOverrides.runZoned(() {
+ final bloc = SimpleBloc();
+ // ignore: invalid_use_of_protected_member
+ verify(() => observer.onCreate(bloc)).called(1);
+ }, blocObserver: observer);
});
test('triggers onClose on observer when closed', () async {
- final bloc = SimpleBloc();
- await bloc.close();
- // ignore: invalid_use_of_protected_member
- verify(() => observer.onClose(bloc)).called(1);
+ await BlocOverrides.runZoned(() async {
+ final bloc = SimpleBloc();
+ await bloc.close();
+ // ignore: invalid_use_of_protected_member
+ verify(() => observer.onClose(bloc)).called(1);
+ }, blocObserver: observer);
});
test('close does not emit new states over the state stream', () async {
@@ -50,71 +53,77 @@ void main() {
});
test('should map single event to correct state', () {
- final expectedStates = ['data', emitsDone];
+ BlocOverrides.runZoned(() {
+ final expectedStates = ['data', emitsDone];
+ final simpleBloc = SimpleBloc();
- expectLater(
- simpleBloc.stream,
- emitsInOrder(expectedStates),
- ).then((dynamic _) {
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onTransition(
- simpleBloc,
- const Transition(
- currentState: '',
- event: 'event',
- nextState: 'data',
+ expectLater(
+ simpleBloc.stream,
+ emitsInOrder(expectedStates),
+ ).then((dynamic _) {
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onTransition(
+ simpleBloc,
+ const Transition(
+ currentState: '',
+ event: 'event',
+ nextState: 'data',
+ ),
),
- ),
- ).called(1);
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onChange(
- simpleBloc,
- const Change(currentState: '', nextState: 'data'),
- ),
- ).called(1);
- expect(simpleBloc.state, 'data');
- });
+ ).called(1);
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onChange(
+ simpleBloc,
+ const Change(currentState: '', nextState: 'data'),
+ ),
+ ).called(1);
+ expect(simpleBloc.state, 'data');
+ });
- simpleBloc
- ..add('event')
- ..close();
+ simpleBloc
+ ..add('event')
+ ..close();
+ }, blocObserver: observer);
});
test('should map multiple events to correct states', () {
- final expectedStates = ['data', emitsDone];
+ BlocOverrides.runZoned(() {
+ final expectedStates = ['data', emitsDone];
+ final simpleBloc = SimpleBloc();
- expectLater(
- simpleBloc.stream,
- emitsInOrder(expectedStates),
- ).then((dynamic _) {
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onTransition(
- simpleBloc,
- const Transition(
- currentState: '',
- event: 'event1',
- nextState: 'data',
+ expectLater(
+ simpleBloc.stream,
+ emitsInOrder(expectedStates),
+ ).then((dynamic _) {
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onTransition(
+ simpleBloc,
+ const Transition(
+ currentState: '',
+ event: 'event1',
+ nextState: 'data',
+ ),
),
- ),
- ).called(1);
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onChange(
- simpleBloc,
- const Change(currentState: '', nextState: 'data'),
- ),
- ).called(1);
- expect(simpleBloc.state, 'data');
- });
+ ).called(1);
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onChange(
+ simpleBloc,
+ const Change(currentState: '', nextState: 'data'),
+ ),
+ ).called(1);
+ expect(simpleBloc.state, 'data');
+ });
- simpleBloc
- ..add('event1')
- ..add('event2')
- ..add('event3')
- ..close();
+ simpleBloc
+ ..add('event1')
+ ..add('event2')
+ ..add('event3')
+ ..close();
+ }, blocObserver: observer);
});
test('is a broadcast stream', () {
@@ -147,7 +156,6 @@ void main() {
setUp(() {
complexBloc = ComplexBloc();
observer = MockBlocObserver();
- Bloc.observer = observer;
});
test('close does not emit new states over the state stream', () async {
@@ -165,37 +173,40 @@ void main() {
});
test('should map single event to correct state', () {
- final expectedStates = [ComplexStateB()];
+ BlocOverrides.runZoned(() {
+ final expectedStates = [ComplexStateB()];
+ final complexBloc = ComplexBloc();
- expectLater(
- complexBloc.stream,
- emitsInOrder(expectedStates),
- ).then((dynamic _) {
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onTransition(
- complexBloc,
- Transition(
- currentState: ComplexStateA(),
- event: ComplexEventB(),
- nextState: ComplexStateB(),
+ expectLater(
+ complexBloc.stream,
+ emitsInOrder(expectedStates),
+ ).then((dynamic _) {
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onTransition(
+ complexBloc,
+ Transition(
+ currentState: ComplexStateA(),
+ event: ComplexEventB(),
+ nextState: ComplexStateB(),
+ ),
),
- ),
- ).called(1);
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onChange(
- complexBloc,
- Change(
- currentState: ComplexStateA(),
- nextState: ComplexStateB(),
+ ).called(1);
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onChange(
+ complexBloc,
+ Change(
+ currentState: ComplexStateA(),
+ nextState: ComplexStateB(),
+ ),
),
- ),
- ).called(1);
- expect(complexBloc.state, ComplexStateB());
- });
+ ).called(1);
+ expect(complexBloc.state, ComplexStateB());
+ });
- complexBloc.add(ComplexEventB());
+ complexBloc.add(ComplexEventB());
+ }, blocObserver: observer);
});
test('should map multiple events to correct states', () async {
@@ -262,7 +273,6 @@ void main() {
},
);
observer = MockBlocObserver();
- Bloc.observer = observer;
});
test('state returns correct value initially', () {
@@ -272,116 +282,132 @@ void main() {
});
test('single Increment event updates state to 1', () {
- final expectedStates = [1, emitsDone];
- final expectedTransitions = [
- '''Transition { currentState: 0, event: CounterEvent.increment, nextState: 1 }'''
- ];
+ BlocOverrides.runZoned(() {
+ final expectedStates = [1, emitsDone];
+ final expectedTransitions = [
+ '''Transition { currentState: 0, event: CounterEvent.increment, nextState: 1 }'''
+ ];
+ final counterBloc = CounterBloc(
+ onEventCallback: events.add,
+ onTransitionCallback: (transition) {
+ transitions.add(transition.toString());
+ },
+ );
- expectLater(
- counterBloc.stream,
- emitsInOrder(expectedStates),
- ).then((dynamic _) {
- expectLater(transitions, expectedTransitions);
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onTransition(
- counterBloc,
- const Transition(
- currentState: 0,
- event: CounterEvent.increment,
- nextState: 1,
+ expectLater(
+ counterBloc.stream,
+ emitsInOrder(expectedStates),
+ ).then((dynamic _) {
+ expectLater(transitions, expectedTransitions);
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onTransition(
+ counterBloc,
+ const Transition(
+ currentState: 0,
+ event: CounterEvent.increment,
+ nextState: 1,
+ ),
),
- ),
- ).called(1);
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onChange(
- counterBloc,
- const Change(currentState: 0, nextState: 1),
- ),
- ).called(1);
- expect(counterBloc.state, 1);
- });
+ ).called(1);
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onChange(
+ counterBloc,
+ const Change(currentState: 0, nextState: 1),
+ ),
+ ).called(1);
+ expect(counterBloc.state, 1);
+ });
- counterBloc
- ..add(CounterEvent.increment)
- ..close();
+ counterBloc
+ ..add(CounterEvent.increment)
+ ..close();
+ }, blocObserver: observer);
});
test('multiple Increment event updates state to 3', () {
- final expectedStates = [1, 2, 3, emitsDone];
- final expectedTransitions = [
- '''Transition { currentState: 0, event: CounterEvent.increment, nextState: 1 }''',
- '''Transition { currentState: 1, event: CounterEvent.increment, nextState: 2 }''',
- '''Transition { currentState: 2, event: CounterEvent.increment, nextState: 3 }''',
- ];
+ BlocOverrides.runZoned(() {
+ final expectedStates = [1, 2, 3, emitsDone];
+ final expectedTransitions = [
+ '''Transition { currentState: 0, event: CounterEvent.increment, nextState: 1 }''',
+ '''Transition { currentState: 1, event: CounterEvent.increment, nextState: 2 }''',
+ '''Transition { currentState: 2, event: CounterEvent.increment, nextState: 3 }''',
+ ];
+ final counterBloc = CounterBloc(
+ onEventCallback: events.add,
+ onTransitionCallback: (transition) {
+ transitions.add(transition.toString());
+ },
+ );
- expectLater(
- counterBloc.stream,
- emitsInOrder(expectedStates),
- ).then((dynamic _) {
- expect(transitions, expectedTransitions);
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onTransition(
- counterBloc,
- const Transition(
- currentState: 0,
- event: CounterEvent.increment,
- nextState: 1,
+ expectLater(
+ counterBloc.stream,
+ emitsInOrder(expectedStates),
+ ).then((dynamic _) {
+ expect(transitions, expectedTransitions);
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onTransition(
+ counterBloc,
+ const Transition(
+ currentState: 0,
+ event: CounterEvent.increment,
+ nextState: 1,
+ ),
),
- ),
- ).called(1);
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onChange(
- counterBloc,
- const Change(currentState: 0, nextState: 1),
- ),
- ).called(1);
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onTransition(
- counterBloc,
- const Transition(
- currentState: 1,
- event: CounterEvent.increment,
- nextState: 2,
+ ).called(1);
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onChange(
+ counterBloc,
+ const Change(currentState: 0, nextState: 1),
),
- ),
- ).called(1);
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onChange(
- counterBloc,
- const Change(currentState: 1, nextState: 2),
- ),
- ).called(1);
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onTransition(
- counterBloc,
- const Transition(
- currentState: 2,
- event: CounterEvent.increment,
- nextState: 3,
+ ).called(1);
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onTransition(
+ counterBloc,
+ const Transition(
+ currentState: 1,
+ event: CounterEvent.increment,
+ nextState: 2,
+ ),
),
- ),
- ).called(1);
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onChange(
- counterBloc,
- const Change(currentState: 2, nextState: 3),
- ),
- ).called(1);
- });
+ ).called(1);
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onChange(
+ counterBloc,
+ const Change(currentState: 1, nextState: 2),
+ ),
+ ).called(1);
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onTransition(
+ counterBloc,
+ const Transition(
+ currentState: 2,
+ event: CounterEvent.increment,
+ nextState: 3,
+ ),
+ ),
+ ).called(1);
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onChange(
+ counterBloc,
+ const Change(currentState: 2, nextState: 3),
+ ),
+ ).called(1);
+ });
- counterBloc
- ..add(CounterEvent.increment)
- ..add(CounterEvent.increment)
- ..add(CounterEvent.increment)
- ..close();
+ counterBloc
+ ..add(CounterEvent.increment)
+ ..add(CounterEvent.increment)
+ ..add(CounterEvent.increment)
+ ..close();
+ }, blocObserver: observer);
});
test('is a broadcast stream', () {
@@ -465,14 +491,13 @@ void main() {
late MockBlocObserver observer;
setUpAll(() {
- registerFallbackValue>(FakeBlocBase());
- registerFallbackValue(StackTrace.empty);
+ registerFallbackValue(FakeBlocBase());
+ registerFallbackValue(StackTrace.empty);
});
setUp(() {
asyncBloc = AsyncBloc();
observer = MockBlocObserver();
- Bloc.observer = observer;
});
test('close does not emit new states over the state stream', () async {
@@ -486,21 +511,22 @@ void main() {
test(
'close while events are pending finishes processing pending events '
'and does not trigger onError', () async {
- final expectedStates = [
- AsyncState.initial().copyWith(isLoading: true),
- AsyncState.initial().copyWith(isSuccess: true),
- ];
- final states = [];
-
- asyncBloc
- ..stream.listen(states.add)
- ..add(AsyncEvent());
+ await BlocOverrides.runZoned(() async {
+ final expectedStates = [
+ AsyncState.initial().copyWith(isLoading: true),
+ AsyncState.initial().copyWith(isSuccess: true),
+ ];
+ final states = [];
+ final asyncBloc = AsyncBloc()
+ ..stream.listen(states.add)
+ ..add(AsyncEvent());
- await asyncBloc.close();
+ await asyncBloc.close();
- expect(states, expectedStates);
- // ignore: invalid_use_of_protected_member
- verifyNever(() => observer.onError(any(), any(), any()));
+ expect(states, expectedStates);
+ // ignore: invalid_use_of_protected_member
+ verifyNever(() => observer.onError(any(), any(), any()));
+ }, blocObserver: observer);
});
test('state returns correct value initially', () {
@@ -508,206 +534,212 @@ void main() {
});
test('should map single event to correct state', () {
- final expectedStates = [
- AsyncState(isLoading: true, hasError: false, isSuccess: false),
- AsyncState(isLoading: false, hasError: false, isSuccess: true),
- emitsDone,
- ];
+ BlocOverrides.runZoned(() {
+ final expectedStates = [
+ AsyncState(isLoading: true, hasError: false, isSuccess: false),
+ AsyncState(isLoading: false, hasError: false, isSuccess: true),
+ emitsDone,
+ ];
+ final asyncBloc = AsyncBloc();
- expectLater(
- asyncBloc.stream,
- emitsInOrder(expectedStates),
- ).then((dynamic _) {
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onTransition(
- asyncBloc,
- Transition(
- currentState: AsyncState(
- isLoading: false,
- hasError: false,
- isSuccess: false,
- ),
- event: AsyncEvent(),
- nextState: AsyncState(
- isLoading: true,
- hasError: false,
- isSuccess: false,
+ expectLater(
+ asyncBloc.stream,
+ emitsInOrder(expectedStates),
+ ).then((dynamic _) {
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onTransition(
+ asyncBloc,
+ Transition(
+ currentState: AsyncState(
+ isLoading: false,
+ hasError: false,
+ isSuccess: false,
+ ),
+ event: AsyncEvent(),
+ nextState: AsyncState(
+ isLoading: true,
+ hasError: false,
+ isSuccess: false,
+ ),
),
),
- ),
- ).called(1);
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onChange(
- asyncBloc,
- Change(
- currentState: AsyncState(
- isLoading: false,
- hasError: false,
- isSuccess: false,
- ),
- nextState: AsyncState(
- isLoading: true,
- hasError: false,
- isSuccess: false,
+ ).called(1);
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onChange(
+ asyncBloc,
+ Change(
+ currentState: AsyncState(
+ isLoading: false,
+ hasError: false,
+ isSuccess: false,
+ ),
+ nextState: AsyncState(
+ isLoading: true,
+ hasError: false,
+ isSuccess: false,
+ ),
),
),
- ),
- ).called(1);
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onTransition(
- asyncBloc,
- Transition(
- currentState: AsyncState(
- isLoading: true,
- hasError: false,
- isSuccess: false,
- ),
- event: AsyncEvent(),
- nextState: AsyncState(
- isLoading: false,
- hasError: false,
- isSuccess: true,
+ ).called(1);
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onTransition(
+ asyncBloc,
+ Transition(
+ currentState: AsyncState(
+ isLoading: true,
+ hasError: false,
+ isSuccess: false,
+ ),
+ event: AsyncEvent(),
+ nextState: AsyncState(
+ isLoading: false,
+ hasError: false,
+ isSuccess: true,
+ ),
),
),
- ),
- ).called(1);
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onChange(
- asyncBloc,
- Change(
- currentState: AsyncState(
- isLoading: true,
- hasError: false,
- isSuccess: false,
- ),
- nextState: AsyncState(
- isLoading: false,
- hasError: false,
- isSuccess: true,
+ ).called(1);
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onChange(
+ asyncBloc,
+ Change(
+ currentState: AsyncState(
+ isLoading: true,
+ hasError: false,
+ isSuccess: false,
+ ),
+ nextState: AsyncState(
+ isLoading: false,
+ hasError: false,
+ isSuccess: true,
+ ),
),
),
- ),
- ).called(1);
- expect(
- asyncBloc.state,
- AsyncState(
- isLoading: false,
- hasError: false,
- isSuccess: true,
- ),
- );
- });
+ ).called(1);
+ expect(
+ asyncBloc.state,
+ AsyncState(
+ isLoading: false,
+ hasError: false,
+ isSuccess: true,
+ ),
+ );
+ });
- asyncBloc
- ..add(AsyncEvent())
- ..close();
+ asyncBloc
+ ..add(AsyncEvent())
+ ..close();
+ }, blocObserver: observer);
});
test('should map multiple events to correct states', () {
- final expectedStates = [
- AsyncState(isLoading: true, hasError: false, isSuccess: false),
- AsyncState(isLoading: false, hasError: false, isSuccess: true),
- AsyncState(isLoading: true, hasError: false, isSuccess: false),
- AsyncState(isLoading: false, hasError: false, isSuccess: true),
- emitsDone,
- ];
+ BlocOverrides.runZoned(() {
+ final expectedStates = [
+ AsyncState(isLoading: true, hasError: false, isSuccess: false),
+ AsyncState(isLoading: false, hasError: false, isSuccess: true),
+ AsyncState(isLoading: true, hasError: false, isSuccess: false),
+ AsyncState(isLoading: false, hasError: false, isSuccess: true),
+ emitsDone,
+ ];
+ final asyncBloc = AsyncBloc();
- expectLater(
- asyncBloc.stream,
- emitsInOrder(expectedStates),
- ).then((dynamic _) {
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onTransition(
- asyncBloc,
- Transition(
- currentState: AsyncState(
- isLoading: false,
- hasError: false,
- isSuccess: false,
- ),
- event: AsyncEvent(),
- nextState: AsyncState(
- isLoading: true,
- hasError: false,
- isSuccess: false,
+ expectLater(
+ asyncBloc.stream,
+ emitsInOrder(expectedStates),
+ ).then((dynamic _) {
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onTransition(
+ asyncBloc,
+ Transition(
+ currentState: AsyncState(
+ isLoading: false,
+ hasError: false,
+ isSuccess: false,
+ ),
+ event: AsyncEvent(),
+ nextState: AsyncState(
+ isLoading: true,
+ hasError: false,
+ isSuccess: false,
+ ),
),
),
- ),
- ).called(1);
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onChange(
- asyncBloc,
- Change(
- currentState: AsyncState(
- isLoading: false,
- hasError: false,
- isSuccess: false,
- ),
- nextState: AsyncState(
- isLoading: true,
- hasError: false,
- isSuccess: false,
+ ).called(1);
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onChange(
+ asyncBloc,
+ Change(
+ currentState: AsyncState(
+ isLoading: false,
+ hasError: false,
+ isSuccess: false,
+ ),
+ nextState: AsyncState(
+ isLoading: true,
+ hasError: false,
+ isSuccess: false,
+ ),
),
),
- ),
- ).called(1);
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onTransition(
- asyncBloc,
- Transition(
- currentState: AsyncState(
- isLoading: true,
- hasError: false,
- isSuccess: false,
- ),
- event: AsyncEvent(),
- nextState: AsyncState(
- isLoading: false,
- hasError: false,
- isSuccess: true,
+ ).called(1);
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onTransition(
+ asyncBloc,
+ Transition(
+ currentState: AsyncState(
+ isLoading: true,
+ hasError: false,
+ isSuccess: false,
+ ),
+ event: AsyncEvent(),
+ nextState: AsyncState(
+ isLoading: false,
+ hasError: false,
+ isSuccess: true,
+ ),
),
),
- ),
- ).called(2);
- verify(
- // ignore: invalid_use_of_protected_member
- () => observer.onChange(
- asyncBloc,
- Change(
- currentState: AsyncState(
- isLoading: true,
- hasError: false,
- isSuccess: false,
- ),
- nextState: AsyncState(
- isLoading: false,
- hasError: false,
- isSuccess: true,
+ ).called(2);
+ verify(
+ // ignore: invalid_use_of_protected_member
+ () => observer.onChange(
+ asyncBloc,
+ Change(
+ currentState: AsyncState(
+ isLoading: true,
+ hasError: false,
+ isSuccess: false,
+ ),
+ nextState: AsyncState(
+ isLoading: false,
+ hasError: false,
+ isSuccess: true,
+ ),
),
),
- ),
- ).called(2);
- expect(
- asyncBloc.state,
- AsyncState(
- isLoading: false,
- hasError: false,
- isSuccess: true,
- ),
- );
- });
+ ).called(2);
+ expect(
+ asyncBloc.state,
+ AsyncState(
+ isLoading: false,
+ hasError: false,
+ isSuccess: true,
+ ),
+ );
+ });
- asyncBloc
- ..add(AsyncEvent())
- ..add(AsyncEvent())
- ..close();
+ asyncBloc
+ ..add(AsyncEvent())
+ ..add(AsyncEvent())
+ ..close();
+ }, blocObserver: observer);
});
test('is a broadcast stream', () {
@@ -1058,11 +1090,7 @@ void main() {
await bloc.close();
}, (error, stackTrace) => uncaughtError = error);
expect(states, equals(expectedStates));
- expect(
- uncaughtError,
- isA()
- .having((e) => e.error, 'error', error),
- );
+ expect(uncaughtError, equals(error));
});
test('unawaited onEach throws AssertionError', () async {
@@ -1269,11 +1297,7 @@ void main() {
}, (error, stack) => uncaughtError = error);
expect(states, equals(expectedStates));
- expect(
- uncaughtError,
- isA()
- .having((e) => e.error, 'error', error),
- );
+ expect(uncaughtError, equals(error));
});
});
@@ -1321,13 +1345,7 @@ void main() {
..add(CounterEvent.decrement)
..close();
}, (Object error, StackTrace stackTrace) {
- expect(
- (error as BlocUnhandledErrorException).toString(),
- contains(
- 'Unhandled error Exception: fatal exception occurred '
- 'in Instance of \'CounterExceptionBloc\'.',
- ),
- );
+ expect(error.toString(), equals('Exception: fatal exception'));
expect(stackTrace, isNotNull);
expect(stackTrace, isNot(StackTrace.empty));
});
@@ -1342,21 +1360,15 @@ void main() {
onErrorCallback: (Object _, StackTrace __) {},
)..addError(expectedError, StackTrace.current);
}, (Object error, StackTrace stackTrace) {
- expect(
- (error as BlocUnhandledErrorException).toString(),
- contains(
- 'Unhandled error Exception: fatal exception occurred '
- 'in Instance of \'OnExceptionBloc\'.',
- ),
- );
+ expect(error, equals(expectedError));
expect(stackTrace, isNotNull);
expect(stackTrace, isNot(StackTrace.empty));
});
});
test('triggers onError from on', () {
+ final exception = Exception('fatal exception');
runZonedGuarded(() {
- final exception = Exception('fatal exception');
Object? expectedError;
StackTrace? expectedStacktrace;
@@ -1381,42 +1393,32 @@ void main() {
..add(CounterEvent.increment)
..close();
}, (Object error, StackTrace stackTrace) {
- expect(
- (error as BlocUnhandledErrorException).toString(),
- contains(
- 'Unhandled error Exception: fatal exception occurred '
- 'in Instance of \'OnExceptionBloc\'.',
- ),
- );
+ expect(error, equals(exception));
expect(stackTrace, isNotNull);
expect(stackTrace, isNot(StackTrace.empty));
});
});
test('triggers onError from onEvent', () {
+ final exception = Exception('fatal exception');
runZonedGuarded(() {
- final exception = Exception('fatal exception');
-
OnEventErrorBloc(exception: exception)
..add(CounterEvent.increment)
..close();
}, (Object error, StackTrace stackTrace) {
- expect(
- (error as BlocUnhandledErrorException).toString(),
- contains(
- 'Unhandled error Exception: fatal exception occurred '
- 'in Instance of \'OnEventErrorBloc\'.',
- ),
- );
+ expect(error, equals(exception));
expect(stackTrace, isNotNull);
expect(stackTrace, isNot(StackTrace.empty));
});
});
- test('does not trigger onError from add', () {
+ test(
+ 'add throws StateError and triggers onError '
+ 'when bloc is closed', () {
+ Object? capturedError;
+ StackTrace? capturedStacktrace;
+ var didThrow = false;
runZonedGuarded(() {
- Object? capturedError;
- StackTrace? capturedStacktrace;
final counterBloc = CounterBloc(
onErrorCallback: (error, stackTrace) {
capturedError = error;
@@ -1427,17 +1429,27 @@ void main() {
expectLater(
counterBloc.stream,
emitsInOrder([emitsDone]),
- ).then((dynamic _) {
- expect(capturedError, isNull);
- expect(capturedStacktrace, isNull);
- });
+ );
counterBloc
..close()
..add(CounterEvent.increment);
- }, (Object _, StackTrace __) {
- fail('should not throw when add is called after bloc is closed');
+ }, (Object error, StackTrace stackTrace) {
+ didThrow = true;
+ expect(error, equals(capturedError));
+ expect(stackTrace, equals(capturedStacktrace));
});
+
+ expect(didThrow, isTrue);
+ expect(
+ capturedError,
+ isA().having(
+ (e) => e.message,
+ 'message',
+ 'Cannot add new events after calling close',
+ ),
+ );
+ expect(capturedStacktrace, isNotNull);
});
});
@@ -1533,6 +1545,38 @@ void main() {
expect(counterBloc.state, 42);
await counterBloc.close();
});
+
+ test(
+ 'throws StateError and triggers onError '
+ 'when bloc is closed', () async {
+ Object? capturedError;
+ StackTrace? capturedStacktrace;
+
+ final states = [];
+ final expectedStateError = isA().having(
+ (e) => e.message,
+ 'message',
+ 'Cannot emit new states after calling close',
+ );
+
+ final counterBloc = CounterBloc(
+ onErrorCallback: (error, stackTrace) {
+ capturedError = error;
+ capturedStacktrace = stackTrace;
+ },
+ )..stream.listen(states.add);
+
+ await counterBloc.close();
+
+ expect(counterBloc.isClosed, isTrue);
+ expect(counterBloc.state, equals(0));
+ expect(states, isEmpty);
+ expect(() => counterBloc.emit(1), throwsA(expectedStateError));
+ expect(counterBloc.state, equals(0));
+ expect(states, isEmpty);
+ expect(capturedError, expectedStateError);
+ expect(capturedStacktrace, isNotNull);
+ });
});
group('close', () {
diff --git a/packages/bloc/test/cubit_test.dart b/packages/bloc/test/cubit_test.dart
index 3f657205ac7..2a5a21fdc07 100644
--- a/packages/bloc/test/cubit_test.dart
+++ b/packages/bloc/test/cubit_test.dart
@@ -19,13 +19,14 @@ void main() {
setUp(() {
observer = MockBlocObserver();
- Bloc.observer = observer;
});
test('triggers onCreate on observer', () {
- final cubit = CounterCubit();
- // ignore: invalid_use_of_protected_member
- verify(() => observer.onCreate(cubit)).called(1);
+ BlocOverrides.runZoned(() {
+ final cubit = CounterCubit();
+ // ignore: invalid_use_of_protected_member
+ verify(() => observer.onCreate(cubit)).called(1);
+ }, blocObserver: observer);
});
});
@@ -36,29 +37,35 @@ void main() {
});
group('addError', () {
- BlocObserver observer;
+ late BlocObserver observer;
setUp(() {
observer = MockBlocObserver();
- Bloc.observer = observer;
});
test('triggers onError', () async {
- final expectedError = Exception('fatal exception');
-
- runZonedGuarded(() {
- CounterCubit().addError(expectedError, StackTrace.current);
- }, (Object error, StackTrace stackTrace) {
- expect(
- (error as BlocUnhandledErrorException).toString(),
- contains(
- 'Unhandled error Exception: fatal exception occurred '
- 'in Instance of \'CounterCubit\'.',
- ),
- );
- expect(stackTrace, isNotNull);
- expect(stackTrace, isNot(StackTrace.empty));
- });
+ BlocOverrides.runZoned(() {
+ final expectedError = Exception('fatal exception');
+ final expectedStackTrace = StackTrace.current;
+ final errors =