Files
reelbox/test/download_row_ui_test.dart

242 lines
7.4 KiB
Dart

import 'dart:async';
import 'package:background_downloader/background_downloader.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:reelbox/media/download_service.dart';
import 'package:reelbox/media/downloads_page.dart';
import 'package:reelbox/state/app_state.dart';
import 'catalog_widget_test.dart' show CatalogApi, mountCatalog;
Future<AppState> mountRow(WidgetTester tester, Widget row) async {
final state = await mountCatalog(tester, CatalogApi(), const Scaffold());
await tester.pumpWidget(
AppScope(
state: state,
child: MaterialApp(home: Scaffold(body: row)),
),
);
await tester.pump();
return state;
}
void main() {
testWidgets('secondary external downloaders live behind the quiet overflow', (
tester,
) async {
final selected = <String>[];
final state = await mountRow(
tester,
ExternalDownloadMenu(onSelected: selected.add),
);
addTearDown(state.dispose);
expect(find.byIcon(Icons.more_vert), findsOneWidget);
expect(find.byIcon(Icons.download_for_offline_outlined), findsNothing);
expect(find.text('ADM'), findsNothing);
expect(selected, isEmpty);
await tester.tap(find.byIcon(Icons.more_vert));
await tester.pumpAndSettle();
expect(find.text('External downloader: ADM'), findsOneWidget);
expect(find.text('External downloader: IDM'), findsOneWidget);
await tester.tap(find.text('External downloader: ADM'));
await tester.pumpAndSettle();
expect(selected, ['ADM']);
});
for (final status in [TaskStatus.paused, TaskStatus.failed]) {
testWidgets(
'${status.name} exposes its labeled recovery action and readable failures',
(tester) async {
var attempts = 0;
final entry = DownloadEntry.fromTask(
DownloadTask(url: 'https://media.example/movie.mp4'),
taskStatus: status,
);
Future<void> recover() async {
attempts++;
throw StateError(
'Cannot resume: this server does not support ranges.',
);
}
final state = await mountRow(
tester,
DownloadControls(
entry: entry,
onPause: () async {},
onResume: recover,
onRetry: recover,
onCancel: () async {},
),
);
addTearDown(state.dispose);
final label = status == TaskStatus.paused ? 'Resume' : 'Retry';
expect(find.widgetWithText(FilledButton, label), findsOneWidget);
await tester.tap(find.text(label));
await tester.pumpAndSettle();
expect(attempts, 1);
expect(
find.text('Cannot resume: this server does not support ranges.'),
findsOneWidget,
);
expect(tester.takeException(), isNull);
},
);
}
testWidgets(
'failed unknown-length transfer stops animating and shows its actual cause',
(tester) async {
final entry = DownloadEntry.fromTask(
DownloadTask(url: 'https://media.example/movie.mp4'),
taskStatus: TaskStatus.failed,
error: 'Connection refused by media server',
);
final state = await mountRow(
tester,
DownloadTransferDetails(entry: entry),
);
addTearDown(state.dispose);
expect(
tester
.widget<LinearProgressIndicator>(
find.byType(LinearProgressIndicator),
)
.value,
0,
);
expect(find.textContaining('%'), findsNothing);
expect(find.text('Connection refused by media server'), findsOneWidget);
await tester.pumpAndSettle();
expect(tester.takeException(), isNull);
},
);
testWidgets(
'download controls are labeled and dispatch once while an operation is pending',
(tester) async {
final pending = Completer<void>();
var pauses = 0;
var cancels = 0;
final entry = DownloadEntry.fromTask(
DownloadTask(url: 'https://media.example/movie.mp4'),
taskStatus: TaskStatus.running,
);
final state = await mountRow(
tester,
DownloadControls(
entry: entry,
onPause: () {
pauses++;
return pending.future;
},
onResume: () async {},
onRetry: () async {},
onCancel: () async {
cancels++;
},
),
);
addTearDown(state.dispose);
expect(find.widgetWithText(FilledButton, 'Pause'), findsOneWidget);
expect(find.widgetWithText(OutlinedButton, 'Cancel'), findsOneWidget);
await tester.tap(find.text('Pause'));
await tester.tap(find.text('Pause'));
await tester.pump();
expect(pauses, 1);
expect(
tester
.widget<OutlinedButton>(
find.widgetWithText(OutlinedButton, 'Cancel'),
)
.onPressed,
isNull,
);
pending.complete();
await tester.pumpAndSettle();
await tester.tap(find.text('Cancel'));
await tester.pumpAndSettle();
expect(cancels, 1);
expect(tester.takeException(), isNull);
},
);
for (final status in <TaskStatus, String>{
TaskStatus.enqueued: 'Queued',
TaskStatus.running: 'Downloading',
TaskStatus.waitingToRetry: 'Waiting to retry',
TaskStatus.paused: 'Paused',
TaskStatus.complete: 'Downloaded',
TaskStatus.failed: 'Failed',
TaskStatus.canceled: 'Canceled',
TaskStatus.notFound: 'Source not found',
}.entries) {
testWidgets(
'${status.key.name} has a readable status and preserves measured progress',
(tester) async {
final entry = DownloadEntry.fromTask(
DownloadTask(url: 'https://media.example/movie.mp4'),
taskStatus: status.key,
progress: .25,
totalBytes: 4096,
speed: 2048,
error: status.key == TaskStatus.failed ? 'HTTP 403: expired URL' : '',
);
final state = await mountRow(
tester,
DownloadTransferDetails(entry: entry),
);
addTearDown(state.dispose);
expect(find.text(status.value), findsOneWidget);
expect(
tester
.widget<LinearProgressIndicator>(
find.byType(LinearProgressIndicator),
)
.value,
status.key == TaskStatus.complete ? 1 : .25,
);
expect(
find.textContaining('/s'),
status.key == TaskStatus.running ? findsOneWidget : findsNothing,
);
if (status.key == TaskStatus.failed) {
expect(find.text('HTTP 403: expired URL'), findsOneWidget);
}
expect(tester.takeException(), isNull);
},
);
}
testWidgets(
'queued Wi-Fi download shows its constraint without invented progress',
(tester) async {
final entry = DownloadEntry.fromTask(
DownloadTask(
url: 'https://media.example/movie.mp4',
requiresWiFi: true,
),
);
final state = await mountRow(
tester,
DownloadTransferDetails(entry: entry),
);
addTearDown(state.dispose);
expect(find.text('Queued · Wi-Fi required'), findsOneWidget);
expect(find.textContaining('0%'), findsNothing);
expect(find.textContaining('/s'), findsNothing);
expect(
tester
.widget<LinearProgressIndicator>(
find.byType(LinearProgressIndicator),
)
.value,
isNull,
);
expect(tester.takeException(), isNull);
},
);
}