mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-29 12:52:13 +02:00
251 lines
9.3 KiB
Dart
251 lines
9.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:material_3_expressive/material_3_expressive.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:spotiflac_android/l10n/l10n.dart';
|
|
import 'package:spotiflac_android/services/app_remote_config_service.dart';
|
|
import 'package:spotiflac_android/services/update_checker.dart';
|
|
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
|
import 'package:spotiflac_android/widgets/app_alert_dialog.dart';
|
|
import 'package:spotiflac_android/widgets/app_announcement_dialog.dart';
|
|
import 'package:spotiflac_android/widgets/app_bottom_sheet.dart';
|
|
import 'package:spotiflac_android/widgets/app_choice_chip.dart';
|
|
import 'package:spotiflac_android/widgets/mornye_chrome.dart';
|
|
import 'package:spotiflac_android/widgets/update_dialog.dart';
|
|
|
|
Widget _app(ThemeData theme, Widget home) => ProviderScope(
|
|
child: MaterialApp(
|
|
theme: theme,
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: home,
|
|
),
|
|
);
|
|
|
|
void main() {
|
|
for (final mornye in [false, true]) {
|
|
for (final brightness in Brightness.values) {
|
|
final theme = mornye
|
|
? MornyeTheme.build(brightness)
|
|
: ThemeData(brightness: brightness);
|
|
final variant = 'Mornye: $mornye, $brightness';
|
|
|
|
testWidgets('sheet selects, disables and returns values ($variant)', (
|
|
tester,
|
|
) async {
|
|
tester.view.physicalSize = const Size(360, 740);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
var selected = false;
|
|
var disabledTaps = 0;
|
|
bool? result;
|
|
await tester.pumpWidget(
|
|
_app(
|
|
theme,
|
|
Builder(
|
|
builder: (context) => Scaffold(
|
|
body: TextButton(
|
|
child: const Text('Open'),
|
|
onPressed: () async {
|
|
result = await showAppBottomSheet<bool>(
|
|
context: context,
|
|
title: 'Library filter',
|
|
builder: (context) => StatefulBuilder(
|
|
builder: (context, setSheetState) => Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Wrap(
|
|
children: [
|
|
AppChoiceChip(
|
|
label: const Text('Downloaded tracks'),
|
|
selected: selected,
|
|
onSelected: (value) => setSheetState(() {
|
|
selected = value;
|
|
}),
|
|
),
|
|
],
|
|
),
|
|
AppSheetOption(
|
|
enabled: false,
|
|
title: const Text('Unavailable'),
|
|
onTap: () => disabledTaps++,
|
|
),
|
|
AppSheetOption(
|
|
title: const Text('Apply'),
|
|
onTap: () => Navigator.pop(context, selected),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.tap(find.text('Open'));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.byType(MornyeGlassPanel),
|
|
mornye ? findsOneWidget : findsNothing,
|
|
);
|
|
expect(find.byType(M3EChip), mornye ? findsNothing : findsOneWidget);
|
|
await tester.tap(find.text('Unavailable'));
|
|
expect(disabledTaps, 0);
|
|
await tester.tap(find.text('Downloaded tracks'));
|
|
await tester.pumpAndSettle();
|
|
expect(selected, isTrue);
|
|
await tester.tap(find.text('Apply'));
|
|
await tester.pumpAndSettle();
|
|
expect(result, isTrue);
|
|
expect(find.text('Library filter'), findsNothing);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('nested dialog retains theme and navigation ($variant)', (
|
|
tester,
|
|
) async {
|
|
final nestedNavigator = GlobalKey<NavigatorState>();
|
|
bool? result;
|
|
await tester.pumpWidget(
|
|
_app(
|
|
ThemeData(),
|
|
Navigator(
|
|
key: nestedNavigator,
|
|
onGenerateRoute: (_) => MaterialPageRoute<void>(
|
|
builder: (_) => Theme(
|
|
data: theme,
|
|
child: Builder(
|
|
builder: (context) => Scaffold(
|
|
body: TextButton(
|
|
child: const Text('Open import'),
|
|
onPressed: () async {
|
|
result = await showAppDialog<bool>(
|
|
context: context,
|
|
useRootNavigator: false,
|
|
builder: (context) => AppAlertDialog(
|
|
title: const Text('Import playlist'),
|
|
actions: [
|
|
AppDialogAction(
|
|
onPressed: () => Navigator.pop(context, true),
|
|
child: const Text('Confirm'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.tap(find.text('Open import'));
|
|
await tester.pumpAndSettle();
|
|
expect(nestedNavigator.currentState!.canPop(), isTrue);
|
|
expect(
|
|
find.byType(MornyeGlassPanel),
|
|
mornye ? findsOneWidget : findsNothing,
|
|
);
|
|
expect(
|
|
Theme.of(tester.element(find.text('Import playlist'))).brightness,
|
|
brightness,
|
|
);
|
|
await tester.tap(find.text('Confirm'));
|
|
await tester.pumpAndSettle();
|
|
expect(result, isTrue);
|
|
expect(nestedNavigator.currentState!.canPop(), isFalse);
|
|
expect(find.text('Open import'), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('required update resists back and barrier ($variant)', (
|
|
tester,
|
|
) async {
|
|
tester.view.physicalSize = const Size(393, 740);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
await tester.pumpWidget(
|
|
_app(
|
|
theme,
|
|
Builder(
|
|
builder: (context) => Scaffold(
|
|
body: TextButton(
|
|
child: const Text('Update'),
|
|
onPressed: () => showUpdateDialog(
|
|
context,
|
|
updateInfo: UpdateInfo(
|
|
version: '9.0.0',
|
|
changelog: 'Playback and library improvements.\n' * 8,
|
|
downloadUrl: 'https://example.com/release',
|
|
publishedAt: DateTime(2026),
|
|
releasesBehind: 3,
|
|
),
|
|
onDisableUpdates: () =>
|
|
fail('Cannot disable required update'),
|
|
forced: true,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.tap(find.text('Update'));
|
|
await tester.pumpAndSettle();
|
|
expect(tester.takeException(), isNull);
|
|
await tester.tapAt(const Offset(5, 5));
|
|
await tester.binding.handlePopRoute();
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(UpdateDialog), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
}
|
|
|
|
testWidgets('announcement keeps explicit dismissal (Mornye: $mornye)', (
|
|
tester,
|
|
) async {
|
|
var dismissals = 0;
|
|
await tester.pumpWidget(
|
|
_app(
|
|
mornye ? MornyeTheme.build(Brightness.dark) : ThemeData(),
|
|
Builder(
|
|
builder: (context) => Scaffold(
|
|
body: TextButton(
|
|
child: const Text('Open notice'),
|
|
onPressed: () => showAppAnnouncementDialog(
|
|
context,
|
|
announcement: const RemoteAnnouncement(
|
|
id: 'notice',
|
|
enabled: true,
|
|
title: 'Service notice',
|
|
message: 'An announcement without an external action.',
|
|
dismissible: false,
|
|
),
|
|
onDismiss: () => dismissals++,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.tap(find.text('Open notice'));
|
|
await tester.pumpAndSettle();
|
|
await tester.tapAt(const Offset(5, 5));
|
|
await tester.binding.handlePopRoute();
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Service notice'), findsOneWidget);
|
|
expect(dismissals, 0);
|
|
await tester.tap(find.byTooltip('Close'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Service notice'), findsNothing);
|
|
expect(dismissals, 1);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
}
|
|
}
|