import 'dart:io'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.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/providers/download_history_provider.dart'; import 'package:spotiflac_android/screens/track_metadata_screen.dart'; import 'package:spotiflac_android/services/library_database.dart'; import 'package:spotiflac_android/widgets/album_detail_header.dart'; import 'package:spotiflac_android/widgets/audio_quality_badges.dart'; import 'package:spotiflac_android/theme/mornye_theme.dart'; import 'package:spotiflac_android/widgets/mornye_chrome.dart'; import 'package:spotiflac_android/widgets/mornye_metadata_row.dart'; void main() { for (final useFileUri in [false, true]) { testWidgets('Mornye metadata renders local artwork (URI=$useFileUri)', ( tester, ) async { final cover = await tester.runAsync(() async { final directory = await Directory.systemTemp.createTemp( 'metadata-cover-', ); addTearDown(() => directory.delete(recursive: true)); final recorder = ui.PictureRecorder(); Canvas(recorder).drawColor(Colors.green, BlendMode.src); final picture = recorder.endRecording(); final image = await picture.toImage(32, 32); final bytes = await image.toByteData(format: ui.ImageByteFormat.png); image.dispose(); picture.dispose(); return File('${directory.path}/cover with spaces #1.png') ..writeAsBytesSync(bytes!.buffer.asUint8List()); }); final item = LocalLibraryItem( id: 'local-cover-track', trackName: 'Track with artwork', artistName: 'Artist', albumName: 'Album', filePath: '/missing/local-cover-track.flac', coverPath: useFileUri ? cover!.uri.toString() : cover!.path, scannedAt: DateTime(2026), ); await tester.runAsync(() async { await tester.pumpWidget( ProviderScope( child: MaterialApp( theme: MornyeTheme.build(Brightness.dark), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: TrackMetadataScreen(localItem: item), ), ), ); await precacheImage( FileImage(cover), tester.element(find.byType(TrackMetadataScreen)), ); }); await tester.pumpAndSettle(); final artwork = find.descendant( of: find.byType(Hero), matching: find.byType(RawImage), ); expect(artwork, findsOneWidget); expect(tester.widget(artwork).image, isNotNull); expect(find.byIcon(Icons.music_note), findsNothing); expect(tester.takeException(), isNull); await tester.pumpWidget(const SizedBox()); await FileImage(cover).evict(); }); } for (final brightness in Brightness.values) { testWidgets( 'Mornye metadata has single-level sections and stacked values ($brightness)', (tester) async { await tester.binding.setSurfaceSize(const Size(320, 780)); addTearDown(() => tester.binding.setSurfaceSize(null)); final item = DownloadHistoryItem( id: 'metadata-track', trackName: 'A long track title for a narrow screen', artistName: 'Artist', albumName: 'Album', filePath: '/missing/track.flac', service: 'provider-a', downloadedAt: DateTime(2026), isrc: 'USAAA2600001', ); await tester.pumpWidget( ProviderScope( child: MaterialApp( theme: MornyeTheme.build(brightness), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, builder: (context, child) => MediaQuery( data: MediaQuery.of( context, ).copyWith(textScaler: TextScaler.linear(1.6)), child: child!, ), home: TrackMetadataScreen(item: item), ), ), ); await tester.pumpAndSettle(); expect(find.byIcon(CupertinoIcons.chevron_back), findsOneWidget); expect(find.byIcon(CupertinoIcons.ellipsis), findsOneWidget); expect(find.byType(Card), findsNothing); final row = find.widgetWithText(MornyeMetadataRow, 'Track name'); expect( find.ancestor(of: row, matching: find.byType(MornyeGlass)), findsNothing, ); expect( find.ancestor( of: row, matching: find.byWidgetPredicate( (widget) => widget is Material && widget.color == MornyeTheme.controlFill(tester.element(row)), ), ), findsOneWidget, ); final value = find.descendant( of: row, matching: find.text(item.trackName), ); expect( tester.getTopLeft(value).dy, greaterThan(tester.getBottomLeft(find.text('Track name')).dy), ); expect( tester.widget(value).style?.color, MornyeTheme.build(brightness).colorScheme.onSurface, ); await tester.tap(find.byIcon(CupertinoIcons.ellipsis)); await tester.pumpAndSettle(); expect(find.byIcon(CupertinoIcons.square_stack), findsOneWidget); expect(find.byIcon(CupertinoIcons.gear_alt), findsNothing); expect(tester.takeException(), isNull); }, ); } testWidgets('metadata hero keeps technical text legible in light theme', ( tester, ) async { await tester.binding.setSurfaceSize(const Size(430, 900)); addTearDown(() => tester.binding.setSurfaceSize(null)); final item = DownloadHistoryItem( id: 'history-track', trackName: 'Track', artistName: 'Artist', albumName: 'Album', filePath: r'Z:\missing\track.flac', service: 'provider-a', downloadedAt: DateTime(2026), duration: 250, bitDepth: 16, sampleRate: 44100, format: 'flac', explicit: true, ); await tester.pumpWidget( ProviderScope( child: MaterialApp( theme: ThemeData.light(useMaterial3: true), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: TrackMetadataScreen(item: item), ), ), ); await tester.pump(); final headerMeta = find.byType(HeaderMetaRow); expect(headerMeta, findsOneWidget); expect(find.byType(ExplicitBadge), findsNWidgets(2)); expect(find.text('Explicit'), findsNothing); for (final label in const ['16-bit/44.1kHz', '4:10', 'Provider-a']) { final text = tester.widget( find.descendant(of: headerMeta, matching: find.text(label)), ); expect(text.style?.color, Colors.white); } final separators = tester.widgetList( find.descendant(of: headerMeta, matching: find.text('•')), ); expect(separators, isNotEmpty); expect( separators.every((text) => text.style?.color == Colors.white70), isTrue, ); final durationLabel = find.text('Duration'); final metadataRow = find .ancestor(of: durationLabel, matching: find.byType(Row)) .first; final row = tester.widget(metadataRow); expect(row.crossAxisAlignment, CrossAxisAlignment.baseline); expect(row.textBaseline, TextBaseline.alphabetic); }); }