242 lines
7.4 KiB
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);
|
|
},
|
|
);
|
|
}
|