251 lines
11 KiB
Dart
251 lines
11 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:jj_mkvmaker/app/download_manager.dart';
|
|
import 'package:jj_mkvmaker/app/settings.dart';
|
|
import 'package:jj_mkvmaker/core/download_detect.dart';
|
|
import 'package:jj_mkvmaker/services/downloader.dart';
|
|
import 'package:jj_mkvmaker/ui/downloads_page.dart';
|
|
|
|
class _Backend implements DownloadBackend {
|
|
@override
|
|
final DownloadKind kind;
|
|
_Backend(this.kind);
|
|
@override
|
|
Future<(String, List<PlaylistEntry>)?> expandPlaylist(String url) async => null;
|
|
@override
|
|
Future<void> start(DownloadTask t, void Function() changed) async {
|
|
t
|
|
..state = DownloadState.downloading
|
|
..progress = 0.4
|
|
..speed = '1.2MB/s'
|
|
..title = t.kind == DownloadKind.video ? '테스트 영상' : '리눅스 ISO';
|
|
changed();
|
|
}
|
|
|
|
@override
|
|
Future<void> pause(DownloadTask t) async => t.state = DownloadState.paused;
|
|
@override
|
|
Future<void> cancel(DownloadTask t) async => t.state = DownloadState.cancelled;
|
|
@override
|
|
Future<void> shutdown() async {}
|
|
}
|
|
|
|
/// 이름을 바꾸지 않는 백엔드 (마그넷 메타데이터를 아직 받지 못한 상태)
|
|
class _Quiet extends _Backend {
|
|
_Quiet(super.kind);
|
|
@override
|
|
Future<void> start(DownloadTask t, void Function() changed) async {
|
|
t
|
|
..state = DownloadState.downloading
|
|
..progress = null;
|
|
changed();
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
DownloadManager make() => DownloadManager(
|
|
backends: [_Backend(DownloadKind.video), _Backend(DownloadKind.torrent)],
|
|
settings: () => AppSettings()..downloadRoot = r'D:\dl',
|
|
readClipboard: () async => null,
|
|
);
|
|
|
|
testWidgets('다운로드 목록: 추가 · 전체 선택 · 일시정지 · 완료 정리', (tester) async {
|
|
tester.view.physicalSize = const Size(1400, 900);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.reset);
|
|
final d = make();
|
|
|
|
await tester.pumpWidget(MaterialApp(home: DownloadsPage(d: d)));
|
|
expect(find.textContaining('다운로드가 없습니다'), findsOneWidget);
|
|
|
|
await tester.enterText(find.byType(TextField), 'https://youtu.be/AAA111');
|
|
await tester.tap(find.text('추가'));
|
|
await tester.pump();
|
|
d.add('magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567');
|
|
await tester.pump();
|
|
expect(find.text('테스트 영상'), findsOneWidget);
|
|
expect(find.text('리눅스 ISO'), findsOneWidget);
|
|
// 진행 막대 안에 퍼센트, 아래에 상태 · 속도
|
|
expect(find.byType(DownloadProgressBar), findsNWidgets(2));
|
|
expect(find.text('40.0%'), findsNWidgets(2));
|
|
expect(find.textContaining('받는 중 · 1.2MB/s'), findsNWidgets(2));
|
|
|
|
await tester.tap(find.text('전체 선택'));
|
|
await tester.pump();
|
|
expect(find.text('2개 선택'), findsOneWidget);
|
|
await tester.tap(find.text('일시정지'));
|
|
await tester.pump();
|
|
expect(d.tasks.every((t) => t.state == DownloadState.paused), isTrue);
|
|
|
|
d.tasks.first.state = DownloadState.done;
|
|
d.tasks.last.progress = null; // 진행률을 모르는 상태 (마그넷 정보 받는 중 등)
|
|
d.tasks.last.state = DownloadState.downloading;
|
|
d.selectNone(); // 화면 갱신
|
|
await tester.pump();
|
|
expect(find.text('100.0%'), findsOneWidget); // 완료는 가득 찬 막대
|
|
expect(find.text('준비 중…'), findsOneWidget);
|
|
await tester.tap(find.text('완료 정리'));
|
|
await tester.pump();
|
|
expect(d.tasks, hasLength(1));
|
|
|
|
// 44: [다운로드 취소] 는 일시정지 · 재개와 떨어져 있고, 누르면 묻는다 ([계속 받기] 면 그대로 - 185)
|
|
await tester.tap(find.text('전체 선택'));
|
|
await tester.pump();
|
|
expect(tester.getTopLeft(find.text('다운로드 취소')).dx - tester.getTopRight(find.text('재개')).dx, greaterThan(16));
|
|
await tester.tap(find.text('다운로드 취소'));
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
expect(find.text('선택한 다운로드 1개를 취소할까요? 잠깐 멈추려면 [일시정지] 를 쓰세요.'), findsOneWidget);
|
|
await tester.tap(find.text('계속 받기'));
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
expect(d.tasks.single.state, DownloadState.downloading);
|
|
await tester.tap(find.text('다운로드 취소'));
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
await tester.tap(find.widgetWithText(FilledButton, '다운로드 취소'));
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
expect(d.tasks.single.state, DownloadState.cancelled);
|
|
});
|
|
|
|
testWidgets('종료: 다운로드 중이면 목록 → 종료 → 확인 메시지', (tester) async {
|
|
tester.view.physicalSize = const Size(1400, 900);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.reset);
|
|
final d = make();
|
|
bool? result;
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
home: Builder(
|
|
builder: (ctx) => TextButton(
|
|
onPressed: () async => result = await confirmExit(ctx, d),
|
|
child: const Text('X'),
|
|
),
|
|
),
|
|
));
|
|
|
|
// 다운로드 없으면 바로 종료
|
|
await tester.tap(find.text('X'));
|
|
await tester.pumpAndSettle();
|
|
expect(result, isTrue);
|
|
|
|
d.add('https://youtu.be/AAA111');
|
|
d.add('https://youtu.be/BBB222');
|
|
await tester.pump();
|
|
result = null;
|
|
await tester.tap(find.text('X'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('받는 중인 다운로드 2개'), findsOneWidget);
|
|
|
|
// 목록에서 하나 삭제
|
|
await tester.tap(find.byTooltip('삭제 (중지하고 받던 파일 삭제)').first);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('받는 중인 다운로드 1개'), findsOneWidget);
|
|
|
|
// 종료 → 확인 메시지 → 취소 (다운로드 목록 유지)
|
|
await tester.tap(find.widgetWithText(FilledButton, '종료'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('다운로드를 종료하시겠습니까?'), findsOneWidget);
|
|
await tester.tap(find.text('취소'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('받는 중인 다운로드 1개'), findsOneWidget);
|
|
expect(result, isNull);
|
|
|
|
// X 버튼 → 확인 메시지 → 종료
|
|
await tester.tap(find.byTooltip('종료'));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.widgetWithText(FilledButton, '종료').last);
|
|
await tester.pumpAndSettle();
|
|
expect(result, isTrue);
|
|
});
|
|
|
|
testWidgets('26: 직접 넣은 일반 동영상 사이트 주소도 받는다 (yt-dlp) · 주소가 없으면 알림', (tester) async {
|
|
tester.view.physicalSize = const Size(1400, 900);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.reset);
|
|
final d = make();
|
|
await tester.pumpWidget(MaterialApp(home: DownloadsPage(d: d)));
|
|
await tester.enterText(find.byType(TextField), '이것 보세요 https://vimeo.com/123456 재밌어요');
|
|
await tester.tap(find.text('추가'));
|
|
await tester.pump();
|
|
expect(d.tasks.single.source, 'https://vimeo.com/123456');
|
|
expect(d.tasks.single.kind, DownloadKind.video);
|
|
await tester.enterText(find.byType(TextField), '주소 아님');
|
|
await tester.tap(find.text('추가'));
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(find.text('주소 (http · https · 마그넷) 를 찾지 못했거나 이미 받는 중입니다.'), findsOneWidget);
|
|
expect(d.tasks, hasLength(1));
|
|
});
|
|
|
|
testWidgets('35: aria2 가 순서를 기다리게 한 토렌트는 "대기 중"', (tester) async {
|
|
tester.view.physicalSize = const Size(1400, 900);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.reset);
|
|
final d = make();
|
|
await tester.pumpWidget(MaterialApp(home: DownloadsPage(d: d)));
|
|
d.add('magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567');
|
|
await tester.pump();
|
|
d.tasks.single
|
|
..extra['waiting'] = true
|
|
..progress = null;
|
|
d.selectNone();
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
expect(find.textContaining('대기 중'), findsWidgets);
|
|
expect(find.textContaining('받는 중'), findsNothing);
|
|
});
|
|
|
|
test('관리자 지시 (10/20): 마그넷 이름 (dn) - 주소 글 풀기 · + 는 빈칸 · 없거나 마그넷이 아니면 null', () {
|
|
const h = 'magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567';
|
|
expect(magnetName('$h&dn=Ubuntu+24.04%20Desktop&tr=udp%3A%2F%2Ft.example%3A80'), 'Ubuntu 24.04 Desktop');
|
|
expect(magnetName('$h&DN=%ED%95%9C%EA%B8%80%20%EC%9D%B4%EB%A6%84'), '한글 이름');
|
|
expect(magnetName('magnet:?dn=first&xt=urn:btih:00&dn=second'), 'first', reason: '처음 것');
|
|
expect(magnetName('$h&dn=bad%zz'), 'bad%zz', reason: '잘못 적힌 % 는 그대로');
|
|
expect(magnetName('$h&dn=a%0Ab'), 'a b', reason: '줄바꿈은 빈칸 (한 줄 제목)');
|
|
expect(magnetName('$h&dn='), isNull);
|
|
expect(magnetName('$h&dn=%20+'), isNull);
|
|
expect(magnetName(h), isNull);
|
|
expect(magnetName('https://example.com/?dn=x'), isNull);
|
|
final t = DownloadTask(id: '1', kind: DownloadKind.torrent, source: '$h&dn=Big+Buck+Bunny', dir: r'D:\dl');
|
|
expect(t.title, 'Big Buck Bunny');
|
|
expect(DownloadTask(id: '2', kind: DownloadKind.torrent, source: h, dir: r'D:\dl').title, h, reason: '이름이 없으면 주소 그대로');
|
|
});
|
|
|
|
testWidgets('관리자 지시 (10/20): 마그넷 줄 - 메타데이터 전에도 dn 이름 · 멈추면 막대가 움직이지 않고 주황 "멈춤"', (tester) async {
|
|
tester.view.physicalSize = const Size(1400, 900);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.reset);
|
|
final d = DownloadManager(
|
|
backends: [_Quiet(DownloadKind.video), _Quiet(DownloadKind.torrent)],
|
|
settings: () => AppSettings()..downloadRoot = r'D:\dl',
|
|
readClipboard: () async => null,
|
|
);
|
|
await tester.pumpWidget(MaterialApp(home: DownloadsPage(d: d)));
|
|
const url = 'magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567&dn=Big+Buck+Bunny&tr=udp%3A%2F%2Ft.example%3A80';
|
|
d.add(url);
|
|
await tester.pump();
|
|
expect(d.tasks.single.state, DownloadState.downloading);
|
|
expect(find.text('Big Buck Bunny'), findsOneWidget, reason: '마그넷 주소 전체가 아니라 이름');
|
|
expect(find.textContaining('urn:btih'), findsNothing);
|
|
// 메타데이터를 받는 중: 움직이는 "준비 중…"
|
|
LinearProgressIndicator bar() => tester.widget(find.descendant(of: find.byType(DownloadProgressBar), matching: find.byType(LinearProgressIndicator)));
|
|
expect(find.text('준비 중…'), findsOneWidget);
|
|
expect(bar().value, isNull);
|
|
// 정한 시간 동안 트래커 · 피어가 없음 → 멈춤
|
|
d.tasks.single.extra
|
|
..['stalled'] = true
|
|
..['stallSince'] = DateTime.now().millisecondsSinceEpoch - 61000
|
|
..['stallReasons'] = ['firewall'];
|
|
d.selectNone();
|
|
await tester.pump();
|
|
expect(find.textContaining('트래커 · 피어에 닿지 않습니다'), findsOneWidget);
|
|
expect(find.text('멈춤 (시작 안 됨)'), findsOneWidget);
|
|
expect(find.text('준비 중…'), findsNothing);
|
|
expect(bar().value, 0, reason: '움직이지 않는 막대');
|
|
expect(bar().color, Colors.orangeAccent.withValues(alpha: 0.85));
|
|
await tester.pumpAndSettle(); // 움직이는 막대가 있으면 끝나지 않는다
|
|
// 다시 받기 시작하면 (멈춤 풀림) 원래대로
|
|
d.tasks.single.extra.remove('stalled');
|
|
d.selectNone();
|
|
await tester.pump();
|
|
expect(find.text('준비 중…'), findsOneWidget);
|
|
expect(bar().value, isNull);
|
|
});
|
|
}
|