Files
SpotiFLAC-Mobile/test/player_widget_ownership_test.dart
T

200 lines
6.6 KiB
Dart

import 'dart:async';
import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:spotiflac_android/controllers/player_metadata_controller.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/models/track.dart';
import 'package:spotiflac_android/providers/music_player_provider.dart';
import 'package:spotiflac_android/routes/now_playing_route.dart';
import 'package:spotiflac_android/utils/playback_seek_preview.dart';
import 'package:spotiflac_android/widgets/audio_quality_badges.dart';
import 'package:spotiflac_android/widgets/material_player_layout.dart';
import 'package:spotiflac_android/widgets/player_details_sheet.dart';
import 'package:spotiflac_android/widgets/player_gesture_regions.dart';
MediaItem _item(String id, {String? source}) => MediaItem(
id: id,
title: id,
artist: 'Artist',
album: 'Album',
duration: const Duration(minutes: 3),
extras: {'source': source ?? '/music/$id.flac'},
);
class _FooterRoute extends NowPlayingRoute {
_FooterRoute(this.onOpenQueue) : super(child: const SizedBox());
final VoidCallback onOpenQueue;
@override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) => PlayerQueueSwipeRegion(
onOpenQueue: onOpenQueue,
child: const ColoredBox(
key: ValueKey('gesture-player'),
color: Colors.black,
),
);
}
void main() {
testWidgets('metadata and mode changes leave the player visual intact', (
tester,
) async {
final read = Completer<Map<String, dynamic>>();
final metadata = PlayerMetadataController(readMetadata: (_) => read.future);
final seekPreview = PlaybackSeekPreview();
final playback = StreamController<PlaybackState>.broadcast();
addTearDown(metadata.dispose);
addTearDown(seekPreview.dispose);
addTearDown(playback.close);
final item = _item('First');
final pending = metadata.load(item);
var visualBuilds = 0;
await tester.pumpWidget(
ProviderScope(
overrides: [
playbackStateProvider.overrideWith((ref) => playback.stream),
playerCollectionTrackProvider.overrideWith(
(ref, item) async => Track(
id: item.id,
name: item.title,
artistName: item.artist ?? '',
albumName: item.album ?? '',
duration: 180,
source: 'local',
),
),
],
child: MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Builder(
builder: (context) => Scaffold(
body: MaterialPlayerLayout(
mediaItem: item,
metadata: metadata,
seekPreview: seekPreview,
colorScheme: Theme.of(context).colorScheme,
showLyrics: true,
lyricsBuilder: (_) {
visualBuilds++;
return const SizedBox.expand();
},
onOpenQueue: () {},
),
),
),
),
),
);
await tester.pumpAndSettle();
final beforeMetadata = visualBuilds;
read.complete({
'explicit': true,
'format': 'FLAC',
'sample_rate': 48000,
'bit_depth': 24,
});
await pending;
await tester.pumpAndSettle();
expect(visualBuilds, beforeMetadata);
expect(
tester
.widget<ExplicitTrackTitle>(find.byType(ExplicitTrackTitle))
.explicit,
isTrue,
);
const quality = 'FLAC · 24-bit · 48 kHz';
final qualityWidget = tester.widget<Text>(find.text(quality));
playback.add(
PlaybackState(
processingState: AudioProcessingState.ready,
updatePosition: const Duration(seconds: 45),
shuffleMode: AudioServiceShuffleMode.all,
repeatMode: AudioServiceRepeatMode.one,
),
);
await tester.pump();
await tester.pump();
expect(visualBuilds, beforeMetadata);
expect(find.text('0:45'), findsOneWidget);
expect(
identical(tester.widget<Text>(find.text(quality)), qualityWidget),
isTrue,
);
expect(tester.takeException(), isNull);
});
testWidgets('details retain their captured item when playback changes', (
tester,
) async {
final read = Completer<Map<String, dynamic>>();
final metadata = PlayerMetadataController(
readMetadata: (path) => path.startsWith('content://')
? read.future
: Future.value({'title': 'Second tags'}),
);
addTearDown(metadata.dispose);
final first = _item('First', source: 'content://library/first.flac');
await metadata.load(first);
await tester.pumpWidget(
MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Builder(
builder: (context) => Scaffold(
body: TextButton(
onPressed: () => showPlayerDetailsSheet(
context,
Theme.of(context).colorScheme,
metadata: metadata,
mediaItem: first,
),
child: const Text('Details'),
),
),
),
),
);
await tester.tap(find.text('Details'));
await tester.pumpAndSettle();
expect(find.text('First'), findsOneWidget);
await metadata.load(_item('Second'));
read.complete({'title': 'First tags'});
await tester.pumpAndSettle();
expect(find.text('First tags'), findsOneWidget);
expect(find.text('Second tags'), findsNothing);
expect(tester.takeException(), isNull);
});
testWidgets('footer opens the queue upwards and dismisses downwards', (
tester,
) async {
final navigator = GlobalKey<NavigatorState>();
var queueOpens = 0;
await tester.pumpWidget(
MaterialApp(navigatorKey: navigator, home: const SizedBox()),
);
navigator.currentState!.push(_FooterRoute(() => queueOpens++));
await tester.pumpAndSettle();
final footer = find.byKey(const ValueKey('gesture-player'));
await tester.fling(footer, const Offset(0, -80), 600);
await tester.pumpAndSettle();
expect(queueOpens, 1);
expect(footer, findsOneWidget);
await tester.fling(footer, const Offset(0, 240), 1000);
await tester.pumpAndSettle();
expect(queueOpens, 1);
expect(footer, findsNothing);
expect(tester.takeException(), isNull);
});
}