import 'dart:async'; import 'dart:io'; import 'dart:ui' as ui; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; import 'package:spotiflac_android/theme/app_theme.dart'; import 'package:spotiflac_android/theme/mornye_theme.dart'; import 'package:spotiflac_android/widgets/hires_check_card.dart'; const _limitedResult = { 'supported': true, 'verdict': 'band_limited', 'declared_sample_rate': 96000, 'cutoff_frequency_hz': 27164.0625, 'declared_bit_depth': 24, 'effective_bit_depth': 24, 'analyzed_duration_s': 30, }; void main() { TestWidgetsFlutterBinding.ensureInitialized(); const channel = MethodChannel('com.zarz.spotiflac/backend'); final capture = GlobalKey(); var calls = 0; setUpAll(() async { for (final (family, asset) in [ ('MaterialIcons', 'fonts/MaterialIcons-Regular.otf'), ('Google Sans Flex', 'assets/fonts/GoogleSansFlex.ttf'), ('Inter', 'assets/fonts/InterVariable.ttf'), ( 'packages/cupertino_icons/CupertinoIcons', 'packages/cupertino_icons/assets/CupertinoIcons.ttf', ), ]) { await (FontLoader(family)..addFont(rootBundle.load(asset))).load(); } }); setUp(() { calls = 0; TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(channel, (call) async { expect(call.method, 'checkHiResAuthenticity'); calls++; return _limitedResult; }); }); tearDown(() { TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(channel, null); }); Future openCard( WidgetTester tester, { bool mornye = true, Brightness brightness = Brightness.light, double textScale = 1, String path = '/music/track.flac', }) async { tester.view.physicalSize = const Size(390, 844); tester.view.devicePixelRatio = 1; addTearDown(tester.view.reset); await tester.pumpWidget( MaterialApp( theme: mornye ? MornyeTheme.build(brightness) : brightness == Brightness.light ? AppTheme.light() : AppTheme.dark(), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, locale: const Locale('en'), builder: (context, child) => MediaQuery( data: MediaQuery.of( context, ).copyWith(textScaler: TextScaler.linear(textScale)), child: RepaintBoundary(key: capture, child: child), ), home: Scaffold( body: SingleChildScrollView( padding: const EdgeInsets.all(16), child: HiResCheckCard(filePath: path), ), ), ), ); await tester.pump(); } Future captureCard(WidgetTester tester, String name) async { if (!const bool.fromEnvironment('CAPTURE_HIRES_CARD')) return; await tester.runAsync(() async { final boundary = capture.currentContext!.findRenderObject()! as RenderRepaintBoundary; final image = await boundary.toImage(pixelRatio: 2); final data = await image.toByteData(format: ui.ImageByteFormat.png); await File( '/tmp/spotiflac-hires-$name.png', ).writeAsBytes(data!.buffer.asUint8List()); image.dispose(); }); } for (final mornye in [true, false]) { for (final brightness in Brightness.values) { final name = '${mornye ? 'mornye' : 'material'}-${brightness.name}'; testWidgets('centered action and neutral bandwidth result ($name)', ( tester, ) async { await openCard(tester, mornye: mornye, brightness: brightness); await tester.pumpAndSettle(); final button = find.ancestor( of: find.text('Check'), matching: mornye ? find.byType(CupertinoButton) : find.bySubtype(), ); expect(button, findsOneWidget); expect(tester.getSize(button).width, greaterThan(300)); expect(tester.getCenter(button).dx, closeTo(195, 1)); expect(find.byType(Card), mornye ? findsNothing : findsOneWidget); await captureCard(tester, '$name-idle'); await tester.tap(find.text('Check')); await tester.pumpAndSettle(); expect(calls, 1); expect(find.text('Limited bandwidth'), findsOneWidget); expect(find.text('Fake Hi-Res'), findsNothing); expect(find.text('96.0 kHz'), findsOneWidget); expect(find.text('48.0 kHz'), findsOneWidget); expect(find.text('~27.2 kHz'), findsOneWidget); expect(find.text('24 / 24'), findsOneWidget); final rowValue = tester.getRect(find.text('~27.2 kHz')); final rowLabel = tester.getRect(find.text('Estimated cutoff')); expect(rowValue.top, closeTo(rowLabel.top, 1)); expect(rowValue.left, greaterThan(rowLabel.right)); expect(rowValue.right, closeTo(354, 1)); expect(tester.takeException(), isNull); await captureCard(tester, '$name-result'); await tester.tap(find.byTooltip('Re-analyze')); await tester.pumpAndSettle(); expect(calls, 2); }); } } for (final mornye in [true, false]) { testWidgets('large text stays readable ($mornye)', (tester) async { await openCard(tester, mornye: mornye, textScale: 2); await tester.tap(find.text('Check')); await tester.pumpAndSettle(); await tester.scrollUntilVisible( find.textContaining('Based on 30.0 seconds'), 180, ); expect(tester.takeException(), isNull); }); } testWidgets( 'a result from the previous track cannot overwrite the new track', (tester) async { final pending = Completer>(); TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(channel, (_) => pending.future); await openCard(tester); await tester.tap(find.text('Check')); await tester.pump(); await openCard(tester, path: '/music/another.flac'); pending.complete(_limitedResult); await tester.pumpAndSettle(); expect(find.text('Check'), findsOneWidget); expect(find.text('Limited bandwidth'), findsNothing); }, ); testWidgets('legacy suspect results are not presented as proven fakes', ( tester, ) async { TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler( channel, (_) async => { ..._limitedResult, 'verdict': 'fake_hires', 'confidence': 'suspect', }, ); await openCard(tester); await tester.tap(find.text('Check')); await tester.pumpAndSettle(); expect(find.text('Limited bandwidth'), findsOneWidget); expect(find.textContaining('Declares'), findsNothing); }); }