From 526104bf332b6fba6de213d2f6df3a7446d36d82 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 16 Sep 2021 21:57:26 +0200 Subject: [PATCH 1/7] Migrate to null-safety Also fixes some issues with upgrading Flutter and audioplayers versions. We also use the stable flutter channel, as we don't need any bleeding edge features or bug fixes not in the current stable. Signed-off-by: Leandro Lucarella --- .github/workflows/ci.yml | 5 +- .gitignore | 4 + README.md | 6 + example/lib/main.dart | 2 +- example/pubspec.yaml | 14 +- example/test_integration/main_test.dart | 4 +- lib/src/action_player.dart | 5 +- lib/src/bundle_player.dart | 9 +- lib/src/button_player.dart | 8 +- .../button_player/image_button_player.dart | 8 +- .../button_player/styled_button_player.dart | 12 +- lib/src/dynamic_dispatch_registry.dart | 6 +- lib/src/media_player/controller_registry.dart | 8 +- lib/src/media_player/media_player_error.dart | 6 +- .../media_progress_indicator.dart | 5 +- lib/src/media_player/multi_medium_player.dart | 11 +- lib/src/media_player/multi_medium_state.dart | 9 +- .../multi_medium_track_state.dart | 35 +- .../multi_medium_track_widget.dart | 2 +- lib/src/media_player/multi_medium_widget.dart | 2 +- lib/src/media_player/playable_state.dart | 6 +- lib/src/media_player/playlist_player.dart | 11 +- lib/src/media_player/playlist_state.dart | 18 +- lib/src/media_player/playlist_widget.dart | 2 +- .../single_medium_controller.dart | 136 ++++--- .../media_player/single_medium_player.dart | 17 +- lib/src/media_player/single_medium_state.dart | 39 +- .../media_player/single_medium_widget.dart | 11 +- lib/src/menu_player.dart | 2 +- lib/src/menu_player/grid_menu_player.dart | 22 +- lib/src/platform_services.dart | 12 +- lib/src/playable_player.dart | 16 +- pubspec.lock | 246 ++++++++---- pubspec.yaml | 24 +- test/unit/action_player_test.dart | 43 +- test/unit/bundle_player_test.dart | 16 +- .../image_button_player_test.dart | 18 +- .../styled_button_player_test.dart | 27 +- test/unit/button_player_test.dart | 21 +- test/unit/dynamic_dispatch_registry_test.dart | 4 +- .../controller_registry_test.dart | 33 +- .../media_progress_indicator_test.dart | 8 +- test/unit/media_player/mocks.dart | 26 ++ .../multi_medium_player_test.dart | 377 +++++++++--------- .../media_player/multi_medium_state_test.dart | 190 +++++---- .../multi_medium_track_state_test.dart | 184 ++++----- .../multi_medium_track_widget_test.dart | 6 +- .../multi_medium_widget_test.dart | 12 +- .../media_player/playlist_player_test.dart | 27 +- .../media_player/playlist_state_test.dart | 135 ++++--- .../media_player/playlist_widget_test.dart | 51 +-- .../audio_video_player_controller_test.dart | 105 +++-- .../image_player_controller_test.dart | 23 +- .../single_medium_controller_common.dart | 20 +- .../single_medium_player_test.dart | 140 +++---- .../single_medium_state_test.dart | 59 ++- .../single_medium_widget_test.dart | 31 +- .../menu_player/grid_menu_player_test.dart | 40 +- test/unit/menu_player_test.dart | 10 +- test/unit/playable_player_test.dart | 40 +- test/util/finders.dart | 6 +- test/util/test_asset_bundle.dart | 2 +- tool/ci | 4 + 63 files changed, 1223 insertions(+), 1158 deletions(-) create mode 100644 test/unit/media_player/mocks.dart diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d2b700..cc3f55a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: branches: [ main, 'v[0-9]+.[0-9x]+.[0-9x]+*' ] env: - FLUTTER_CHANNEL: beta + FLUTTER_CHANNEL: stable JAVA_VERSION: 12.x jobs: @@ -35,6 +35,9 @@ jobs: - name: Install dependencies run: flutter pub get + - name: Generate unit tests mocks + run: tool/ci mocks-gen + # Analyze step needs different config for pull_request and push, so it is # duplicated with if conditions to use the correct configuration for each - name: Analyze (push) diff --git a/.gitignore b/.gitignore index f2d7395..c0971b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# Generated Mocks + +test/**/*.mocks.dart + # Miscellaneous *.class *.log diff --git a/README.md b/README.md index 4e09ca0..ea120ee 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,12 @@ A Flutter widget to play content bundles for Lunofono. This project is written in [Flutter](https://flutter.dev/). Once you have a working Flutter SDK installed, you can test it using `flutter test`. +To run the tests you need to first generate the mocks: + +```sh +flutter pub run build_runner build +``` + ### Git Hooks This repository provides some useful Git hooks to make sure new commits have diff --git a/example/lib/main.dart b/example/lib/main.dart index 9656016..f6fda43 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -155,7 +155,7 @@ final bundle = Bundle( ); class MyHomePage extends StatefulWidget { - MyHomePage({Key key, this.title}) : super(key: key); + MyHomePage({required this.title, Key? key}) : super(key: key); // This widget is the home page of your application. It is stateful, meaning // that it has a State object (defined below) that contains fields that affect diff --git a/example/pubspec.yaml b/example/pubspec.yaml index eb8e531..e4d2189 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -2,7 +2,7 @@ name: lunofono_player_example description: Demonstrate how to use the lunofono_player package environment: - sdk: ">=2.7.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' flutter: ">=1.17.0 <2.0.0" dependencies: @@ -15,13 +15,19 @@ dependencies: lunofono_bundle: git: url: https://github.com/lunofono/lunofono_bundle.git - ref: v0.3.0 + ref: v0.4.1 dev_dependencies: + flutter_driver: + sdk: flutter + flutter_test: sdk: flutter - integration_test: ^0.9.2+2 - pedantic: ^1.9.0 + + integration_test: + sdk: flutter + + pedantic: ^1.11.1 flutter: uses-material-design: true diff --git a/example/test_integration/main_test.dart b/example/test_integration/main_test.dart index cf68661..e884b36 100644 --- a/example/test_integration/main_test.dart +++ b/example/test_integration/main_test.dart @@ -39,9 +39,9 @@ Finder findSubString(String substring) { return find.byWidgetPredicate((widget) { if (widget is Text) { if (widget.data != null) { - return widget.data.contains(substring); + return widget.data!.contains(substring); } - return widget.textSpan.toPlainText().contains(substring); + return widget.textSpan!.toPlainText().contains(substring); } return false; }); diff --git a/lib/src/action_player.dart b/lib/src/action_player.dart index 6207f49..fadaeb6 100644 --- a/lib/src/action_player.dart +++ b/lib/src/action_player.dart @@ -34,7 +34,7 @@ abstract class ActionPlayer { final wrap = registry.getFunction(action); assert( wrap != null, 'Unimplemented ActionPlayer for ${action.runtimeType}'); - return wrap(action); + return wrap!(action); } /// Constructs an [ActionPlayer]. @@ -59,8 +59,7 @@ class PlayContentActionPlayer extends ActionPlayer { /// Constructs a [PlayContentActionPlayer] using [action] as the underlaying /// [Action]. PlayContentActionPlayer(this.action) - : assert(action != null), - content = PlayablePlayer.wrap(action.content); + : content = PlayablePlayer.wrap(action.content); /// Plays the [content]. @override diff --git a/lib/src/bundle_player.dart b/lib/src/bundle_player.dart index 2d44130..73cbe55 100644 --- a/lib/src/bundle_player.dart +++ b/lib/src/bundle_player.dart @@ -25,10 +25,9 @@ class BundlePlayer extends StatefulWidget { /// [bundle] cannot be null. const BundlePlayer( this.bundle, { - PlatformServices platformServices, - Key key, - }) : assert(bundle != null), - platformServices = platformServices ?? const PlatformServices(), + PlatformServices? platformServices, + Key? key, + }) : platformServices = platformServices ?? const PlatformServices(), super(key: key); @override @@ -38,7 +37,7 @@ class BundlePlayer extends StatefulWidget { /// A state for a [BundlePlayer]. class _BundlePlayerState extends State { /// The [MenuPlayer] used to play this [widget.menu]. - MenuPlayer rootMenu; + late MenuPlayer rootMenu; /// Initialized this [_BundlePlayerState]. /// diff --git a/lib/src/button_player.dart b/lib/src/button_player.dart index 42a9070..beff15e 100644 --- a/lib/src/button_player.dart +++ b/lib/src/button_player.dart @@ -38,13 +38,11 @@ abstract class ButtonPlayer { final wrap = registry.getFunction(button); assert( wrap != null, 'Unimplemented ButtonPlayer for ${button.runtimeType}'); - return wrap(button); + return wrap!(button); } /// Constructs a [ButtonPlayer]. - ButtonPlayer(Button button) - : assert(button != null), - action = ActionPlayer.wrap(button.action); + ButtonPlayer(Button button) : action = ActionPlayer.wrap(button.action); /// The [ActionPlayer] wrapping the [Action] for this [button]. final ActionPlayer action; @@ -56,7 +54,7 @@ abstract class ButtonPlayer { /// /// Returns null by default, as not all [Button] types have a background /// color. - Color get backgroundColor => null; + Color? get backgroundColor => null; /// Creates a [GridButtonItem] from the underlaying [button]. /// diff --git a/lib/src/button_player/image_button_player.dart b/lib/src/button_player/image_button_player.dart index 3cc46ba..e68e7aa 100644 --- a/lib/src/button_player/image_button_player.dart +++ b/lib/src/button_player/image_button_player.dart @@ -12,9 +12,7 @@ class ImageButtonPlayer extends ButtonPlayer { final ImageButton button; /// Constructs a [ButtonPlayer] using [button] as the underlaying [Button]. - ImageButtonPlayer(this.button) - : assert(button != null), - super(button); + ImageButtonPlayer(this.button) : super(button); /// The background [Color] of the underlaying [button]. /// @@ -37,9 +35,7 @@ class ImageButtonWidget extends StatelessWidget { final ImageButtonPlayer button; /// Creates a new [ImageButtonWidget] to display [button]. - const ImageButtonWidget({@required this.button, Key key}) - : assert(button != null), - super(key: key); + const ImageButtonWidget({required this.button, Key? key}) : super(key: key); @override Widget build(BuildContext context) => GestureDetector( diff --git a/lib/src/button_player/styled_button_player.dart b/lib/src/button_player/styled_button_player.dart index a63c082..34374e8 100644 --- a/lib/src/button_player/styled_button_player.dart +++ b/lib/src/button_player/styled_button_player.dart @@ -12,16 +12,14 @@ class StyledButtonPlayer extends ButtonPlayer { final StyledButton button; /// Constructs a [ButtonPlayer] using [button] as the underlaying [Button]. - StyledButtonPlayer(this.button) - : assert(button != null), - super(button); + StyledButtonPlayer(this.button) : super(button); /// The background [Color] of the underlaying [button]. @override - Color get backgroundColor => button.backgroundColor; + Color? get backgroundColor => button.backgroundColor; /// The foreground image [Uri] of the underlaying [button]. - Uri get foregroundImage => button.foregroundImage; + Uri? get foregroundImage => button.foregroundImage; @override Widget build(BuildContext context) => @@ -34,9 +32,7 @@ class StyledButtonWidget extends StatelessWidget { final StyledButtonPlayer button; /// Creates a new [StyledButtonWidget] to display [button]. - const StyledButtonWidget({@required this.button, Key key}) - : assert(button != null), - super(key: key); + const StyledButtonWidget({required this.button, Key? key}) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/src/dynamic_dispatch_registry.dart b/lib/src/dynamic_dispatch_registry.dart index 8359b3e..61d064a 100644 --- a/lib/src/dynamic_dispatch_registry.dart +++ b/lib/src/dynamic_dispatch_registry.dart @@ -18,7 +18,7 @@ class DynamicDispatchRegistry { /// /// If [type] was already registered, it is replaced and the old registered /// [fucntion] is returned. Otherwise it returns null. - T register(Type type, T function) { + T? register(Type type, T function) { final old = _registry[type]; _registry[type] = function; return old; @@ -28,14 +28,14 @@ class DynamicDispatchRegistry { /// /// Returns the registered [T] function for [type] or null if there was no /// function registered. - T unregister(Type type) { + T? unregister(Type type) { return _registry.remove(type); } /// Gets the registered function [T] for [instance.runtimeType]. /// /// If there is no registered function, then null is returned. - T getFunction(B instance) => _registry[instance.runtimeType]; + T? getFunction(B instance) => _registry[instance.runtimeType]; @override String toString() { diff --git a/lib/src/media_player/controller_registry.dart b/lib/src/media_player/controller_registry.dart index fa6839e..c99747a 100644 --- a/lib/src/media_player/controller_registry.dart +++ b/lib/src/media_player/controller_registry.dart @@ -15,7 +15,7 @@ export 'single_medium_controller.dart' show SingleMediumController; /// This callback should never return null. typedef ControllerCreateFunction = SingleMediumController Function( SingleMedium medium, { - void Function(BuildContext) onMediumFinished, + void Function(BuildContext)? onMediumFinished, }); /// A registry so [SingleMediumController]s can be created dynamically. @@ -36,7 +36,7 @@ class ControllerRegistry ControllerRegistry.defaults() { register( Audio, - (SingleMedium medium, {void Function(BuildContext) onMediumFinished}) => + (SingleMedium medium, {void Function(BuildContext)? onMediumFinished}) => // XXX: This is a hack that should be removed after #15 and #16 are // resolved. kIsWeb @@ -48,13 +48,13 @@ class ControllerRegistry register( Image, - (SingleMedium medium, {void Function(BuildContext) onMediumFinished}) => + (SingleMedium medium, {void Function(BuildContext)? onMediumFinished}) => ImagePlayerController(medium, onMediumFinished: onMediumFinished), ); register( Video, - (SingleMedium medium, {void Function(BuildContext) onMediumFinished}) => + (SingleMedium medium, {void Function(BuildContext)? onMediumFinished}) => VideoPlayerController(medium, onMediumFinished: onMediumFinished), ); } diff --git a/lib/src/media_player/media_player_error.dart b/lib/src/media_player/media_player_error.dart index 51964ae..bc6767e 100644 --- a/lib/src/media_player/media_player_error.dart +++ b/lib/src/media_player/media_player_error.dart @@ -12,7 +12,7 @@ class MediaPlayerError extends StatelessWidget { /// Constructs a new [MediaPlayerError]. /// /// The widget will display the error description provided by [error]. - const MediaPlayerError(this.error, {Key key}) : super(key: key); + const MediaPlayerError(this.error, {Key? key}) : super(key: key); /// Builds this [error] message. /// @@ -20,7 +20,7 @@ class MediaPlayerError extends StatelessWidget { /// be shown. String buildMessage() { var details = ''; - var message = error.toString(); + String? message = error.toString(); if (error is PlatformException) { final platformError = error as PlatformException; if (platformError.details != null) { @@ -46,7 +46,7 @@ class MediaPlayerError extends StatelessWidget { child: Text( 'Media could not be played: ${buildMessage()}', textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headline6.copyWith( + style: Theme.of(context).textTheme.headline6!.copyWith( color: Colors.white, ), ), diff --git a/lib/src/media_player/media_progress_indicator.dart b/lib/src/media_player/media_progress_indicator.dart index e138e5a..a78eca0 100644 --- a/lib/src/media_player/media_progress_indicator.dart +++ b/lib/src/media_player/media_progress_indicator.dart @@ -10,9 +10,8 @@ class MediaProgressIndicator extends StatelessWidget { final bool isVisualizable; /// Constructs a [MediaProgressIndicator] setting if it's [visualizable]. - const MediaProgressIndicator({@required bool visualizable}) - : assert(visualizable != null), - isVisualizable = visualizable; + const MediaProgressIndicator({required bool visualizable}) + : isVisualizable = visualizable; /// Builds the widget. @override diff --git a/lib/src/media_player/multi_medium_player.dart b/lib/src/media_player/multi_medium_player.dart index f9f0907..8486831 100644 --- a/lib/src/media_player/multi_medium_player.dart +++ b/lib/src/media_player/multi_medium_player.dart @@ -26,7 +26,7 @@ class MultiMediumPlayer extends StatelessWidget { final Color backgroundColor; /// The action to perform when this player stops. - final void Function(BuildContext) onMediaStopped; + final void Function(BuildContext)? onMediaStopped; /// Constructs a new [MultiMediumPlayer]. /// @@ -36,12 +36,11 @@ class MultiMediumPlayer extends StatelessWidget { /// by the user, the [onMediaStopped] callback will be called (if non-null). /// const MultiMediumPlayer({ - @required this.medium, - Color backgroundColor, + required this.medium, + Color? backgroundColor, this.onMediaStopped, - Key key, - }) : assert(medium != null), - backgroundColor = backgroundColor ?? Colors.black, + Key? key, + }) : backgroundColor = backgroundColor ?? Colors.black, super(key: key); /// Builds the UI for this widget. diff --git a/lib/src/media_player/multi_medium_state.dart b/lib/src/media_player/multi_medium_state.dart index 1ce9b11..ab6bc9c 100644 --- a/lib/src/media_player/multi_medium_state.dart +++ b/lib/src/media_player/multi_medium_state.dart @@ -16,15 +16,15 @@ class MultiMediumState /// The function that will be called when the main track finishes playing. @override - final void Function(BuildContext context) onFinished; + final void Function(BuildContext context)? onFinished; /// The state of the main track. MultiMediumTrackState get mainTrackState => _mainTrackState; - MultiMediumTrackState _mainTrackState; + late final MultiMediumTrackState _mainTrackState; /// The state of the background track. MultiMediumTrackState get backgroundTrackState => _backgroundTrackState; - MultiMediumTrackState _backgroundTrackState; + late final MultiMediumTrackState _backgroundTrackState; /// True when all the media in both tracks is initialized. bool get isInitialized => _allInitialized; @@ -36,8 +36,7 @@ class MultiMediumState /// is provided, it will be called when the medium finishes playing the /// [multimedium.mainTrack]. MultiMediumState(MultiMedium multimedium, {this.onFinished}) - : assert(multimedium != null), - playable = multimedium { + : playable = multimedium { _mainTrackState = MultiMediumTrackState.main( track: multimedium.mainTrack, onFinished: _onMainTrackFinished, diff --git a/lib/src/media_player/multi_medium_track_state.dart b/lib/src/media_player/multi_medium_track_state.dart index 92f1199..17ac2da 100644 --- a/lib/src/media_player/multi_medium_track_state.dart +++ b/lib/src/media_player/multi_medium_track_state.dart @@ -18,10 +18,10 @@ import 'single_medium_state.dart' show SingleMediumState; class MultiMediumTrackState with ChangeNotifier, DiagnosticableTreeMixin { /// Function used to create [SingleMediumState] instances. @visibleForTesting - static var createSingleMediumState = ( + static SingleMediumState Function(SingleMedium medium, {required bool isVisualizable, void Function(BuildContext)? onFinished}) createSingleMediumState = ( SingleMedium medium, { - bool isVisualizable, - void Function(BuildContext) onFinished, + required bool isVisualizable, + void Function(BuildContext)? onFinished, }) => SingleMediumState( medium, @@ -57,10 +57,11 @@ class MultiMediumTrackState with ChangeNotifier, DiagnosticableTreeMixin { bool get isNotEmpty => mediaState.isNotEmpty; /// The current [SingleMediumState] being played, or null if [isFinished]. - SingleMediumState get current => isFinished ? null : mediaState[currentIndex]; + SingleMediumState? get current => + isFinished ? null : mediaState[currentIndex]; /// The last [SingleMediumState] in this track. - SingleMediumState get last => mediaState.last; + SingleMediumState? get last => mediaState.last; /// Constructs a [MultiMediumTrackState] from a [SingleMedium] list. /// @@ -74,12 +75,10 @@ class MultiMediumTrackState with ChangeNotifier, DiagnosticableTreeMixin { /// medium finished playing, then this [onFinished] will be called. @protected MultiMediumTrackState.internal({ - @required List media, - @required bool visualizable, - void Function(BuildContext context) onFinished, - }) : assert(media != null), - assert(media.isNotEmpty), - assert(visualizable != null), + required List media, + required bool visualizable, + void Function(BuildContext context)? onFinished, + }) : assert(media.isNotEmpty), isVisualizable = visualizable { void _playNext(BuildContext context) { currentIndex++; @@ -109,10 +108,10 @@ class MultiMediumTrackState with ChangeNotifier, DiagnosticableTreeMixin { /// [track] must be non-null. If [onFinished] is provided and non-null, /// it will be called when all the tracks finished playing. MultiMediumTrackState.main({ - @required MultiMediumTrack track, - void Function(BuildContext context) onFinished, + required MultiMediumTrack track, + void Function(BuildContext context)? onFinished, }) : this.internal( - media: track?.media, + media: track.media, visualizable: track is VisualizableMultiMediumTrack, onFinished: onFinished, ); @@ -127,12 +126,12 @@ class MultiMediumTrackState with ChangeNotifier, DiagnosticableTreeMixin { /// /// [track] must be non-null. static MultiMediumTrackState background({ - @required BackgroundMultiMediumTrack track, + required BackgroundMultiMediumTrack track, }) => track is NoTrack ? MultiMediumTrackState.empty() : MultiMediumTrackState.internal( - media: track?.media, + media: track.media, visualizable: track is VisualizableBackgroundMultiMediumTrack, ); @@ -152,10 +151,10 @@ class MultiMediumTrackState with ChangeNotifier, DiagnosticableTreeMixin { } /// Plays the current [SingleMediumState]. - Future play(BuildContext context) => current?.play(context); + Future? play(BuildContext context) => current?.play(context); /// Pauses the current [SingleMediumState]. - Future pause(BuildContext context) => current?.pause(context); + Future? pause(BuildContext context) => current?.pause(context); /// Disposes all the [SingleMediumState] in [mediaState]. @override diff --git a/lib/src/media_player/multi_medium_track_widget.dart b/lib/src/media_player/multi_medium_track_widget.dart index eb93b09..e2b0094 100644 --- a/lib/src/media_player/multi_medium_track_widget.dart +++ b/lib/src/media_player/multi_medium_track_widget.dart @@ -24,7 +24,7 @@ import 'single_medium_widget.dart' show SingleMediumWidget; /// initialization is still in progress. class MultiMediumTrackWidget extends StatelessWidget { /// Constructs a [MultiMediumTrackWidget]. - const MultiMediumTrackWidget({Key key}) : super(key: key); + const MultiMediumTrackWidget({Key? key}) : super(key: key); /// Creates a [MultiMediumTrackWidget]. /// diff --git a/lib/src/media_player/multi_medium_widget.dart b/lib/src/media_player/multi_medium_widget.dart index 0550736..b05b24e 100644 --- a/lib/src/media_player/multi_medium_widget.dart +++ b/lib/src/media_player/multi_medium_widget.dart @@ -28,7 +28,7 @@ import 'multi_medium_track_widget.dart' show MultiMediumTrackWidget; class MultiMediumWidget extends StatelessWidget { /// Constructs a [MultiMediumWidget]. const MultiMediumWidget({ - Key key, + Key? key, }) : super(key: key); /// Creates a [MultiMediumTrackWidget]. diff --git a/lib/src/media_player/playable_state.dart b/lib/src/media_player/playable_state.dart index f596794..971bf46 100644 --- a/lib/src/media_player/playable_state.dart +++ b/lib/src/media_player/playable_state.dart @@ -10,7 +10,7 @@ abstract class PlayableState implements ChangeNotifier, DiagnosticableTree { /// The function to call when this playable finishes playing. @visibleForTesting - void Function(BuildContext context) get onFinished; + void Function(BuildContext context)? get onFinished; /// Initializes this playable state. /// @@ -21,12 +21,12 @@ abstract class PlayableState implements ChangeNotifier, DiagnosticableTree { /// Plays this playable. /// /// It does nothing if the playable was already playing or finished. - Future play(BuildContext context); + Future? play(BuildContext context); /// Pauses this playable. /// /// It does nothing if the playable was already paused or finished. - Future pause(BuildContext context); + Future? pause(BuildContext context); /// Disposes this playable. /// diff --git a/lib/src/media_player/playlist_player.dart b/lib/src/media_player/playlist_player.dart index 660addc..5c3c61d 100644 --- a/lib/src/media_player/playlist_player.dart +++ b/lib/src/media_player/playlist_player.dart @@ -25,7 +25,7 @@ class PlaylistPlayer extends StatelessWidget { final Color backgroundColor; /// The action to perform when this player stops. - final void Function(BuildContext) onMediaStopped; + final void Function(BuildContext)? onMediaStopped; /// Creates a new [PlaylistPlayer]. /// @@ -34,12 +34,11 @@ class PlaylistPlayer extends StatelessWidget { /// playing, either because it was played completely or because it was stopped /// by the user, the [onMediaStopped] callback will be called (if non-null). const PlaylistPlayer({ - @required this.playlist, - Color backgroundColor, + required this.playlist, + Color? backgroundColor, this.onMediaStopped, - Key key, - }) : assert(playlist != null), - backgroundColor = backgroundColor ?? Colors.black, + Key? key, + }) : backgroundColor = backgroundColor ?? Colors.black, super(key: key); /// Builds the UI for this widget. diff --git a/lib/src/media_player/playlist_state.dart b/lib/src/media_player/playlist_state.dart index 535c655..33e9576 100644 --- a/lib/src/media_player/playlist_state.dart +++ b/lib/src/media_player/playlist_state.dart @@ -21,10 +21,11 @@ class PlaylistState @visibleForTesting static PlayableState Function( Medium medium, { - void Function(BuildContext) onFinished, + void Function(BuildContext)? onFinished, }) createPlayableState = (medium, {onFinished}) { if (medium is SingleMedium) { - return SingleMediumState(medium, onFinished: onFinished); + return SingleMediumState(medium, + onFinished: onFinished, isVisualizable: true); } if (medium is MultiMedium) { return MultiMediumState(medium, onFinished: onFinished); @@ -51,16 +52,16 @@ class PlaylistState /// Function to call when the [playlist] finished playing all the media. @override - final void Function(BuildContext context) onFinished; + final void Function(BuildContext context)? onFinished; /// If true, all the media in this playlist has finished playing. bool get isFinished => currentIndex >= mediaState.length; /// The current [PlayableState] being played, or null if [isFinished]. - PlayableState get current => isFinished ? null : mediaState[currentIndex]; + PlayableState? get current => isFinished ? null : mediaState[currentIndex]; /// The last [PlayableState] in this playlist. - PlayableState get last => mediaState.last; + PlayableState? get last => mediaState.last; /// Plays the next medium in the [playlist]. void _playNext(BuildContext context) { @@ -85,8 +86,7 @@ class PlaylistState PlaylistState( Playlist playlist, { this.onFinished, - }) : assert(playlist != null), - playable = playlist { + }) : playable = playlist { mediaState.addAll(playlist.media .map((m) => createPlayableState(m, onFinished: _playNext))); } @@ -106,13 +106,13 @@ class PlaylistState /// If it's already playing or it finished playing all the media (and /// it's not looping), it does nothing. @override - Future play(BuildContext context) => current?.play(context); + Future? play(BuildContext context) => current?.play(context); /// Pauses the playing of the media in this [playlist]. /// /// It does nothing if it's already paused. @override - Future pause(BuildContext context) => current?.pause(context); + Future? pause(BuildContext context) => current?.pause(context); /// Disposes all the [PlayableState] in [mediaState]. @override diff --git a/lib/src/media_player/playlist_widget.dart b/lib/src/media_player/playlist_widget.dart index 481e30b..81fcdbd 100644 --- a/lib/src/media_player/playlist_widget.dart +++ b/lib/src/media_player/playlist_widget.dart @@ -33,7 +33,7 @@ class PlaylistWidget extends StatelessWidget { static Widget Function() createMultiMediumWidget = () => MultiMediumWidget(); /// Constructs a [MultiMediumTrackWidget]. - const PlaylistWidget({Key key}) : super(key: key); + const PlaylistWidget({Key? key}) : super(key: key); /// Builds the UI for this widget. @override diff --git a/lib/src/media_player/single_medium_controller.dart b/lib/src/media_player/single_medium_controller.dart index d7917f2..64aa95b 100644 --- a/lib/src/media_player/single_medium_controller.dart +++ b/lib/src/media_player/single_medium_controller.dart @@ -3,8 +3,8 @@ import 'dart:async' show Completer; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; -import 'package:audioplayers/audioplayers.dart' show AudioPlayer, ReleaseMode; -import 'package:audioplayers/audio_cache.dart' show AudioCache; +import 'package:audioplayers/audioplayers.dart' + show AudioCache, AudioPlayer, ReleaseMode; import 'package:video_player/video_player.dart' as video_player; import 'package:lunofono_bundle/lunofono_bundle.dart' @@ -21,12 +21,12 @@ abstract class SingleMediumController { final SingleMedium medium; /// The key to use to create the main [Widget] in [build()]. - final Key widgetKey; + final Key? widgetKey; /// The callback to be called when the medium finishes playing. /// /// This callback is called when the medium finishes playing by itself. - final void Function(BuildContext) onMediumFinished; + final void Function(BuildContext)? onMediumFinished; /// The timer used to finish the medium if it has a maximum duration. /// @@ -36,7 +36,7 @@ abstract class SingleMediumController { /// [pause] and [onMediumFinished] when it expires) but it's not started until /// [play] is called. Then [play] and [pause] will start and pause the timer. @protected - PausableTimer maxDurationTimer; + PausableTimer? maxDurationTimer; /// {@template ui_player_media_player_medium_player_controller_initialize} /// Initializes this controller, getting the size of the media to be played. @@ -47,17 +47,23 @@ abstract class SingleMediumController { /// The [build()] method should never be called before the initialization is /// done. /// {@endtemplate} - @mustCallSuper - Future initialize(BuildContext context) async { - final futureNull = Future.value(null); - if (medium.maxDuration == const UnlimitedDuration()) return futureNull; + Future initialize(BuildContext context); + + /// Initializes the maxDurationTimer. + /// + /// This timer is only initialized if the underlying [medium] has a non-null + /// maxDuration. + /// + /// Any subclass wanting to use this timer must initialize it in their own + /// initialize() method. + @protected + void initializeMaxDurationTimer(BuildContext context) { + if (medium.maxDuration == const UnlimitedDuration()) return; maxDurationTimer = PausableTimer(medium.maxDuration, () async { await pause(context); onMediumFinished?.call(context); }); - - return futureNull; } /// Play the [medium] controlled by this controller. @@ -84,30 +90,31 @@ abstract class SingleMediumController { /// If a [widgetKey] is provided, it will be used to create the main player /// [Widget] in the [build()] function. /// {@endtemplate} - SingleMediumController(this.medium, {this.onMediumFinished, this.widgetKey}) - : assert(medium != null); + SingleMediumController(this.medium, {this.onMediumFinished, this.widgetKey}); } /// A video player controller. class VideoPlayerController extends SingleMediumController { /// The video player controller. - video_player.VideoPlayerController _controller; + video_player.VideoPlayerController? _controller; /// The video player controller. @visibleForTesting - video_player.VideoPlayerController get controller => _controller; + video_player.VideoPlayerController? get controller => _controller; /// {@macro ui_player_media_player_medium_player_controller_constructor} VideoPlayerController( SingleMedium medium, { - void Function(BuildContext) onMediumFinished, - Key widgetKey, + void Function(BuildContext)? onMediumFinished, + Key? widgetKey, }) : super(medium, onMediumFinished: onMediumFinished, widgetKey: widgetKey); /// Disposes this controller. @override - Future dispose() => Future.wait( - [_controller?.dispose(), super.dispose()].where((f) => f != null)); + Future dispose() => Future.wait([ + _controller?.dispose() ?? Future.value(), + super.dispose(), + ]); /// Creates a new [video_player.VideoPlayerController]. /// @@ -125,46 +132,48 @@ class VideoPlayerController extends SingleMediumController { /// {@macro ui_player_media_player_medium_player_controller_initialize} @override Future initialize(BuildContext context) async { - VoidCallback listener; + initializeMaxDurationTimer(context); + + final controller = createController(); + _controller = controller; + + late final VoidCallback listener; listener = () { - final value = _controller.value; + final value = controller.value; // value.duration can be null during initialization // If the position reaches the duration (we use >= just to be extra // careful) and it is not playing anymore, we assumed it finished playing. // Also this should happen once and only once, as we don't expose any // seeking or loop playing. - if (value.duration != null && - value.position >= value.duration && - !value.isPlaying) { + if (value.position >= value.duration && !value.isPlaying) { onMediumFinished?.call(context); - _controller.removeListener(listener); + controller.removeListener(listener); } }; - _controller = createController(); - _controller.addListener(listener); + controller.addListener(listener); - await Future.wait([super.initialize(context), _controller.initialize()]); + await controller.initialize(); - return _controller.value.size; + return controller.value.size; } /// Play the [medium] controlled by this controller. @override Future play(BuildContext context) => - Future.wait([super.play(context), _controller.play()]); + Future.wait([super.play(context), _controller!.play()]); /// Pause the [medium] controlled by this controller. @override Future pause(BuildContext context) => - Future.wait([super.pause(context), _controller.pause()]); + Future.wait([super.pause(context), _controller!.pause()]); /// Builds the [Widget] that plays the medium this controller controls. @override Widget build(BuildContext context) { return AspectRatio( - aspectRatio: _controller.value.aspectRatio, - child: video_player.VideoPlayer(_controller), + aspectRatio: _controller!.value.aspectRatio, + child: video_player.VideoPlayer(_controller!), key: widgetKey, ); } @@ -183,8 +192,8 @@ class WebAudioPlayerController extends VideoPlayerController { /// {@macro ui_player_media_player_medium_player_controller_constructor} WebAudioPlayerController( SingleMedium medium, { - void Function(BuildContext) onMediumFinished, - Key widgetKey, + void Function(BuildContext)? onMediumFinished, + Key? widgetKey, }) : super(medium, onMediumFinished: onMediumFinished, widgetKey: widgetKey); /// Builds the [Widget] that plays the medium this controller controls. @@ -207,12 +216,16 @@ class AudioPlayerController extends SingleMediumController { /// {@macro ui_player_media_player_medium_player_controller_constructor} AudioPlayerController( SingleMedium medium, { - void Function(BuildContext) onMediumFinished, - Key widgetKey, + void Function(BuildContext)? onMediumFinished, + Key? widgetKey, }) : super(medium, onMediumFinished: onMediumFinished, widgetKey: widgetKey); /// The video player controller. - AudioCache _controller; + AudioCache? _controller; + + /// The video player controller. + @visibleForTesting + AudioCache? get controller => _controller; /// True if playing was started, false otherwise. /// @@ -220,15 +233,13 @@ class AudioPlayerController extends SingleMediumController { /// should be used instead of play(). bool _playStarted = false; - /// The video player controller. - @visibleForTesting - AudioCache get controller => _controller; - /// Disposes this controller. @override Future dispose() { - return Future.wait([_controller?.fixedPlayer?.dispose(), super.dispose()] - .where((f) => f != null)); + return Future.wait([ + _controller?.fixedPlayer?.dispose() ?? Future.value(), + super.dispose(), + ]); } /// Creates a new [video_player.VideoPlayerController]. @@ -241,21 +252,21 @@ class AudioPlayerController extends SingleMediumController { /// {@macro ui_player_media_player_medium_player_controller_initialize} @override Future initialize(BuildContext context) async { - _controller = createController(); + initializeMaxDurationTimer(context); + + final controller = createController(); + _controller = controller; final player = AudioPlayer(); player.onPlayerCompletion.listen((_) { onMediumFinished?.call(context); }); - _controller.fixedPlayer = player; + controller.fixedPlayer = player; - await Future.wait([ - super.initialize(context), - player - .setReleaseMode(ReleaseMode.STOP) - .then((_) => _controller.load(medium.resource.toString())), - ]); + await player + .setReleaseMode(ReleaseMode.STOP) + .then((_) => controller.load(medium.resource.toString())); return Size.zero; } @@ -264,8 +275,8 @@ class AudioPlayerController extends SingleMediumController { @override Future play(BuildContext context) { final controllerPlayFuture = _playStarted - ? _controller.fixedPlayer.resume() - : _controller.play(medium.resource.toString()); + ? _controller!.fixedPlayer!.resume() + : _controller!.play(medium.resource.toString()); _playStarted = true; return Future.wait([super.play(context), controllerPlayFuture]); } @@ -273,7 +284,8 @@ class AudioPlayerController extends SingleMediumController { /// Pause the [medium] controlled by this controller. @override Future pause(BuildContext context) { - return Future.wait([super.pause(context), _controller.fixedPlayer.pause()]); + return Future.wait( + [super.pause(context), _controller!.fixedPlayer!.pause()]); } /// Builds the [Widget] that plays the medium this controller controls. @@ -290,7 +302,7 @@ class AudioPlayerController extends SingleMediumController { /// An image player controller. class ImagePlayerController extends SingleMediumController { /// The image that this controller will [build()]. - Image _image; + late final Image _image; /// The image that this controller will [build()]. Image get image => _image; @@ -298,15 +310,17 @@ class ImagePlayerController extends SingleMediumController { /// {@macro ui_player_media_player_medium_player_controller_constructor] ImagePlayerController( SingleMedium medium, { - void Function(BuildContext) onMediumFinished, - Key widgetKey, + void Function(BuildContext)? onMediumFinished, + Key? widgetKey, }) : super(medium, onMediumFinished: onMediumFinished, widgetKey: widgetKey); /// {@macro ui_player_media_player_medium_player_controller_initialize] @override Future initialize(BuildContext context) async { + initializeMaxDurationTimer(context); + final completer = Completer(); - Size size; + late final Size size; _image = Image.asset( medium.resource.toString(), @@ -321,13 +335,13 @@ class ImagePlayerController extends SingleMediumController { info.image.width.toDouble(), info.image.height.toDouble()); completer.complete(); }, - onError: (dynamic error, StackTrace stackTrace) { + onError: (Object error, StackTrace? stackTrace) { completer.completeError(error, stackTrace); }, ), ); - await Future.wait([super.initialize(context), completer.future]); + await completer.future; return size; } diff --git a/lib/src/media_player/single_medium_player.dart b/lib/src/media_player/single_medium_player.dart index 6729534..612af06 100644 --- a/lib/src/media_player/single_medium_player.dart +++ b/lib/src/media_player/single_medium_player.dart @@ -24,7 +24,7 @@ class SingleMediumPlayer extends StatelessWidget { final Color backgroundColor; /// The action to perform when this player stops. - final void Function(BuildContext) onMediaStopped; + final void Function(BuildContext)? onMediaStopped; /// Creates a new [SingleMediumPlayer]. /// @@ -33,21 +33,20 @@ class SingleMediumPlayer extends StatelessWidget { /// playing, either because it was played completely or because it was stopped /// by the user, the [onMediaStopped] callback will be called (if non-null). const SingleMediumPlayer({ - @required this.medium, - Color backgroundColor, + required this.medium, + Color? backgroundColor, this.onMediaStopped, - Key key, - }) : assert(medium != null), - backgroundColor = backgroundColor ?? Colors.black, + Key? key, + }) : backgroundColor = backgroundColor ?? Colors.black, super(key: key); /// Builds the UI for this widget. @override Widget build(BuildContext context) => ChangeNotifierProvider( - create: (context) => - SingleMediumState(medium, onFinished: onMediaStopped) - ..initialize(context, startPlaying: true), + create: (context) => SingleMediumState(medium, + onFinished: onMediaStopped, isVisualizable: true) + ..initialize(context, startPlaying: true), child: Builder( builder: (context) => GestureDetector( onTap: () { diff --git a/lib/src/media_player/single_medium_state.dart b/lib/src/media_player/single_medium_state.dart index 0f00a0f..0754f32 100644 --- a/lib/src/media_player/single_medium_state.dart +++ b/lib/src/media_player/single_medium_state.dart @@ -25,14 +25,14 @@ class SingleMediumState /// The function to call when this medium finishes playing. @override - final void Function(BuildContext context) onFinished; + final void Function(BuildContext context)? onFinished; /// The player controller used to control this medium. /// /// It can be null if there was an error while creating the controller (if /// it is null, error is non-null). - SingleMediumController get controller => _controller; - SingleMediumController _controller; + SingleMediumController? get controller => _controller; + SingleMediumController? _controller; /// If true, the medium needs to be visualized, otherwise it plays in the /// background without any visual representation (except for errors or @@ -49,7 +49,7 @@ class SingleMediumState /// The size is only available after [initialize()] is successful, so if this /// is non-null, it means the [controller] for this medium was initialized /// successfully. - Size size; + Size? size; /// True if there was an error ([error] is non-null). bool get isErroneous => error != null; @@ -63,20 +63,17 @@ class SingleMediumState /// Creates a state from a [medium]. /// - /// The [medium] and [isVisualizable] must be non-null. A [controller] will - /// be created using the global [ControllerRegistry.instance]. If there is no - /// controller registered for this kind of [medium], then an [error] will - /// be set. + /// A [controller] will be created using the global + /// [ControllerRegistry.instance]. If there is no controller registered for + /// this kind of [medium], then an [error] will be set. /// /// If [onFinished] is provided, it will be called when the medium finishes /// playing (if ever). SingleMediumState( SingleMedium medium, { - this.isVisualizable = true, + required this.isVisualizable, this.onFinished, - }) : assert(medium != null), - assert(isVisualizable != null), - playable = medium { + }) : playable = medium { final create = ControllerRegistry.instance.getFunction(medium); if (create == null) { error = 'Unsupported type ${medium.runtimeType} for ${medium.resource}'; @@ -102,7 +99,7 @@ class SingleMediumState // error should be already set if (controller == null) return; try { - size = await controller.initialize(context); + size = await controller!.initialize(context); } catch (e) { error = e; } @@ -118,10 +115,11 @@ class SingleMediumState // track. @override Future play(BuildContext context) => - controller?.play(context)?.catchError((dynamic error) { + controller?.play(context).catchError((dynamic error) { this.error = error; notifyListeners(); - }); + }) ?? + Future.value(); /// Pauses this medium using [controller]. /// @@ -131,10 +129,11 @@ class SingleMediumState // in the track. @override Future pause(BuildContext context) => - controller?.pause(context)?.catchError((dynamic error) { + controller?.pause(context).catchError((dynamic error) { this.error = error; notifyListeners(); - }); + }) ?? + Future.value(); /// Disposes this medium's [controller]. /// @@ -144,14 +143,14 @@ class SingleMediumState Future dispose() async { await controller ?.dispose() - ?.catchError((dynamic error) => this.error = error); + .catchError((dynamic error) => this.error = error); super.dispose(); } @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { final sizeStr = - size == null ? 'uninitialized' : '${size.width}x${size.height}'; + size == null ? 'uninitialized' : '${size!.width}x${size!.height}'; final errorStr = error == null ? '' : 'error: $error'; return '$runtimeType("${playable.resource}", $sizeStr$errorStr)'; } @@ -163,6 +162,6 @@ class SingleMediumState ..add(DiagnosticsProperty('playable', playable)) ..add(DiagnosticsProperty('error', error, defaultValue: null)) ..add(DiagnosticsProperty('size', - size == null ? '' : '${size.width}x${size.height}')); + size == null ? '' : '${size!.width}x${size!.height}')); } } diff --git a/lib/src/media_player/single_medium_widget.dart b/lib/src/media_player/single_medium_widget.dart index dc70a25..d0fea38 100644 --- a/lib/src/media_player/single_medium_widget.dart +++ b/lib/src/media_player/single_medium_widget.dart @@ -23,7 +23,7 @@ import 'single_medium_state.dart' show SingleMediumState; /// is still in progress. class SingleMediumWidget extends StatelessWidget { /// Creates a [SingleMediumWidget]. - const SingleMediumWidget({Key key}) : super(key: key); + const SingleMediumWidget({Key? key}) : super(key: key); /// Builds the UI for this widget. @override @@ -34,16 +34,19 @@ class SingleMediumWidget extends StatelessWidget { } if (state.isInitialized) { + final controller = state.controller!; + final size = state.size!; + // FIXME: This is a bit hacky. At some point it might be better to // have 2 build methods in SingleMediumController: buildAudible() // and buildVisualizable() and use then depeding on what kind of // track we are showing. if (!state.isVisualizable) { - return Container(key: state.controller.widgetKey); + return Container(key: controller.widgetKey); } - var widget = state.controller.build(context); - if (state.size.width > state.size.height) { + var widget = controller.build(context); + if (size.width > size.height) { widget = RotatedBox( quarterTurns: 1, child: widget, diff --git a/lib/src/menu_player.dart b/lib/src/menu_player.dart index 5468428..a42d10a 100644 --- a/lib/src/menu_player.dart +++ b/lib/src/menu_player.dart @@ -31,7 +31,7 @@ abstract class MenuPlayer { static MenuPlayer wrap(Menu menu) { final wrap = registry.getFunction(menu); assert(wrap != null, 'Unimplemented MenuPlayer for ${menu.runtimeType}'); - return wrap(menu); + return wrap!(menu); } /// The underlaying model's [Menu]. diff --git a/lib/src/menu_player/grid_menu_player.dart b/lib/src/menu_player/grid_menu_player.dart index a255974..9523852 100644 --- a/lib/src/menu_player/grid_menu_player.dart +++ b/lib/src/menu_player/grid_menu_player.dart @@ -28,8 +28,7 @@ class GridMenuPlayer extends MenuPlayer { /// /// This also wrap all the [menu.buttons] to store [ButtonPlayer]s instead. GridMenuPlayer(this.menu) - : assert(menu != null), - buttons = List.from( + : buttons = List.from( menu.buttons.map((b) => ButtonPlayer.wrap(b))); /// Builds the UI for this [menu]. @@ -49,12 +48,10 @@ class GridMenuWidget extends StatelessWidget { /// Creates a new [GridMenuWidget] for [menu]. const GridMenuWidget({ - @required this.menu, + required this.menu, this.padding = 10.0, - Key key, - }) : assert(menu != null), - assert(padding != null), - super(key: key); + Key? key, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -84,14 +81,11 @@ class GridMenuRowWidget extends StatelessWidget { /// Creates a new [GridMenuRowWidget] to display the [row] row from [menu]. const GridMenuRowWidget({ - @required this.row, - @required this.menu, + required this.row, + required this.menu, this.padding = 10.0, - Key key, - }) : assert(row != null), - assert(menu != null), - assert(padding != null), - super(key: key); + Key? key, + }) : super(key: key); @override Widget build(BuildContext context) => Expanded( diff --git a/lib/src/platform_services.dart b/lib/src/platform_services.dart index a539baf..b7ea1ed 100644 --- a/lib/src/platform_services.dart +++ b/lib/src/platform_services.dart @@ -1,7 +1,5 @@ import 'package:flutter/services.dart' - show SystemChrome, DeviceOrientation, SystemUiOverlay; - -import 'package:meta/meta.dart' show required; + show SystemChrome, DeviceOrientation, SystemUiMode; import 'package:wakelock/wakelock.dart' show Wakelock; @@ -16,9 +14,9 @@ class PlatformServices { const PlatformServices(); /// Set fullscreen mode on or off. - Future setFullScreen({@required bool on}) async { - return SystemChrome.setEnabledSystemUIOverlays( - on ? [] : SystemUiOverlay.values); + Future setFullScreen({required bool on}) async { + return SystemChrome.setEnabledSystemUIMode( + on ? SystemUiMode.immersiveSticky : SystemUiMode.edgeToEdge); } /// Set the preferred screen orientation(s). @@ -70,7 +68,7 @@ class PlatformServices { /// /// This is also known as "wakelock", when taking the "wakelock" means the /// device doesn't go to sleep. - Future inhibitScreenOff({@required bool on}) async { + Future inhibitScreenOff({required bool on}) async { // Take wakelock so the device isn't locked after some time inactive return on ? Wakelock.enable() : Wakelock.disable(); } diff --git a/lib/src/playable_player.dart b/lib/src/playable_player.dart index 76d8c26..42a0e02 100644 --- a/lib/src/playable_player.dart +++ b/lib/src/playable_player.dart @@ -51,14 +51,14 @@ abstract class PlayablePlayer { final wrap = registry.getFunction(playable); assert(wrap != null, 'Unimplemented PlayablePlayer for ${playable.runtimeType}'); - return wrap(playable); + return wrap!(playable); } /// The underlaying model's [Playable]. Playable get playable; /// Plays this [Playable] with an optional [backgroundColor]. - void play(BuildContext context, [Color backgroundColor]); + void play(BuildContext context, [Color? backgroundColor]); } class SingleMediumPlayablePlayer extends PlayablePlayer { @@ -68,7 +68,7 @@ class SingleMediumPlayablePlayer extends PlayablePlayer { /// Creates a [SingleMediumPlayablePlayer] using [playable] as the underlaying /// [Playable]. - SingleMediumPlayablePlayer(this.playable) : assert(playable != null); + SingleMediumPlayablePlayer(this.playable); /// Plays a [SingleMedium] by pushing a new page with a [SingleMediumPlayer]. /// @@ -76,7 +76,7 @@ class SingleMediumPlayablePlayer extends PlayablePlayer { /// [SingleMediumPlayer.backgroundColor]. Otherwise, [Colors.black] will be /// used. @override - void play(BuildContext context, [Color backgroundColor]) { + void play(BuildContext context, [Color? backgroundColor]) { Navigator.push( context, MaterialPageRoute( @@ -97,7 +97,7 @@ class PlaylistPlayablePlayer extends PlayablePlayer { /// Constructs a [SingleMediumWidget] using [playable] as the underlaying /// [Playable]. - PlaylistPlayablePlayer(this.playable) : assert(playable != null); + PlaylistPlayablePlayer(this.playable); /// Plays a [SingleMedium] by pushing a new page with a [PlaylistPlayer]. /// @@ -105,7 +105,7 @@ class PlaylistPlayablePlayer extends PlayablePlayer { /// [PlaylistPlayer.backgroundColor]. Otherwise, [Colors.black] will be /// used. @override - void play(BuildContext context, [Color backgroundColor]) { + void play(BuildContext context, [Color? backgroundColor]) { Navigator.push( context, MaterialPageRoute( @@ -126,7 +126,7 @@ class MultiMediumPlayablePlayer extends PlayablePlayer { /// Constructs a [SingleMediumWidget] using [playable] as the underlaying /// [Playable]. - MultiMediumPlayablePlayer(this.playable) : assert(playable != null); + MultiMediumPlayablePlayer(this.playable); /// Plays a [SingleMedium] by pushing a new page with a [MultiMediumPlayer]. /// @@ -134,7 +134,7 @@ class MultiMediumPlayablePlayer extends PlayablePlayer { /// [MultiMediumPlayer.backgroundColor]. Otherwise, [Colors.black] will be /// used. @override - void play(BuildContext context, [Color backgroundColor]) { + void play(BuildContext context, [Color? backgroundColor]) { Navigator.push( context, MaterialPageRoute( diff --git a/pubspec.lock b/pubspec.lock index d355d71..2196e86 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,35 +7,35 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "14.0.0" + version: "25.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.41.2" + version: "2.2.0" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.2.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "2.8.1" audioplayers: dependency: "direct main" description: name: audioplayers url: "https://pub.dartlang.org" source: hosted - version: "0.17.3" + version: "0.20.0" boolean_selector: dependency: transitive description: @@ -49,21 +49,56 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" + version: "2.1.0" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "7.1.0" built_collection: dependency: transitive description: name: built_collection url: "https://pub.dartlang.org" source: hosted - version: "5.0.0" + version: "5.1.1" built_value: dependency: transitive description: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "8.0.0" + version: "8.1.2" characters: dependency: transitive description: @@ -77,14 +112,21 @@ packages: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" cli_util: dependency: transitive description: name: cli_util url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.3.3" clock: dependency: transitive description: @@ -98,7 +140,7 @@ packages: name: code_builder url: "https://pub.dartlang.org" source: hosted - version: "3.6.0" + version: "4.1.0" collection: dependency: transitive description: @@ -112,42 +154,35 @@ packages: name: convert url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "3.0.1" coverage: dependency: transitive description: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "0.15.2" + version: "1.0.3" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.16.2" + version: "3.0.1" dart_style: dependency: transitive description: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "1.3.12" + version: "2.1.0" equatable: dependency: transitive description: name: equatable url: "https://pub.dartlang.org" source: hosted - version: "1.2.6" + version: "2.0.3" fake_async: dependency: transitive description: @@ -161,14 +196,14 @@ packages: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "1.1.2" file: dependency: transitive description: name: file url: "https://pub.dartlang.org" source: hosted - version: "6.0.0" + version: "6.1.2" fixnum: dependency: transitive description: @@ -191,27 +226,41 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" glob: dependency: transitive description: name: glob url: "https://pub.dartlang.org" source: hosted + version: "2.0.1" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted version: "2.0.0" - html: + http: dependency: transitive description: - name: html + name: http url: "https://pub.dartlang.org" source: hosted - version: "0.14.0+4" + version: "0.13.3" http_multi_server: dependency: transitive description: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "3.0.1" http_parser: dependency: transitive description: @@ -219,20 +268,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.0" - import_js_library: - dependency: transitive - description: - name: import_js_library - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" io: dependency: transitive description: name: io url: "https://pub.dartlang.org" source: hosted - version: "0.3.4" + version: "1.0.3" js: dependency: transitive description: @@ -240,22 +282,29 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.3" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.0" logging: dependency: transitive description: name: logging url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" lunofono_bundle: dependency: "direct main" description: path: "." - ref: "v0.3.0" - resolved-ref: c33f53545ab549cb7a8aaad58b04445e8b5178de + ref: "v0.4.1" + resolved-ref: "0d6f9f957e2a76508aefb62549598ea6709a62b5" url: "https://github.com/lunofono/lunofono_bundle.git" source: git - version: "1.0.0" + version: "0.4.1" matcher: dependency: transitive description: @@ -269,7 +318,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.7.0" mime: dependency: transitive description: @@ -283,28 +332,28 @@ packages: name: mockito url: "https://pub.dartlang.org" source: hosted - version: "4.1.4" + version: "5.0.15" nested: dependency: transitive description: name: nested url: "https://pub.dartlang.org" source: hosted - version: "0.0.4" + version: "1.0.0" node_preamble: dependency: transitive description: name: node_preamble url: "https://pub.dartlang.org" source: hosted - version: "1.4.13" + version: "2.0.1" package_config: dependency: transitive description: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "2.0.0" path: dependency: transitive description: @@ -318,63 +367,63 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.6.27" + version: "2.0.3" path_provider_linux: dependency: transitive description: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+2" + version: "2.0.2" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.4+8" + version: "2.0.2" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.1" path_provider_windows: dependency: transitive description: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.4+3" + version: "2.0.3" pausable_timer: dependency: "direct main" description: name: pausable_timer url: "https://pub.dartlang.org" source: hosted - version: "0.1.0+2" + version: "1.0.0+1" pedantic: dependency: "direct dev" description: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.10.0" + version: "1.11.1" platform: dependency: transitive description: name: platform url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.0.2" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.0.1" pool: dependency: transitive description: @@ -388,14 +437,14 @@ packages: name: process url: "https://pub.dartlang.org" source: hosted - version: "4.1.0" + version: "4.2.3" provider: dependency: "direct main" description: name: provider url: "https://pub.dartlang.org" source: hosted - version: "4.3.3" + version: "6.0.0" pub_semver: dependency: transitive description: @@ -403,34 +452,41 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.2.0" shelf_packages_handler: dependency: transitive description: name: shelf_packages_handler url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "3.0.0" shelf_static: dependency: transitive description: name: shelf_static url: "https://pub.dartlang.org" source: hosted - version: "0.2.9+2" + version: "1.1.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "0.2.4" + version: "1.0.1" sky_engine: dependency: transitive description: flutter @@ -442,7 +498,7 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "0.9.10+2" + version: "1.1.0" source_map_stack_trace: dependency: transitive description: @@ -463,7 +519,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" stack_trace: dependency: transitive description: @@ -478,6 +534,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" string_scanner: dependency: transitive description: @@ -498,21 +561,28 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.16.2" + version: "1.17.10" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19" + version: "0.4.2" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.3.13" + version: "0.4.0" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" typed_data: dependency: transitive description: @@ -526,7 +596,7 @@ packages: name: uuid url: "https://pub.dartlang.org" source: hosted - version: "2.2.2" + version: "3.0.4" vector_math: dependency: transitive description: @@ -540,49 +610,63 @@ packages: name: video_player url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.1.15" video_player_platform_interface: dependency: "direct dev" description: name: video_player_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "4.1.0" video_player_web: dependency: transitive description: name: video_player_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.4+1" + version: "2.0.3" vm_service: dependency: transitive description: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "6.0.1" + version: "7.3.0" wakelock: dependency: "direct main" description: name: wakelock url: "https://pub.dartlang.org" source: hosted - version: "0.2.1+1" + version: "0.5.3+3" + wakelock_macos: + dependency: transitive + description: + name: wakelock_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0+2" wakelock_platform_interface: dependency: transitive description: name: wakelock_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "0.1.0+1" + version: "0.2.1+2" wakelock_web: dependency: transitive description: name: wakelock_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.0+3" + version: "0.2.0+2" + wakelock_windows: + dependency: transitive + description: + name: wakelock_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0+1" watcher: dependency: transitive description: @@ -596,35 +680,35 @@ packages: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.1.0" webkit_inspection_protocol: dependency: transitive description: name: webkit_inspection_protocol url: "https://pub.dartlang.org" source: hosted - version: "0.7.5" + version: "1.0.0" win32: dependency: transitive description: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "1.7.4+1" + version: "2.2.9" xdg_directories: dependency: transitive description: name: xdg_directories url: "https://pub.dartlang.org" source: hosted - version: "0.1.2" + version: "0.2.0" yaml: dependency: transitive description: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.1.0" sdks: - dart: ">=2.12.0-0.0 <3.0.0" - flutter: ">=1.20.0" + dart: ">=2.13.0 <3.0.0" + flutter: ">=2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 4ac3d52..99c73af 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,32 +8,32 @@ repository: https://github.com/lunofono/lunofono_player publish_to: none environment: - sdk: ">=2.7.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' flutter: ">=1.17.0 <2.0.0" dependencies: - audioplayers: ^0.17.3 + audioplayers: ^0.20.0 flutter: sdk: flutter lunofono_bundle: git: url: https://github.com/lunofono/lunofono_bundle.git - ref: v0.3.0 + ref: v0.4.1 - pausable_timer: ^0.1.0 - provider: ^4.3.2+2 - video_player: ^1.0.0 - wakelock: ^0.2.1 + pausable_timer: ^1.0.0+1 + provider: ^6.0.0 + video_player: ^2.1.15 + wakelock: ^0.5.3+3 dev_dependencies: + build_runner: ^2.1.2 flutter_test: sdk: flutter - - mockito: ^4.1.1 - pedantic: ^1.9.0 - test: ^1.15.2 - video_player_platform_interface: ^2.0.0 + mockito: ^5.0.15 + pedantic: ^1.11.1 + test: ^1.17.10 + video_player_platform_interface: ^4.1.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/test/unit/action_player_test.dart b/test/unit/action_player_test.dart index 821db94..81bf4fd 100644 --- a/test/unit/action_player_test.dart +++ b/test/unit/action_player_test.dart @@ -13,10 +13,10 @@ import 'package:lunofono_player/src/playable_player.dart' show PlayablePlayer, PlayablePlayerRegistry; class FakePlayable extends Playable { - BuildContext playedContext; - Color playedColor; + BuildContext? playedContext; + Color? playedColor; - void expectCalled(BuildContext context, Color backgroundColor) { + void expectCalled(BuildContext? context, Color backgroundColor) { expect(playedContext, context); expect(playedColor, backgroundColor); } @@ -26,20 +26,20 @@ class FakePlayablePlayer extends PlayablePlayer { @override final FakePlayable playable; @override - void play(BuildContext context, [Color backgroundColor]) { + void play(BuildContext context, [Color? backgroundColor]) { playable.playedContext = context; playable.playedColor = backgroundColor; } - FakePlayablePlayer(this.playable) : assert(playable != null); + FakePlayablePlayer(this.playable); } class FakeAction extends Action {} class FakeActionPlayer extends ActionPlayer { - Action calledAction; - BuildContext calledContext; - ButtonPlayer calledButton; + late final Action calledAction; + late final BuildContext calledContext; + late final ButtonPlayer calledButton; @override final FakeAction action; @@ -50,7 +50,7 @@ class FakeActionPlayer extends ActionPlayer { calledButton = button; } - FakeActionPlayer(this.action) : assert(action != null); + FakeActionPlayer(this.action); } class FakeButtonPlayer extends Fake implements ButtonPlayer { @@ -63,9 +63,9 @@ class FakeContext extends Fake implements BuildContext {} void main() { group('ActionPlayer', () { final oldActionRegistry = ActionPlayer.registry; - FakeContext fakeContext; - FakeAction fakeAction; - FakeButtonPlayer fakeButton; + FakeContext? fakeContext; + FakeAction? fakeAction; + FakeButtonPlayer? fakeButton; setUp(() { fakeContext = FakeContext(); @@ -78,7 +78,8 @@ void main() { test('empty', () { ActionPlayer.registry = ActionPlayerRegistry(); expect(ActionPlayer.registry.isEmpty, isTrue); - expect(() => ActionPlayer.wrap(fakeAction).act(fakeContext, fakeButton), + expect( + () => ActionPlayer.wrap(fakeAction!).act(fakeContext!, fakeButton!), throwsAssertionError); }); @@ -86,8 +87,8 @@ void main() { ActionPlayer.registry = ActionPlayerRegistry(); ActionPlayer.registry.register( FakeAction, (action) => FakeActionPlayer(action as FakeAction)); - final actionPlayer = ActionPlayer.wrap(fakeAction) as FakeActionPlayer; - actionPlayer.act(fakeContext, fakeButton); + final actionPlayer = ActionPlayer.wrap(fakeAction!) as FakeActionPlayer; + actionPlayer.act(fakeContext!, fakeButton!); expect(actionPlayer.calledAction, same(fakeAction)); expect(actionPlayer.calledContext, same(fakeContext)); expect(actionPlayer.calledButton, same(fakeButton)); @@ -106,21 +107,17 @@ void main() { tearDown(() => PlayablePlayer.registry = oldPlayableRegistry); - test('constructor throws if action is null', () { - expect(() => PlayContentActionPlayer(null), throwsAssertionError); - }); - test('dynamic dispatch', () { final Action action = PlayContentAction(fakePlayable); final actionPlayer = ActionPlayer.wrap(action); - actionPlayer.act(fakeContext, fakeButton); - fakePlayable.expectCalled(fakeContext, fakeButton.backgroundColor); + actionPlayer.act(fakeContext!, fakeButton!); + fakePlayable.expectCalled(fakeContext, fakeButton!.backgroundColor); }); test('direct call', () { final action = ActionPlayer.wrap(PlayContentAction(fakePlayable)); - action.act(fakeContext, fakeButton); - fakePlayable.expectCalled(fakeContext, fakeButton.backgroundColor); + action.act(fakeContext!, fakeButton!); + fakePlayable.expectCalled(fakeContext, fakeButton!.backgroundColor); }); }); }); diff --git a/test/unit/bundle_player_test.dart b/test/unit/bundle_player_test.dart index 411f2b7..0ddfdfb 100644 --- a/test/unit/bundle_player_test.dart +++ b/test/unit/bundle_player_test.dart @@ -13,10 +13,6 @@ import 'package:lunofono_player/src/menu_player.dart' void main() { group('BundlePlayer', () { - test('constructor asserts on a null bundle', () { - expect(() => BundlePlayer(null), throwsAssertionError); - }); - testWidgets( 'PlatformServices are called and the rootMenu is built', (WidgetTester tester) async { @@ -52,7 +48,7 @@ class FakeMenu extends Menu {} class FakeMenuPlayer extends MenuPlayer { static Key globalKey = GlobalKey(debugLabel: 'FakeMenuPlayerKey'); - FakeMenuPlayer(this.menu) : assert(menu != null); + FakeMenuPlayer(this.menu); @override final FakeMenu menu; @override @@ -62,11 +58,11 @@ class FakeMenuPlayer extends MenuPlayer { } class FakePlatformServices extends PlatformServices { - bool calledFullScreen; - Orientation calledOrientation; - bool calledInhibitScreenOff; + bool? calledFullScreen; + Orientation? calledOrientation; + bool? calledInhibitScreenOff; @override - Future setFullScreen({@required bool on}) async { + Future setFullScreen({required bool on}) async { calledFullScreen = on; } @@ -76,7 +72,7 @@ class FakePlatformServices extends PlatformServices { } @override - Future inhibitScreenOff({@required bool on}) async { + Future inhibitScreenOff({required bool on}) async { calledInhibitScreenOff = on; } } diff --git a/test/unit/button_player/image_button_player_test.dart b/test/unit/button_player/image_button_player_test.dart index bc128f5..9e04b28 100644 --- a/test/unit/button_player/image_button_player_test.dart +++ b/test/unit/button_player/image_button_player_test.dart @@ -24,7 +24,7 @@ class FakeActionPlayer extends ActionPlayer { @override void act(BuildContext context, ButtonPlayer button) => action.actCalls.add(button); - FakeActionPlayer(this.action) : assert(action != null); + FakeActionPlayer(this.action); } class FakeContext extends Fake implements BuildContext {} @@ -42,16 +42,12 @@ void main() { tearDown(() => ActionPlayer.registry = oldActionRegistry); group('ImageButtonPlayer', () { - FakeContext fakeContext; + late FakeContext fakeContext; setUp(() { fakeContext = FakeContext(); }); - test('constructor asserts on null', () { - expect(() => ImageButtonPlayer(null), throwsAssertionError); - }); - test('build creates a ImageButtonWidget', () { final button = ImageButton(FakeAction(), imageUri); final buttonPlayer = ButtonPlayer.wrap(button); @@ -63,27 +59,23 @@ void main() { }); group('ImageButtonWidget', () { - test('constructor asserts on null button', () { - expect(() => ImageButtonWidget(button: null), throwsAssertionError); - }); - testWidgets('tapping calls action.act()', (tester) async { final action = FakeAction(); final button = ImageButton(action, imageUri); final buttonPlayer = ButtonPlayer.wrap(button); - Widget widget; + Widget? widget; await tester.pumpWidget( MaterialApp( home: DefaultAssetBundle( bundle: TestAssetBundle(), child: Builder(builder: (context) { widget = buttonPlayer.build(context); - return widget; + return widget!; }), ), ), ); - expect(widget.key, ObjectKey(button)); + expect(widget!.key, ObjectKey(button)); expect(widget, isA()); expect((widget as ImageButtonWidget).button, same(buttonPlayer)); expect(action.actCalls.length, 0); diff --git a/test/unit/button_player/styled_button_player_test.dart b/test/unit/button_player/styled_button_player_test.dart index e6f2c9d..7ec3f10 100644 --- a/test/unit/button_player/styled_button_player_test.dart +++ b/test/unit/button_player/styled_button_player_test.dart @@ -25,7 +25,7 @@ class FakeActionPlayer extends ActionPlayer { @override void act(BuildContext context, ButtonPlayer button) => action.actCalls.add(button); - FakeActionPlayer(this.action) : assert(action != null); + FakeActionPlayer(this.action); } class FakeContext extends Fake implements BuildContext {} @@ -41,18 +41,14 @@ void main() { tearDown(() => ActionPlayer.registry = oldActionRegistry); group('StyledButtonPlayer', () { - FakeContext fakeContext; - Color color; + late FakeContext fakeContext; + Color? color; setUp(() { fakeContext = FakeContext(); color = Color(0x12ab4523); }); - test('constructor asserts on null', () { - expect(() => StyledButtonPlayer(null), throwsAssertionError); - }); - test('build creates a StyledButtonWidget', () { final styledButton = StyledButton(FakeAction(), backgroundColor: color); final buttonPlayer = ButtonPlayer.wrap(styledButton); @@ -64,26 +60,22 @@ void main() { }); group('StyledButtonWidget', () { - test('constructor asserts on null button', () { - expect(() => StyledButtonWidget(button: null), throwsAssertionError); - }); - testWidgets('tapping calls action.act()', (tester) async { final action = FakeAction(); final button = StyledButton(action); final buttonPlayer = ButtonPlayer.wrap(button); - Widget widget; + Widget? widget; await tester.pumpWidget( MaterialApp( home: Container( child: Builder(builder: (context) { widget = buttonPlayer.build(context); - return widget; + return widget!; }), ), ), ); - expect(widget.key, ObjectKey(button)); + expect(widget!.key, ObjectKey(button)); expect(widget, isA()); expect((widget as StyledButtonWidget).button, same(buttonPlayer)); expect(find.byType(Image), findsNothing); @@ -105,7 +97,7 @@ void main() { final button = StyledButton(action, foregroundImage: Uri.parse('assets/10x10-red.png')); final buttonPlayer = ButtonPlayer.wrap(button); - Widget widget; + late final Widget widget; await tester.pumpWidget( MaterialApp( home: DefaultAssetBundle( @@ -125,7 +117,10 @@ void main() { expect(imageFinder, findsOneWidget); // tap the button should call button.act() - await tester.tap(imageFinder); + // FIXME: The warnIfMissed had to be added in one update of Flutter, and + // it is not clear why it happens that it says the images is not being + // hit, but the action is called. + await tester.tap(imageFinder, warnIfMissed: false); await tester.pump(); expect(action.actCalls.length, 1); expect(action.actCalls.last, buttonPlayer); diff --git a/test/unit/button_player_test.dart b/test/unit/button_player_test.dart index 6b6b84f..39a5b4d 100644 --- a/test/unit/button_player_test.dart +++ b/test/unit/button_player_test.dart @@ -17,21 +17,21 @@ class FakeActionPlayer extends ActionPlayer { final FakeAction action; @override void act(BuildContext context, ButtonPlayer button) {} - FakeActionPlayer(this.action) : assert(action != null); + FakeActionPlayer(this.action); } class FakeButton extends Button { FakeButton() : super(FakeAction()); } -class FakeButtonPlayer extends ButtonPlayer { +class _FakeButtonPlayer extends ButtonPlayer { @override final FakeButton button; @override Widget build(BuildContext context) => Container(key: ObjectKey(button)); - FakeButtonPlayer(this.button) : super(button); + _FakeButtonPlayer(this.button) : super(button); } class FakeContext extends Fake implements BuildContext {} @@ -39,9 +39,9 @@ class FakeContext extends Fake implements BuildContext {} void main() { group('ButtonPlayer', () { final oldButtonRegistry = ButtonPlayer.registry; - FakeButton fakeButton; - FakeContext fakeContext; - Color color; + FakeButton? fakeButton; + late FakeContext fakeContext; + Color? color; setUp(() { fakeButton = FakeButton(); @@ -58,17 +58,16 @@ void main() { test('empty registry is empty', () { ButtonPlayer.registry = ButtonPlayerRegistry(); expect(ButtonPlayer.registry, isEmpty); - expect(() => ButtonPlayer.wrap(fakeButton), throwsAssertionError); + expect(() => ButtonPlayer.wrap(fakeButton!), throwsAssertionError); }); test('registration and base ButtonPlayer implementation works', () { - expect(() => FakeButtonPlayer(null), throwsAssertionError); ButtonPlayer.registry = ButtonPlayerRegistry(); ButtonPlayer.registry - .register(FakeButton, (b) => FakeButtonPlayer(b as FakeButton)); - final buttonPlayer = ButtonPlayer.wrap(fakeButton); + .register(FakeButton, (b) => _FakeButtonPlayer(b as FakeButton)); + final buttonPlayer = ButtonPlayer.wrap(fakeButton!); expect(buttonPlayer.backgroundColor, isNull); - expect(buttonPlayer.action.action, fakeButton.action); + expect(buttonPlayer.action.action, fakeButton!.action); final widget = buttonPlayer.build(fakeContext); expect(widget.key, ObjectKey(fakeButton)); }); diff --git a/test/unit/dynamic_dispatch_registry_test.dart b/test/unit/dynamic_dispatch_registry_test.dart index 0c38b98..d26fdf0 100644 --- a/test/unit/dynamic_dispatch_registry_test.dart +++ b/test/unit/dynamic_dispatch_registry_test.dart @@ -9,7 +9,7 @@ import 'package:lunofono_player/src/dynamic_dispatch_registry.dart'; class Base {} class Item extends Base { - int x; + int? x; } class NoSubClass {} @@ -17,7 +17,7 @@ class NoSubClass {} void main() { group('DynamicLibrary', () { group('from empty', () { - DynamicDispatchRegistry registry; + late DynamicDispatchRegistry registry; void baseFunction() {} void itemFunction1() {} void itemFunction2() {} diff --git a/test/unit/media_player/controller_registry_test.dart b/test/unit/media_player/controller_registry_test.dart index a003d74..e9dd7b4 100644 --- a/test/unit/media_player/controller_registry_test.dart +++ b/test/unit/media_player/controller_registry_test.dart @@ -1,7 +1,7 @@ @Tags(['unit', 'player']) import 'package:flutter/foundation.dart' show kIsWeb; -import 'package:flutter/material.dart' show BuildContext; +import 'package:flutter/material.dart' show BuildContext, Container, Widget; import 'package:test/test.dart'; @@ -10,23 +10,34 @@ import 'package:lunofono_bundle/lunofono_bundle.dart' import 'package:lunofono_player/src/media_player/controller_registry.dart'; import 'package:lunofono_player/src/media_player/single_medium_controller.dart'; -class FakeSingleMedium extends SingleMedium { - FakeSingleMedium(Uri resource) : super(resource); +class _FakeSingleMedium extends SingleMedium { + _FakeSingleMedium(Uri resource) : super(resource); +} + +class _FakeSingleMediumController extends SingleMediumController { + _FakeSingleMediumController() + : super(_FakeSingleMedium(Uri.parse('fake-single-medium'))); + @override + Future initialize(BuildContext context) => Future.value(Size(0, 0)); + @override + Widget build(BuildContext context) => Container(); } void main() { group('ControllerRegistry', () { test('default constructor', () { SingleMediumController f(SingleMedium medium, - {void Function(BuildContext) onMediumFinished}) => - null; - final fakeMedium = FakeSingleMedium(Uri.parse('fake-medium')); + {void Function(BuildContext)? onMediumFinished}) => + _FakeSingleMediumController(); + final fakeMedium = _FakeSingleMedium(Uri.parse('fake-medium')); final registry = ControllerRegistry(); expect(registry.isEmpty, isTrue); - final oldRegisteredFunction = registry.register(FakeSingleMedium, f); + final oldRegisteredFunction = registry.register(_FakeSingleMedium, f); expect(oldRegisteredFunction, isNull); expect(registry.isEmpty, isFalse); - final create = registry.getFunction(fakeMedium); + final SingleMediumController? Function(SingleMedium, + {void Function(BuildContext) onMediumFinished})? create = + registry.getFunction(fakeMedium); expect(create, f); }); @@ -34,7 +45,7 @@ void main() { expect(registry.isEmpty, isFalse); final audio = Audio(Uri.parse('fake-audio')); - var controller = registry.getFunction(audio)(audio); + var controller = registry.getFunction(audio)!(audio); // XXX: This is a hack that should be removed after #15 and #16 are // resolved. if (kIsWeb) { @@ -45,12 +56,12 @@ void main() { expect(controller.medium, audio); final image = Image(Uri.parse('fake-image')); - controller = registry.getFunction(image)(image); + controller = registry.getFunction(image)!(image); expect(controller, isA()); expect(controller.medium, image); final video = Video(Uri.parse('fake-video')); - controller = registry.getFunction(video)(video); + controller = registry.getFunction(video)!(video); expect(controller, isA()); expect(controller.medium, video); } diff --git a/test/unit/media_player/media_progress_indicator_test.dart b/test/unit/media_player/media_progress_indicator_test.dart index 3d06d3d..55d5461 100644 --- a/test/unit/media_player/media_progress_indicator_test.dart +++ b/test/unit/media_player/media_progress_indicator_test.dart @@ -8,14 +8,8 @@ import 'package:lunofono_player/src/media_player/media_progress_indicator.dart'; void main() { group('MediaProgressIndicator', () { - testWidgets('constructor asserts if visualizable is null', - (WidgetTester tester) async { - expect(() => MediaProgressIndicator(visualizable: null), - throwsAssertionError); - }); - Future testInnerWidgets(WidgetTester tester, - {@required IconData icon, @required bool visualizable}) async { + {required IconData icon, required bool visualizable}) async { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, diff --git a/test/unit/media_player/mocks.dart b/test/unit/media_player/mocks.dart new file mode 100644 index 0000000..7d47451 --- /dev/null +++ b/test/unit/media_player/mocks.dart @@ -0,0 +1,26 @@ +import 'package:mockito/annotations.dart' show GenerateMocks; + +import 'package:lunofono_bundle/lunofono_bundle.dart' + show Playlist, SingleMedium; + +import 'package:lunofono_player/src/media_player/multi_medium_state.dart' + show MultiMediumState; +import 'package:lunofono_player/src/media_player/playable_state.dart' + show PlayableState; +import 'package:lunofono_player/src/media_player/playlist_state.dart' + show PlaylistState; +import 'package:lunofono_player/src/media_player/single_medium_controller.dart' + show SingleMediumController; +import 'package:lunofono_player/src/media_player/single_medium_state.dart' + show SingleMediumState; + +@GenerateMocks([ + MultiMediumState, + PlayableState, + Playlist, + PlaylistState, + SingleMedium, + SingleMediumController, + SingleMediumState, +]) +void dummy() {} diff --git a/test/unit/media_player/multi_medium_player_test.dart b/test/unit/media_player/multi_medium_player_test.dart index 717e99d..81a49df 100644 --- a/test/unit/media_player/multi_medium_player_test.dart +++ b/test/unit/media_player/multi_medium_player_test.dart @@ -20,16 +20,12 @@ import '../../util/finders.dart' show findSubString; // https://github.com/flutter/flutter/issues/65324 void main() { group('MultiMediumPlayer', () { - MediaPlayerTester playerTester; + MediaPlayerTester? playerTester; tearDown(() => playerTester?.dispose()); - test('constructor asserts on null media', () { - expect(() => MultiMediumPlayer(medium: null), throwsAssertionError); - }); - Future testUnregisteredMedium( - WidgetTester tester, FakeSingleMedium medium) async { + WidgetTester tester, _FakeSingleMedium medium) async { // TODO: Second medium in a track is unregistered final player = MultiMediumPlayer( medium: MultiMedium.fromSingleMedium(medium), @@ -50,7 +46,7 @@ void main() { testWidgets( 'shows a MediaPlayerErrors if audible controller is not registered', (WidgetTester tester) async { - final medium = FakeAudibleSingleMedium( + final medium = _FakeAudibleSingleMedium( 'unregisteredAudibleMedium', size: Size(0.0, 0.0), ); @@ -60,7 +56,7 @@ void main() { testWidgets( 'shows a MediaPlayerErrors if visualizable controller is not registered', (WidgetTester tester) async { - final medium = FakeVisualizableSingleMedium( + final medium = _FakeVisualizableSingleMedium( 'unregisteredVisualizableMedium', size: Size(10.0, 10.0), ); @@ -68,19 +64,19 @@ void main() { }); Future testInitializationError( - WidgetTester tester, FakeSingleMedium medium) async { + WidgetTester tester, _FakeSingleMedium medium) async { // TODO: Second medium in a track is unregistered playerTester = MediaPlayerTester(tester, medium); - await playerTester.testInitializationDone(); - playerTester.expectErrorWidget(); - playerTester.expectPlayingStatus(finished: false); + await playerTester!.testInitializationDone(); + playerTester!.expectErrorWidget(); + playerTester!.expectPlayingStatus(finished: false); expect(findSubString(medium.info.exception.toString()), findsOneWidget); } testWidgets('initializes audible with error', (WidgetTester tester) async { final exception = Exception('Initialization Error'); - final medium = FakeAudibleSingleMedium('exceptionAudibleMedium', + final medium = _FakeAudibleSingleMedium('exceptionAudibleMedium', exception: exception); await testInitializationError(tester, medium); }); @@ -88,75 +84,76 @@ void main() { testWidgets('initializes visualizable with error', (WidgetTester tester) async { final exception = Exception('Initialization Error'); - final medium = FakeVisualizableSingleMedium('exceptionVisualizableMedium', + final medium = _FakeVisualizableSingleMedium( + 'exceptionVisualizableMedium', exception: exception); await testInitializationError(tester, medium); }); testWidgets('player should not be rotated for square visualizable media', (WidgetTester tester) async { - final notRotatedSquareMedium = FakeVisualizableSingleMedium( + final notRotatedSquareMedium = _FakeVisualizableSingleMedium( 'notRotatedSquareMedium', size: Size(10.0, 10.0), duration: Duration(seconds: 1), ); playerTester = MediaPlayerTester(tester, notRotatedSquareMedium); - await playerTester.testInitializationDone(); - playerTester.expectPlayerWidget(rotated: false); - playerTester.expectPlayingStatus(finished: false); + await playerTester!.testInitializationDone(); + playerTester!.expectPlayerWidget(rotated: false); + playerTester!.expectPlayingStatus(finished: false); }); testWidgets('player should not be rotated for portrait visualizable media', (WidgetTester tester) async { - final notRotatedPortraitMedium = FakeVisualizableSingleMedium( + final notRotatedPortraitMedium = _FakeVisualizableSingleMedium( 'notRotatedPortraitMedium', size: Size(10.0, 20.0), duration: Duration(seconds: 1), ); playerTester = MediaPlayerTester(tester, notRotatedPortraitMedium); - await playerTester.testInitializationDone(); - playerTester.expectPlayerWidget(rotated: false); - playerTester.expectPlayingStatus(finished: false); + await playerTester!.testInitializationDone(); + playerTester!.expectPlayerWidget(rotated: false); + playerTester!.expectPlayingStatus(finished: false); }); testWidgets('player should be rotated for landscape visualizable media', (WidgetTester tester) async { - final rotatedLandscapeMedium = FakeVisualizableSingleMedium( + final rotatedLandscapeMedium = _FakeVisualizableSingleMedium( 'rotatedLandscapeMedium', size: Size(20.0, 10.0), duration: Duration(seconds: 1), ); playerTester = MediaPlayerTester(tester, rotatedLandscapeMedium); - await playerTester.testInitializationDone(); - playerTester.expectPlayerWidget(rotated: true); - playerTester.expectPlayingStatus(finished: false); + await playerTester!.testInitializationDone(); + playerTester!.expectPlayerWidget(rotated: true); + playerTester!.expectPlayingStatus(finished: false); }); Future testPlayMediaUntilEnd( - WidgetTester tester, FakeSingleMedium medium) async { + WidgetTester tester, _FakeSingleMedium medium) async { playerTester = MediaPlayerTester(tester, medium); - await playerTester.testInitializationDone(); - playerTester.expectPlayerWidget(); - playerTester.expectPlayingStatus(finished: false); + await playerTester!.testInitializationDone(); + playerTester!.expectPlayerWidget(); + playerTester!.expectPlayingStatus(finished: false); // Wait until half of the media was played, it should keep playing await tester.pump(medium.info.duration ~/ 2); - playerTester.expectPlayerWidget(); - playerTester.expectPlayingStatus(finished: false); + playerTester!.expectPlayerWidget(); + playerTester!.expectPlayingStatus(finished: false); // Wait until the media stops playing by itself await tester.pump(medium.info.duration ~/ 2); - playerTester.expectPlayerWidget(); - playerTester.expectPlayingStatus(finished: true); + playerTester!.expectPlayerWidget(); + playerTester!.expectPlayingStatus(finished: true); } testWidgets('plays limited audible media until the end', (WidgetTester tester) async { - final medium = FakeAudibleSingleMedium( + final medium = _FakeAudibleSingleMedium( 'limitedAudibleMedium', size: Size(0.0, 0.0), duration: Duration(seconds: 1), @@ -166,7 +163,7 @@ void main() { testWidgets('plays limited visualizable media until the end', (WidgetTester tester) async { - final medium = FakeVisualizableSingleMedium( + final medium = _FakeVisualizableSingleMedium( 'limitedVisualizableMedium', size: Size(10.0, 10.0), duration: Duration(seconds: 1), @@ -176,22 +173,22 @@ void main() { testWidgets('plays unlimited media forever(ish, 10 days)', (WidgetTester tester) async { - final unlimitedMedium = FakeVisualizableSingleMedium('unlimitedMedium', + final unlimitedMedium = _FakeVisualizableSingleMedium('unlimitedMedium', size: Size(10.0, 10.0)); playerTester = MediaPlayerTester(tester, unlimitedMedium); - await playerTester.testInitializationDone(); - playerTester.expectPlayerWidget(); - playerTester.expectPlayingStatus(finished: false); + await playerTester!.testInitializationDone(); + playerTester!.expectPlayerWidget(); + playerTester!.expectPlayingStatus(finished: false); // Wait until half of the media was played, it should keep playing await tester.pump(Duration(days: 10)); - playerTester.expectPlayerWidget(); - playerTester.expectPlayingStatus(finished: false); + playerTester!.expectPlayerWidget(); + playerTester!.expectPlayingStatus(finished: false); }); testWidgets('tap stops while initializing', (WidgetTester tester) async { - final tapInitMedium = FakeVisualizableSingleMedium( + final tapInitMedium = _FakeVisualizableSingleMedium( 'tapInitMedium', size: Size(10.0, 10.0), duration: Duration(seconds: 1), @@ -201,45 +198,45 @@ void main() { // The player should be initializing await tester.pumpWidget( - playerTester.player, tapInitMedium.info.initDelay ~/ 2); - playerTester.expectInitializationWidget(); - playerTester.expectPlayingStatus(finished: false); + playerTester!.player, tapInitMedium.info.initDelay ~/ 2); + playerTester!.expectInitializationWidget(); + playerTester!.expectPlayingStatus(finished: false); // Tap and the reaction should reach the controller final widgetToTap = find.byType(CircularProgressIndicator); expect(widgetToTap, findsOneWidget); await tester.tap(widgetToTap); await tester.pump(); - playerTester.expectInitializationWidget(); - playerTester.expectPlayingStatus( - finished: false, stoppedTimes: 1, paused: true); + playerTester!.expectInitializationWidget(); + playerTester! + .expectPlayingStatus(finished: false, stoppedTimes: 1, paused: true); }); testWidgets('tap stops while playing', (WidgetTester tester) async { - final tapPlayMedium = FakeVisualizableSingleMedium( + final tapPlayMedium = _FakeVisualizableSingleMedium( 'PlaynitMedium', size: Size(10.0, 10.0), duration: Duration(seconds: 1), ); playerTester = MediaPlayerTester(tester, tapPlayMedium); - await playerTester.testInitializationDone(); - playerTester.expectPlayerWidget(); - playerTester.expectPlayingStatus(finished: false); + await playerTester!.testInitializationDone(); + playerTester!.expectPlayerWidget(); + playerTester!.expectPlayingStatus(finished: false); // Wait until half of the media was played, it should keep playing await tester.pump(tapPlayMedium.info.duration ~/ 2); - playerTester.expectPlayerWidget(); - playerTester.expectPlayingStatus(finished: false); + playerTester!.expectPlayerWidget(); + playerTester!.expectPlayingStatus(finished: false); // Tap and the player should stop var widgetToTap = find.byKey(tapPlayMedium.info.widgetKey); expect(widgetToTap, findsOneWidget); await tester.tap(widgetToTap); await tester.pump(); - playerTester.expectPlayerWidget(); - playerTester.expectPlayingStatus( - finished: false, stoppedTimes: 1, paused: true); + playerTester!.expectPlayerWidget(); + playerTester! + .expectPlayingStatus(finished: false, stoppedTimes: 1, paused: true); // Tap again should do nothing new (but to call the onMediaStopped // callback again). @@ -247,57 +244,57 @@ void main() { expect(widgetToTap, findsOneWidget); await tester.tap(widgetToTap); await tester.pump(); - playerTester.expectPlayerWidget(); - playerTester.expectPlayingStatus( - finished: false, stoppedTimes: 2, paused: true); + playerTester!.expectPlayerWidget(); + playerTester! + .expectPlayingStatus(finished: false, stoppedTimes: 2, paused: true); }); testWidgets('tap does nothing when playing is done', (WidgetTester tester) async { - final tapPlayDoneMedium = FakeVisualizableSingleMedium( + final tapPlayDoneMedium = _FakeVisualizableSingleMedium( 'tapPlayDoneMedium', size: Size(10.0, 10.0), duration: Duration(seconds: 1), ); playerTester = MediaPlayerTester(tester, tapPlayDoneMedium); - await playerTester.testInitializationDone(); - playerTester.expectPlayerWidget(); - playerTester.expectPlayingStatus(finished: false); + await playerTester!.testInitializationDone(); + playerTester!.expectPlayerWidget(); + playerTester!.expectPlayingStatus(finished: false); // Wait until the media stops playing by itself await tester.pump(tapPlayDoneMedium.info.duration); - playerTester.expectPlayerWidget(); - playerTester.expectPlayingStatus(finished: true); + playerTester!.expectPlayerWidget(); + playerTester!.expectPlayingStatus(finished: true); // Tap again should do nothing but to get a reaction final widgetToTap = find.byKey(tapPlayDoneMedium.info.widgetKey); expect(widgetToTap, findsOneWidget); await tester.tap(widgetToTap); await tester.pump(); - playerTester.expectPlayerWidget(); + playerTester!.expectPlayerWidget(); // In this case it should not be paused, because pause() is only called if // the medium didn't finished by itself. - playerTester.expectPlayingStatus(finished: true, stoppedTimes: 2); + playerTester!.expectPlayingStatus(finished: true, stoppedTimes: 2); }); testWidgets('initialization of visualizable multi-medium mainTrack', (WidgetTester tester) async { - final medium1 = FakeAudibleVisualizableSingleMedium( + final medium1 = _FakeAudibleVisualizableSingleMedium( 'medium1(audible, visualizable)', size: Size(20.0, 10.0), duration: Duration(seconds: 7), initDelay: Duration(seconds: 2), ); - final medium2 = FakeVisualizableSingleMedium( + final medium2 = _FakeVisualizableSingleMedium( 'medium2(visualizable)', size: Size(10.0, 10.0), duration: Duration(seconds: 1), initDelay: Duration(seconds: 1), ); - final medium3 = FakeVisualizableSingleMedium( + final medium3 = _FakeVisualizableSingleMedium( 'medium3(visualizable)', size: Size(10.0, 20.0), duration: Duration(seconds: 1), @@ -310,12 +307,12 @@ void main() { playerTester = MediaPlayerTester(tester, medium); - final mainTrack = List.from(medium.mainTrack.media - .map((m) => m as FakeSingleMedium)); + final mainTrack = List<_FakeSingleMedium>.from(medium.mainTrack.media + .map<_FakeSingleMedium>((m) => m as _FakeSingleMedium)); // The first time the player is pumped, it should be initializing. - await tester.pumpWidget(playerTester.player); - playerTester.expectMultiTrackIsInitialzing(); + await tester.pumpWidget(playerTester!.player); + playerTester!.expectMultiTrackIsInitialzing(); // Initialization (since it's done in parallel) should take the time it // takes to the medium with the maximum initialization time. @@ -338,34 +335,34 @@ void main() { }; await pump(minInit); - playerTester.expectMultiTrackIsInitialzing(); + playerTester!.expectMultiTrackIsInitialzing(); await pump(left ~/ 2); - playerTester.expectMultiTrackIsInitialzing(); + playerTester!.expectMultiTrackIsInitialzing(); await pump(left - Duration(milliseconds: 1)); - playerTester.expectMultiTrackIsInitialzing(); + playerTester!.expectMultiTrackIsInitialzing(); await pump(Duration(milliseconds: 1)); // Now the first medium should have started playing and not be finished - playerTester.expectPlayerWidget(mainMediumIndex: 0); - playerTester.expectPlayingStatus(mainMediumIndex: 0, finished: false); - expect(playerTester.mainControllers.first.isPlaying, isTrue); + playerTester!.expectPlayerWidget(mainMediumIndex: 0); + playerTester!.expectPlayingStatus(mainMediumIndex: 0, finished: false); + expect(playerTester!.mainControllers.first.isPlaying, isTrue); // And the following media should have not started nor finished - playerTester.mainTrackIndexes.skip(1).forEach((n) => playerTester + playerTester!.mainTrackIndexes.skip(1).forEach((n) => playerTester! .expectPlayingStatus(mainMediumIndex: n, finished: false)); - playerTester.mainTrackIndexes.skip(1).forEach( - (n) => expect(playerTester.mainControllers[n].isPlaying, isFalse)); + playerTester!.mainTrackIndexes.skip(1).forEach( + (n) => expect(playerTester!.mainControllers[n].isPlaying, isFalse)); }); testWidgets('plays a audible multi-medium mainTrack until the end', (WidgetTester tester) async { - final medium1 = FakeAudibleSingleMedium( + final medium1 = _FakeAudibleSingleMedium( 'medium1(audible)', size: Size(0.0, 0.0), duration: Duration(seconds: 1), ); - final medium2 = FakeAudibleVisualizableSingleMedium( + final medium2 = _FakeAudibleVisualizableSingleMedium( 'medium2(audible, visualizable)', size: Size(10.0, 20.0), duration: Duration(seconds: 7), @@ -376,24 +373,24 @@ void main() { ); playerTester = MediaPlayerTester(tester, multiAudibleMainTrackMedium); - await playerTester.testMultiTrackPlay(untilFinished: true); + await playerTester!.testMultiTrackPlay(untilFinished: true); }); testWidgets('plays a visualizable multi-medium mainTrack until the end', (WidgetTester tester) async { - final medium1 = FakeAudibleVisualizableSingleMedium( + final medium1 = _FakeAudibleVisualizableSingleMedium( 'medium1(audible, visualizable)', size: Size(20.0, 10.0), duration: Duration(seconds: 7), ); - final medium2 = FakeVisualizableSingleMedium( + final medium2 = _FakeVisualizableSingleMedium( 'medium2(visualizable)', size: Size(10.0, 10.0), duration: Duration(seconds: 1), ); - final medium3 = FakeVisualizableSingleMedium( + final medium3 = _FakeVisualizableSingleMedium( 'medium3(visualizable)', size: Size(10.0, 20.0), duration: Duration(seconds: 1), @@ -405,13 +402,13 @@ void main() { playerTester = MediaPlayerTester(tester, multiVisualizableMainTrackMedium); - await playerTester.testMultiTrackPlay(untilFinished: true); + await playerTester!.testMultiTrackPlay(untilFinished: true); }); testWidgets( 'plays an audible multi-medium mainTrack with the same medium 2 times', (WidgetTester tester) async { - final medium1 = FakeAudibleSingleMedium( + final medium1 = _FakeAudibleSingleMedium( 'medium1(audible)', size: Size(0.0, 0.0), duration: Duration(seconds: 1), @@ -422,13 +419,13 @@ void main() { ); playerTester = MediaPlayerTester(tester, medium); - await playerTester.testMultiTrackPlay(untilFinished: true); + await playerTester!.testMultiTrackPlay(untilFinished: true); }); testWidgets( 'plays a visualizable multi-medium mainTrack with the same medium 2 times', (WidgetTester tester) async { - final medium1 = FakeVisualizableSingleMedium( + final medium1 = _FakeVisualizableSingleMedium( 'medium1(audible)', size: Size(0.0, 0.0), duration: Duration(seconds: 1), @@ -439,24 +436,24 @@ void main() { ); playerTester = MediaPlayerTester(tester, medium); - await playerTester.testMultiTrackPlay(untilFinished: true); + await playerTester!.testMultiTrackPlay(untilFinished: true); }); testWidgets('tap stops while playing the second medium', (WidgetTester tester) async { - final medium1 = FakeAudibleVisualizableSingleMedium( + final medium1 = _FakeAudibleVisualizableSingleMedium( 'medium1(audible, visualizable)', size: Size(20.0, 10.0), duration: Duration(seconds: 7), ); - final medium2 = FakeVisualizableSingleMedium( + final medium2 = _FakeVisualizableSingleMedium( 'medium2(visualizable)', size: Size(10.0, 10.0), duration: Duration(seconds: 1), ); - final medium3 = FakeVisualizableSingleMedium( + final medium3 = _FakeVisualizableSingleMedium( 'medium3(visualizable)', size: Size(10.0, 20.0), duration: Duration(seconds: 1), @@ -468,44 +465,45 @@ void main() { playerTester = MediaPlayerTester(tester, multiVisualizableMainTrackMedium); - await playerTester.testMultiTrackPlay(untilMainIndex: 1); // plays medium1 + await playerTester! + .testMultiTrackPlay(untilMainIndex: 1); // plays medium1 // Now medim2 (index 1) should be playing (and the only one), prev media // finished and next media not finished - playerTester.expectPlayerWidget(mainMediumIndex: 1); - playerTester.expectPlayingStatus( + playerTester!.expectPlayerWidget(mainMediumIndex: 1); + playerTester!.expectPlayingStatus( mainMediumIndex: 0, finished: true, stoppedTimes: 0); - expect(playerTester.mainControllers[0].isPlaying, isFalse); - playerTester.expectPlayingStatus(mainMediumIndex: 1, finished: false); - expect(playerTester.mainControllers[1].isPlaying, isTrue); - playerTester.expectPlayingStatus(mainMediumIndex: 2, finished: false); - expect(playerTester.mainControllers[2].isPlaying, isFalse); + expect(playerTester!.mainControllers[0].isPlaying, isFalse); + playerTester!.expectPlayingStatus(mainMediumIndex: 1, finished: false); + expect(playerTester!.mainControllers[1].isPlaying, isTrue); + playerTester!.expectPlayingStatus(mainMediumIndex: 2, finished: false); + expect(playerTester!.mainControllers[2].isPlaying, isFalse); // Play medium2 halfway await tester.pump(medium2.info.duration ~/ 2); - playerTester.expectPlayerWidget(mainMediumIndex: 1); - playerTester.expectPlayingStatus( + playerTester!.expectPlayerWidget(mainMediumIndex: 1); + playerTester!.expectPlayingStatus( mainMediumIndex: 0, finished: true, stoppedTimes: 0); - playerTester.expectPlayingStatus(mainMediumIndex: 1, finished: false); - expect(playerTester.mainControllers[1].isPlaying, isTrue); - playerTester.expectPlayingStatus(mainMediumIndex: 2, finished: false); + playerTester!.expectPlayingStatus(mainMediumIndex: 1, finished: false); + expect(playerTester!.mainControllers[1].isPlaying, isTrue); + playerTester!.expectPlayingStatus(mainMediumIndex: 2, finished: false); // Tap and the player should stop var widgetToTap = find.byKey(medium2.info.widgetKey); expect(widgetToTap, findsOneWidget); await tester.tap(widgetToTap); await tester.pump(); - playerTester.expectPlayerWidget(mainMediumIndex: 1); + playerTester!.expectPlayerWidget(mainMediumIndex: 1); // medium1 should be finished and the MultiMediumPlayer should have // stopped - playerTester.expectPlayingStatus( + playerTester!.expectPlayingStatus( mainMediumIndex: 0, finished: true, stoppedTimes: 1); // medium2 and medium3 should NOT be finished (and the MultiMediumPlayer // should have stopped). medium2's controller should have received the // stop reaction. - playerTester.expectPlayingStatus( + playerTester!.expectPlayingStatus( mainMediumIndex: 1, finished: false, stoppedTimes: 1, paused: true); - playerTester.expectPlayingStatus( + playerTester!.expectPlayingStatus( mainMediumIndex: 2, finished: false, stoppedTimes: 1); }); }); @@ -530,17 +528,15 @@ class MediaPlayerTester { // Automatically initialized final ControllerRegistry originalRegistry; - final mainControllers = []; - Widget player; + final mainControllers = <_FakeSingleMediumController>[]; + late Widget player; var playerHasStoppedTimes = 0; // Constant final playerKey = GlobalKey(debugLabel: 'playerKey'); MediaPlayerTester(this.tester, Medium medium) - : assert(tester != null), - assert(medium != null), - assert(medium is SingleMedium || medium is MultiMedium), + : assert(medium is SingleMedium || medium is MultiMedium), originalRegistry = ControllerRegistry.instance, medium = medium is SingleMedium ? MultiMedium.fromSingleMedium(medium) @@ -558,18 +554,18 @@ class MediaPlayerTester { void _registerControllers() { SingleMediumController createController(SingleMedium medium, - {void Function(BuildContext) onMediumFinished}) { - final fakeMedium = medium as FakeSingleMedium; - final c = FakeSingleMediumController( + {void Function(BuildContext)? onMediumFinished}) { + final fakeMedium = medium as _FakeSingleMedium; + final c = _FakeSingleMediumController( fakeMedium, onMediumFinished, fakeMedium.info.widgetKey); mainControllers.add(c); return c; } final registry = ControllerRegistry(); - registry.register(FakeAudibleSingleMedium, createController); - registry.register(FakeVisualizableSingleMedium, createController); - registry.register(FakeAudibleVisualizableSingleMedium, createController); + registry.register(_FakeAudibleSingleMedium, createController); + registry.register(_FakeVisualizableSingleMedium, createController); + registry.register(_FakeAudibleVisualizableSingleMedium, createController); ControllerRegistry.instance = registry; } @@ -587,13 +583,13 @@ class MediaPlayerTester { ); } - FakeSingleMedium getMainMediumAt(int index) { - assert(index != null && index >= 0); - return medium.mainTrack.media[index] as FakeSingleMedium; + _FakeSingleMedium getMainMediumAt(int index) { + assert(index >= 0); + return medium.mainTrack.media[index] as _FakeSingleMedium; } - FakeSingleMediumController getMainControllerAt(int index) { - assert(index != null && index >= 0); + _FakeSingleMediumController? getMainControllerAt(int index) { + assert(index >= 0); return index < mainControllers.length ? mainControllers[index] : null; } @@ -628,9 +624,9 @@ class MediaPlayerTester { /// [untilFinished] is used, it must be true and it is an alias for /// [untilMainIndex] = mainTrack.media.length. Future testMultiTrackPlay( - {int untilMainIndex, bool untilFinished}) async { - final mainTrack = List.from(medium.mainTrack.media - .map((m) => m as FakeSingleMedium)); + {int? untilMainIndex, bool? untilFinished}) async { + final mainTrack = List<_FakeSingleMedium>.from(medium.mainTrack.media + .map<_FakeSingleMedium>((m) => m as _FakeSingleMedium)); assert(untilMainIndex != null || untilFinished == true); if (untilFinished == true) { @@ -660,7 +656,7 @@ class MediaPlayerTester { // Now the first media should be playing and we check all media plays in // sequence. - for (var currentIndex = 0; currentIndex < untilMainIndex; currentIndex++) { + for (var currentIndex = 0; currentIndex < untilMainIndex!; currentIndex++) { final current = mainTrack[currentIndex]; final prev = mainTrackIndexes.take(currentIndex); final next = mainTrackIndexes.skip(currentIndex + 1); @@ -695,9 +691,9 @@ class MediaPlayerTester { } } - void _expectMediumWidget(FakeSingleMedium expectedMedium) { + void _expectMediumWidget(_FakeSingleMedium? expectedMedium) { for (final m in medium.mainTrack.media) { - expect(find.byKey((m as FakeSingleMedium).info.widgetKey), + expect(find.byKey((m as _FakeSingleMedium).info.widgetKey), identical(m, expectedMedium) ? findsOneWidget : findsNothing); } } @@ -714,10 +710,7 @@ class MediaPlayerTester { void expectInitializationWidget({int mainMediumIndex = 0}) { final currentMedium = getMainMediumAt(mainMediumIndex); - final currentController = getMainControllerAt(mainMediumIndex); - assert(currentMedium != null); - assert(currentController != null); - assert(currentController.medium != null); + final currentController = getMainControllerAt(mainMediumIndex)!; expect(currentController.medium.resource, currentMedium.resource); expect(find.byKey(playerKey), findsOneWidget); expect(find.byType(CircularProgressIndicator), findsOneWidget); @@ -730,7 +723,7 @@ class MediaPlayerTester { void _expectPlayerInitializationDone({int mainMediumIndex = 0}) { final currentMedium = getMainMediumAt(mainMediumIndex); - final currentController = getMainControllerAt(mainMediumIndex); + final currentController = getMainControllerAt(mainMediumIndex)!; expect(currentController.medium.resource, currentMedium.resource); expect(find.byType(CircularProgressIndicator), findsNothing); expect(find.byKey(playerKey), findsOneWidget); @@ -744,7 +737,7 @@ class MediaPlayerTester { expect(currentMedium.info.size, isNull); } - void expectPlayerWidget({int mainMediumIndex = 0, bool rotated}) { + void expectPlayerWidget({int mainMediumIndex = 0, bool? rotated}) { final currentMedium = getMainMediumAt(mainMediumIndex); _expectPlayerInitializationDone(mainMediumIndex: mainMediumIndex); _expectMediumWidget(currentMedium); @@ -758,8 +751,8 @@ class MediaPlayerTester { void expectPlayingStatus({ int mainMediumIndex = 0, - @required bool finished, - int stoppedTimes, + required bool finished, + int? stoppedTimes, bool paused = false, }) { // TODO: add check for isPlaying. @@ -777,18 +770,18 @@ class MediaPlayerTester { } } -class SingleMediumInfo { - final Size size; +class _SingleMediumInfo { + final Size? size; final Duration duration; final Duration initDelay; - final Exception exception; + final Exception? exception; final Key widgetKey; - SingleMediumInfo( + _SingleMediumInfo( String location, { this.size, this.exception, - Duration duration, - Duration initDelay, + Duration? duration, + Duration? initDelay, }) : assert(exception != null && size == null || exception == null && size != null), initDelay = initDelay ?? const Duration(seconds: 1), @@ -796,16 +789,16 @@ class SingleMediumInfo { widgetKey = GlobalKey(debugLabel: 'widgetKey(${location}'); } -abstract class FakeSingleMedium extends SingleMedium { - final SingleMediumInfo info; - FakeSingleMedium( +abstract class _FakeSingleMedium extends SingleMedium { + final _SingleMediumInfo info; + _FakeSingleMedium( String location, { - Duration maxDuration, - Size size, - Exception exception, - Duration duration, - Duration initDelay, - }) : info = SingleMediumInfo(location, + Duration? maxDuration, + Size? size, + Exception? exception, + Duration? duration, + Duration? initDelay, + }) : info = _SingleMediumInfo(location, size: size, exception: exception, duration: duration, @@ -813,14 +806,14 @@ abstract class FakeSingleMedium extends SingleMedium { super(Uri.parse(location), maxDuration: maxDuration); } -class FakeAudibleSingleMedium extends FakeSingleMedium implements Audible { - FakeAudibleSingleMedium( +class _FakeAudibleSingleMedium extends _FakeSingleMedium implements Audible { + _FakeAudibleSingleMedium( String location, { - Duration maxDuration, - Size size, - Exception exception, - Duration duration, - Duration initDelay, + Duration? maxDuration, + Size? size, + Exception? exception, + Duration? duration, + Duration? initDelay, }) : super(location, maxDuration: maxDuration, size: size, @@ -829,15 +822,15 @@ class FakeAudibleSingleMedium extends FakeSingleMedium implements Audible { initDelay: initDelay); } -class FakeVisualizableSingleMedium extends FakeSingleMedium +class _FakeVisualizableSingleMedium extends _FakeSingleMedium implements Visualizable { - FakeVisualizableSingleMedium( + _FakeVisualizableSingleMedium( String location, { - Duration maxDuration, - Size size, - Exception exception, - Duration duration, - Duration initDelay, + Duration? maxDuration, + Size? size, + Exception? exception, + Duration? duration, + Duration? initDelay, }) : super(location, maxDuration: maxDuration, size: size, @@ -846,15 +839,15 @@ class FakeVisualizableSingleMedium extends FakeSingleMedium initDelay: initDelay); } -class FakeAudibleVisualizableSingleMedium extends FakeSingleMedium +class _FakeAudibleVisualizableSingleMedium extends _FakeSingleMedium implements Audible, Visualizable { - FakeAudibleVisualizableSingleMedium( + _FakeAudibleVisualizableSingleMedium( String location, { - Duration maxDuration, - Size size, - Exception exception, - Duration duration, - Duration initDelay, + Duration? maxDuration, + Size? size, + Exception? exception, + Duration? duration, + Duration? initDelay, }) : super(location, maxDuration: maxDuration, size: size, @@ -863,12 +856,12 @@ class FakeAudibleVisualizableSingleMedium extends FakeSingleMedium initDelay: initDelay); } -class FakeSingleMediumController extends Fake +class _FakeSingleMediumController extends Fake implements SingleMediumController { // Internal state - Timer _initTimer; + Timer? _initTimer; bool get isInitializing => _initTimer?.isActive ?? false; - Timer _playingTimer; + Timer? _playingTimer; bool get isPlaying => _playingTimer?.isActive ?? false; final _initCompleter = Completer(); // State to do checks @@ -880,26 +873,26 @@ class FakeSingleMediumController extends Fake bool _isPaused = false; bool get isPaused => _isPaused; - void Function(BuildContext) playerOnMediaStopped; + void Function(BuildContext)? playerOnMediaStopped; @override - FakeSingleMedium medium; + _FakeSingleMedium medium; @override Key widgetKey; - FakeSingleMediumController( + _FakeSingleMediumController( this.medium, this.playerOnMediaStopped, this.widgetKey, - ) : assert(medium != null); + ); @override Future initialize(BuildContext context) { _initTimer = Timer(medium.info.initDelay, () { if (initError) { try { - throw medium.info.exception; + throw medium.info.exception!; } catch (e, stack) { _initCompleter.completeError(e, stack); } @@ -939,9 +932,9 @@ class FakeSingleMediumController extends Fake @override void Function(BuildContext) get onMediumFinished => (BuildContext context) { _finishedTimes++; - playerOnMediaStopped(context); + playerOnMediaStopped!(context); }; @override - Widget build(BuildContext context) => Container(key: widgetKey); + Widget build(BuildContext context) => Text('FakeWidget', key: widgetKey); } diff --git a/test/unit/media_player/multi_medium_state_test.dart b/test/unit/media_player/multi_medium_state_test.dart index 782fd8b..6335aa9 100644 --- a/test/unit/media_player/multi_medium_state_test.dart +++ b/test/unit/media_player/multi_medium_state_test.dart @@ -45,14 +45,6 @@ void main() { ), ); - group('constructor', () { - group('asserts on', () { - test('null multimedium', () { - expect(() => MultiMediumState(null), throwsAssertionError); - }); - }); - }); - test('play/pause cycle works with main track only', () async { var finished = false; final state = MultiMediumState(audibleMultiMedium, @@ -61,7 +53,7 @@ void main() { expect(state.isInitialized, isFalse); expect(state.backgroundTrackState, isEmpty); state.mainTrackState.mediaState - .forEach((s) => expect(s.controller.asFake.calls, isEmpty)); + .forEach((s) => expect(s.controller.asFake!.calls, isEmpty)); var notifyCalled = false; final updateNotifyCalled = () => notifyCalled = true; @@ -71,12 +63,13 @@ void main() { expect(finished, isFalse); expect(state.isInitialized, isTrue); expect(state.backgroundTrackState, isEmpty); - expect(state.mainTrackState.current.controller.asFake.calls, + expect(state.mainTrackState.current!.controller.asFake!.calls, ['initialize', 'play']); - expect(state.mainTrackState.last.controller.asFake.calls, ['initialize']); + expect( + state.mainTrackState.last!.controller.asFake!.calls, ['initialize']); notifyCalled = true; expect(notifyCalled, isTrue); - final first = state.mainTrackState.current; + final first = state.mainTrackState.current!; // Pause notifyCalled = false; @@ -84,9 +77,10 @@ void main() { expect(finished, isFalse); expect(state.isInitialized, isTrue); expect(state.backgroundTrackState, isEmpty); - expect(state.mainTrackState.current.controller.asFake.calls, + expect(state.mainTrackState.current!.controller.asFake!.calls, ['initialize', 'play', 'pause']); - expect(state.mainTrackState.last.controller.asFake.calls, ['initialize']); + expect( + state.mainTrackState.last!.controller.asFake!.calls, ['initialize']); notifyCalled = true; expect(notifyCalled, isTrue); @@ -96,42 +90,45 @@ void main() { expect(finished, isFalse); expect(state.isInitialized, isTrue); expect(state.backgroundTrackState, isEmpty); - expect(state.mainTrackState.current.controller.asFake.calls, + expect(state.mainTrackState.current!.controller.asFake!.calls, ['initialize', 'play', 'pause', 'play']); - expect(state.mainTrackState.last.controller.asFake.calls, ['initialize']); + expect( + state.mainTrackState.last!.controller.asFake!.calls, ['initialize']); notifyCalled = true; expect(notifyCalled, isTrue); state.addListener(updateNotifyCalled); // First medium finishes notifyCalled = false; - state.mainTrackState.current.controller.onMediumFinished(_FakeContext()); + state.mainTrackState.current!.controller! + .onMediumFinished!(_FakeContext()); expect(notifyCalled, isFalse); state.removeListener(updateNotifyCalled); // Second (and last) medium finishes, onMediumFinished should be called. - state.mainTrackState.current.controller.onMediumFinished(_FakeContext()); + state.mainTrackState.current!.controller! + .onMediumFinished!(_FakeContext()); expect(notifyCalled, isFalse); expect(finished, isTrue); expect(state.isInitialized, isTrue); expect(state.backgroundTrackState, isEmpty); expect(state.mainTrackState.current, isNull); - expect(first.controller.asFake.calls, + expect(first.controller.asFake!.calls, ['initialize', 'play', 'pause', 'play']); - expect(state.mainTrackState.last.controller.asFake.calls, + expect(state.mainTrackState.last!.controller.asFake!.calls, ['initialize', 'play']); await state.dispose(); - expect(first.controller.asFake.calls, + expect(first.controller.asFake!.calls, ['initialize', 'play', 'pause', 'play', 'dispose']); - expect(state.mainTrackState.last.controller.asFake.calls, + expect(state.mainTrackState.last!.controller.asFake!.calls, ['initialize', 'play', 'dispose']); }); group('play cycle works with main and background track', () { - bool finished; - int notifyCalls; + late bool finished; + late int notifyCalls; setUp(() { finished = false; @@ -147,9 +144,9 @@ void main() { expect(state.isInitialized, isFalse); expect(state.backgroundTrackState, isNotEmpty); state.mainTrackState.mediaState - .forEach((s) => expect(s.controller.asFake.calls, isEmpty)); + .forEach((s) => expect(s.controller.asFake!.calls, isEmpty)); state.backgroundTrackState.mediaState - .forEach((s) => expect(s.controller.asFake.calls, isEmpty)); + .forEach((s) => expect(s.controller.asFake!.calls, isEmpty)); var notifyCalled = false; final checkInitialized = () { @@ -161,13 +158,13 @@ void main() { expect(finished, isFalse); expect(state.isInitialized, isTrue); expect(state.backgroundTrackState, isNotEmpty); - expect(state.mainTrackState.current.controller.asFake.calls, + expect(state.mainTrackState.current!.controller.asFake!.calls, ['initialize', 'play']); - expect( - state.mainTrackState.last.controller.asFake.calls, ['initialize']); - expect(state.backgroundTrackState.current.controller.asFake.calls, + expect(state.mainTrackState.last!.controller.asFake!.calls, + ['initialize']); + expect(state.backgroundTrackState.current!.controller.asFake!.calls, ['initialize', 'play']); - expect(state.backgroundTrackState.last.controller.asFake.calls, + expect(state.backgroundTrackState.last!.controller.asFake!.calls, ['initialize']); expect(notifyCalled, isTrue); @@ -175,94 +172,94 @@ void main() { return state; } - void testFirstMediaPlayed(MultiMediumState state) async { - final firstMain = state.mainTrackState.current; - final firstBack = state.backgroundTrackState.current; + Future testFirstMediaPlayed(MultiMediumState state) async { + final firstMain = state.mainTrackState.current!; + final firstBack = state.backgroundTrackState.current!; // First main medium finishes - state.mainTrackState.current.controller - .onMediumFinished(_FakeContext()); + state.mainTrackState.current!.controller! + .onMediumFinished!(_FakeContext()); expect(notifyCalls, 0); expect(state.mainTrackState.current, same(state.mainTrackState.last)); - expect(firstMain.controller.asFake.calls, ['initialize', 'play']); - expect(state.mainTrackState.last.controller.asFake.calls, + expect(firstMain.controller.asFake!.calls, ['initialize', 'play']); + expect(state.mainTrackState.last!.controller.asFake!.calls, ['initialize', 'play']); expect(state.backgroundTrackState.current, same(firstBack)); - expect(firstBack.controller.asFake.calls, ['initialize', 'play']); - expect(state.backgroundTrackState.last.controller.asFake.calls, + expect(firstBack.controller.asFake!.calls, ['initialize', 'play']); + expect(state.backgroundTrackState.last!.controller.asFake!.calls, ['initialize']); // First background medium finishes - state.backgroundTrackState.current.controller - .onMediumFinished(_FakeContext()); + state.backgroundTrackState.current!.controller! + .onMediumFinished!(_FakeContext()); expect(notifyCalls, 0); expect(state.mainTrackState.current, same(state.mainTrackState.last)); - expect(firstMain.controller.asFake.calls, ['initialize', 'play']); - expect(state.mainTrackState.last.controller.asFake.calls, + expect(firstMain.controller.asFake!.calls, ['initialize', 'play']); + expect(state.mainTrackState.last!.controller.asFake!.calls, ['initialize', 'play']); expect(state.backgroundTrackState.current, same(state.backgroundTrackState.last)); - expect(firstBack.controller.asFake.calls, ['initialize', 'play']); - expect(state.backgroundTrackState.last.controller.asFake.calls, + expect(firstBack.controller.asFake!.calls, ['initialize', 'play']); + expect(state.backgroundTrackState.last!.controller.asFake!.calls, ['initialize', 'play']); } test('when background track finishes first', () async { final state = await testInitialize(); - final firstMain = state.mainTrackState.current; - final firstBack = state.backgroundTrackState.current; + final firstMain = state.mainTrackState.current!; + final firstBack = state.backgroundTrackState.current!; state.addListener(updateNotifyCalled); await testFirstMediaPlayed(state); // Second background medium finishes - state.backgroundTrackState.current.controller - .onMediumFinished(_FakeContext()); + state.backgroundTrackState.current!.controller! + .onMediumFinished!(_FakeContext()); expect(notifyCalls, 0); expect(state.mainTrackState.current, same(state.mainTrackState.last)); - expect(firstMain.controller.asFake.calls, ['initialize', 'play']); - expect(state.mainTrackState.last.controller.asFake.calls, + expect(firstMain.controller.asFake!.calls, ['initialize', 'play']); + expect(state.mainTrackState.last!.controller.asFake!.calls, ['initialize', 'play']); expect(state.backgroundTrackState.current, isNull); - expect(firstBack.controller.asFake.calls, ['initialize', 'play']); - expect(state.backgroundTrackState.last.controller.asFake.calls, + expect(firstBack.controller.asFake!.calls, ['initialize', 'play']); + expect(state.backgroundTrackState.last!.controller.asFake!.calls, ['initialize', 'play']); state.removeListener(updateNotifyCalled); // Second (and last) main medium finishes, onMediumFinished should be // called. - state.mainTrackState.current.controller - .onMediumFinished(_FakeContext()); + state.mainTrackState.current!.controller! + .onMediumFinished!(_FakeContext()); expect(notifyCalls, 0); expect(finished, isTrue); expect(state.isInitialized, isTrue); expect(state.backgroundTrackState, isNotEmpty); expect(state.mainTrackState.current, isNull); - expect(firstMain.controller.asFake.calls, ['initialize', 'play']); - expect(state.mainTrackState.last.controller.asFake.calls, + expect(firstMain.controller.asFake!.calls, ['initialize', 'play']); + expect(state.mainTrackState.last!.controller.asFake!.calls, ['initialize', 'play']); expect(state.backgroundTrackState.current, isNull); - expect(firstBack.controller.asFake.calls, ['initialize', 'play']); - expect(state.backgroundTrackState.last.controller.asFake.calls, + expect(firstBack.controller.asFake!.calls, ['initialize', 'play']); + expect(state.backgroundTrackState.last!.controller.asFake!.calls, ['initialize', 'play']); await state.dispose(); - expect(firstMain.controller.asFake.calls, + expect(firstMain.controller.asFake!.calls, ['initialize', 'play', 'dispose']); - expect(state.mainTrackState.last.controller.asFake.calls, + expect(state.mainTrackState.last!.controller.asFake!.calls, ['initialize', 'play', 'dispose']); - expect(firstBack.controller.asFake.calls, + expect(firstBack.controller.asFake!.calls, ['initialize', 'play', 'dispose']); - expect(state.backgroundTrackState.last.controller.asFake.calls, + expect(state.backgroundTrackState.last!.controller.asFake!.calls, ['initialize', 'play', 'dispose']); }); test('when main track finishes first', () async { final state = await testInitialize(); - final firstMain = state.mainTrackState.current; - final firstBack = state.backgroundTrackState.current; + final firstMain = state.mainTrackState.current!; + final firstBack = state.backgroundTrackState.current!; state.addListener(updateNotifyCalled); @@ -270,32 +267,32 @@ void main() { // Second (and last) main medium finishes, onMediumFinished should be // called. - state.mainTrackState.current.controller - .onMediumFinished(_FakeContext()); + state.mainTrackState.current!.controller! + .onMediumFinished!(_FakeContext()); expect(notifyCalls, 0); expect(finished, isTrue); expect(state.isInitialized, isTrue); expect(state.backgroundTrackState, isNotEmpty); expect(state.mainTrackState.current, isNull); - expect(firstMain.controller.asFake.calls, ['initialize', 'play']); - expect(state.mainTrackState.last.controller.asFake.calls, + expect(firstMain.controller.asFake!.calls, ['initialize', 'play']); + expect(state.mainTrackState.last!.controller.asFake!.calls, ['initialize', 'play']); expect(state.backgroundTrackState.current, state.backgroundTrackState.last); - expect(firstBack.controller.asFake.calls, ['initialize', 'play']); - expect(state.backgroundTrackState.last.controller.asFake.calls, + expect(firstBack.controller.asFake!.calls, ['initialize', 'play']); + expect(state.backgroundTrackState.last!.controller.asFake!.calls, ['initialize', 'play', 'pause']); state.removeListener(updateNotifyCalled); await state.dispose(); - expect(firstMain.controller.asFake.calls, + expect(firstMain.controller.asFake!.calls, ['initialize', 'play', 'dispose']); - expect(state.mainTrackState.last.controller.asFake.calls, + expect(state.mainTrackState.last!.controller.asFake!.calls, ['initialize', 'play', 'dispose']); - expect(firstBack.controller.asFake.calls, + expect(firstBack.controller.asFake!.calls, ['initialize', 'play', 'dispose']); - expect(state.backgroundTrackState.last.controller.asFake.calls, + expect(state.backgroundTrackState.last!.controller.asFake!.calls, ['initialize', 'play', 'pause', 'dispose']); }); }); @@ -393,15 +390,14 @@ void main() { class _FakeContext extends Fake implements BuildContext {} class _SingleMediumInfo { - final Size size; - final Exception exception; + final Size? size; + final Exception? exception; final Key widgetKey; _SingleMediumInfo( String location, { this.size, this.exception, - }) : assert(location != null), - assert(exception != null && size == null || + }) : assert(exception != null && size == null || exception == null && size != null), widgetKey = GlobalKey(debugLabel: 'widgetKey(${location}'); } @@ -410,8 +406,8 @@ abstract class _FakeSingleMedium extends SingleMedium { final _SingleMediumInfo info; _FakeSingleMedium( String location, { - Size size, - Exception exception, + Size? size, + Exception? exception, }) : info = _SingleMediumInfo(location, size: size, exception: exception), super(Uri.parse(location)); } @@ -419,8 +415,8 @@ abstract class _FakeSingleMedium extends SingleMedium { class _FakeAudibleSingleMedium extends _FakeSingleMedium implements Audible { _FakeAudibleSingleMedium( String location, { - Size size, - Exception exception, + Size? size, + Exception? exception, }) : super(location, size: size, exception: exception); } @@ -428,8 +424,8 @@ class _FakeVisualizableSingleMedium extends _FakeSingleMedium implements Visualizable { _FakeVisualizableSingleMedium( String location, { - Size size, - Exception exception, + Size? size, + Exception? exception, }) : super(location, size: size, exception: exception); } @@ -441,7 +437,7 @@ class _FakeVisualizableBackgroundMultiMediumTrack void _registerControllers(ControllerRegistry registry) { SingleMediumController createController(SingleMedium medium, - {void Function(BuildContext) onMediumFinished}) { + {void Function(BuildContext)? onMediumFinished}) { final fakeMedium = medium as _FakeSingleMedium; final c = _FakeSingleMediumController(fakeMedium, widgetKey: fakeMedium.info.widgetKey, @@ -462,21 +458,20 @@ class _FakeSingleMediumController extends Fake Key widgetKey; @override - void Function(BuildContext context) onMediumFinished; + void Function(BuildContext context)? onMediumFinished; final calls = []; _FakeSingleMediumController( this.medium, { - Key widgetKey, + Key? widgetKey, this.onMediumFinished, - }) : assert(medium != null), - widgetKey = widgetKey ?? GlobalKey(debugLabel: 'mediumKey'); + }) : widgetKey = widgetKey ?? GlobalKey(debugLabel: 'mediumKey'); - Future _errorOr(String name, [T value]) { + Future _errorOr(String name, [T? value]) { calls.add(name); return medium.info.exception != null - ? Future.error(medium.info.exception) + ? Future.error(medium.info.exception!) : Future.value(value); } @@ -485,10 +480,10 @@ class _FakeSingleMediumController extends Fake _errorOr('initialize', medium.info.size); @override - Future play(BuildContext context) => _errorOr('play'); + Future play(BuildContext? context) => _errorOr('play'); @override - Future pause(BuildContext context) => _errorOr('pause'); + Future pause(BuildContext? context) => _errorOr('pause'); @override Future dispose() => _errorOr('dispose'); @@ -500,6 +495,7 @@ class _FakeSingleMediumController extends Fake } } -extension _AsFakeSingleMediumController on SingleMediumController { - _FakeSingleMediumController get asFake => this as _FakeSingleMediumController; +extension _AsFakeSingleMediumController on SingleMediumController? { + _FakeSingleMediumController? get asFake => + this as _FakeSingleMediumController?; } diff --git a/test/unit/media_player/multi_medium_track_state_test.dart b/test/unit/media_player/multi_medium_track_state_test.dart index a595bdb..5c3bbf6 100644 --- a/test/unit/media_player/multi_medium_track_state_test.dart +++ b/test/unit/media_player/multi_medium_track_state_test.dart @@ -15,12 +15,14 @@ import 'package:lunofono_player/src/media_player/multi_medium_track_state.dart' import 'package:lunofono_player/src/media_player/single_medium_state.dart' show SingleMediumState; +import 'mocks.mocks.dart' show MockSingleMediumState; + void main() { group('MultiMediumTrackState', () { MultiMediumTrackState.createSingleMediumState = ( SingleMedium medium, { - bool isVisualizable, - void Function(BuildContext) onFinished, + required bool isVisualizable, + void Function(BuildContext)? onFinished, }) { final state = _MockSingleMediumState( medium, @@ -37,7 +39,6 @@ void main() { _FakeAudibleSingleMedium(name: 'audibleMedium1', size: Size(0.0, 0.0)); final audibleMedium2 = _FakeAudibleSingleMedium( name: 'audibleMedium2', size: Size(10.0, 12.0)); - final audibleMainTrack = _FakeAudibleMultiMediumTrack([audibleMedium]); final audibleMainTrack2 = _FakeAudibleMultiMediumTrack([ audibleMedium, audibleMedium2, @@ -59,43 +60,12 @@ void main() { group('constructor', () { group('.internal() asserts on', () { - test('null media', () { - expect( - () => _TestMultiMediumTrackState(media: null, visualizable: true), - throwsAssertionError, - ); - }); test('empty media', () { expect( () => _TestMultiMediumTrackState(media: [], visualizable: true), throwsAssertionError, ); }); - test('null visualizable', () { - expect( - () => _TestMultiMediumTrackState( - visualizable: null, media: audibleMainTrack.media), - throwsAssertionError, - ); - }); - }); - - group('.main() asserts on', () { - test('null track', () { - expect( - () => MultiMediumTrackState.main(track: null), - throwsAssertionError, - ); - }); - }); - - group('.background() asserts on', () { - test('null track', () { - expect( - () => MultiMediumTrackState.background(track: null), - throwsAssertionError, - ); - }); }); void testContructorWithMedia( @@ -108,12 +78,12 @@ void main() { expect(state.isNotEmpty, isTrue); expect(state.current, state.mediaState.first); expect(state.last, state.mediaState.last); - expect(state.current.playable, media.first); - expect(state.current.isVisualizable, state.isVisualizable); - expect(state.current.onFinished, isNotNull); - expect(state.last.playable, media.last); - expect(state.last.isVisualizable, state.isVisualizable); - expect(state.last.onFinished, isNotNull); + expect(state.current!.playable, media.first); + expect(state.current!.isVisualizable, state.isVisualizable); + expect(state.current!.onFinished, isNotNull); + expect(state.last!.playable, media.last); + expect(state.last!.isVisualizable, state.isVisualizable); + expect(state.last!.onFinished, isNotNull); } test('.main() create mediaState correctly', () { @@ -186,7 +156,7 @@ void main() { expect(onFinishedCalled, false); expect(state.isFinished, isFalse); expect(state.current, same(first)); - verify(state.current.play(fakeContext)).called(1); + verify(state.current!.play(fakeContext)).called(1); state.mediaState.forEach(verifyNoMoreInteractions); state.mediaState.forEach(clearInteractions); @@ -194,7 +164,7 @@ void main() { expect(onFinishedCalled, false); expect(state.isFinished, isFalse); expect(state.current, same(first)); - verify(state.current.pause(fakeContext)).called(1); + verify(state.current!.pause(fakeContext)).called(1); state.mediaState.forEach(verifyNoMoreInteractions); state.mediaState.forEach(clearInteractions); @@ -202,21 +172,21 @@ void main() { expect(onFinishedCalled, false); expect(state.isFinished, isFalse); expect(state.current, same(first)); - verify(state.current.play(fakeContext)).called(1); + verify(state.current!.play(fakeContext)).called(1); state.mediaState.forEach(verifyNoMoreInteractions); // after the current track finished, the next one is played state.mediaState.forEach(clearInteractions); - state.current.onFinished(fakeContext); + state.current!.onFinished!(fakeContext); expect(onFinishedCalled, false); expect(state.isFinished, isFalse); expect(state.current, same(state.last)); - verify(state.current.play(fakeContext)).called(1); + verify(state.current!.play(fakeContext)).called(1); state.mediaState.forEach(verifyNoMoreInteractions); // after the last track finished, the controller should be finished state.mediaState.forEach(clearInteractions); - state.current.onFinished(fakeContext); + state.current!.onFinished!(fakeContext); expect(onFinishedCalled, true); expect(state.isFinished, isTrue); expect(state.current, isNull); @@ -244,10 +214,10 @@ void main() { await state.play(fakeContext); expect(notifyCalls, 1); // ends first, second starts playing - state.current.onFinished(fakeContext); + state.current!.onFinished!(fakeContext); expect(notifyCalls, 2); // ends second, onFinished should be called - state.current.onFinished(fakeContext); + state.current!.onFinished!(fakeContext); expect(notifyCalls, 3); await state.pause(fakeContext); expect(notifyCalls, 3); @@ -273,41 +243,74 @@ void main() { ); }); - test('debugFillProperties() and debugDescribeChildren()', () async { - final identityHash = RegExp(r'#[0-9a-f]{5}'); - - // XXX: No fake singleMediumStateFactory here because we would have to - // fake all the diagnostics class hierarchy too, which is overkill. + test('debugFillProperties()', () async { + final state = MultiMediumTrackState.main(track: audibleMainTrack2); + var builder = DiagnosticPropertiesBuilder(); + state.debugFillProperties(builder); expect( - MultiMediumTrackState.main( - track: audibleMainTrack2, - ).toStringDeep().replaceAll(identityHash, ''), - 'MultiMediumTrackState\n' - ' │ audible\n' - ' │ currentIndex: 0\n' - ' │ mediaState.length: 2\n' - ' │\n' - ' ├─0: _MockSingleMediumState(_FakeAudibleSingleMedium(audibleMedium1))\n' - ' └─1: _MockSingleMediumState(_FakeAudibleSingleMedium(audibleMedium2))\n' - '', - ); + builder.properties.toString(), + [ + FlagProperty('isInitialized', + value: false, ifTrue: 'all tracks are initialized'), + FlagProperty('visualizable', + value: false, ifTrue: 'visualizble', ifFalse: 'audible'), + IntProperty('currentIndex', 0), + IntProperty('mediaState.length', 2), + ].toString()); + + await state.initialize(fakeContext); + builder = DiagnosticPropertiesBuilder(); + state.debugFillProperties(builder); expect( - MultiMediumTrackState.background(track: const NoTrack()) - .toStringDeep() - .replaceAll(identityHash, ''), - 'MultiMediumTrackState\n' - ' empty\n' - '', - ); + builder.properties.toString(), + [ + FlagProperty('isInitialized', + value: true, ifTrue: 'all tracks are initialized'), + FlagProperty('visualizable', + value: false, ifTrue: 'visualizble', ifFalse: 'audible'), + IntProperty('currentIndex', 0), + IntProperty('mediaState.length', 2), + ].toString()); + }); + + test('debugDescribeChildren()', () async { + final state = MultiMediumTrackState.main(track: audibleMainTrack2); + final fakeNode = _FakeDiagnosticsNode(); + state.mediaState.forEach((s) => when(s.toDiagnosticsNode( + name: captureAnyNamed('name'), style: captureAnyNamed('style'))) + .thenReturn(fakeNode)); + + expect(state.debugDescribeChildren(), [fakeNode, fakeNode]); + state.mediaState.asMap().forEach( + (i, s) => expect( + verify(s.toDiagnosticsNode( + name: captureAnyNamed('name'), + style: captureThat(isNull, named: 'style'))) + .captured, + ['$i', null], + ), + ); + + await state.initialize(fakeContext); + expect(state.debugDescribeChildren(), [fakeNode, fakeNode]); + state.mediaState.asMap().forEach( + (i, s) => expect( + verify(s.toDiagnosticsNode( + name: captureAnyNamed('name'), + style: captureThat(isNull, named: 'style'))) + .captured, + ['$i', null], + ), + ); }); }); } class _TestMultiMediumTrackState extends MultiMediumTrackState { _TestMultiMediumTrackState({ - @required List media, - @required bool visualizable, - void Function(BuildContext context) onFinished, + required List media, + required bool visualizable, + void Function(BuildContext context)? onFinished, }) : super.internal( media: media, visualizable: visualizable, @@ -318,15 +321,15 @@ class _TestMultiMediumTrackState extends MultiMediumTrackState { class _FakeContext extends Fake implements BuildContext {} abstract class _FakeSingleMedium extends Fake implements SingleMedium { - final String name; - final Size size; + final String? name; + final Size? size; final dynamic error; final Key widgetKey; _FakeSingleMedium({ this.name, this.size, this.error, - Key widgetKey, + Key? widgetKey, }) : assert(error != null && size == null || error == null && size != null), widgetKey = widgetKey ?? GlobalKey(debugLabel: 'widgetKey'); @@ -339,10 +342,10 @@ abstract class _FakeSingleMedium extends Fake implements SingleMedium { class _FakeAudibleSingleMedium extends _FakeSingleMedium implements Audible { _FakeAudibleSingleMedium({ - String name, - Size size, + String? name, + Size? size, dynamic error, - Key widgetKey, + Key? widgetKey, }) : super(name: name, size: size, error: error, widgetKey: widgetKey); } @@ -360,21 +363,24 @@ class _FakeAudibleBackgroundMultiMediumTrack extends Fake _FakeAudibleBackgroundMultiMediumTrack(this.media); } -class _MockSingleMediumState extends Mock - with DiagnosticableTreeMixin - implements SingleMediumState { +class _FakeDiagnosticsNode extends Fake implements DiagnosticsNode { + @override + String toString( + {TextTreeConfiguration? parentConfiguration, + DiagnosticLevel minLevel = DiagnosticLevel.info}) => + 'FakeNode'; +} + +class _MockSingleMediumState extends MockSingleMediumState { @override final SingleMedium playable; @override final bool isVisualizable; @override - final void Function(BuildContext) onFinished; + final void Function(BuildContext)? onFinished; _MockSingleMediumState( this.playable, { - this.isVisualizable, + required this.isVisualizable, this.onFinished, }); - - @override - String toStringShort() => '$runtimeType($playable)'; } diff --git a/test/unit/media_player/multi_medium_track_widget_test.dart b/test/unit/media_player/multi_medium_track_widget_test.dart index 7731a29..77b31ff 100644 --- a/test/unit/media_player/multi_medium_track_widget_test.dart +++ b/test/unit/media_player/multi_medium_track_widget_test.dart @@ -95,13 +95,13 @@ class _FakeMultiMediumTrackState extends Fake with DiagnosticableTreeMixin, ChangeNotifier implements MultiMediumTrackState { @override - _FakeSingleMediumState current; + _FakeSingleMediumState? current; @override - _FakeSingleMediumState last; + _FakeSingleMediumState? last; @override Future dispose() async => super.dispose(); _FakeMultiMediumTrackState({ this.current, - _FakeSingleMediumState last, + _FakeSingleMediumState? last, }) : last = last ?? current; } diff --git a/test/unit/media_player/multi_medium_widget_test.dart b/test/unit/media_player/multi_medium_widget_test.dart index 84e85cf..c2344a1 100644 --- a/test/unit/media_player/multi_medium_widget_test.dart +++ b/test/unit/media_player/multi_medium_widget_test.dart @@ -168,9 +168,9 @@ class _FakeMultiMediumTrackState extends Fake with DiagnosticableTreeMixin, ChangeNotifier implements MultiMediumTrackState { @override - _FakeSingleMediumState current; + _FakeSingleMediumState? current; @override - _FakeSingleMediumState last; + _FakeSingleMediumState? last; @override final bool isVisualizable; @override @@ -185,7 +185,7 @@ class _FakeMultiMediumTrackState extends Fake 'isVisualizable: $isVisualizable'; _FakeMultiMediumTrackState({ this.current, - _FakeSingleMediumState last, + _FakeSingleMediumState? last, this.isVisualizable = false, }) : last = last ?? current; } @@ -208,9 +208,9 @@ class _FakeMultiMediumState extends Fake @override Future dispose() async => super.dispose(); _FakeMultiMediumState({ - _FakeMultiMediumTrackState mainTrack, - _FakeMultiMediumTrackState backgroundTrack, - bool isInitialized, + _FakeMultiMediumTrackState? mainTrack, + _FakeMultiMediumTrackState? backgroundTrack, + bool? isInitialized, }) : isInitialized = isInitialized ?? mainTrack != null, mainTrack = mainTrack ?? _FakeMultiMediumTrackState(current: _FakeSingleMediumState('main')), diff --git a/test/unit/media_player/playlist_player_test.dart b/test/unit/media_player/playlist_player_test.dart index c9052b6..13f2ca8 100644 --- a/test/unit/media_player/playlist_player_test.dart +++ b/test/unit/media_player/playlist_player_test.dart @@ -1,6 +1,5 @@ @Tags(['unit', 'player']) -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -11,20 +10,19 @@ import 'package:lunofono_player/src/media_player/controller_registry.dart'; import 'package:lunofono_player/src/media_player/playlist_player.dart'; import 'package:lunofono_player/src/media_player/playlist_widget.dart'; +import 'mocks.mocks.dart' + show MockSingleMedium, MockSingleMediumController, MockPlaylist; + void main() { test('PlaylistWidget.createSingleMediumWidget()', () { expect(PlaylistPlayer.createPlaylistWidget(), isA()); }); group('PlaylistPlayer', () { - final mockPlaylist = _MockPlaylist(); + final mockPlaylist = MockPlaylist(); setUp(() => reset(mockPlaylist)); - test('constructor asserts on null media', () { - expect(() => PlaylistPlayer(playlist: null), throwsAssertionError); - }); - test('default constructor uses the expected defaults', () { final player = PlaylistPlayer(playlist: mockPlaylist); expect(player.playlist, same(mockPlaylist)); @@ -55,8 +53,8 @@ void main() { // then we mock the controller, so we can mock a normal play (otherwise // just an error will be shown if the mock medium doesn't have // a registered controller - final mockController = _MockSingleMediumController(); - ControllerRegistry.instance.register(_MockSingleMedium, (medium, + final mockController = MockSingleMediumController(); + ControllerRegistry.instance.register(MockSingleMedium, (medium, {onMediumFinished}) { when(mockController.medium).thenReturn(medium); when(mockController.onMediumFinished).thenReturn(onMediumFinished); @@ -64,7 +62,7 @@ void main() { }); // finally we add a stub to the mock playlist to return a mock medium - final mockMedium = _MockSingleMedium(); + final mockMedium = MockSingleMedium(); when(mockPlaylist.media).thenReturn([mockMedium]); when(mockMedium.resource).thenReturn(Uri.parse('medium/path')); @@ -98,16 +96,7 @@ void main() { }); } -class _MockSingleMediumController extends Mock - implements SingleMediumController {} - -class _MockSingleMedium extends Mock implements SingleMedium {} - class _FakePlaylistWidget extends StatelessWidget { @override - Widget build(BuildContext context) => Container(); + Widget build(BuildContext context) => Text('test'); } - -class _MockPlaylist extends Mock - with DiagnosticableTreeMixin - implements Playlist {} diff --git a/test/unit/media_player/playlist_state_test.dart b/test/unit/media_player/playlist_state_test.dart index 4d2e6f5..bf0cb0d 100644 --- a/test/unit/media_player/playlist_state_test.dart +++ b/test/unit/media_player/playlist_state_test.dart @@ -17,6 +17,8 @@ import 'package:lunofono_player/src/media_player/playlist_state.dart' import 'package:lunofono_player/src/media_player/single_medium_state.dart' show SingleMediumState; +import 'mocks.mocks.dart'; + void main() { group('PlaylistState.createPlayableState()', () { test('creates SingleMediumState for SingleMedium', () { @@ -69,7 +71,8 @@ void main() { () => PlaylistState.createPlayableState = originalCreatePlayableState); void stubAll(List states) { - states.forEach((s) { + states.forEach((state) { + final s = state as _MockPlayableState; when(s.initialize(fakeContext, startPlaying: anyNamed('startPlaying'))) .thenAnswer((_) => Future.value()); when(s.play(fakeContext)).thenAnswer((_) => Future.value()); @@ -79,7 +82,8 @@ void main() { } PlaylistState createPlaylistState( - {List media, void Function(BuildContext context) onFinished}) { + {List? media, + void Function(BuildContext context)? onFinished}) { final state = PlaylistState(Playlist(media ?? testMedia), onFinished: onFinished); stubAll(state.mediaState); @@ -87,22 +91,16 @@ void main() { } group('constructor', () { - group('asserts on', () { - test('null playlist', () { - expect(() => PlaylistState(null), throwsAssertionError); - }); - }); - void testContructorWithMedia(PlaylistState state, List media) { expect(state.mediaState.length, media.length); expect(state.currentIndex, 0); expect(state.isFinished, isFalse); expect(state.current, state.mediaState.first); expect(state.last, state.mediaState.last); - expect(state.current.playable, same(media.first)); - expect(state.current.onFinished, isNotNull); - expect(state.last.playable, same(media.last)); - expect(state.last.onFinished, isNotNull); + expect(state.current!.playable, same(media.first)); + expect(state.current!.onFinished, isNotNull); + expect(state.last!.playable, same(media.last)); + expect(state.last!.onFinished, isNotNull); } test('create mediaState correctly', () { @@ -153,7 +151,7 @@ void main() { expect(onFinished, false); expect(state.isFinished, isFalse); expect(state.current, same(first)); - verify(state.current.play(fakeContext)).called(1); + verify(state.current!.play(fakeContext)).called(1); state.mediaState.forEach(verifyNoMoreInteractions); state.mediaState.forEach(clearInteractions); @@ -161,7 +159,7 @@ void main() { expect(onFinished, false); expect(state.isFinished, isFalse); expect(state.current, same(first)); - verify(state.current.pause(fakeContext)).called(1); + verify(state.current!.pause(fakeContext)).called(1); state.mediaState.forEach(verifyNoMoreInteractions); state.mediaState.forEach(clearInteractions); @@ -169,21 +167,21 @@ void main() { expect(onFinished, false); expect(state.isFinished, isFalse); expect(state.current, same(first)); - verify(state.current.play(fakeContext)).called(1); + verify(state.current!.play(fakeContext)).called(1); state.mediaState.forEach(verifyNoMoreInteractions); // after the current track finished, the next one is played state.mediaState.forEach(clearInteractions); - state.current.onFinished(fakeContext); + state.current!.onFinished!(fakeContext); expect(onFinished, false); expect(state.isFinished, isFalse); expect(state.current, same(state.last)); - verify(state.current.play(fakeContext)).called(1); + verify(state.current!.play(fakeContext)).called(1); state.mediaState.forEach(verifyNoMoreInteractions); // after the last track finished, the controller should be finished state.mediaState.forEach(clearInteractions); - state.current.onFinished(fakeContext); + state.current!.onFinished!(fakeContext); expect(onFinished, true); expect(state.isFinished, isTrue); expect(state.current, isNull); @@ -210,10 +208,10 @@ void main() { await state.play(fakeContext); expect(notifyCalls, 1); // ends first, second starts playing - state.current.onFinished(fakeContext); + state.current!.onFinished!(fakeContext); expect(notifyCalls, 2); // ends second, onFinished should be called - state.current.onFinished(fakeContext); + state.current!.onFinished!(fakeContext); expect(notifyCalls, 3); await state.pause(fakeContext); expect(notifyCalls, 3); @@ -230,33 +228,62 @@ void main() { expect(state.toString(), 'PlaylistState(current: 0, media: 2)'); }); - test('debugFillProperties() and debugDescribeChildren()', () async { + test('debugFillProperties()', () async { final state = createPlaylistState(); - final identityHash = RegExp(r'#[0-9a-f]{5}'); + var builder = DiagnosticPropertiesBuilder(); + state.debugFillProperties(builder); expect( - state.toStringDeep().replaceAll(identityHash, ''), - 'PlaylistState\n' - ' │ \n' - ' │ currentIndex: 0\n' - ' │ mediaState.length: 2\n' - ' │\n' - ' ├─0: _MockPlayableState(_FakeMedium(first))\n' - ' └─1: _MockPlayableState(_FakeMedium(second))\n' - '', - ); + builder.properties.toString(), + [ + FlagProperty('isInitialized', + value: false, ifFalse: ''), + IntProperty('currentIndex', 0), + IntProperty('mediaState.length', 2), + ].toString()); await state.initialize(fakeContext); + builder = DiagnosticPropertiesBuilder(); + state.debugFillProperties(builder); expect( - state.toStringDeep().replaceAll(identityHash, ''), - 'PlaylistState\n' - ' │ currentIndex: 0\n' - ' │ mediaState.length: 2\n' - ' │\n' - ' ├─0: _MockPlayableState(_FakeMedium(first))\n' - ' └─1: _MockPlayableState(_FakeMedium(second))\n' - '', - ); + builder.properties.toString(), + [ + FlagProperty('isInitialized', + value: true, ifFalse: ''), + IntProperty('currentIndex', 0), + IntProperty('mediaState.length', 2), + ].toString()); + }); + + test('debugDescribeChildren()', () async { + final state = createPlaylistState(); + final fakeNode = _FakeDiagnosticsNode(); + state.mediaState.forEach((s) => when(s.toDiagnosticsNode( + name: captureAnyNamed('name'), style: captureAnyNamed('style'))) + .thenReturn(fakeNode)); + + expect(state.debugDescribeChildren(), [fakeNode, fakeNode]); + state.mediaState.asMap().forEach( + (i, s) => expect( + verify(s.toDiagnosticsNode( + name: captureAnyNamed('name'), + style: captureThat(isNull, named: 'style'))) + .captured, + ['$i', null], + ), + ); + + await state.initialize(fakeContext); + expect(state.debugDescribeChildren(), [fakeNode, fakeNode]); + state.mediaState.asMap().forEach( + (i, s) => expect( + verify(s.toDiagnosticsNode( + name: captureAnyNamed('name'), + style: captureThat(isNull, named: 'style'))) + .captured, + ['$i', null], + ), + ); }); }); } @@ -264,7 +291,7 @@ void main() { class _FakeContext extends Fake implements BuildContext {} class _FakeMedium extends Fake implements Medium { - final String name; + final String? name; _FakeMedium([this.name]); @override @@ -274,7 +301,7 @@ class _FakeMedium extends Fake implements Medium { class _FakeSingleMedium extends _FakeMedium implements SingleMedium { @override Uri get resource => Uri.parse(name ?? ''); - _FakeSingleMedium([String name]) : super(name); + _FakeSingleMedium([String? name]) : super(name); } class _FakeMultiMedium extends _FakeMedium implements MultiMedium { @@ -285,7 +312,7 @@ class _FakeMultiMedium extends _FakeMedium implements MultiMedium { @override BackgroundMultiMediumTrack get backgroundTrack => NoTrack(); - _FakeMultiMedium([String name]) : super(name); + _FakeMultiMedium([String? name]) : super(name); } class _FakeMultiMediumTrack extends Fake implements MultiMediumTrack { @@ -293,22 +320,24 @@ class _FakeMultiMediumTrack extends Fake implements MultiMediumTrack { final List media; _FakeMultiMediumTrack(_FakeSingleMedium medium) - : assert(medium != null), - media = [medium]; + : media = [medium]; +} + +class _FakeDiagnosticsNode extends Fake implements DiagnosticsNode { + @override + String toString( + {TextTreeConfiguration? parentConfiguration, + DiagnosticLevel minLevel = DiagnosticLevel.info}) => + 'FakeNode'; } -class _MockPlayableState extends Mock - with DiagnosticableTreeMixin - implements PlayableState { +class _MockPlayableState extends MockPlayableState { @override final Medium playable; @override - final void Function(BuildContext) onFinished; + final void Function(BuildContext)? onFinished; _MockPlayableState( this.playable, { this.onFinished, }); - - @override - String toStringShort() => '$runtimeType($playable)'; } diff --git a/test/unit/media_player/playlist_widget_test.dart b/test/unit/media_player/playlist_widget_test.dart index f7d54b3..db242ec 100644 --- a/test/unit/media_player/playlist_widget_test.dart +++ b/test/unit/media_player/playlist_widget_test.dart @@ -1,6 +1,5 @@ @Tags(['unit', 'player']) -import 'package:flutter/foundation.dart' show DiagnosticableTreeMixin; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -12,23 +11,24 @@ import 'package:lunofono_player/src/media_player/media_player_error.dart' show MediaPlayerError; import 'package:lunofono_player/src/media_player/media_progress_indicator.dart' show MediaProgressIndicator; -import 'package:lunofono_player/src/media_player/multi_medium_state.dart' - show MultiMediumState; import 'package:lunofono_player/src/media_player/multi_medium_widget.dart' show MultiMediumWidget; -import 'package:lunofono_player/src/media_player/playable_state.dart' - show PlayableState; import 'package:lunofono_player/src/media_player/playlist_state.dart' show PlaylistState; import 'package:lunofono_player/src/media_player/playlist_widget.dart' show PlaylistWidget; import 'package:lunofono_player/src/media_player/single_medium_widget.dart' show SingleMediumWidget; -import 'package:lunofono_player/src/media_player/single_medium_state.dart' - show SingleMediumState; import '../../util/finders.dart' show findSubString; +import 'mocks.mocks.dart' + show + MockMultiMediumState, + MockPlayableState, + MockPlaylistState, + MockSingleMediumState; + void main() { test('PlaylistWidget.createSingleMediumWidget()', () { expect( @@ -50,7 +50,7 @@ void main() { PlaylistWidget.createMultiMediumWidget = createMultiMediumWidget; }); - Widget createWidget(_MockPlaylistState state) => Directionality( + Widget createWidget(MockPlaylistState state) => Directionality( textDirection: TextDirection.ltr, child: ChangeNotifierProvider.value( value: state, @@ -60,7 +60,7 @@ void main() { testWidgets('shows a MediaProgressIndicator if initializing', (WidgetTester tester) async { - final state = _MockPlaylistState(); + final state = MockPlaylistState(); when(state.isInitialized).thenReturn(false); await tester.pumpWidget(createWidget(state)); verify(state.isInitialized).called(1); @@ -69,9 +69,10 @@ void main() { testWidgets('shows a MediaPlayerError if current is of unknown type', (WidgetTester tester) async { - final state = _MockPlaylistState(); + final state = MockPlaylistState(); when(state.isInitialized).thenReturn(true); - when(state.current).thenReturn(_MockUnknownMediumState()); + final unknownPlayableState = MockPlayableState(); + when(state.current).thenReturn(unknownPlayableState); await tester.pumpWidget(createWidget(state)); verify(state.isInitialized).called(1); expect(find.byType(MediaPlayerError), findsOneWidget); @@ -80,9 +81,9 @@ void main() { testWidgets('shows a SingleMediumWidget if current is a SingleMediumState', (WidgetTester tester) async { - final state = _MockPlaylistState(); + final state = MockPlaylistState(); when(state.isInitialized).thenReturn(true); - final current = _MockSingleMediumState(); + final current = MockSingleMediumState(); when(state.current).thenReturn(current); when(current.isErroneous).thenReturn(true); when(current.isVisualizable).thenReturn(true); @@ -92,47 +93,31 @@ void main() { testWidgets('shows a MultiMediumWidget if current is a MultiMediumState', (WidgetTester tester) async { - final state = _MockPlaylistState(); + final state = MockPlaylistState(); when(state.isInitialized).thenReturn(true); - when(state.current).thenReturn(_MockMultiMediumState()); + when(state.current).thenReturn(MockMultiMediumState()); await tester.pumpWidget(createWidget(state)); expect(find.byType(_FakeMultiMediumWidget), findsOneWidget); }); testWidgets('shows the last state if current is null', (WidgetTester tester) async { - final state = _MockPlaylistState(); + final state = MockPlaylistState(); when(state.isInitialized).thenReturn(true); when(state.current).thenReturn(null); - when(state.last).thenReturn(_MockMultiMediumState()); + when(state.last).thenReturn(MockMultiMediumState()); await tester.pumpWidget(createWidget(state)); expect(find.byType(_FakeMultiMediumWidget), findsOneWidget); }); }); } -class _MockUnknownMediumState extends Mock - with DiagnosticableTreeMixin - implements PlayableState {} - -class _MockSingleMediumState extends Mock - with DiagnosticableTreeMixin - implements SingleMediumState {} - class _FakeSingleMediumWidget extends StatelessWidget { @override Widget build(BuildContext context) => Container(); } -class _MockMultiMediumState extends Mock - with DiagnosticableTreeMixin - implements MultiMediumState {} - class _FakeMultiMediumWidget extends StatelessWidget { @override Widget build(BuildContext context) => Container(); } - -class _MockPlaylistState extends Mock - with DiagnosticableTreeMixin - implements PlaylistState {} diff --git a/test/unit/media_player/single_medium_controller/audio_video_player_controller_test.dart b/test/unit/media_player/single_medium_controller/audio_video_player_controller_test.dart index 6a9498f..807fe1c 100644 --- a/test/unit/media_player/single_medium_controller/audio_video_player_controller_test.dart +++ b/test/unit/media_player/single_medium_controller/audio_video_player_controller_test.dart @@ -15,18 +15,16 @@ import 'package:lunofono_player/src/media_player/single_medium_controller.dart'; import 'single_medium_controller_common.dart'; import '../../../util/finders.dart' show findSubString; -FakeVideoInfo globalFakeInfo; +FakeVideoInfo? globalFakeInfo; + +class _FakeContext extends Fake implements BuildContext {} void main() { group('VideoPlayerController', () { final video = Video(Uri.parse('fake-video.avi')); - TestVideoPlayerController controller; - tearDown(() async => await controller?.dispose()); - - test('constructor asserts on null medium', () { - expect(() => VideoPlayerController(null), throwsAssertionError); - }); + late TestVideoPlayerController controller; + tearDown(() async => await controller.dispose()); test('can instantiate a video_player.VideoPlayerController', () async { controller = TestVideoPlayerController(video, null); @@ -53,10 +51,10 @@ void main() { expect(controller.value.isPlaying, false); await tester.pumpAndSettle(); - expectError(tester, widget); - expect(findSubString(globalFakeInfo.initError), findsOneWidget); - expect(hasFinished, false); + expectInitError(tester, widget); + expect(findSubString(globalFakeInfo!.initError), findsOneWidget); expect(controller.value.isPlaying, false); + expect(hasFinished, false); }, ); @@ -64,9 +62,10 @@ void main() { 'errors in play()', (WidgetTester tester) async { var hasFinished = false; + final videoInfo = FakeVideoInfo.playError(); controller = TestVideoPlayerController( video, - FakeVideoInfo.playError('Play error'), + videoInfo, onMediumFinished: (context) => hasFinished = true, widgetKey: globalSuccessKey, ); @@ -77,8 +76,9 @@ void main() { expect(controller.value.isPlaying, false); await tester.pumpAndSettle(); - expectError(tester, widget); - expect(findSubString(globalFakeInfo.playError), findsOneWidget); + expectPlayError(tester, widget); + expect(globalSize, videoInfo.size); + expect(findSubString(globalFakeInfo!.playError), findsOneWidget); expect(hasFinished, false); expect(controller.value.isPlaying, false); }, @@ -108,7 +108,7 @@ void main() { // Since loading is emulated, in the next frame it should be ready. // Video should start playing. final fakeController = - controller.controller as FakeVideoPlayerController; + controller.controller as _FakeVideoPlayerController; await tester.pump(fakeController.initDelay); expectSuccess(tester, widget, size: videoInfo.size, findWidget: video_player.VideoPlayer); @@ -116,8 +116,8 @@ void main() { expect(controller.value.isPlaying, true); // Emulate the video almost ended playing - final seekPosition = - Duration(milliseconds: globalFakeInfo.duration.inMilliseconds - 1); + final seekPosition = Duration( + milliseconds: globalFakeInfo!.duration!.inMilliseconds - 1); fakeController.fakeSeekTo(seekPosition); await tester.pump(seekPosition); expectSuccess(tester, widget, @@ -164,7 +164,7 @@ void main() { expect(controller.value.isPlaying, false); final fakeController = - controller.controller as FakeVideoPlayerController; + controller.controller as _FakeVideoPlayerController; await tester.pump(fakeController.initDelay); expectSuccess(tester, widget, size: videoInfo.size, findWidget: video_player.VideoPlayer); @@ -218,7 +218,7 @@ void main() { // Since loading is emulated, in the next frame it should be ready. // Video should start playing. final fakeController = - controller.controller as FakeVideoPlayerController; + controller.controller as _FakeVideoPlayerController; await tester.pump(fakeController.initDelay); expectSuccess(tester, widget, size: videoInfo.size, findWidget: video_player.VideoPlayer); @@ -226,8 +226,8 @@ void main() { expect(controller.value.isPlaying, true); // Emulate the video played halfway - final seekPosition = - Duration(milliseconds: globalFakeInfo.duration.inMilliseconds ~/ 2); + final seekPosition = Duration( + milliseconds: globalFakeInfo!.duration!.inMilliseconds ~/ 2); fakeController.fakeSeekTo(seekPosition); await tester.pump(seekPosition); expectSuccess(tester, widget, @@ -239,7 +239,7 @@ void main() { // XXX: we have to not await for it because the future is delayed and // the passage of time is emulated, so it will never called unless we // pump() some time. - unawaited(controller.pause(null)); + unawaited(controller.pause(_FakeContext())); // Advance the time it takes to pause the video controller, the // onMediumFinished callback should not have been called since the video @@ -257,13 +257,6 @@ void main() { group('WebAudioPlayerController', () { final audio = Audio(Uri.parse('fake-audio.avi')); - TestAudioPlayerController controller; - tearDown(() async => await controller?.dispose()); - - test('constructor asserts on null medium', () { - expect(() => WebAudioPlayerController(null), throwsAssertionError); - }); - test('is a subclass of VideoPlayerController', () async { final controller = WebAudioPlayerController(audio); expect(controller, isA()); @@ -273,7 +266,7 @@ void main() { testWidgets('initializes and plays showing an empty container', (WidgetTester tester) async { final videoInfo = FakeVideoInfo(Duration(seconds: 1), Size(0.0, 0.0)); - controller = TestAudioPlayerController(audio, videoInfo, + final controller = TestAudioPlayerController(audio, videoInfo, widgetKey: globalSuccessKey); final widget = TestWidget(controller); @@ -283,30 +276,34 @@ void main() { // Since loading is emulated, in the next frame it should be ready. // Video should start playing. - final fakeController = controller.controller as FakeVideoPlayerController; + final fakeController = + controller.controller as _FakeVideoPlayerController; await tester.pump(fakeController.initDelay); final foundWidget = expectSuccess(tester, widget, - size: videoInfo.size, findWidget: Container); + size: videoInfo.size, findWidget: Container)!; expect((foundWidget as Container).child, isNull); expect(controller.value.isPlaying, true); + addTearDown(() async => await controller.dispose()); }); }); } class FakeVideoInfo { - Duration duration; - Size size; - String initError; - String playError; + Duration? duration; + Size? size; + String? initError; + String? playError; FakeVideoInfo(this.duration, this.size, {this.initError, this.playError}); FakeVideoInfo.initError(String errorDescription) : this(null, null, initError: errorDescription); - FakeVideoInfo.playError(String errorDescription) - : this(null, null, playError: errorDescription); + FakeVideoInfo.playError( + {this.playError = 'Play error', + this.duration = const Duration(milliseconds: 1000), + this.size = const Size(10.0, 20.0)}); } // Only one instance can live at each time -class FakeVideoPlayerController extends Fake +class _FakeVideoPlayerController extends Fake implements video_player.VideoPlayerController { Duration initDelay = Duration.zero; Duration playDelay = Duration.zero; @@ -319,7 +316,7 @@ class FakeVideoPlayerController extends Fake @override int get textureId => 1; - FakeVideoPlayerController(this.dataSource) : assert(dataSource != null); + _FakeVideoPlayerController(this.dataSource); @override void addListener(VoidCallback listener) => listeners.add(listener); @@ -339,12 +336,12 @@ class FakeVideoPlayerController extends Fake @override Future initialize() { return Future.delayed(initDelay, () { - if (globalFakeInfo.initError != null) { - throw Exception(globalFakeInfo.initError); + if (globalFakeInfo!.initError != null) { + throw Exception(globalFakeInfo!.initError); } value = value.copyWith( - duration: globalFakeInfo.duration, - size: globalFakeInfo.size, + duration: globalFakeInfo!.duration, + size: globalFakeInfo!.size, ); expect(listeners, isNotEmpty); notifyListeners(); @@ -354,8 +351,8 @@ class FakeVideoPlayerController extends Fake @override Future play() { return Future.delayed(playDelay, () { - if (globalFakeInfo.playError != null) { - throw Exception(globalFakeInfo.playError); + if (globalFakeInfo!.playError != null) { + throw Exception(globalFakeInfo!.playError); } value = value.copyWith(isPlaying: true); expect(listeners, isNotEmpty); @@ -396,18 +393,18 @@ class FakeVideoPlayerController extends Fake class TestVideoPlayerController extends VideoPlayerController { TestVideoPlayerController( SingleMedium medium, - FakeVideoInfo info, { - void Function(BuildContext) onMediumFinished, - Key widgetKey, + FakeVideoInfo? info, { + void Function(BuildContext)? onMediumFinished, + Key? widgetKey, }) : super(medium, onMediumFinished: onMediumFinished, widgetKey: widgetKey) { globalFakeInfo = info; } - video_player.VideoPlayerValue get value => controller.value; + video_player.VideoPlayerValue get value => controller!.value; @override video_player.VideoPlayerController createController() { - return FakeVideoPlayerController(medium.toString()); + return _FakeVideoPlayerController(medium.toString()); } video_player.VideoPlayerController testCreateVideoPlayerController() { @@ -419,16 +416,16 @@ class TestAudioPlayerController extends WebAudioPlayerController { TestAudioPlayerController( SingleMedium medium, FakeVideoInfo info, { - void Function(BuildContext) onMediumFinished, - Key widgetKey, + void Function(BuildContext)? onMediumFinished, + Key? widgetKey, }) : super(medium, onMediumFinished: onMediumFinished, widgetKey: widgetKey) { globalFakeInfo = info; } - video_player.VideoPlayerValue get value => controller.value; + video_player.VideoPlayerValue get value => controller!.value; @override video_player.VideoPlayerController createController() { - return FakeVideoPlayerController(medium.resource.toString()); + return _FakeVideoPlayerController(medium.resource.toString()); } } diff --git a/test/unit/media_player/single_medium_controller/image_player_controller_test.dart b/test/unit/media_player/single_medium_controller/image_player_controller_test.dart index e287be9..e0be713 100644 --- a/test/unit/media_player/single_medium_controller/image_player_controller_test.dart +++ b/test/unit/media_player/single_medium_controller/image_player_controller_test.dart @@ -12,14 +12,12 @@ import 'package:lunofono_player/src/media_player/single_medium_controller.dart'; import 'single_medium_controller_common.dart'; +class _FakeContext extends Fake implements BuildContext {} + void main() { group('ImagePlayerController', () { - ImagePlayerController controller; - tearDown(() async => await controller?.dispose()); - - test('constructor asserts on null location', () { - expect(() => ImagePlayerController(null), throwsAssertionError); - }); + late ImagePlayerController controller; + tearDown(() async => await controller.dispose()); testWidgets( 'initializes with error', @@ -33,7 +31,7 @@ void main() { expectLoading(tester, widget); await tester.pumpAndSettle(); - expectError(tester, widget); + expectInitError(tester, widget); }, ); @@ -79,7 +77,7 @@ void main() { expect(hasStopped, false); // pause() has not effect when there is no maxDuration. - await controller.pause(null); + await controller.pause(_FakeContext()); await tester.pump(Duration(days: 10)); expectSuccess(tester, widget, size: size, findWidget: Image); @@ -161,7 +159,7 @@ void main() { expect(hasStopped, false); // Now we pause and let a day go by and it should be still playing - await controller.pause(null); + await controller.pause(_FakeContext()); await tester.pump(Duration(days: 1)); expectSuccess(tester, widget, size: size, findWidget: Image); expect(find.byWidget(controller.image), findsOneWidget); @@ -169,7 +167,7 @@ void main() { // Now we resume playing and let the final half of the maxDuration pass // and it should have finished - await controller.play(null); + await controller.play(_FakeContext()); await tester.pump(image.maxDuration ~/ 2); expectSuccess(tester, widget, size: size, findWidget: Image); expect(find.byWidget(controller.image), findsOneWidget); @@ -208,7 +206,7 @@ void main() { await tester.pump(image.maxDuration * 5); // Now start playing - await controller.play(null); + await controller.play(_FakeContext()); expectSuccess(tester, widget, size: size, findWidget: Image); expect(find.byWidget(controller.image), findsOneWidget); expect(hasStopped, false); @@ -302,5 +300,6 @@ Future undeadlockAsync() async { ]; final codec = await ui.instantiateImageCodec(Uint8List.fromList(kTransparentImage)); - return await codec.getNextFrame(); + await codec.getNextFrame(); + return; } diff --git a/test/unit/media_player/single_medium_controller/single_medium_controller_common.dart b/test/unit/media_player/single_medium_controller/single_medium_controller_common.dart index 14a3b13..44b8f17 100644 --- a/test/unit/media_player/single_medium_controller/single_medium_controller_common.dart +++ b/test/unit/media_player/single_medium_controller/single_medium_controller_common.dart @@ -13,7 +13,7 @@ class NoCheckSize extends Size { final globalSuccessKey = GlobalKey(debugLabel: 'successKey'); -Size globalSize; +Size? globalSize; void expectLoading(WidgetTester tester, TestWidget widget) { expect(find.byKey(widget.loadingKey), findsOneWidget); @@ -22,15 +22,21 @@ void expectLoading(WidgetTester tester, TestWidget widget) { expect(globalSize, null); } -void expectError(WidgetTester tester, TestWidget widget) { +void expectInitError(WidgetTester tester, TestWidget widget) { expect(find.byKey(widget.errorKey), findsOneWidget); expect(find.byKey(widget.loadingKey), findsNothing); expect(find.byKey(globalSuccessKey), findsNothing); expect(globalSize, null); } -Widget expectSuccess(WidgetTester tester, TestWidget widget, - {Size size = const NoCheckSize(), Type findWidget}) { +void expectPlayError(WidgetTester tester, TestWidget widget) { + expect(find.byKey(widget.errorKey), findsOneWidget); + expect(find.byKey(widget.loadingKey), findsNothing); + expect(find.byKey(globalSuccessKey), findsNothing); +} + +Widget? expectSuccess(WidgetTester tester, TestWidget widget, + {Size? size = const NoCheckSize(), Type? findWidget}) { expect(find.byKey(globalSuccessKey), findsOneWidget); expect(find.byKey(widget.loadingKey), findsNothing); expect(find.byKey(widget.errorKey), findsNothing); @@ -51,10 +57,8 @@ class TestWidget extends StatelessWidget { final SingleMediumController controller; final AssetBundle bundle; final bool startPlaying; - TestWidget(this.controller, {AssetBundle bundle, this.startPlaying = true}) - : assert(controller != null), - assert(startPlaying != null), - errorKey = GlobalKey(debugLabel: 'errorKey'), + TestWidget(this.controller, {AssetBundle? bundle, this.startPlaying = true}) + : errorKey = GlobalKey(debugLabel: 'errorKey'), loadingKey = GlobalKey(debugLabel: 'loadingKey'), bundle = bundle ?? TestAssetBundle() { globalSize = null; diff --git a/test/unit/media_player/single_medium_player_test.dart b/test/unit/media_player/single_medium_player_test.dart index cc875cb..ccb7024 100644 --- a/test/unit/media_player/single_medium_player_test.dart +++ b/test/unit/media_player/single_medium_player_test.dart @@ -20,14 +20,10 @@ import '../../util/finders.dart' show findSubString; // https://github.com/flutter/flutter/issues/65324 void main() { group('SingleMediumPlayer', () { - _SingleMediumPlayerTester playerTester; + _SingleMediumPlayerTester? playerTester; tearDown(() => playerTester?.dispose()); - test('constructor asserts on null media', () { - expect(() => SingleMediumPlayer(medium: null), throwsAssertionError); - }); - Future testUnregisteredMedium( WidgetTester tester, _FakeSingleMedium medium) async { // TODO: Second medium in a track is unregistered @@ -70,18 +66,18 @@ void main() { // The player should be initializing await tester.pumpWidget( - playerTester.player, tapInitMedium.info.initDelay ~/ 2); - playerTester.expectInitializationWidget(); - playerTester.expectPlayingStatus(finished: false); + playerTester!.player, tapInitMedium.info.initDelay ~/ 2); + playerTester!.expectInitializationWidget(); + playerTester!.expectPlayingStatus(finished: false); // Tap and the reaction should reach the controller final widgetToTap = find.byType(CircularProgressIndicator); expect(widgetToTap, findsOneWidget); await tester.tap(widgetToTap); await tester.pump(); - playerTester.expectInitializationWidget(); - playerTester.expectPlayingStatus( - finished: false, stoppedTimes: 1, paused: true); + playerTester!.expectInitializationWidget(); + playerTester! + .expectPlayingStatus(finished: false, stoppedTimes: 1, paused: true); }); testWidgets('tap stops while playing', (WidgetTester tester) async { @@ -92,23 +88,23 @@ void main() { ); playerTester = _SingleMediumPlayerTester(tester, tapPlayMedium); - await playerTester.testInitializationDone(); - playerTester.expectPlayerWidget(); - playerTester.expectPlayingStatus(finished: false); + await playerTester!.testInitializationDone(); + playerTester!.expectPlayerWidget(); + playerTester!.expectPlayingStatus(finished: false); // Wait until half of the media was played, it should keep playing await tester.pump(tapPlayMedium.info.duration ~/ 2); - playerTester.expectPlayerWidget(); - playerTester.expectPlayingStatus(finished: false); + playerTester!.expectPlayerWidget(); + playerTester!.expectPlayingStatus(finished: false); // Tap and the player should stop var widgetToTap = find.byKey(tapPlayMedium.info.widgetKey); expect(widgetToTap, findsOneWidget); await tester.tap(widgetToTap); await tester.pump(); - playerTester.expectPlayerWidget(); - playerTester.expectPlayingStatus( - finished: false, stoppedTimes: 1, paused: true); + playerTester!.expectPlayerWidget(); + playerTester! + .expectPlayingStatus(finished: false, stoppedTimes: 1, paused: true); // Tap again should do nothing new (but to call the onMediaStopped // callback again). @@ -116,9 +112,9 @@ void main() { expect(widgetToTap, findsOneWidget); await tester.tap(widgetToTap); await tester.pump(); - playerTester.expectPlayerWidget(); - playerTester.expectPlayingStatus( - finished: false, stoppedTimes: 2, paused: true); + playerTester!.expectPlayerWidget(); + playerTester! + .expectPlayingStatus(finished: false, stoppedTimes: 2, paused: true); }); testWidgets('tap does nothing when playing is done', @@ -130,23 +126,23 @@ void main() { ); playerTester = _SingleMediumPlayerTester(tester, tapPlayDoneMedium); - await playerTester.testInitializationDone(); - playerTester.expectPlayerWidget(); - playerTester.expectPlayingStatus(finished: false); + await playerTester!.testInitializationDone(); + playerTester!.expectPlayerWidget(); + playerTester!.expectPlayingStatus(finished: false); // Wait until the media stops playing by itself await tester.pump(tapPlayDoneMedium.info.duration); - playerTester.expectPlayerWidget(); - playerTester.expectPlayingStatus(finished: true); + playerTester!.expectPlayerWidget(); + playerTester!.expectPlayingStatus(finished: true); // Tap again should do nothing but to get a reaction final widgetToTap = find.byKey(tapPlayDoneMedium.info.widgetKey); expect(widgetToTap, findsOneWidget); await tester.tap(widgetToTap); await tester.pump(); - playerTester.expectPlayerWidget(); - playerTester.expectPlayingStatus( - finished: true, paused: true, stoppedTimes: 2); + playerTester!.expectPlayerWidget(); + playerTester! + .expectPlayingStatus(finished: true, paused: true, stoppedTimes: 2); }); }); } @@ -170,17 +166,15 @@ class _SingleMediumPlayerTester { // Automatically initialized final ControllerRegistry originalRegistry; - _FakeSingleMediumController controller; - Widget player; + _FakeSingleMediumController? controller; + late Widget player; var playerHasStoppedTimes = 0; // Constant final playerKey = GlobalKey(debugLabel: 'playerKey'); _SingleMediumPlayerTester(this.tester, this.medium) - : assert(tester != null), - assert(medium != null), - originalRegistry = ControllerRegistry.instance { + : originalRegistry = ControllerRegistry.instance { _registerController(); player = _createPlayer(); } @@ -192,7 +186,7 @@ class _SingleMediumPlayerTester { void _registerController() { SingleMediumController createController(SingleMedium medium, - {void Function(BuildContext) onMediumFinished}) { + {void Function(BuildContext)? onMediumFinished}) { final fakeMedium = medium as _FakeSingleMedium; final c = _FakeSingleMediumController( fakeMedium, onMediumFinished, fakeMedium.info.widgetKey); @@ -238,10 +232,8 @@ class _SingleMediumPlayerTester { } void expectInitializationWidget() { - assert(medium != null); assert(controller != null); - assert(controller.medium != null); - expect(controller.medium.resource, medium.resource); + expect(controller!.medium.resource, medium.resource); expect(find.byKey(playerKey), findsOneWidget); expect(find.byType(CircularProgressIndicator), findsOneWidget); expect(find.byType(MediaPlayerError), findsNothing); @@ -251,12 +243,12 @@ class _SingleMediumPlayerTester { } void _expectPlayerInitializationDone() { - expect(controller.medium.resource, medium.resource); + expect(controller!.medium.resource, medium.resource); expect(find.byType(CircularProgressIndicator), findsNothing); expect(find.byKey(playerKey), findsOneWidget); } - void expectPlayerWidget({int mainMediumIndex = 0, bool rotated}) { + void expectPlayerWidget({int mainMediumIndex = 0, bool? rotated}) { _expectPlayerInitializationDone(); expect(find.byKey(medium.info.widgetKey), findsOneWidget); expect(find.byType(MediaPlayerError), findsNothing); @@ -268,8 +260,8 @@ class _SingleMediumPlayerTester { void expectPlayingStatus({ int mainMediumIndex = 0, - @required bool finished, - int stoppedTimes, + required bool finished, + int? stoppedTimes, bool paused = false, }) { stoppedTimes = stoppedTimes ?? (finished ? 1 : 0); @@ -285,17 +277,17 @@ class _SingleMediumPlayerTester { } class _SingleMediumInfo { - final Size size; + final Size? size; final Duration duration; final Duration initDelay; - final Exception exception; + final Exception? exception; final Key widgetKey; _SingleMediumInfo( String location, { this.size, this.exception, - Duration duration, - Duration initDelay, + Duration? duration, + Duration? initDelay, }) : assert(exception != null && size == null || exception == null && size != null), initDelay = initDelay ?? const Duration(seconds: 1), @@ -307,11 +299,11 @@ abstract class _FakeSingleMedium extends SingleMedium { final _SingleMediumInfo info; _FakeSingleMedium( String location, { - Duration maxDuration, - Size size, - Exception exception, - Duration duration, - Duration initDelay, + Duration? maxDuration, + Size? size, + Exception? exception, + Duration? duration, + Duration? initDelay, }) : info = _SingleMediumInfo(location, size: size, exception: exception, @@ -323,11 +315,11 @@ abstract class _FakeSingleMedium extends SingleMedium { class _FakeAudibleSingleMedium extends _FakeSingleMedium implements Audible { _FakeAudibleSingleMedium( String location, { - Duration maxDuration, - Size size, - Exception exception, - Duration duration, - Duration initDelay, + Duration? maxDuration, + Size? size, + Exception? exception, + Duration? duration, + Duration? initDelay, }) : super(location, maxDuration: maxDuration, size: size, @@ -340,11 +332,11 @@ class _FakeVisualizableSingleMedium extends _FakeSingleMedium implements Visualizable { _FakeVisualizableSingleMedium( String location, { - Duration maxDuration, - Size size, - Exception exception, - Duration duration, - Duration initDelay, + Duration? maxDuration, + Size? size, + Exception? exception, + Duration? duration, + Duration? initDelay, }) : super(location, maxDuration: maxDuration, size: size, @@ -357,11 +349,11 @@ class _FakeAudibleVisualizableSingleMedium extends _FakeSingleMedium implements Audible, Visualizable { _FakeAudibleVisualizableSingleMedium( String location, { - Duration maxDuration, - Size size, - Exception exception, - Duration duration, - Duration initDelay, + Duration? maxDuration, + Size? size, + Exception? exception, + Duration? duration, + Duration? initDelay, }) : super(location, maxDuration: maxDuration, size: size, @@ -373,9 +365,9 @@ class _FakeAudibleVisualizableSingleMedium extends _FakeSingleMedium class _FakeSingleMediumController extends Fake implements SingleMediumController { // Internal state - Timer _initTimer; + Timer? _initTimer; bool get isInitializing => _initTimer?.isActive ?? false; - Timer _playingTimer; + Timer? _playingTimer; bool get isPlaying => _playingTimer?.isActive ?? false; final _initCompleter = Completer(); // State to do checks @@ -387,7 +379,7 @@ class _FakeSingleMediumController extends Fake bool _isPaused = false; bool get isPaused => _isPaused; - void Function(BuildContext) playerOnMediaStopped; + void Function(BuildContext)? playerOnMediaStopped; @override _FakeSingleMedium medium; @@ -399,14 +391,14 @@ class _FakeSingleMediumController extends Fake this.medium, this.playerOnMediaStopped, this.widgetKey, - ) : assert(medium != null); + ); @override Future initialize(BuildContext context) { _initTimer = Timer(medium.info.initDelay, () { if (initError) { try { - throw medium.info.exception; + throw medium.info.exception!; } catch (e, stack) { _initCompleter.completeError(e, stack); } @@ -446,9 +438,9 @@ class _FakeSingleMediumController extends Fake @override void Function(BuildContext) get onMediumFinished => (BuildContext context) { _finishedTimes++; - playerOnMediaStopped(context); + playerOnMediaStopped!(context); }; @override - Widget build(BuildContext context) => Container(key: widgetKey); + Widget build(BuildContext context) => Text('FakeWidget', key: widgetKey); } diff --git a/test/unit/media_player/single_medium_state_test.dart b/test/unit/media_player/single_medium_state_test.dart index 07d10a2..503f5ed 100644 --- a/test/unit/media_player/single_medium_state_test.dart +++ b/test/unit/media_player/single_medium_state_test.dart @@ -17,7 +17,7 @@ void main() { void verifyStateInvariants( SingleMediumState state, _FakeSingleMediumController controller) { expect(state.controller, same(controller)); - expect(state.controller.widgetKey, controller.widgetKey); + expect(state.controller!.widgetKey, controller.widgetKey); } void verifyStateInitialization( @@ -48,7 +48,7 @@ void main() { group('without a registered medium', () { test('constructs a state with an error', () { final medium = _FakeSingleMedium('bad-medium', size: Size(1, 1)); - final state = SingleMediumState(medium); + final state = SingleMediumState(medium, isVisualizable: true); expect(state.isErroneous, true); expect(state.error, contains('Unsupported type')); }); @@ -57,8 +57,8 @@ void main() { group('on bad medium', () { Exception error; _FakeSingleMedium medium; - _FakeSingleMediumController controller; - SingleMediumState state; + late _FakeSingleMediumController controller; + late SingleMediumState state; setUp(() { error = Exception('Initialization Error'); @@ -68,7 +68,7 @@ void main() { _FakeSingleMedium, (medium, {onMediumFinished}) => controller, ); - state = SingleMediumState(medium); + state = SingleMediumState(medium, isVisualizable: true); }); test('the state is properly initialized', () { @@ -128,10 +128,10 @@ void main() { }); group('on good medium', () { - Size size; - _FakeSingleMedium medium; - _FakeSingleMediumController controller; - SingleMediumState state; + Size? size; + late _FakeSingleMedium medium; + late _FakeSingleMediumController controller; + late SingleMediumState state; setUp(() { size = Size(0.0, 0.0); @@ -141,7 +141,7 @@ void main() { _FakeSingleMedium, (medium, {onMediumFinished}) => controller, ); - state = SingleMediumState(medium); + state = SingleMediumState(medium, isVisualizable: true); }); void verifyStateInitialized() { @@ -152,15 +152,6 @@ void main() { expect(state.isErroneous, isFalse); } - test('constructor asserts on null controller', () { - expect(() => SingleMediumState(null), throwsAssertionError); - }); - - test('constructor asserts on null isVisualizable', () { - expect(() => SingleMediumState(medium, isVisualizable: null), - throwsAssertionError); - }); - test('the state is properly initialized', () { verifyStateInitialization(state, controller); }); @@ -202,8 +193,8 @@ void main() { }); test('.build() builds a widget with the expected key', () { - final widget = state.controller.build(_FakeContext()); - expect(widget.key, state.controller.widgetKey); + final widget = state.controller!.build(_FakeContext()); + expect(widget.key, state.controller!.widgetKey); }); test('toString()', () async { @@ -239,15 +230,14 @@ void main() { class _FakeContext extends Fake implements BuildContext {} class _SingleMediumInfo { - final Size size; - final Exception exception; + final Size? size; + final Exception? exception; final Key widgetKey; _SingleMediumInfo( String location, { this.size, this.exception, - }) : assert(location != null), - assert(exception != null && size == null || + }) : assert(exception != null && size == null || exception == null && size != null), widgetKey = GlobalKey(debugLabel: 'widgetKey(${location}'); } @@ -256,8 +246,8 @@ class _FakeSingleMedium extends SingleMedium { final _SingleMediumInfo info; _FakeSingleMedium( String location, { - Size size, - Exception exception, + Size? size, + Exception? exception, }) : info = _SingleMediumInfo(location, size: size, exception: exception), super(Uri.parse(location)); } @@ -271,21 +261,20 @@ class _FakeSingleMediumController extends Fake Key widgetKey; @override - void Function(BuildContext context) onMediumFinished; + void Function(BuildContext context)? onMediumFinished; final calls = []; _FakeSingleMediumController( this.medium, { - Key widgetKey, + Key? widgetKey, this.onMediumFinished, - }) : assert(medium != null), - widgetKey = widgetKey ?? GlobalKey(debugLabel: 'mediumKey'); + }) : widgetKey = widgetKey ?? GlobalKey(debugLabel: 'mediumKey'); - Future _errorOr(String name, [T value]) { + Future _errorOr(String name, [T? value]) { calls.add(name); return medium.info.exception != null - ? Future.error(medium.info.exception) + ? Future.error(medium.info.exception!) : Future.value(value); } @@ -294,10 +283,10 @@ class _FakeSingleMediumController extends Fake _errorOr('initialize', medium.info.size); @override - Future play(BuildContext context) => _errorOr('play'); + Future play(BuildContext? context) => _errorOr('play'); @override - Future pause(BuildContext context) => _errorOr('pause'); + Future pause(BuildContext? context) => _errorOr('pause'); @override Future dispose() => _errorOr('dispose'); diff --git a/test/unit/media_player/single_medium_widget_test.dart b/test/unit/media_player/single_medium_widget_test.dart index dd14485..424e066 100644 --- a/test/unit/media_player/single_medium_widget_test.dart +++ b/test/unit/media_player/single_medium_widget_test.dart @@ -4,8 +4,7 @@ import 'package:flutter/foundation.dart' show DiagnosticableTreeMixin; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart' - show Fake, Mock, any, throwOnMissingStub, when; +import 'package:mockito/mockito.dart' show Fake, any, throwOnMissingStub, when; import 'package:provider/provider.dart' show ChangeNotifierProvider; @@ -20,6 +19,8 @@ import 'package:lunofono_player/src/media_player/single_medium_state.dart' import 'package:lunofono_player/src/media_player/single_medium_widget.dart' show SingleMediumWidget; +import 'mocks.mocks.dart' show MockSingleMediumController; + void main() { group('SingleMediumWidget', () { final uninitializedState = _FakeSingleMediumState( @@ -44,7 +45,7 @@ void main() { (WidgetTester tester) async { await pumpPlayer(tester, errorState); - expect(find.byKey(errorState.controller.widgetKey), findsNothing); + expect(find.byKey(errorState.controller.widgetKey!), findsNothing); expect(find.byType(MediaProgressIndicator), findsNothing); expect(find.byType(RotatedBox), findsNothing); @@ -60,7 +61,7 @@ void main() { await pumpPlayer(tester, state); expect(find.byType(MediaPlayerError), findsNothing); - expect(find.byKey(state.controller.widgetKey), findsNothing); + expect(find.byKey(state.controller.widgetKey!), findsNothing); expect(find.byType(RotatedBox), findsNothing); final progressFinder = find.byType(MediaProgressIndicator); @@ -84,12 +85,12 @@ void main() { ) async { await pumpPlayer(tester, state); expect(find.byType(MediaPlayerError), findsNothing); - expect(find.byKey(errorState.controller.widgetKey), findsNothing); + expect(find.byKey(errorState.controller.widgetKey!), findsNothing); expect( - find.byKey(uninitializedState.controller.widgetKey), findsNothing); + find.byKey(uninitializedState.controller.widgetKey!), findsNothing); expect(find.byType(MediaProgressIndicator), findsNothing); - final playerFinder = find.byKey(state.controller.widgetKey); + final playerFinder = find.byKey(state.controller.widgetKey!); expect(playerFinder, findsOneWidget); return tester.widget(playerFinder); } @@ -133,9 +134,6 @@ void main() { }); } -class _MockSingleMediumController extends Mock - implements SingleMediumController {} - class _FakeSingleMediumState extends Fake with DiagnosticableTreeMixin, ChangeNotifier implements SingleMediumState { @@ -144,11 +142,11 @@ class _FakeSingleMediumState extends Fake @override dynamic error; @override - Size size; + Size? size; @override Future dispose() async => super.dispose(); @override - final controller = _MockSingleMediumController(); + final SingleMediumController controller = MockSingleMediumController(); @override bool get isInitialized => size != null; @override @@ -163,13 +161,14 @@ class _FakeSingleMediumState extends Fake this.error, this.size, this.isVisualizable = true, - Key widgetKey, + Key? widgetKey, }) { // Make sure any non-stubbed interaction throws and stub the build method // and widgetKey property. final key = widgetKey ?? GlobalKey(debugLabel: 'widgetKey'); - throwOnMissingStub(controller as _MockSingleMediumController); - when(controller.build(any)).thenReturn(Container(key: key)); - when(controller.widgetKey).thenReturn(key); + final mockController = controller as MockSingleMediumController; + throwOnMissingStub(mockController); + when(mockController.build(any)).thenReturn(Container(key: key)); + when(mockController.widgetKey).thenReturn(key); } } diff --git a/test/unit/menu_player/grid_menu_player_test.dart b/test/unit/menu_player/grid_menu_player_test.dart index a88adf8..1be2e21 100644 --- a/test/unit/menu_player/grid_menu_player_test.dart +++ b/test/unit/menu_player/grid_menu_player_test.dart @@ -20,8 +20,8 @@ void main() { _FakeButton fakeButtonRed; _FakeButton fakeButtonBlue; - GridMenu menu; - GridMenuPlayer menuPlayer; + late GridMenu menu; + GridMenuPlayer? menuPlayer; setUp(() { ActionPlayer.registry = ActionPlayerRegistry(); @@ -53,37 +53,27 @@ void main() { group('GridMenuPlayer', () { setUp(() {}); - test('constructor asserts if menu is null', () { - expect(() => GridMenuPlayer(null), throwsAssertionError); - }); - testWidgets('build() builds the right widgets', (WidgetTester tester) async { // Matches the underlaying menu - expect(menuPlayer.rows, menu.rows); - expect(menuPlayer.columns, menu.columns); - expect(menuPlayer.buttons.first.button, menu.buttonAt(0, 0)); + expect(menuPlayer!.rows, menu.rows); + expect(menuPlayer!.columns, menu.columns); + expect(menuPlayer!.buttons.first.button, menu.buttonAt(0, 0)); // Build returns a GridMenuWidget - final menuWidget = menuPlayer.build(_FakeContext()); + final menuWidget = menuPlayer!.build(_FakeContext()); expect(menuWidget, isA()); expect((menuWidget as GridMenuWidget).menu, same(menuPlayer)); }); }); group('GridMenuWidget', () { - test('constructor asserts if menu or padding is null', () { - expect(() => GridMenuWidget(menu: null), throwsAssertionError); - expect(() => GridMenuWidget(menu: menuPlayer, padding: null), - throwsAssertionError); - }); - testWidgets('shows all buttons', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: Container( child: Builder( - builder: (context) => menuPlayer.build(context), + builder: (context) => menuPlayer!.build(context), ), ), ), @@ -96,16 +86,6 @@ void main() { }); group('GridMenuRowWidget', () { - test('constructor asserts if menu, row or padding is null', () { - expect( - () => GridMenuRowWidget(row: null, menu: null), throwsAssertionError); - expect(() => GridMenuRowWidget(row: null, menu: menuPlayer), - throwsAssertionError); - expect(() => GridMenuRowWidget(row: 0, menu: null), throwsAssertionError); - expect(() => GridMenuRowWidget(row: 0, menu: menuPlayer, padding: null), - throwsAssertionError); - }); - testWidgets('shows all buttons in the row', (WidgetTester tester) async { Widget fakeApp(Widget row) => MaterialApp( home: Column( @@ -114,12 +94,12 @@ void main() { ); await tester - .pumpWidget(fakeApp(GridMenuRowWidget(menu: menuPlayer, row: 0))); + .pumpWidget(fakeApp(GridMenuRowWidget(menu: menuPlayer!, row: 0))); expect(find.byKey(ObjectKey(menu.buttons.first)), findsOneWidget); expect(find.byKey(ObjectKey(menu.buttons.last)), findsNothing); await tester - .pumpWidget(fakeApp(GridMenuRowWidget(menu: menuPlayer, row: 1))); + .pumpWidget(fakeApp(GridMenuRowWidget(menu: menuPlayer!, row: 1))); expect(find.byKey(ObjectKey(menu.buttons.first)), findsNothing); expect(find.byKey(ObjectKey(menu.buttons.last)), findsOneWidget); }); @@ -137,7 +117,7 @@ class _FakeAction extends Action { class _FakeActionPlayer extends ActionPlayer { @override final _FakeAction action; - _FakeActionPlayer(this.action) : assert(action != null); + _FakeActionPlayer(this.action); @override void act(BuildContext context, ButtonPlayer button) => action.actCalls.add(button); diff --git a/test/unit/menu_player_test.dart b/test/unit/menu_player_test.dart index 7790715..5e857e4 100644 --- a/test/unit/menu_player_test.dart +++ b/test/unit/menu_player_test.dart @@ -37,8 +37,8 @@ void main() { group('MenuPlayerRegistry', () { final oldMenuRegistry = MenuPlayer.registry; - _FakeContext fakeContext; - _FakeMenu fakeMenu; + _FakeContext? fakeContext; + late _FakeMenu fakeMenu; setUp(() { fakeContext = _FakeContext(); @@ -58,7 +58,7 @@ void main() { MenuPlayer.registry .register(_FakeMenu, (m) => _FakeMenuPlayer(m as _FakeMenu)); - final builtWidget = MenuPlayer.wrap(fakeMenu).build(fakeContext); + final builtWidget = MenuPlayer.wrap(fakeMenu).build(fakeContext!); expect(fakeMenu.buildCalls.length, 1); expect(fakeMenu.buildCalls.last.context, same(fakeContext)); expect(fakeMenu.buildCalls.last.returnedWidget, same(builtWidget)); @@ -96,7 +96,7 @@ class _FakeMenu extends Menu { class _FakeMenuPlayer extends MenuPlayer { @override final _FakeMenu menu; - _FakeMenuPlayer(this.menu) : assert(menu != null); + _FakeMenuPlayer(this.menu); static Key globalKey = GlobalKey(debugLabel: 'FakeMenuPlayerKey'); @override Widget build(BuildContext context) { @@ -115,7 +115,7 @@ class _FakeAction extends Action { class _FakeActionPlayer extends ActionPlayer { @override final _FakeAction action; - _FakeActionPlayer(this.action) : assert(action != null); + _FakeActionPlayer(this.action); @override void act(BuildContext context, ButtonPlayer button) => action.actCalls.add(button); diff --git a/test/unit/playable_player_test.dart b/test/unit/playable_player_test.dart index 89534f5..b537aa6 100644 --- a/test/unit/playable_player_test.dart +++ b/test/unit/playable_player_test.dart @@ -13,9 +13,9 @@ import 'package:lunofono_player/src/playable_player.dart'; void main() { group('PlayablePlayer', () { final oldPlayableRegistry = PlayablePlayer.registry; - FakeContext fakeContext; - FakePlayable fakePlayable; - Color color; + FakeContext? fakeContext; + FakePlayable? fakePlayable; + Color? color; setUp(() { fakePlayable = FakePlayable(); @@ -28,23 +28,23 @@ void main() { test('empty', () { PlayablePlayer.registry = PlayablePlayerRegistry(); expect(PlayablePlayer.registry, isEmpty); - expect(() => PlayablePlayer.wrap(fakePlayable), throwsAssertionError); + expect(() => PlayablePlayer.wrap(fakePlayable!), throwsAssertionError); }); test('registration and calling from empty', () { PlayablePlayer.registry = PlayablePlayerRegistry(); PlayablePlayer.registry.register(FakePlayable, (playable) => FakePlayablePlayer(playable as FakePlayable)); - PlayablePlayer.wrap(fakePlayable).play(fakeContext, color); - expect(fakePlayable.calledPlayable, same(fakePlayable)); - expect(fakePlayable.calledContext, same(fakeContext)); - expect(fakePlayable.calledColor, same(color)); + PlayablePlayer.wrap(fakePlayable!).play(fakeContext!, color); + expect(fakePlayable!.calledPlayable, same(fakePlayable)); + expect(fakePlayable!.calledContext, same(fakeContext)); + expect(fakePlayable!.calledColor, same(color)); }); group('SingleMedium', () { final homeKey = GlobalKey(debugLabel: 'homeKey'); - void testPlayable(WidgetTester tester, Widget homeWidget) async { + Future testPlayable(WidgetTester tester, Widget homeWidget) async { // We need a MaterialApp to use the Navigator await tester.pumpWidget(MaterialApp(home: homeWidget)); final homeFinder = find.byKey(homeKey); @@ -66,7 +66,7 @@ void main() { final context = tester.element(playerFinder); // The HomeWidget should be back - mediaPlayer.onMediaStopped(context); // Should call Navigator.pop() + mediaPlayer.onMediaStopped!(context); // Should call Navigator.pop() await tester.pump(); // Same with .push() about the double pump() await tester.pump(Duration(seconds: 1)); expect(find.byKey(homeKey), findsOneWidget); @@ -92,7 +92,7 @@ void main() { group('MultiMedium', () { final homeKey = GlobalKey(debugLabel: 'homeKey'); - void testPlayable(WidgetTester tester, Widget homeWidget) async { + Future testPlayable(WidgetTester tester, Widget homeWidget) async { // We need a MaterialApp to use the Navigator await tester.pumpWidget(MaterialApp(home: homeWidget)); final homeFinder = find.byKey(homeKey); @@ -114,7 +114,7 @@ void main() { final context = tester.element(playerFinder); // The HomeWidget should be back - mediaPlayer.onMediaStopped(context); // Should call Navigator.pop() + mediaPlayer.onMediaStopped!(context); // Should call Navigator.pop() await tester.pump(); // Same with .push() about the double pump() await tester.pump(Duration(seconds: 1)); expect(find.byKey(homeKey), findsOneWidget); @@ -140,7 +140,7 @@ void main() { group('Playlist', () { final homeKey = GlobalKey(debugLabel: 'homeKey'); - void testPlayable(WidgetTester tester, Widget homeWidget) async { + Future testPlayable(WidgetTester tester, Widget homeWidget) async { // We need a MaterialApp to use the Navigator await tester.pumpWidget(MaterialApp(home: homeWidget)); final homeFinder = find.byKey(homeKey); @@ -162,7 +162,7 @@ void main() { final context = tester.element(playerFinder); // The HomeWidget should be back - mediaPlayer.onMediaStopped(context); // Should call Navigator.pop() + mediaPlayer.onMediaStopped!(context); // Should call Navigator.pop() await tester.pump(); // Same with .push() about the double pump() await tester.pump(Duration(seconds: 1)); expect(find.byKey(homeKey), findsOneWidget); @@ -193,28 +193,28 @@ void main() { class FakeContext extends Fake implements BuildContext {} class FakePlayable extends Playable { - Playable calledPlayable; - BuildContext calledContext; - Color calledColor; + Playable? calledPlayable; + BuildContext? calledContext; + Color? calledColor; } class FakePlayablePlayer extends PlayablePlayer { @override final FakePlayable playable; @override - void play(BuildContext context, [Color backgroundColor]) { + void play(BuildContext context, [Color? backgroundColor]) { playable.calledPlayable = playable; playable.calledContext = context; playable.calledColor = backgroundColor; } - FakePlayablePlayer(this.playable) : assert(playable != null); + FakePlayablePlayer(this.playable); } class HomeWidgetPlayable extends StatelessWidget { final Color color = Colors.red; final PlayablePlayer playable; - const HomeWidgetPlayable(this.playable, {Key key}) : super(key: key); + const HomeWidgetPlayable(this.playable, {Key? key}) : super(key: key); @override Widget build(BuildContext context) { diff --git a/test/util/finders.dart b/test/util/finders.dart index 836f235..e658f94 100644 --- a/test/util/finders.dart +++ b/test/util/finders.dart @@ -3,13 +3,13 @@ import 'package:flutter/material.dart' show Text; import 'package:flutter_test/flutter_test.dart' show find, Finder; /// Finds a [Text] widget whose content contains the [substring]. -Finder findSubString(String substring) { +Finder findSubString(String? substring) { return find.byWidgetPredicate((widget) { if (widget is Text) { if (widget.data != null) { - return widget.data.contains(substring); + return widget.data!.contains(substring!); } - return widget.textSpan.toPlainText().contains(substring); + return widget.textSpan!.toPlainText().contains(substring!); } return false; }); diff --git a/test/util/test_asset_bundle.dart b/test/util/test_asset_bundle.dart index 2a98640..bc5938e 100644 --- a/test/util/test_asset_bundle.dart +++ b/test/util/test_asset_bundle.dart @@ -22,7 +22,7 @@ class TestAssetBundle extends CachingAssetBundle { /// `flutter test file.dart` will not. This class accounts for this difference /// and makes the assets always load from the top-level directory of the /// project so you don't have to worry about it. - TestAssetBundle([String assetsDirectory]) + TestAssetBundle([String? assetsDirectory]) : assetsDirectory = assetsDirectory ?? 'test/asset_bundle'; @override diff --git a/tool/ci b/tool/ci index cd73cd3..06995c7 100755 --- a/tool/ci +++ b/tool/ci @@ -15,6 +15,7 @@ main() { cmd_ci() { cmd_check + cmd_mocks_gen cmd_test_unit cmd_cov_check } @@ -32,6 +33,9 @@ cmd_analyze() { cmd_format() { echo "Checking format..." flutter format -n --set-exit-if-changed lib test + +cmd_mocks_gen() { + flutter pub run build_runner build } test_normal() { From f3394275994d9c51b518f72bd5bd4798aa504eba Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Sat, 18 Sep 2021 21:54:28 +0200 Subject: [PATCH 2/7] Update android dependencies Android dependencies must be updated because of audioplayers: * (compile+target)SdkVersion from 29 to 30 * Kotlin from 1.3.50 to 1.5.21 * Gradle from 3.5.0 to 4.1.0 * Gradle wrapper from 5.6.2 to 6.7 --- android/app/build.gradle | 4 ++-- .../res/drawable-v21/launch_background.xml | 12 ++++++++++++ .../app/src/main/res/values-night/styles.xml | 18 ++++++++++++++++++ android/build.gradle | 4 ++-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- example/android/app/build.gradle | 4 ++-- .../res/drawable-v21/launch_background.xml | 12 ++++++++++++ example/android/build.gradle | 4 ++-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- 9 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 android/app/src/main/res/values-night/styles.xml create mode 100644 example/android/app/src/main/res/drawable-v21/launch_background.xml diff --git a/android/app/build.gradle b/android/app/build.gradle index eeb859c..2ca6ba9 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 29 + compileSdkVersion 30 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -40,7 +40,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.lunofono_player" minSdkVersion 16 - targetSdkVersion 29 + targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..449a9f9 --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/build.gradle b/android/build.gradle index 3100ad2..ec6a0c8 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.5.21' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.android.tools.build:gradle:4.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 296b146..bc6a58a 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 9c91cce..d4a73dc 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 29 + compileSdkVersion 30 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -40,7 +40,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.example" minSdkVersion 23 - targetSdkVersion 29 + targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/build.gradle b/example/android/build.gradle index 3100ad2..ec6a0c8 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.5.21' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.android.tools.build:gradle:4.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 296b146..bc6a58a 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip From f8a1fbc0ba04e27c7e5e6e14a1638eebdda293a9 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Sat, 18 Sep 2021 21:55:53 +0200 Subject: [PATCH 3/7] Add Flutter proposed lint rules The following fixes had to be added: * Add Key argument to Widgets constructor * Add const where possible * Ignore prefer_function_declarations_over_variables link for create*() static methods intended for testing only * Convert variables with function literals with function declarations * Convert .forEach() to for loops when function literals are passed * Avoid using unnecessary {} in string interpolation * Avoid using unnecessary string interpolation --- analysis_options.yaml | 32 +++- example/analysis_options.yaml | 29 ++++ example/lib/main.dart | 24 +-- example/pubspec.yaml | 4 +- example/test/widget_test.dart | 2 +- example/test_integration/main_test.dart | 2 +- .../media_progress_indicator.dart | 7 +- lib/src/media_player/multi_medium_player.dart | 2 +- .../multi_medium_track_state.dart | 11 +- .../multi_medium_track_widget.dart | 2 +- lib/src/media_player/multi_medium_widget.dart | 2 +- lib/src/media_player/playlist_player.dart | 3 +- lib/src/media_player/playlist_state.dart | 9 +- lib/src/media_player/playlist_widget.dart | 9 +- .../single_medium_controller.dart | 2 +- .../media_player/single_medium_player.dart | 2 +- pubspec.lock | 14 ++ pubspec.yaml | 1 + test/unit/action_player_test.dart | 2 +- test/unit/bundle_player_test.dart | 2 +- .../styled_button_player_test.dart | 12 +- test/unit/button_player_test.dart | 2 +- .../controller_registry_test.dart | 3 +- .../media_player/media_player_error_test.dart | 3 +- .../multi_medium_player_test.dart | 144 ++++++++++-------- .../media_player/multi_medium_state_test.dart | 38 +++-- .../multi_medium_track_state_test.dart | 28 ++-- .../multi_medium_widget_test.dart | 2 +- .../media_player/playlist_player_test.dart | 4 +- .../media_player/playlist_state_test.dart | 28 ++-- .../media_player/playlist_widget_test.dart | 2 +- .../audio_video_player_controller_test.dart | 19 +-- .../image_player_controller_test.dart | 20 +-- .../single_medium_controller_common.dart | 6 +- .../single_medium_player_test.dart | 20 +-- .../single_medium_state_test.dart | 6 +- .../single_medium_widget_test.dart | 14 +- .../menu_player/grid_menu_player_test.dart | 6 +- test/unit/menu_player_test.dart | 2 +- test/unit/platform_services_test.dart | 2 +- test/unit/playable_player_test.dart | 18 +-- 41 files changed, 320 insertions(+), 220 deletions(-) create mode 100644 example/analysis_options.yaml diff --git a/analysis_options.yaml b/analysis_options.yaml index 6a0499d..19e1450 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,6 +1,36 @@ -include: package:pedantic/analysis_options.1.9.0.yaml +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml analyzer: strong-mode: implicit-casts: false implicit-dynamic: false + exclude: + - test/**.mocks.dart + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/example/lib/main.dart b/example/lib/main.dart index f6fda43..311dbfa 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,10 +4,12 @@ import 'package:lunofono_player/lunofono_player.dart'; import 'package:lunofono_bundle/lunofono_bundle.dart'; void main() { - runApp(MyApp()); + runApp(const MyApp()); } class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + // This widget is the root of your application. @override Widget build(BuildContext context) { @@ -78,7 +80,7 @@ final bundle = Bundle( PlayContentAction( Image( Uri.parse('assets/heilshorn-cows.jpg'), - maxDuration: Duration(seconds: 2), + maxDuration: const Duration(seconds: 2), ), ), backgroundColor: Colors.blue, @@ -87,7 +89,7 @@ final bundle = Bundle( PlayContentAction( Audio( Uri.parse('assets/Farm-SoundBible.com-1720780826.opus'), - maxDuration: Duration(seconds: 2), + maxDuration: const Duration(seconds: 2), ), ), backgroundColor: Colors.green, @@ -98,11 +100,11 @@ final bundle = Bundle( [ Image( Uri.parse('assets/heilshorn-cows.jpg'), - maxDuration: Duration(seconds: 2), + maxDuration: const Duration(seconds: 2), ), Image( Uri.parse('assets/heilshorn-cows2.jpg'), - maxDuration: Duration(seconds: 2), + maxDuration: const Duration(seconds: 2), ), ], ), @@ -117,7 +119,7 @@ final bundle = Bundle( VisualizableMultiMediumTrack( [ Image(Uri.parse('assets/heilshorn-cows2.jpg'), - maxDuration: Duration(seconds: 2)), + maxDuration: const Duration(seconds: 2)), ], ), backgroundTrack: AudibleBackgroundMultiMediumTrack( @@ -132,7 +134,7 @@ final bundle = Bundle( [ Audio( Uri.parse('assets/Farm-SoundBible.com-1720780826.opus'), - maxDuration: Duration(seconds: 4)), + maxDuration: const Duration(seconds: 4)), ], ), backgroundTrack: VisualizableBackgroundMultiMediumTrack( @@ -143,7 +145,7 @@ final bundle = Bundle( ), Image( Uri.parse('assets/heilshorn-cows2.jpg'), - maxDuration: Duration(seconds: 2), + maxDuration: const Duration(seconds: 2), ), ], ), @@ -155,7 +157,7 @@ final bundle = Bundle( ); class MyHomePage extends StatefulWidget { - MyHomePage({required this.title, Key? key}) : super(key: key); + const MyHomePage({required this.title, Key? key}) : super(key: key); // This widget is the home page of your application. It is stateful, meaning // that it has a State object (defined below) that contains fields that affect @@ -220,7 +222,7 @@ class _MyHomePageState extends State { // horizontal). mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( + const Text( 'You have pushed the button this many times:', ), Text( @@ -233,7 +235,7 @@ class _MyHomePageState extends State { floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', - child: Icon(Icons.add), + child: const Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index e4d2189..b84673a 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -21,14 +21,14 @@ dev_dependencies: flutter_driver: sdk: flutter + flutter_lints: ^1.0.4 + flutter_test: sdk: flutter integration_test: sdk: flutter - pedantic: ^1.11.1 - flutter: uses-material-design: true assets: diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index ee6adf2..1c4c2dc 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -6,7 +6,7 @@ import 'package:lunofono_player_example/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); + await tester.pumpWidget(const MyApp()); expect(find.byType(BundlePlayer), findsOneWidget); }); diff --git a/example/test_integration/main_test.dart b/example/test_integration/main_test.dart index e884b36..4928fa1 100644 --- a/example/test_integration/main_test.dart +++ b/example/test_integration/main_test.dart @@ -10,7 +10,7 @@ void main() { testWidgets('test video playing from local assets', (WidgetTester tester) async { - await tester.pumpWidget(MyApp()); + await tester.pumpWidget(const MyApp()); await tester.pumpAndSettle(); diff --git a/lib/src/media_player/media_progress_indicator.dart b/lib/src/media_player/media_progress_indicator.dart index a78eca0..c86e718 100644 --- a/lib/src/media_player/media_progress_indicator.dart +++ b/lib/src/media_player/media_progress_indicator.dart @@ -10,8 +10,9 @@ class MediaProgressIndicator extends StatelessWidget { final bool isVisualizable; /// Constructs a [MediaProgressIndicator] setting if it's [visualizable]. - const MediaProgressIndicator({required bool visualizable}) - : isVisualizable = visualizable; + const MediaProgressIndicator({required bool visualizable, Key? key}) + : isVisualizable = visualizable, + super(key: key); /// Builds the widget. @override @@ -20,7 +21,7 @@ class MediaProgressIndicator extends StatelessWidget { alignment: Alignment.center, children: [ Icon(isVisualizable ? Icons.local_movies : Icons.music_note), - CircularProgressIndicator(), + const CircularProgressIndicator(), ], ); } diff --git a/lib/src/media_player/multi_medium_player.dart b/lib/src/media_player/multi_medium_player.dart index 8486831..322877d 100644 --- a/lib/src/media_player/multi_medium_player.dart +++ b/lib/src/media_player/multi_medium_player.dart @@ -55,7 +55,7 @@ class MultiMediumPlayer extends StatelessWidget { child: Material( elevation: 0, color: backgroundColor, - child: Center( + child: const Center( child: MultiMediumWidget(), ), ), diff --git a/lib/src/media_player/multi_medium_track_state.dart b/lib/src/media_player/multi_medium_track_state.dart index 17ac2da..4b79e81 100644 --- a/lib/src/media_player/multi_medium_track_state.dart +++ b/lib/src/media_player/multi_medium_track_state.dart @@ -18,11 +18,12 @@ import 'single_medium_state.dart' show SingleMediumState; class MultiMediumTrackState with ChangeNotifier, DiagnosticableTreeMixin { /// Function used to create [SingleMediumState] instances. @visibleForTesting - static SingleMediumState Function(SingleMedium medium, {required bool isVisualizable, void Function(BuildContext)? onFinished}) createSingleMediumState = ( - SingleMedium medium, { - required bool isVisualizable, - void Function(BuildContext)? onFinished, - }) => + static var createSingleMediumState = _createSingleMediumState; + + /// Creates a [SingleMediumState] instance + static SingleMediumState _createSingleMediumState(SingleMedium medium, + {required bool isVisualizable, + void Function(BuildContext)? onFinished}) => SingleMediumState( medium, isVisualizable: isVisualizable, diff --git a/lib/src/media_player/multi_medium_track_widget.dart b/lib/src/media_player/multi_medium_track_widget.dart index e2b0094..3557128 100644 --- a/lib/src/media_player/multi_medium_track_widget.dart +++ b/lib/src/media_player/multi_medium_track_widget.dart @@ -31,7 +31,7 @@ class MultiMediumTrackWidget extends StatelessWidget { /// This is mainly useful for testing. @protected @visibleForTesting - Widget createSingleMediumWidget() => SingleMediumWidget(); + Widget createSingleMediumWidget() => const SingleMediumWidget(); /// Builds the UI for this widget. @override diff --git a/lib/src/media_player/multi_medium_widget.dart b/lib/src/media_player/multi_medium_widget.dart index b05b24e..cd3b39e 100644 --- a/lib/src/media_player/multi_medium_widget.dart +++ b/lib/src/media_player/multi_medium_widget.dart @@ -35,7 +35,7 @@ class MultiMediumWidget extends StatelessWidget { /// /// This is mainly useful for testing. @protected - MultiMediumTrackWidget createTrackWidget() => MultiMediumTrackWidget(); + MultiMediumTrackWidget createTrackWidget() => const MultiMediumTrackWidget(); /// Builds the UI for this widget. @override diff --git a/lib/src/media_player/playlist_player.dart b/lib/src/media_player/playlist_player.dart index 5c3c61d..f44987e 100644 --- a/lib/src/media_player/playlist_player.dart +++ b/lib/src/media_player/playlist_player.dart @@ -16,7 +16,8 @@ import 'playlist_widget.dart' show PlaylistWidget; /// a [PlaylistState] that is provided via a [ChangeNotifierProvider]. class PlaylistPlayer extends StatelessWidget { @visibleForTesting - static Widget Function() createPlaylistWidget = () => PlaylistWidget(); + static var createPlaylistWidget = _createPlaylistWidget; + static Widget _createPlaylistWidget() => const PlaylistWidget(); /// The [Playlist] to play by this player. final Playlist playlist; diff --git a/lib/src/media_player/playlist_state.dart b/lib/src/media_player/playlist_state.dart index 33e9576..bd44bb1 100644 --- a/lib/src/media_player/playlist_state.dart +++ b/lib/src/media_player/playlist_state.dart @@ -19,10 +19,13 @@ class PlaylistState implements PlayableState { /// Function used to create the concrete [PlayableState] instances. @visibleForTesting - static PlayableState Function( + static var createPlayableState = _createPlayableState; + + /// Creates an appropriate [PlayableState] instance based on the medium type. + static PlayableState _createPlayableState( Medium medium, { void Function(BuildContext)? onFinished, - }) createPlayableState = (medium, {onFinished}) { + }) { if (medium is SingleMedium) { return SingleMediumState(medium, onFinished: onFinished, isVisualizable: true); @@ -31,7 +34,7 @@ class PlaylistState return MultiMediumState(medium, onFinished: onFinished); } throw UnsupportedMediumTypeError(); - }; + } /// The playlist this state represents. @override diff --git a/lib/src/media_player/playlist_widget.dart b/lib/src/media_player/playlist_widget.dart index 81fcdbd..86bf3f0 100644 --- a/lib/src/media_player/playlist_widget.dart +++ b/lib/src/media_player/playlist_widget.dart @@ -26,11 +26,12 @@ import 'single_medium_widget.dart' show SingleMediumWidget; /// current medium is displayed (or the last if playing is done). class PlaylistWidget extends StatelessWidget { @visibleForTesting - static Widget Function() createSingleMediumWidget = - () => SingleMediumWidget(); + static Widget Function() createSingleMediumWidget = _createSingleMediumWidget; + static Widget _createSingleMediumWidget() => const SingleMediumWidget(); @visibleForTesting - static Widget Function() createMultiMediumWidget = () => MultiMediumWidget(); + static Widget Function() createMultiMediumWidget = _createMultiMediumWidget; + static Widget _createMultiMediumWidget() => const MultiMediumWidget(); /// Constructs a [MultiMediumTrackWidget]. const PlaylistWidget({Key? key}) : super(key: key); @@ -55,7 +56,7 @@ class PlaylistWidget extends StatelessWidget { } // Still initializing - return MediaProgressIndicator(visualizable: true); + return const MediaProgressIndicator(visualizable: true); }, ); } diff --git a/lib/src/media_player/single_medium_controller.dart b/lib/src/media_player/single_medium_controller.dart index 64aa95b..c27684a 100644 --- a/lib/src/media_player/single_medium_controller.dart +++ b/lib/src/media_player/single_medium_controller.dart @@ -328,7 +328,7 @@ class ImagePlayerController extends SingleMediumController { key: widgetKey, ); - _image.image.resolve(ImageConfiguration()).addListener( + _image.image.resolve(const ImageConfiguration()).addListener( ImageStreamListener( (ImageInfo info, bool _) { size = Size( diff --git a/lib/src/media_player/single_medium_player.dart b/lib/src/media_player/single_medium_player.dart index 612af06..e24a535 100644 --- a/lib/src/media_player/single_medium_player.dart +++ b/lib/src/media_player/single_medium_player.dart @@ -59,7 +59,7 @@ class SingleMediumPlayer extends StatelessWidget { child: Material( elevation: 0, color: backgroundColor, - child: Center( + child: const Center( child: SingleMediumWidget(), ), ), diff --git a/pubspec.lock b/pubspec.lock index 2196e86..b0e9ce8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -216,6 +216,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" flutter_test: dependency: "direct dev" description: flutter @@ -289,6 +296,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.1.0" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" logging: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 99c73af..440825a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,6 +28,7 @@ dependencies: dev_dependencies: build_runner: ^2.1.2 + flutter_lints: ^1.0.4 flutter_test: sdk: flutter mockito: ^5.0.15 diff --git a/test/unit/action_player_test.dart b/test/unit/action_player_test.dart index 81bf4fd..e409a2f 100644 --- a/test/unit/action_player_test.dart +++ b/test/unit/action_player_test.dart @@ -55,7 +55,7 @@ class FakeActionPlayer extends ActionPlayer { class FakeButtonPlayer extends Fake implements ButtonPlayer { @override - Color get backgroundColor => Color(0x12345678); + Color get backgroundColor => const Color(0x12345678); } class FakeContext extends Fake implements BuildContext {} diff --git a/test/unit/bundle_player_test.dart b/test/unit/bundle_player_test.dart index 0ddfdfb..c3f7e74 100644 --- a/test/unit/bundle_player_test.dart +++ b/test/unit/bundle_player_test.dart @@ -53,7 +53,7 @@ class FakeMenuPlayer extends MenuPlayer { final FakeMenu menu; @override Widget build(BuildContext context) { - return Container(child: Text('FakeMenu'), key: globalKey); + return Container(child: const Text('FakeMenu'), key: globalKey); } } diff --git a/test/unit/button_player/styled_button_player_test.dart b/test/unit/button_player/styled_button_player_test.dart index 7ec3f10..ff15171 100644 --- a/test/unit/button_player/styled_button_player_test.dart +++ b/test/unit/button_player/styled_button_player_test.dart @@ -46,7 +46,7 @@ void main() { setUp(() { fakeContext = FakeContext(); - color = Color(0x12ab4523); + color = const Color(0x12ab4523); }); test('build creates a StyledButtonWidget', () { @@ -67,12 +67,10 @@ void main() { Widget? widget; await tester.pumpWidget( MaterialApp( - home: Container( - child: Builder(builder: (context) { - widget = buttonPlayer.build(context); - return widget!; - }), - ), + home: Builder(builder: (context) { + widget = buttonPlayer.build(context); + return widget!; + }), ), ); expect(widget!.key, ObjectKey(button)); diff --git a/test/unit/button_player_test.dart b/test/unit/button_player_test.dart index 39a5b4d..0830fd3 100644 --- a/test/unit/button_player_test.dart +++ b/test/unit/button_player_test.dart @@ -46,7 +46,7 @@ void main() { setUp(() { fakeButton = FakeButton(); fakeContext = FakeContext(); - color = Color(0x12ab4523); + color = const Color(0x12ab4523); ActionPlayer.registry = ActionPlayerRegistry(); ActionPlayer.registry diff --git a/test/unit/media_player/controller_registry_test.dart b/test/unit/media_player/controller_registry_test.dart index e9dd7b4..5047c27 100644 --- a/test/unit/media_player/controller_registry_test.dart +++ b/test/unit/media_player/controller_registry_test.dart @@ -18,7 +18,8 @@ class _FakeSingleMediumController extends SingleMediumController { _FakeSingleMediumController() : super(_FakeSingleMedium(Uri.parse('fake-single-medium'))); @override - Future initialize(BuildContext context) => Future.value(Size(0, 0)); + Future initialize(BuildContext context) => + Future.value(const Size(0, 0)); @override Widget build(BuildContext context) => Container(); } diff --git a/test/unit/media_player/media_player_error_test.dart b/test/unit/media_player/media_player_error_test.dart index 2825dd4..baa76c9 100644 --- a/test/unit/media_player/media_player_error_test.dart +++ b/test/unit/media_player/media_player_error_test.dart @@ -17,8 +17,7 @@ void main() { final widget = MediaPlayerError(exception); await tester.pumpWidget( Directionality(textDirection: TextDirection.ltr, child: widget)); - expect( - find.text('Media could not be played: ${exception}'), findsWidgets); + expect(find.text('Media could not be played: $exception'), findsWidgets); }); testWidgets('PlatformException', (WidgetTester tester) async { diff --git a/test/unit/media_player/multi_medium_player_test.dart b/test/unit/media_player/multi_medium_player_test.dart index 81a49df..f305ccc 100644 --- a/test/unit/media_player/multi_medium_player_test.dart +++ b/test/unit/media_player/multi_medium_player_test.dart @@ -48,7 +48,7 @@ void main() { (WidgetTester tester) async { final medium = _FakeAudibleSingleMedium( 'unregisteredAudibleMedium', - size: Size(0.0, 0.0), + size: const Size(0.0, 0.0), ); await testUnregisteredMedium(tester, medium); }); @@ -58,7 +58,7 @@ void main() { (WidgetTester tester) async { final medium = _FakeVisualizableSingleMedium( 'unregisteredVisualizableMedium', - size: Size(10.0, 10.0), + size: const Size(10.0, 10.0), ); await testUnregisteredMedium(tester, medium); }); @@ -94,8 +94,8 @@ void main() { (WidgetTester tester) async { final notRotatedSquareMedium = _FakeVisualizableSingleMedium( 'notRotatedSquareMedium', - size: Size(10.0, 10.0), - duration: Duration(seconds: 1), + size: const Size(10.0, 10.0), + duration: const Duration(seconds: 1), ); playerTester = MediaPlayerTester(tester, notRotatedSquareMedium); @@ -108,8 +108,8 @@ void main() { (WidgetTester tester) async { final notRotatedPortraitMedium = _FakeVisualizableSingleMedium( 'notRotatedPortraitMedium', - size: Size(10.0, 20.0), - duration: Duration(seconds: 1), + size: const Size(10.0, 20.0), + duration: const Duration(seconds: 1), ); playerTester = MediaPlayerTester(tester, notRotatedPortraitMedium); @@ -122,8 +122,8 @@ void main() { (WidgetTester tester) async { final rotatedLandscapeMedium = _FakeVisualizableSingleMedium( 'rotatedLandscapeMedium', - size: Size(20.0, 10.0), - duration: Duration(seconds: 1), + size: const Size(20.0, 10.0), + duration: const Duration(seconds: 1), ); playerTester = MediaPlayerTester(tester, rotatedLandscapeMedium); @@ -155,8 +155,8 @@ void main() { (WidgetTester tester) async { final medium = _FakeAudibleSingleMedium( 'limitedAudibleMedium', - size: Size(0.0, 0.0), - duration: Duration(seconds: 1), + size: const Size(0.0, 0.0), + duration: const Duration(seconds: 1), ); await testPlayMediaUntilEnd(tester, medium); }); @@ -165,8 +165,8 @@ void main() { (WidgetTester tester) async { final medium = _FakeVisualizableSingleMedium( 'limitedVisualizableMedium', - size: Size(10.0, 10.0), - duration: Duration(seconds: 1), + size: const Size(10.0, 10.0), + duration: const Duration(seconds: 1), ); await testPlayMediaUntilEnd(tester, medium); }); @@ -174,7 +174,7 @@ void main() { testWidgets('plays unlimited media forever(ish, 10 days)', (WidgetTester tester) async { final unlimitedMedium = _FakeVisualizableSingleMedium('unlimitedMedium', - size: Size(10.0, 10.0)); + size: const Size(10.0, 10.0)); playerTester = MediaPlayerTester(tester, unlimitedMedium); await playerTester!.testInitializationDone(); @@ -182,7 +182,7 @@ void main() { playerTester!.expectPlayingStatus(finished: false); // Wait until half of the media was played, it should keep playing - await tester.pump(Duration(days: 10)); + await tester.pump(const Duration(days: 10)); playerTester!.expectPlayerWidget(); playerTester!.expectPlayingStatus(finished: false); }); @@ -190,9 +190,9 @@ void main() { testWidgets('tap stops while initializing', (WidgetTester tester) async { final tapInitMedium = _FakeVisualizableSingleMedium( 'tapInitMedium', - size: Size(10.0, 10.0), - duration: Duration(seconds: 1), - initDelay: Duration(milliseconds: 100), + size: const Size(10.0, 10.0), + duration: const Duration(seconds: 1), + initDelay: const Duration(milliseconds: 100), ); playerTester = MediaPlayerTester(tester, tapInitMedium); @@ -215,8 +215,8 @@ void main() { testWidgets('tap stops while playing', (WidgetTester tester) async { final tapPlayMedium = _FakeVisualizableSingleMedium( 'PlaynitMedium', - size: Size(10.0, 10.0), - duration: Duration(seconds: 1), + size: const Size(10.0, 10.0), + duration: const Duration(seconds: 1), ); playerTester = MediaPlayerTester(tester, tapPlayMedium); @@ -253,8 +253,8 @@ void main() { (WidgetTester tester) async { final tapPlayDoneMedium = _FakeVisualizableSingleMedium( 'tapPlayDoneMedium', - size: Size(10.0, 10.0), - duration: Duration(seconds: 1), + size: const Size(10.0, 10.0), + duration: const Duration(seconds: 1), ); playerTester = MediaPlayerTester(tester, tapPlayDoneMedium); @@ -282,23 +282,23 @@ void main() { (WidgetTester tester) async { final medium1 = _FakeAudibleVisualizableSingleMedium( 'medium1(audible, visualizable)', - size: Size(20.0, 10.0), - duration: Duration(seconds: 7), - initDelay: Duration(seconds: 2), + size: const Size(20.0, 10.0), + duration: const Duration(seconds: 7), + initDelay: const Duration(seconds: 2), ); final medium2 = _FakeVisualizableSingleMedium( 'medium2(visualizable)', - size: Size(10.0, 10.0), - duration: Duration(seconds: 1), - initDelay: Duration(seconds: 1), + size: const Size(10.0, 10.0), + duration: const Duration(seconds: 1), + initDelay: const Duration(seconds: 1), ); final medium3 = _FakeVisualizableSingleMedium( 'medium3(visualizable)', - size: Size(10.0, 20.0), - duration: Duration(seconds: 1), - initDelay: Duration(seconds: 3), + size: const Size(10.0, 20.0), + duration: const Duration(seconds: 1), + initDelay: const Duration(seconds: 3), ); final medium = MultiMedium( @@ -329,18 +329,18 @@ void main() { .reduce((d1, d2) => d1 > d2 ? d1 : d2); var left = Duration(milliseconds: maxInit.inMilliseconds); - final pump = (Duration t) async { + Future pump(Duration t) async { await tester.pump(t); left -= t; - }; + } await pump(minInit); playerTester!.expectMultiTrackIsInitialzing(); await pump(left ~/ 2); playerTester!.expectMultiTrackIsInitialzing(); - await pump(left - Duration(milliseconds: 1)); + await pump(left - const Duration(milliseconds: 1)); playerTester!.expectMultiTrackIsInitialzing(); - await pump(Duration(milliseconds: 1)); + await pump(const Duration(milliseconds: 1)); // Now the first medium should have started playing and not be finished playerTester!.expectPlayerWidget(mainMediumIndex: 0); @@ -358,14 +358,14 @@ void main() { (WidgetTester tester) async { final medium1 = _FakeAudibleSingleMedium( 'medium1(audible)', - size: Size(0.0, 0.0), - duration: Duration(seconds: 1), + size: const Size(0.0, 0.0), + duration: const Duration(seconds: 1), ); final medium2 = _FakeAudibleVisualizableSingleMedium( 'medium2(audible, visualizable)', - size: Size(10.0, 20.0), - duration: Duration(seconds: 7), + size: const Size(10.0, 20.0), + duration: const Duration(seconds: 7), ); final multiAudibleMainTrackMedium = MultiMedium( @@ -380,20 +380,20 @@ void main() { (WidgetTester tester) async { final medium1 = _FakeAudibleVisualizableSingleMedium( 'medium1(audible, visualizable)', - size: Size(20.0, 10.0), - duration: Duration(seconds: 7), + size: const Size(20.0, 10.0), + duration: const Duration(seconds: 7), ); final medium2 = _FakeVisualizableSingleMedium( 'medium2(visualizable)', - size: Size(10.0, 10.0), - duration: Duration(seconds: 1), + size: const Size(10.0, 10.0), + duration: const Duration(seconds: 1), ); final medium3 = _FakeVisualizableSingleMedium( 'medium3(visualizable)', - size: Size(10.0, 20.0), - duration: Duration(seconds: 1), + size: const Size(10.0, 20.0), + duration: const Duration(seconds: 1), ); final multiVisualizableMainTrackMedium = MultiMedium( @@ -410,8 +410,8 @@ void main() { (WidgetTester tester) async { final medium1 = _FakeAudibleSingleMedium( 'medium1(audible)', - size: Size(0.0, 0.0), - duration: Duration(seconds: 1), + size: const Size(0.0, 0.0), + duration: const Duration(seconds: 1), ); final medium = MultiMedium( @@ -427,8 +427,8 @@ void main() { (WidgetTester tester) async { final medium1 = _FakeVisualizableSingleMedium( 'medium1(audible)', - size: Size(0.0, 0.0), - duration: Duration(seconds: 1), + size: const Size(0.0, 0.0), + duration: const Duration(seconds: 1), ); final medium = MultiMedium( @@ -443,20 +443,20 @@ void main() { (WidgetTester tester) async { final medium1 = _FakeAudibleVisualizableSingleMedium( 'medium1(audible, visualizable)', - size: Size(20.0, 10.0), - duration: Duration(seconds: 7), + size: const Size(20.0, 10.0), + duration: const Duration(seconds: 7), ); final medium2 = _FakeVisualizableSingleMedium( 'medium2(visualizable)', - size: Size(10.0, 10.0), - duration: Duration(seconds: 1), + size: const Size(10.0, 10.0), + duration: const Duration(seconds: 1), ); final medium3 = _FakeVisualizableSingleMedium( 'medium3(visualizable)', - size: Size(10.0, 20.0), - duration: Duration(seconds: 1), + size: const Size(10.0, 20.0), + duration: const Duration(seconds: 1), ); final multiVisualizableMainTrackMedium = MultiMedium( @@ -648,8 +648,9 @@ class MediaPlayerTester { // Now the first medium should have started playing (and only the first // medium) but nothing should have finished yet. expect(mainControllers.first.isPlaying, isTrue); - mainTrackIndexes.forEach( - (n) => expectPlayingStatus(mainMediumIndex: n, finished: false)); + for (var n in mainTrackIndexes) { + expectPlayingStatus(mainMediumIndex: n, finished: false); + } mainTrackIndexes .skip(1) .forEach((n) => expect(mainControllers[n].isPlaying, isFalse)); @@ -665,16 +666,23 @@ class MediaPlayerTester { // be showing expectPlayerWidget(mainMediumIndex: currentIndex); // The previous media should have finished and not be playing. - prev.forEach((p) => expectPlayingStatus( - mainMediumIndex: p, finished: true, stoppedTimes: 0)); - prev.forEach((n) => expect(mainControllers[n].isPlaying, isFalse)); + for (var p in prev) { + expectPlayingStatus( + mainMediumIndex: p, finished: true, stoppedTimes: 0); + } + for (var n in prev) { + expect(mainControllers[n].isPlaying, isFalse); + } // The current should be playing and not finished. expectPlayingStatus(mainMediumIndex: currentIndex, finished: false); expect(mainControllers[currentIndex].isPlaying, isTrue); // And the following should be neither playing nor finished. - next.forEach( - (n) => expectPlayingStatus(mainMediumIndex: n, finished: false)); - next.forEach((n) => expect(mainControllers[n].isPlaying, isFalse)); + for (var n in next) { + expectPlayingStatus(mainMediumIndex: n, finished: false); + } + for (var n in next) { + expect(mainControllers[n].isPlaying, isFalse); + } // Wait until the current finishes playing await tester.pump(current.info.duration); @@ -701,11 +709,13 @@ class MediaPlayerTester { void expectMultiTrackIsInitialzing() { expectInitializationWidget(mainMediumIndex: 0); // All media shouldn't be finished yet - mainTrackIndexes.forEach( - (n) => expectPlayingStatus(mainMediumIndex: n, finished: false)); + for (var n in mainTrackIndexes) { + expectPlayingStatus(mainMediumIndex: n, finished: false); + } // and shouldn't be playing - mainTrackIndexes - .forEach((n) => expect(mainControllers[n].isPlaying, isFalse)); + for (var n in mainTrackIndexes) { + expect(mainControllers[n].isPlaying, isFalse); + } } void expectInitializationWidget({int mainMediumIndex = 0}) { @@ -786,7 +796,7 @@ class _SingleMediumInfo { exception == null && size != null), initDelay = initDelay ?? const Duration(seconds: 1), duration = duration ?? const UnlimitedDuration(), - widgetKey = GlobalKey(debugLabel: 'widgetKey(${location}'); + widgetKey = GlobalKey(debugLabel: 'widgetKey($location'); } abstract class _FakeSingleMedium extends SingleMedium { diff --git a/test/unit/media_player/multi_medium_state_test.dart b/test/unit/media_player/multi_medium_state_test.dart index 6335aa9..35194d1 100644 --- a/test/unit/media_player/multi_medium_state_test.dart +++ b/test/unit/media_player/multi_medium_state_test.dart @@ -28,9 +28,9 @@ void main() { tearDown(() => ControllerRegistry.instance = originalRegistry); final audibleMedium = - _FakeAudibleSingleMedium('audible', size: Size(0.0, 0.0)); + _FakeAudibleSingleMedium('audible', size: const Size(0.0, 0.0)); final audibleMedium2 = - _FakeAudibleSingleMedium('visualizable', size: Size(10.0, 12.0)); + _FakeAudibleSingleMedium('visualizable', size: const Size(10.0, 12.0)); final audibleMultiMedium = MultiMedium( AudibleMultiMediumTrack([audibleMedium, audibleMedium2])); @@ -39,8 +39,10 @@ void main() { AudibleMultiMediumTrack([audibleMedium, audibleMedium2]), backgroundTrack: _FakeVisualizableBackgroundMultiMediumTrack( [ - _FakeVisualizableSingleMedium('visualizable1', size: Size(1.0, 1.0)), - _FakeVisualizableSingleMedium('visualizable2', size: Size(2.0, 2.0)), + _FakeVisualizableSingleMedium('visualizable1', + size: const Size(1.0, 1.0)), + _FakeVisualizableSingleMedium('visualizable2', + size: const Size(2.0, 2.0)), ], ), ); @@ -52,11 +54,12 @@ void main() { expect(finished, isFalse); expect(state.isInitialized, isFalse); expect(state.backgroundTrackState, isEmpty); - state.mainTrackState.mediaState - .forEach((s) => expect(s.controller.asFake!.calls, isEmpty)); + for (var s in state.mainTrackState.mediaState) { + expect(s.controller.asFake!.calls, isEmpty); + } var notifyCalled = false; - final updateNotifyCalled = () => notifyCalled = true; + void updateNotifyCalled() => notifyCalled = true; state.addListener(updateNotifyCalled); await state.initialize(_FakeContext(), startPlaying: true); @@ -143,15 +146,18 @@ void main() { expect(finished, isFalse); expect(state.isInitialized, isFalse); expect(state.backgroundTrackState, isNotEmpty); - state.mainTrackState.mediaState - .forEach((s) => expect(s.controller.asFake!.calls, isEmpty)); - state.backgroundTrackState.mediaState - .forEach((s) => expect(s.controller.asFake!.calls, isEmpty)); + for (var s in state.mainTrackState.mediaState) { + expect(s.controller.asFake!.calls, isEmpty); + } + for (var s in state.backgroundTrackState.mediaState) { + expect(s.controller.asFake!.calls, isEmpty); + } var notifyCalled = false; - final checkInitialized = () { + void checkInitialized() { notifyCalled = true; - }; + } + state.addListener(checkInitialized); await state.initialize(_FakeContext(), startPlaying: true); @@ -299,7 +305,7 @@ void main() { test('toString()', () { expect( - MultiMediumState(multiMedium, onFinished: (context) => null).toString(), + MultiMediumState(multiMedium, onFinished: (context) {}).toString(), 'MultiMediumState(main: MultiMediumTrackState(audible, ' 'current: 0, media: 2), ' 'background: MultiMediumTrackState(visualizable, ' @@ -316,7 +322,7 @@ void main() { final identityHash = RegExp(r'#[0-9a-f]{5}'); expect( - MultiMediumState(multiMedium, onFinished: (context) => null) + MultiMediumState(multiMedium, onFinished: (context) {}) .toStringDeep() .replaceAll(identityHash, ''), 'MultiMediumState\n' @@ -399,7 +405,7 @@ class _SingleMediumInfo { this.exception, }) : assert(exception != null && size == null || exception == null && size != null), - widgetKey = GlobalKey(debugLabel: 'widgetKey(${location}'); + widgetKey = GlobalKey(debugLabel: 'widgetKey($location'); } abstract class _FakeSingleMedium extends SingleMedium { diff --git a/test/unit/media_player/multi_medium_track_state_test.dart b/test/unit/media_player/multi_medium_track_state_test.dart index 5c3bbf6..dd22242 100644 --- a/test/unit/media_player/multi_medium_track_state_test.dart +++ b/test/unit/media_player/multi_medium_track_state_test.dart @@ -35,10 +35,10 @@ void main() { final fakeContext = _FakeContext(); - final audibleMedium = - _FakeAudibleSingleMedium(name: 'audibleMedium1', size: Size(0.0, 0.0)); + final audibleMedium = _FakeAudibleSingleMedium( + name: 'audibleMedium1', size: const Size(0.0, 0.0)); final audibleMedium2 = _FakeAudibleSingleMedium( - name: 'audibleMedium2', size: Size(10.0, 12.0)); + name: 'audibleMedium2', size: const Size(10.0, 12.0)); final audibleMainTrack2 = _FakeAudibleMultiMediumTrack([ audibleMedium, audibleMedium2, @@ -49,13 +49,13 @@ void main() { ]); void stubAll(List media) { - media.forEach((s) { + for (var s in media) { when(s.initialize(fakeContext, startPlaying: false)) .thenAnswer((_) => Future.value()); when(s.play(fakeContext)).thenAnswer((_) => Future.value()); when(s.pause(fakeContext)).thenAnswer((_) => Future.value()); when(s.dispose()).thenAnswer((_) => Future.value()); - }); + } } group('constructor', () { @@ -98,7 +98,7 @@ void main() { }); test('.background() create empty track with NoTrack', () { - final state = MultiMediumTrackState.background(track: NoTrack()); + final state = MultiMediumTrackState.background(track: const NoTrack()); expect(state.isVisualizable, isFalse); expect(state.isFinished, isTrue); expect(state.isEmpty, isTrue); @@ -112,12 +112,12 @@ void main() { stubAll(state.mediaState); await state.initialize(fakeContext); expect(state.isFinished, isFalse); - state.mediaState.forEach((s) { + for (var s in state.mediaState) { verifyInOrder([ s.initialize(fakeContext), ]); verifyNoMoreInteractions(s); - }); + } }); test('initialize(startPlaying) initializes media and starts playing', @@ -195,10 +195,10 @@ void main() { // If we dispose the controller, state.mediaState.forEach(clearInteractions); await state.dispose(); - state.mediaState.forEach((s) { + for (var s in state.mediaState) { verify(s.dispose()).called(1); verifyNoMoreInteractions(s); - }); + } }); test('listening for updates work', () async { @@ -276,9 +276,11 @@ void main() { test('debugDescribeChildren()', () async { final state = MultiMediumTrackState.main(track: audibleMainTrack2); final fakeNode = _FakeDiagnosticsNode(); - state.mediaState.forEach((s) => when(s.toDiagnosticsNode( - name: captureAnyNamed('name'), style: captureAnyNamed('style'))) - .thenReturn(fakeNode)); + for (var s in state.mediaState) { + when(s.toDiagnosticsNode( + name: captureAnyNamed('name'), style: captureAnyNamed('style'))) + .thenReturn(fakeNode); + } expect(state.debugDescribeChildren(), [fakeNode, fakeNode]); state.mediaState.asMap().forEach( diff --git a/test/unit/media_player/multi_medium_widget_test.dart b/test/unit/media_player/multi_medium_widget_test.dart index c2344a1..d0ac8db 100644 --- a/test/unit/media_player/multi_medium_widget_test.dart +++ b/test/unit/media_player/multi_medium_widget_test.dart @@ -158,7 +158,7 @@ class _FakeSingleMediumState extends Fake implements SingleMediumState { final String name; @override - String toStringShort() => '$name'; + String toStringShort() => name; @override Future dispose() async => super.dispose(); _FakeSingleMediumState(this.name); diff --git a/test/unit/media_player/playlist_player_test.dart b/test/unit/media_player/playlist_player_test.dart index 13f2ca8..2474fbc 100644 --- a/test/unit/media_player/playlist_player_test.dart +++ b/test/unit/media_player/playlist_player_test.dart @@ -31,7 +31,7 @@ void main() { }); test('default constructor saves extra arguments as expected', () { - final callback = (BuildContext context) {}; + void callback(BuildContext context) {} final player = PlaylistPlayer( playlist: mockPlaylist, backgroundColor: Colors.red, @@ -98,5 +98,5 @@ void main() { class _FakePlaylistWidget extends StatelessWidget { @override - Widget build(BuildContext context) => Text('test'); + Widget build(BuildContext context) => const Text('test'); } diff --git a/test/unit/media_player/playlist_state_test.dart b/test/unit/media_player/playlist_state_test.dart index bf0cb0d..d4e0b10 100644 --- a/test/unit/media_player/playlist_state_test.dart +++ b/test/unit/media_player/playlist_state_test.dart @@ -23,7 +23,7 @@ void main() { group('PlaylistState.createPlayableState()', () { test('creates SingleMediumState for SingleMedium', () { final medium = _FakeSingleMedium(); - final onFinished = (BuildContext context) {}; + void onFinished(BuildContext context) {} final state = PlaylistState.createPlayableState(medium, onFinished: onFinished); expect(state, isA()); @@ -32,7 +32,7 @@ void main() { test('creates SingleMediumState for SingleMedium', () { final medium = _FakeMultiMedium(); - final onFinished = (BuildContext context) {}; + void onFinished(BuildContext context) {} final state = PlaylistState.createPlayableState(medium, onFinished: onFinished); expect(state, isA()); @@ -41,7 +41,7 @@ void main() { test('throws UnsupportedMediumType on unsupported Medium type', () { final medium = _FakeMedium(); - final onFinished = (BuildContext context) {}; + void onFinished(BuildContext context) {} expect( () => PlaylistState.createPlayableState(medium, onFinished: onFinished), @@ -71,14 +71,14 @@ void main() { () => PlaylistState.createPlayableState = originalCreatePlayableState); void stubAll(List states) { - states.forEach((state) { + for (var state in states) { final s = state as _MockPlayableState; when(s.initialize(fakeContext, startPlaying: anyNamed('startPlaying'))) .thenAnswer((_) => Future.value()); when(s.play(fakeContext)).thenAnswer((_) => Future.value()); when(s.pause(fakeContext)).thenAnswer((_) => Future.value()); when(s.dispose()).thenAnswer((_) => Future.value()); - }); + } } PlaylistState createPlaylistState( @@ -113,12 +113,12 @@ void main() { final state = createPlaylistState(); await state.initialize(fakeContext); expect(state.isFinished, isFalse); - state.mediaState.forEach((s) { + for (var s in state.mediaState) { verifyInOrder([ s.initialize(fakeContext), ]); verifyNoMoreInteractions(s); - }); + } }); test('initialize(startPlaying) initializes media and starts playing', @@ -190,10 +190,10 @@ void main() { // If we dispose the controller, state.mediaState.forEach(clearInteractions); await state.dispose(); - state.mediaState.forEach((s) { + for (var s in state.mediaState) { verify(s.dispose()).called(1); verifyNoMoreInteractions(s); - }); + } }); test('listening for updates work', () async { @@ -258,9 +258,11 @@ void main() { test('debugDescribeChildren()', () async { final state = createPlaylistState(); final fakeNode = _FakeDiagnosticsNode(); - state.mediaState.forEach((s) => when(s.toDiagnosticsNode( - name: captureAnyNamed('name'), style: captureAnyNamed('style'))) - .thenReturn(fakeNode)); + for (var s in state.mediaState) { + when(s.toDiagnosticsNode( + name: captureAnyNamed('name'), style: captureAnyNamed('style'))) + .thenReturn(fakeNode); + } expect(state.debugDescribeChildren(), [fakeNode, fakeNode]); state.mediaState.asMap().forEach( @@ -310,7 +312,7 @@ class _FakeMultiMedium extends _FakeMedium implements MultiMedium { _FakeMultiMediumTrack(_FakeSingleMedium('mainTrack1')); @override - BackgroundMultiMediumTrack get backgroundTrack => NoTrack(); + BackgroundMultiMediumTrack get backgroundTrack => const NoTrack(); _FakeMultiMedium([String? name]) : super(name); } diff --git a/test/unit/media_player/playlist_widget_test.dart b/test/unit/media_player/playlist_widget_test.dart index db242ec..a9083c4 100644 --- a/test/unit/media_player/playlist_widget_test.dart +++ b/test/unit/media_player/playlist_widget_test.dart @@ -54,7 +54,7 @@ void main() { textDirection: TextDirection.ltr, child: ChangeNotifierProvider.value( value: state, - child: PlaylistWidget(), + child: const PlaylistWidget(), ), ); diff --git a/test/unit/media_player/single_medium_controller/audio_video_player_controller_test.dart b/test/unit/media_player/single_medium_controller/audio_video_player_controller_test.dart index 807fe1c..bb1a056 100644 --- a/test/unit/media_player/single_medium_controller/audio_video_player_controller_test.dart +++ b/test/unit/media_player/single_medium_controller/audio_video_player_controller_test.dart @@ -88,8 +88,8 @@ void main() { 'initializes and plays a video until the end', (WidgetTester tester) async { final videoInfo = FakeVideoInfo( - Duration(milliseconds: 1000), - Size(10.0, 20.0), + const Duration(milliseconds: 1000), + const Size(10.0, 20.0), ); var hasFinished = false; controller = TestVideoPlayerController( @@ -142,13 +142,13 @@ void main() { 'initializes and plays a video with a maxDuration until the end', (WidgetTester tester) async { final videoInfo = FakeVideoInfo( - Duration(milliseconds: 1000), - Size(10.0, 20.0), + const Duration(milliseconds: 1000), + const Size(10.0, 20.0), ); var hasFinished = false; final limitedVideo = Video( Uri.parse('fake-video.avi'), - maxDuration: Duration(milliseconds: 600), + maxDuration: const Duration(milliseconds: 600), ); controller = TestVideoPlayerController( limitedVideo, @@ -185,7 +185,7 @@ void main() { seekPosition = Duration(milliseconds: limitedVideo.maxDuration.inMilliseconds + 1); fakeController.fakeSeekTo(seekPosition); - await tester.pump(Duration(milliseconds: 2)); + await tester.pump(const Duration(milliseconds: 2)); // The video should be paused and onMediumFinished called expectSuccess(tester, widget, findWidget: video_player.VideoPlayer); @@ -198,8 +198,8 @@ void main() { 'initializes and plays a video until the user reacts', (WidgetTester tester) async { final videoInfo = FakeVideoInfo( - Duration(milliseconds: 1000), - Size(1024.0, 768.0), + const Duration(milliseconds: 1000), + const Size(1024.0, 768.0), ); var hasFinished = false; controller = TestVideoPlayerController( @@ -265,7 +265,8 @@ void main() { testWidgets('initializes and plays showing an empty container', (WidgetTester tester) async { - final videoInfo = FakeVideoInfo(Duration(seconds: 1), Size(0.0, 0.0)); + final videoInfo = + FakeVideoInfo(const Duration(seconds: 1), const Size(0.0, 0.0)); final controller = TestAudioPlayerController(audio, videoInfo, widgetKey: globalSuccessKey); final widget = TestWidget(controller); diff --git a/test/unit/media_player/single_medium_controller/image_player_controller_test.dart b/test/unit/media_player/single_medium_controller/image_player_controller_test.dart index e0be713..8715f4a 100644 --- a/test/unit/media_player/single_medium_controller/image_player_controller_test.dart +++ b/test/unit/media_player/single_medium_controller/image_player_controller_test.dart @@ -49,7 +49,7 @@ void main() { await tester.runAsync(() async => await undeadlockAsync()); // XXX!!! await tester.pumpAndSettle(); expectSuccess(tester, widget, - size: Size(10.0, 10.0), findWidget: Image); + size: const Size(10.0, 10.0), findWidget: Image); expect(find.byWidget(controller.image), findsOneWidget); }, ); @@ -71,7 +71,7 @@ void main() { await tester.runAsync(() async => await undeadlockAsync()); // XXX!!! await tester.pumpAndSettle(); - final size = Size(10.0, 10.0); + const size = Size(10.0, 10.0); expectSuccess(tester, widget, size: size, findWidget: Image); expect(find.byWidget(controller.image), findsOneWidget); expect(hasStopped, false); @@ -79,7 +79,7 @@ void main() { // pause() has not effect when there is no maxDuration. await controller.pause(_FakeContext()); - await tester.pump(Duration(days: 10)); + await tester.pump(const Duration(days: 10)); expectSuccess(tester, widget, size: size, findWidget: Image); expect(find.byWidget(controller.image), findsOneWidget); expect(hasStopped, false); @@ -92,7 +92,7 @@ void main() { var hasStopped = false; final image = bundle.Image( Uri.parse('assets/10x10-red.png'), - maxDuration: Duration(seconds: 1), + maxDuration: const Duration(seconds: 1), ); controller = ImagePlayerController( image, @@ -107,7 +107,7 @@ void main() { await tester.runAsync(() async => await undeadlockAsync()); // XXX!!! await tester.pumpAndSettle(); - final size = Size(10.0, 10.0); + const size = Size(10.0, 10.0); expectSuccess(tester, widget, size: size, findWidget: Image); expect(find.byWidget(controller.image), findsOneWidget); expect(hasStopped, false); @@ -132,7 +132,7 @@ void main() { var hasStopped = false; final image = bundle.Image( Uri.parse('assets/10x10-red.png'), - maxDuration: Duration(seconds: 1), + maxDuration: const Duration(seconds: 1), ); controller = ImagePlayerController( image, @@ -147,7 +147,7 @@ void main() { await tester.runAsync(() async => await undeadlockAsync()); // XXX!!! await tester.pumpAndSettle(); - final size = Size(10.0, 10.0); + const size = Size(10.0, 10.0); expectSuccess(tester, widget, size: size, findWidget: Image); expect(find.byWidget(controller.image), findsOneWidget); expect(hasStopped, false); @@ -160,7 +160,7 @@ void main() { // Now we pause and let a day go by and it should be still playing await controller.pause(_FakeContext()); - await tester.pump(Duration(days: 1)); + await tester.pump(const Duration(days: 1)); expectSuccess(tester, widget, size: size, findWidget: Image); expect(find.byWidget(controller.image), findsOneWidget); expect(hasStopped, false); @@ -181,7 +181,7 @@ void main() { var hasStopped = false; final image = bundle.Image( Uri.parse('assets/10x10-red.png'), - maxDuration: Duration(seconds: 1), + maxDuration: const Duration(seconds: 1), ); controller = ImagePlayerController( image, @@ -196,7 +196,7 @@ void main() { await tester.runAsync(() async => await undeadlockAsync()); // XXX!!! await tester.pumpAndSettle(); - final size = Size(10.0, 10.0); + const size = Size(10.0, 10.0); expectSuccess(tester, widget, size: size, findWidget: Image); expect(find.byWidget(controller.image), findsOneWidget); expect(hasStopped, false); diff --git a/test/unit/media_player/single_medium_controller/single_medium_controller_common.dart b/test/unit/media_player/single_medium_controller/single_medium_controller_common.dart index 44b8f17..c08324d 100644 --- a/test/unit/media_player/single_medium_controller/single_medium_controller_common.dart +++ b/test/unit/media_player/single_medium_controller/single_medium_controller_common.dart @@ -57,10 +57,12 @@ class TestWidget extends StatelessWidget { final SingleMediumController controller; final AssetBundle bundle; final bool startPlaying; - TestWidget(this.controller, {AssetBundle? bundle, this.startPlaying = true}) + TestWidget(this.controller, + {AssetBundle? bundle, this.startPlaying = true, Key? key}) : errorKey = GlobalKey(debugLabel: 'errorKey'), loadingKey = GlobalKey(debugLabel: 'loadingKey'), - bundle = bundle ?? TestAssetBundle() { + bundle = bundle ?? TestAssetBundle(), + super(key: key) { globalSize = null; } diff --git a/test/unit/media_player/single_medium_player_test.dart b/test/unit/media_player/single_medium_player_test.dart index ccb7024..cd71295 100644 --- a/test/unit/media_player/single_medium_player_test.dart +++ b/test/unit/media_player/single_medium_player_test.dart @@ -40,7 +40,7 @@ void main() { (WidgetTester tester) async { final medium = _FakeAudibleSingleMedium( 'unregisteredAudibleMedium', - size: Size(0.0, 0.0), + size: const Size(0.0, 0.0), ); await testUnregisteredMedium(tester, medium); }); @@ -50,7 +50,7 @@ void main() { (WidgetTester tester) async { final medium = _FakeVisualizableSingleMedium( 'unregisteredVisualizableMedium', - size: Size(10.0, 10.0), + size: const Size(10.0, 10.0), ); await testUnregisteredMedium(tester, medium); }); @@ -58,9 +58,9 @@ void main() { testWidgets('tap stops while initializing', (WidgetTester tester) async { final tapInitMedium = _FakeVisualizableSingleMedium( 'tapInitMedium', - size: Size(10.0, 10.0), - duration: Duration(seconds: 1), - initDelay: Duration(milliseconds: 100), + size: const Size(10.0, 10.0), + duration: const Duration(seconds: 1), + initDelay: const Duration(milliseconds: 100), ); playerTester = _SingleMediumPlayerTester(tester, tapInitMedium); @@ -83,8 +83,8 @@ void main() { testWidgets('tap stops while playing', (WidgetTester tester) async { final tapPlayMedium = _FakeVisualizableSingleMedium( 'tapPlayMedium', - size: Size(10.0, 10.0), - duration: Duration(seconds: 1), + size: const Size(10.0, 10.0), + duration: const Duration(seconds: 1), ); playerTester = _SingleMediumPlayerTester(tester, tapPlayMedium); @@ -121,8 +121,8 @@ void main() { (WidgetTester tester) async { final tapPlayDoneMedium = _FakeVisualizableSingleMedium( 'tapPlayDoneMedium', - size: Size(10.0, 10.0), - duration: Duration(seconds: 1), + size: const Size(10.0, 10.0), + duration: const Duration(seconds: 1), ); playerTester = _SingleMediumPlayerTester(tester, tapPlayDoneMedium); @@ -292,7 +292,7 @@ class _SingleMediumInfo { exception == null && size != null), initDelay = initDelay ?? const Duration(seconds: 1), duration = duration ?? const UnlimitedDuration(), - widgetKey = GlobalKey(debugLabel: 'widgetKey(${location}'); + widgetKey = GlobalKey(debugLabel: 'widgetKey($location'); } abstract class _FakeSingleMedium extends SingleMedium { diff --git a/test/unit/media_player/single_medium_state_test.dart b/test/unit/media_player/single_medium_state_test.dart index 503f5ed..8f9342e 100644 --- a/test/unit/media_player/single_medium_state_test.dart +++ b/test/unit/media_player/single_medium_state_test.dart @@ -47,7 +47,7 @@ void main() { group('SingleMediumState', () { group('without a registered medium', () { test('constructs a state with an error', () { - final medium = _FakeSingleMedium('bad-medium', size: Size(1, 1)); + final medium = _FakeSingleMedium('bad-medium', size: const Size(1, 1)); final state = SingleMediumState(medium, isVisualizable: true); expect(state.isErroneous, true); expect(state.error, contains('Unsupported type')); @@ -134,7 +134,7 @@ void main() { late SingleMediumState state; setUp(() { - size = Size(0.0, 0.0); + size = const Size(0.0, 0.0); medium = _FakeSingleMedium('good-medium', size: size); controller = _FakeSingleMediumController(medium); registry.register( @@ -239,7 +239,7 @@ class _SingleMediumInfo { this.exception, }) : assert(exception != null && size == null || exception == null && size != null), - widgetKey = GlobalKey(debugLabel: 'widgetKey(${location}'); + widgetKey = GlobalKey(debugLabel: 'widgetKey($location'); } class _FakeSingleMedium extends SingleMedium { diff --git a/test/unit/media_player/single_medium_widget_test.dart b/test/unit/media_player/single_medium_widget_test.dart index 424e066..106a01b 100644 --- a/test/unit/media_player/single_medium_widget_test.dart +++ b/test/unit/media_player/single_medium_widget_test.dart @@ -36,7 +36,7 @@ void main() { textDirection: TextDirection.ltr, child: ChangeNotifierProvider.value( value: state, - child: SingleMediumWidget(), + child: const SingleMediumWidget(), ), ), ); @@ -97,8 +97,8 @@ void main() { testWidgets('a Container for an initialized non-visualizable state', (WidgetTester tester) async { - final state = - _FakeSingleMediumState(size: Size(2.0, 1.0), isVisualizable: false); + final state = _FakeSingleMediumState( + size: const Size(2.0, 1.0), isVisualizable: false); final playerWidget = await testPlayer(tester, state); expect(playerWidget, isA()); expect(find.byType(RotatedBox), findsNothing); @@ -106,27 +106,27 @@ void main() { testWidgets('the player for a visualizable state', (WidgetTester tester) async { - final state = _FakeSingleMediumState(size: Size(1.0, 2.0)); + final state = _FakeSingleMediumState(size: const Size(1.0, 2.0)); await testPlayer(tester, state); }); testWidgets('no RotatedBox for square media', (WidgetTester tester) async { - final state = _FakeSingleMediumState(size: Size(1.0, 1.0)); + final state = _FakeSingleMediumState(size: const Size(1.0, 1.0)); await testPlayer(tester, state); expect(find.byType(RotatedBox), findsNothing); }); testWidgets('no RotatedBox for portrait media', (WidgetTester tester) async { - final state = _FakeSingleMediumState(size: Size(1.0, 2.0)); + final state = _FakeSingleMediumState(size: const Size(1.0, 2.0)); await testPlayer(tester, state); expect(find.byType(RotatedBox), findsNothing); }); testWidgets('a RotatedBox for landscape media', (WidgetTester tester) async { - final state = _FakeSingleMediumState(size: Size(2.0, 1.0)); + final state = _FakeSingleMediumState(size: const Size(2.0, 1.0)); await testPlayer(tester, state); expect(find.byType(RotatedBox), findsOneWidget); }); diff --git a/test/unit/menu_player/grid_menu_player_test.dart b/test/unit/menu_player/grid_menu_player_test.dart index 1be2e21..e3d2524 100644 --- a/test/unit/menu_player/grid_menu_player_test.dart +++ b/test/unit/menu_player/grid_menu_player_test.dart @@ -71,10 +71,8 @@ void main() { testWidgets('shows all buttons', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - home: Container( - child: Builder( - builder: (context) => menuPlayer!.build(context), - ), + home: Builder( + builder: (context) => menuPlayer!.build(context), ), ), ); diff --git a/test/unit/menu_player_test.dart b/test/unit/menu_player_test.dart index 5e857e4..2a120f2 100644 --- a/test/unit/menu_player_test.dart +++ b/test/unit/menu_player_test.dart @@ -100,7 +100,7 @@ class _FakeMenuPlayer extends MenuPlayer { static Key globalKey = GlobalKey(debugLabel: 'FakeMenuPlayerKey'); @override Widget build(BuildContext context) { - final widget = Container(child: Text('FakeMenu'), key: globalKey); + final widget = Container(child: const Text('FakeMenu'), key: globalKey); menu.buildCalls.add(_BuildCall(context, widget)); return widget; } diff --git a/test/unit/platform_services_test.dart b/test/unit/platform_services_test.dart index 9149968..59ba6a2 100644 --- a/test/unit/platform_services_test.dart +++ b/test/unit/platform_services_test.dart @@ -56,7 +56,7 @@ void main() { }); group('new instance', () { - testInstance(PlatformServices()); + testInstance(const PlatformServices()); }); }); } diff --git a/test/unit/playable_player_test.dart b/test/unit/playable_player_test.dart index b537aa6..fe17cab 100644 --- a/test/unit/playable_player_test.dart +++ b/test/unit/playable_player_test.dart @@ -20,7 +20,7 @@ void main() { setUp(() { fakePlayable = FakePlayable(); fakeContext = FakeContext(); - color = Color(0x3845bd34); + color = const Color(0x3845bd34); }); tearDown(() => PlayablePlayer.registry = oldPlayableRegistry); @@ -58,7 +58,7 @@ void main() { // https://github.com/flutter/flutter/blob/1.20.3/packages/flutter/test/widgets/navigator_test.dart await tester.pump(); // The second pump we wait a bit because Navigator is animated - await tester.pump(Duration(seconds: 1)); + await tester.pump(const Duration(seconds: 1)); expect(find.byKey(homeKey), findsNothing); final playerFinder = find.byType(SingleMediumPlayer); expect(playerFinder, findsOneWidget); @@ -68,7 +68,7 @@ void main() { // The HomeWidget should be back mediaPlayer.onMediaStopped!(context); // Should call Navigator.pop() await tester.pump(); // Same with .push() about the double pump() - await tester.pump(Duration(seconds: 1)); + await tester.pump(const Duration(seconds: 1)); expect(find.byKey(homeKey), findsOneWidget); expect(find.byType(SingleMediumPlayer), findsNothing); } @@ -106,7 +106,7 @@ void main() { // https://github.com/flutter/flutter/blob/1.20.3/packages/flutter/test/widgets/navigator_test.dart await tester.pump(); // The second pump we wait a bit because Navigator is animated - await tester.pump(Duration(seconds: 1)); + await tester.pump(const Duration(seconds: 1)); expect(find.byKey(homeKey), findsNothing); final playerFinder = find.byType(MultiMediumPlayer); expect(playerFinder, findsOneWidget); @@ -116,7 +116,7 @@ void main() { // The HomeWidget should be back mediaPlayer.onMediaStopped!(context); // Should call Navigator.pop() await tester.pump(); // Same with .push() about the double pump() - await tester.pump(Duration(seconds: 1)); + await tester.pump(const Duration(seconds: 1)); expect(find.byKey(homeKey), findsOneWidget); expect(find.byType(MultiMediumPlayer), findsNothing); } @@ -154,7 +154,7 @@ void main() { // https://github.com/flutter/flutter/blob/1.20.3/packages/flutter/test/widgets/navigator_test.dart await tester.pump(); // The second pump we wait a bit because Navigator is animated - await tester.pump(Duration(seconds: 1)); + await tester.pump(const Duration(seconds: 1)); expect(find.byKey(homeKey), findsNothing); final playerFinder = find.byType(PlaylistPlayer); expect(playerFinder, findsOneWidget); @@ -164,7 +164,7 @@ void main() { // The HomeWidget should be back mediaPlayer.onMediaStopped!(context); // Should call Navigator.pop() await tester.pump(); // Same with .push() about the double pump() - await tester.pump(Duration(seconds: 1)); + await tester.pump(const Duration(seconds: 1)); expect(find.byKey(homeKey), findsOneWidget); expect(find.byType(PlaylistPlayer), findsNothing); } @@ -222,9 +222,7 @@ class HomeWidgetPlayable extends StatelessWidget { onTap: () { playable.play(context, color); }, - child: Container( - child: const Text('home'), - ), + child: const Text('home'), ); } } From 8801cccc111192f1f97e19221ab4548510209b44 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Sat, 18 Sep 2021 21:58:56 +0200 Subject: [PATCH 4/7] example/web: Add new flutter-produced files --- example/web/icons/Icon-maskable-192.png | Bin 0 -> 5594 bytes example/web/icons/Icon-maskable-512.png | Bin 0 -> 20998 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 example/web/icons/Icon-maskable-192.png create mode 100644 example/web/icons/Icon-maskable-512.png diff --git a/example/web/icons/Icon-maskable-192.png b/example/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9b4d76e525556d5d89141648c724331630325d GIT binary patch literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! literal 0 HcmV?d00001 diff --git a/example/web/icons/Icon-maskable-512.png b/example/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..d69c56691fbdb0b7efa65097c7cc1edac12a6d3e GIT binary patch literal 20998 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 MPgg&ebxsLQ025IeI{*Lx literal 0 HcmV?d00001 From 0db3c166daef9e6c6aeb57791be23e4236ab77f2 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Tue, 21 Sep 2021 20:34:05 +0200 Subject: [PATCH 5/7] ci: Stop using kitek/dartanalyzer-annotations-action The dartanalyzer has a bug when excluding files that have been around for a long time now, and since it is now deprecated in flavour of `dart analyze` it is very unlikely to be fixed any time soon. Since this action uses dartanalyzer and we need to exclude mocks, we can't use it anymore. https://github.com/dart-lang/sdk/issues/25551 --- .github/workflows/ci.yml | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc3f55a..3056990 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,24 +38,9 @@ jobs: - name: Generate unit tests mocks run: tool/ci mocks-gen - # Analyze step needs different config for pull_request and push, so it is - # duplicated with if conditions to use the correct configuration for each - - name: Analyze (push) - if: github.event_name == 'push' && matrix.test_type == 'static' - uses: kitek/dartanalyzer-annotations-action@v1 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - check_name: test - commit_sha: ${{ github.sha }} - - name: Analyze (pull_request) - if: github.event_name == 'pull_request' && matrix.test_type == 'static' - uses: kitek/dartanalyzer-annotations-action@v1 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - check_name: test - commit_sha: ${{ github.event.pull_request.head.sha }} + - name: Analyze + if: matrix.test_type == 'static' + run: tool/ci analyze - name: Check format run: flutter format -n --set-exit-if-changed lib test example From ab1712ad62672758201f2d11a8a48585f64c2af9 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Tue, 21 Sep 2021 22:01:43 +0200 Subject: [PATCH 6/7] ci: Use tool/ci for formatting check in action --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3056990..cca5e1b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,8 @@ jobs: run: tool/ci analyze - name: Check format - run: flutter format -n --set-exit-if-changed lib test example + if: matrix.test_type == 'static' + run: tool/ci format - name: Run ${{ matrix.test_type }} tests if: matrix.test_type != 'static' From bd237af198a556698f49f892233d8f078f4746e9 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Tue, 21 Sep 2021 22:06:01 +0200 Subject: [PATCH 7/7] ci: Analyze and format all the projects files Flutter will only run analyze and format over some directories. For example, analyze is not ran for test by default, and sometimes the example directory was not included either. --- tool/ci | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tool/ci b/tool/ci index 06995c7..28914f1 100755 --- a/tool/ci +++ b/tool/ci @@ -27,12 +27,13 @@ cmd_check() { } cmd_analyze() { - flutter analyze lib test + flutter analyze . } cmd_format() { echo "Checking format..." - flutter format -n --set-exit-if-changed lib test + flutter format -n --set-exit-if-changed . +} cmd_mocks_gen() { flutter pub run build_runner build