Repository: felangel/bloc (dart)

Issue:
refactor: bloc_test to depend on core interfaces rather than BlocBase realization
**Description**

Hey! Thanks for a great package, I have a sort of feature request for refactoring :D

At this moment, `blocTest` from `bloc_test` package is [dependent on BlocBase](https://github.com/felangel/bloc/blob/master/packages/bloc_test/lib/src/bloc_test.dart#L139) realization, but since #3012 we have interfaces I believe it should be dependent on, those interfaces, following the SOLID, Dependency Inversion Principle (DIP). For example, it will bring an ability to use the `bloc_test` package for testing custom blocs that are implementing those interfaces, not the `BlocBase`. Of course, it's upon you. Thanks again!

Pull request:
<!--
  Thanks for contributing!

  Provide a description of your changes below and a general summary in the title

  Please look at the following checklist to ensure that your PR can be accepted quickly:
-->

## Status

**READY**

## Breaking Changes

YES

## Description

<!--- Describe your changes in detail -->
- refactor(bloc)!: introduce `EmittableStateStreamableSource`
  - closes https://github.com/felangel/bloc/issues/3377
  - bumps `bloc_test` minimum Dart SDK to 2.14

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ] ✨ New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change)
- [X] 🧹 Code refactor
- [ ] ✅ Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore

Patch:
diff --git a/.github/actions/dart_package/action.yaml b/.github/actions/dart_package/action.yaml
index e1210848fe8..416db1401d7 100644
--- a/.github/actions/dart_package/action.yaml
+++ b/.github/actions/dart_package/action.yaml
@@ -5,6 +5,14 @@ inputs:
   codecov_token:
     required: true
     description: The Codecov token used to upload coverage
+  collect_coverage:
+    required: false
+    default: "true"
+    description: Whether to collect code coverage
+  collect_score:
+    required: false
+    default: "true"
+    description: Whether to collect the pana score
   concurrency:
     required: false
     default: "4"
@@ -69,6 +77,7 @@ runs:
         dart test -j ${{inputs.concurrency}} --coverage=coverage && dart pub global run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info --packages=.dart_tool/package_config.json --report-on=${{inputs.report_on}} --check-ignore
 
     - name: 📦 Detect Package Name
+      if: inputs.collect_coverage == 'true'
       env:
         PACKAGE_PATH: ${{ inputs.working_directory}}
       id: package
@@ -76,6 +85,7 @@ runs:
       run: echo "name=${PACKAGE_PATH##*/}" >> $GITHUB_OUTPUT
 
     - name: ⬆️ Upload Coverage
+      if: inputs.collect_coverage == 'true'
       uses: codecov/codecov-action@v4
       env:
         PACKAGE_PATH: ${{ inputs.working_directory}}
@@ -84,6 +94,7 @@ runs:
         token: ${{ inputs.codecov_token }}
 
     - name: 📊 Verify Coverage
+      if: inputs.collect_coverage == 'true'
       uses: VeryGoodOpenSource/very_good_coverage@v3
       with:
         path: ${{inputs.working_directory}}/coverage/lcov.info
@@ -91,6 +102,7 @@ runs:
         min_coverage: ${{inputs.min_coverage}}
 
     - name: 💯 Verify Pub Score
+      if: inputs.collect_score == 'true'
       working-directory: ${{ inputs.working_directory }}
       shell: ${{ inputs.shell }}
       run: |
diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index 9482171c5de..916c0d35234 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -214,6 +214,9 @@ jobs:
           codecov_token: ${{ secrets.CODECOV_TOKEN }}
           working_directory: packages/${{ matrix.package }}
           min_coverage: 100
+          # TODO: remove once bloc v9 is published
+          # https://github.com/dart-lang/pana/issues/1254
+          collect_score: false
 
   flutter_package_checks:
     needs: changes
@@ -238,6 +241,9 @@ jobs:
           codecov_token: ${{ secrets.CODECOV_TOKEN }}
           working_directory: packages/${{ matrix.package }}
           min_coverage: 100
+          # TODO: remove once bloc v9 is published
+          # https://github.com/dart-lang/pana/issues/1254
+          collect_score: false
 
   angular_dart_example_checks:
     needs: changes
diff --git a/packages/bloc/lib/src/bloc_base.dart b/packages/bloc/lib/src/bloc_base.dart
index 5b9d87a3574..51e374ce500 100644
--- a/packages/bloc/lib/src/bloc_base.dart
+++ b/packages/bloc/lib/src/bloc_base.dart
@@ -45,12 +45,16 @@ abstract class ErrorSink implements Closable {
   void addError(Object error, [StackTrace? stackTrace]);
 }
 
+/// A [StateStreamableSource] that can emit new states.
+abstract class EmittableStateStreamableSource<State>
+    implements StateStreamableSource<State>, Emittable<State> {}
+
 /// {@template bloc_base}
 /// An interface for the core functionality implemented by
 /// both [Bloc] and [Cubit].
 /// {@endtemplate}
 abstract class BlocBase<State>
-    implements StateStreamableSource<State>, Emittable<State>, ErrorSink {
+    implements EmittableStateStreamableSource<State>, ErrorSink {
   /// {@macro bloc_base}
   BlocBase(this._state) {
     // ignore: invalid_use_of_protected_member


Test patch:
diff --git a/packages/bloc_test/lib/src/bloc_test.dart b/packages/bloc_test/lib/src/bloc_test.dart
index ca3275f9944..9c7c5481741 100644
--- a/packages/bloc_test/lib/src/bloc_test.dart
+++ b/packages/bloc_test/lib/src/bloc_test.dart
@@ -136,7 +136,7 @@ import 'package:test/test.dart' as test;
 ///
 /// [configuring tags]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#configuring-tags
 @isTest
-void blocTest<B extends BlocBase<State>, State>(
+void blocTest<B extends EmittableStateStreamableSource<State>, State>(
   String description, {
   required B Function() build,
   FutureOr<void> Function()? setUp,
@@ -173,7 +173,7 @@ void blocTest<B extends BlocBase<State>, State>(
 /// Internal [blocTest] runner which is only visible for testing.
 /// This should never be used directly -- please use [blocTest] instead.
 @visibleForTesting
-Future<void> testBloc<B extends BlocBase<State>, State>({
+Future<void> testBloc<B extends EmittableStateStreamableSource<State>, State>({
   required B Function() build,
   FutureOr<void> Function()? setUp,
   State Function()? seed,
diff --git a/packages/bloc_test/pubspec.yaml b/packages/bloc_test/pubspec.yaml
index 33117ab496f..bab8d7f0ff0 100644
--- a/packages/bloc_test/pubspec.yaml
+++ b/packages/bloc_test/pubspec.yaml
@@ -9,17 +9,17 @@ topics: [bloc, state-management, test]
 funding: [https://github.com/sponsors/felangel]
 
 environment:
-  sdk: ">=2.12.0 <4.0.0"
+  sdk: ">=2.14.0 <4.0.0"
 
 dependencies:
   bloc: ^8.1.1
   diff_match_patch: ^0.4.1
   meta: ^1.3.0
-  mocktail: ">=0.2.0 <2.0.0"
+  mocktail: ^1.0.0
   test: ^1.16.0
 
 dev_dependencies:
-  rxdart: ^0.27.2
+  rxdart: ^0.28.0
 
 screenshots:
   - description: The bloc test package logo.