203 lines
5.9 KiB
Dart
203 lines
5.9 KiB
Dart
import 'dart:async';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:plezy/focus/input_mode_tracker.dart';
|
|
import 'package:plezy/media/media_backend.dart';
|
|
import 'package:plezy/media/media_library.dart';
|
|
import 'package:plezy/screens/libraries/tabs/base_library_tab.dart';
|
|
import 'package:plezy/utils/platform_detector.dart';
|
|
|
|
const _library = MediaLibrary(id: '1', backend: MediaBackend.plex, title: 'Movies');
|
|
const _libraryB = MediaLibrary(id: '2', backend: MediaBackend.plex, title: 'Shows');
|
|
|
|
class _ProbeTab extends BaseLibraryTab<String> {
|
|
const _ProbeTab({super.key, required this.loadedItems, required super.onBack})
|
|
: super(library: _library, isActive: true);
|
|
|
|
final List<String> loadedItems;
|
|
|
|
@override
|
|
State<_ProbeTab> createState() => _ProbeTabState();
|
|
}
|
|
|
|
class _ProbeTabState extends BaseLibraryTabState<String, _ProbeTab> {
|
|
int focusFirstItemCalls = 0;
|
|
|
|
@override
|
|
Future<List<String>> loadData() async => widget.loadedItems;
|
|
|
|
@override
|
|
Widget buildContent(List<String> items) => const SizedBox.shrink();
|
|
|
|
@override
|
|
IconData get emptyIcon => Icons.inbox_rounded;
|
|
|
|
@override
|
|
String get emptyMessage => 'Empty';
|
|
|
|
@override
|
|
String get errorContext => 'probe';
|
|
|
|
@override
|
|
void focusFirstItem() {
|
|
focusFirstItemCalls++;
|
|
}
|
|
}
|
|
|
|
class _ControlledTab extends BaseLibraryTab<String> {
|
|
const _ControlledTab({super.key, required super.library, required this.load, super.onDataLoaded})
|
|
: super(suppressAutoFocus: true);
|
|
|
|
final Future<List<String>> Function(MediaLibrary library) load;
|
|
|
|
@override
|
|
State<_ControlledTab> createState() => _ControlledTabState();
|
|
}
|
|
|
|
class _ControlledTabState extends BaseLibraryTabState<String, _ControlledTab> {
|
|
@override
|
|
Future<List<String>> loadData() => widget.load(widget.library);
|
|
|
|
@override
|
|
Widget buildContent(List<String> items) => ListView(children: items.map(Text.new).toList());
|
|
|
|
@override
|
|
IconData get emptyIcon => Icons.inbox_rounded;
|
|
|
|
@override
|
|
String get emptyMessage => 'Empty';
|
|
|
|
@override
|
|
String get errorContext => 'controlled';
|
|
}
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
setUp(() {
|
|
TvDetectionService.debugSetAppleTVOverride(true);
|
|
});
|
|
|
|
tearDown(() {
|
|
TvDetectionService.debugSetAppleTVOverride(null);
|
|
});
|
|
|
|
Future<_ProbeTabState> pumpProbe(
|
|
WidgetTester tester, {
|
|
required List<String> loadedItems,
|
|
required VoidCallback onBack,
|
|
}) async {
|
|
final key = GlobalKey<_ProbeTabState>();
|
|
await tester.pumpWidget(
|
|
InputModeTracker(
|
|
child: MaterialApp(
|
|
home: _ProbeTab(key: key, loadedItems: loadedItems, onBack: onBack),
|
|
),
|
|
),
|
|
);
|
|
await tester.pump();
|
|
await tester.pump();
|
|
return key.currentState!;
|
|
}
|
|
|
|
testWidgets('empty active tab focuses library chrome fallback', (tester) async {
|
|
var fallbackCalls = 0;
|
|
|
|
final state = await pumpProbe(tester, loadedItems: const [], onBack: () => fallbackCalls++);
|
|
|
|
expect(fallbackCalls, 1);
|
|
expect(state.focusFirstItemCalls, 0);
|
|
});
|
|
|
|
testWidgets('non-empty active tab focuses first item', (tester) async {
|
|
var fallbackCalls = 0;
|
|
|
|
final state = await pumpProbe(tester, loadedItems: const ['item'], onBack: () => fallbackCalls++);
|
|
|
|
expect(fallbackCalls, 0);
|
|
expect(state.focusFirstItemCalls, 1);
|
|
});
|
|
|
|
testWidgets('retained state rejects a completion from the previous library', (tester) async {
|
|
final key = GlobalKey<_ControlledTabState>();
|
|
final a = Completer<List<String>>();
|
|
final b = Completer<List<String>>();
|
|
var loadedCalls = 0;
|
|
|
|
Future<List<String>> load(MediaLibrary library) => library.id == _library.id ? a.future : b.future;
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: _ControlledTab(key: key, library: _library, load: load, onDataLoaded: () => loadedCalls++),
|
|
),
|
|
);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: _ControlledTab(key: key, library: _libraryB, load: load, onDataLoaded: () => loadedCalls++),
|
|
),
|
|
);
|
|
|
|
b.complete(const ['current B']);
|
|
await tester.pump();
|
|
await tester.pump();
|
|
expect(find.text('current B'), findsOneWidget);
|
|
expect(loadedCalls, 1);
|
|
|
|
a.complete(const ['stale A']);
|
|
await tester.pump();
|
|
await tester.pump();
|
|
expect(find.text('current B'), findsOneWidget);
|
|
expect(find.text('stale A'), findsNothing);
|
|
expect(loadedCalls, 1);
|
|
});
|
|
|
|
testWidgets('retained state rejects a stale failure after current success', (tester) async {
|
|
final key = GlobalKey<_ControlledTabState>();
|
|
final a = Completer<List<String>>();
|
|
final b = Completer<List<String>>();
|
|
|
|
Future<List<String>> load(MediaLibrary library) => library.id == _library.id ? a.future : b.future;
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: _ControlledTab(key: key, library: _library, load: load),
|
|
),
|
|
);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: _ControlledTab(key: key, library: _libraryB, load: load),
|
|
),
|
|
);
|
|
|
|
b.complete(const ['current B']);
|
|
await tester.pump();
|
|
a.completeError(StateError('stale failure'));
|
|
await tester.pump();
|
|
|
|
expect(find.text('current B'), findsOneWidget);
|
|
expect(find.textContaining('stale failure'), findsNothing);
|
|
});
|
|
|
|
testWidgets('newest same-library refresh owns the committed result', (tester) async {
|
|
final key = GlobalKey<_ControlledTabState>();
|
|
final loads = [Completer<List<String>>(), Completer<List<String>>()];
|
|
var request = 0;
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: _ControlledTab(key: key, library: _library, load: (_) => loads[request++].future),
|
|
),
|
|
);
|
|
key.currentState!.refresh();
|
|
|
|
loads[1].complete(const ['newer']);
|
|
await tester.pump();
|
|
expect(find.text('newer'), findsOneWidget);
|
|
|
|
loads[0].complete(const ['older']);
|
|
await tester.pump();
|
|
expect(find.text('newer'), findsOneWidget);
|
|
expect(find.text('older'), findsNothing);
|
|
});
|
|
}
|