mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-29 04:42:02 +02:00
feat(concerts): show artwork-themed event details in app
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:spotiflac_android/l10n/app_localizations.dart';
|
||||
import 'package:spotiflac_android/models/artist_concert.dart';
|
||||
import 'package:spotiflac_android/screens/artist_concerts_screen.dart';
|
||||
import 'package:spotiflac_android/screens/concert_detail_screen.dart';
|
||||
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
import 'package:spotiflac_android/widgets/animation_utils.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
const backend = MethodChannel('com.zarz.spotiflac/backend');
|
||||
const calendar = MethodChannel('com.zarz.spotiflac/concert_calendar');
|
||||
final messenger =
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
|
||||
|
||||
ArtistConcert event(String id, {bool details = true}) =>
|
||||
ArtistConcert.parseList([
|
||||
{
|
||||
'id': id,
|
||||
'location': 'Example City',
|
||||
'venue': 'Example Hall',
|
||||
'start_at': '2026-10-07T01:00:00Z',
|
||||
'time_zone': 'America/New_York',
|
||||
if (details) 'detail_id': id,
|
||||
'url': 'https://example.com/events/1',
|
||||
},
|
||||
]).single;
|
||||
|
||||
Widget app(Widget home, {Brightness brightness = Brightness.light}) =>
|
||||
ProviderScope(
|
||||
child: MaterialApp(
|
||||
theme: MornyeTheme.build(brightness),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: home,
|
||||
),
|
||||
);
|
||||
|
||||
setUp(() => SharedPreferences.setMockInitialValues({}));
|
||||
tearDown(() {
|
||||
messenger.setMockMethodCallHandler(backend, null);
|
||||
messenger.setMockMethodCallHandler(calendar, null);
|
||||
});
|
||||
|
||||
for (final brightness in Brightness.values) {
|
||||
testWidgets(
|
||||
'schedule follows $brightness and a row opens an internal dark detail',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
app(
|
||||
ArtistConcertsScreen(
|
||||
artistName: 'Example Artist',
|
||||
concerts: [event('basic-${brightness.name}', details: false)],
|
||||
),
|
||||
brightness: brightness,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
Theme.of(
|
||||
tester.element(find.text('All Upcoming Concerts')),
|
||||
).brightness,
|
||||
brightness,
|
||||
);
|
||||
await tester.tap(find.text('Example City'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(ConcertDetailScreen), findsOneWidget);
|
||||
expect(
|
||||
Theme.of(tester.element(find.text('Example Artist').last)).brightness,
|
||||
Brightness.dark,
|
||||
);
|
||||
expect(find.textContaining('21:00'), findsOneWidget);
|
||||
expect(find.text('Get Tickets'), findsNothing);
|
||||
await tester.tap(find.byTooltip('Back').last);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(ConcertDetailScreen), findsNothing);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets(
|
||||
'details load on demand with skeleton, retry and calendar action',
|
||||
(tester) async {
|
||||
tester.view.physicalSize = const Size(390, 1000);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
final pending = Completer<Object?>();
|
||||
var requests = 0;
|
||||
messenger.setMockMethodCallHandler(backend, (call) async {
|
||||
if (call.method != 'getProviderMetadata') return null;
|
||||
expect(call.arguments, containsPair('resource_type', 'concert'));
|
||||
expect(call.arguments, containsPair('provider_id', 'example.metadata'));
|
||||
requests++;
|
||||
if (requests == 1) return pending.future;
|
||||
return {
|
||||
'concert': {
|
||||
'id': 'load',
|
||||
'address': '123 Example Street',
|
||||
'start_at': '2026-10-07T01:00:00Z',
|
||||
'end_at': '2026-10-07T04:00:00Z',
|
||||
'ticket_url': 'https://example.com/tickets/1',
|
||||
'set_list': {'id': 'list-1', 'name': 'Example Set List'},
|
||||
},
|
||||
};
|
||||
});
|
||||
final calendarCalls = <MethodCall>[];
|
||||
messenger.setMockMethodCallHandler(calendar, (call) async {
|
||||
calendarCalls.add(call);
|
||||
return true;
|
||||
});
|
||||
await tester.pumpWidget(
|
||||
app(
|
||||
ConcertDetailScreen(
|
||||
artistName: 'Example Artist',
|
||||
providerId: 'example.metadata',
|
||||
concert: event('load'),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
expect(find.byType(ShimmerLoading), findsOneWidget);
|
||||
pending.completeError(PlatformException(code: 'offline'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
find.text('Concert details are currently unavailable.'),
|
||||
findsOneWidget,
|
||||
);
|
||||
await tester.tap(find.text('Retry'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Get Tickets'), findsOneWidget);
|
||||
expect(find.text('Example Set List'), findsOneWidget);
|
||||
expect(find.text('123 Example Street'), findsOneWidget);
|
||||
await tester.tap(find.byTooltip('Add to Calendar'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
calendarCalls.single.arguments,
|
||||
containsPair(
|
||||
'start',
|
||||
DateTime.utc(2026, 10, 7, 1).millisecondsSinceEpoch,
|
||||
),
|
||||
);
|
||||
expect(
|
||||
calendarCalls.single.arguments,
|
||||
containsPair(
|
||||
'end',
|
||||
DateTime.utc(2026, 10, 7, 4).millisecondsSinceEpoch,
|
||||
),
|
||||
);
|
||||
expect(tester.takeException(), isNull);
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'optional fields and unsafe links cannot create bogus detail actions',
|
||||
() {
|
||||
final detail = ConcertDetail.fromJson({
|
||||
'ticket_url': 'javascript:alert(1)',
|
||||
'map_url': 'file:///private',
|
||||
'set_list': <Object?>[],
|
||||
'start_at': 'bad date',
|
||||
'artist_name': 42,
|
||||
});
|
||||
expect(detail.ticketUrl, isNull);
|
||||
expect(detail.mapUrl, isNull);
|
||||
expect(detail.start, isNull);
|
||||
expect(detail.setListId, isNull);
|
||||
expect(detail.artistName, isNull);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user