JJ_MKVMaker/test/explorer_search_test.dart

146 lines
6.8 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:jj_mkvmaker/core/file_ops.dart';
import 'package:jj_mkvmaker/ui/explorer_search.dart';
import 'package:path/path.dart' as p;
/// 79: 찾기 조건 (크기 · 바뀐 날 · 종류) · 결과 목록 화면 (2000개마다 멈춤 · 더 보기, 고르고 복사 · 이동 · 지우기, 누르면 열기)
void main() {
final now = DateTime(2026, 10, 17, 12);
FileEntry f(String name, {int size = 0, int daysAgo = 0}) =>
FileEntry('/x/$name', isDir: false, size: size, modified: now.subtract(Duration(days: daysAgo)));
test('조건: "1GB 넘는 30일 지난 영상"', () {
const gb = 1024 * 1024 * 1024;
const c = SearchConditions(minBytes: gb, olderDays: 30, kind: 'video');
expect(c.accepts(f('a.mkv', size: 2 * gb, daysAgo: 40), now: now), isTrue);
expect(c.accepts(f('a.mkv', size: gb ~/ 2, daysAgo: 40), now: now), isFalse, reason: '작다');
expect(c.accepts(f('a.mkv', size: 2 * gb, daysAgo: 3), now: now), isFalse, reason: '최근');
expect(c.accepts(f('a.srt', size: 2 * gb, daysAgo: 40), now: now), isFalse, reason: '동영상 아님');
expect(c.accepts(FileEntry('/x/d', isDir: true, modified: DateTime(2000)), now: now), isFalse, reason: '조건이 있으면 폴더는 아님');
expect(c.summary, contains('1.0GB'));
});
test('조건: N일 안 · 확장자 직접 · 조건 없음', () {
expect(const SearchConditions(newerDays: 7).accepts(f('a', daysAgo: 3), now: now), isTrue);
expect(const SearchConditions(newerDays: 7).accepts(f('a', daysAgo: 9), now: now), isFalse);
const custom = SearchConditions(kind: 'custom', exts: '*.MKV, .mp4 webm');
expect(custom.accepts(f('a.mkv'), now: now), isTrue);
expect(custom.accepts(f('a.webm'), now: now), isTrue);
expect(custom.accepts(f('a.avi'), now: now), isFalse);
expect(const SearchConditions(name: 'x').hasFileConditions, isFalse);
expect(const SearchConditions().isEmpty, isTrue);
});
group('searchFiles + accept', () {
late Directory tmp;
setUp(() {
tmp = Directory.systemTemp.createTempSync('jj_search79_');
File(p.join(tmp.path, 'a', 'ep1.mkv'))
..createSync(recursive: true)
..writeAsBytesSync(List.filled(3000, 0));
File(p.join(tmp.path, 'a', 'ep1.srt')).writeAsStringSync('x');
File(p.join(tmp.path, 'small.mkv')).writeAsBytesSync(List.filled(10, 0));
});
tearDown(() => tmp.deleteSync(recursive: true));
test('이름 없이 조건만 · 이름 + 조건', () async {
const c = SearchConditions(minBytes: 1000, kind: 'video');
expect(await searchFiles(tmp.path, '', accept: c.accepts).map((e) => e.name).toList(), ['ep1.mkv']);
expect(await searchFiles(tmp.path, 'ep1', accept: const SearchConditions(kind: 'subtitle').accepts).map((e) => e.name).toList(),
['ep1.srt']);
expect(await searchFiles(tmp.path, '').toList(), isEmpty, reason: '이름도 조건도 없으면 찾지 않는다 (예전 그대로)');
});
});
group('결과 화면', () {
late Directory tmp;
setUp(() {
tmp = Directory.systemTemp.createTempSync('jj_search79_ui_');
for (var i = 0; i < 5; i++) {
File(p.join(tmp.path, 'ep$i.mkv')).writeAsStringSync('$i');
}
Directory(p.join(tmp.path, 'ep_dir')).createSync();
});
tearDown(() => tmp.deleteSync(recursive: true));
Future<(List<FileEntry>, SearchAction? Function())> show(WidgetTester tester, {int chunk = 2000}) async {
tester.view.physicalSize = const Size(900, 900);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
final opened = <FileEntry>[];
SearchAction? result;
final nav = GlobalKey<NavigatorState>();
await tester.pumpWidget(MaterialApp(navigatorKey: nav, home: const Scaffold(body: Text('탐색기'))));
nav.currentState!
.push<SearchAction>(MaterialPageRoute(
builder: (_) => SearchResultsPage(
root: tmp.path,
cond: const SearchConditions(name: 'ep'),
showHidden: false,
otherDir: p.join(tmp.path, 'dest'),
chunk: chunk,
onOpen: (e) async => opened.add(e),
),
))
.then((r) => result = r);
for (var i = 0; i < 40; i++) {
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 20)));
await tester.pump();
}
return (opened, () => result);
}
testWidgets('2개마다 멈추고 [2개 더 보기] - 끝까지', (tester) async {
await show(tester, chunk: 2);
expect(find.text('찾기 결과 · 2개'), findsOneWidget);
expect(find.text('2개에서 멈췄습니다'), findsOneWidget);
await tester.tap(find.text('2개 더 보기'));
for (var i = 0; i < 20; i++) {
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 20)));
await tester.pump();
}
expect(find.text('찾기 결과 · 4개'), findsOneWidget);
await tester.tap(find.text('2개 더 보기'));
for (var i = 0; i < 20; i++) {
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 20)));
await tester.pump();
}
expect(find.text('찾기 결과 · 6개'), findsOneWidget, reason: '파일 5 + 폴더 1');
expect(find.textContaining('멈췄습니다'), findsNothing);
});
testWidgets('누르면 열기 (화면 그대로) · 폴더는 그 자리로 · 고르면 복사 → 다른 창 · 지우기', (tester) async {
final (opened, result) = await show(tester);
await tester.tap(find.text('ep1.mkv'));
await tester.pump();
expect(opened.single.name, 'ep1.mkv');
expect(find.text('찾기 결과 · 6개'), findsOneWidget, reason: '열어도 결과 화면은 그대로');
// 고르면 아래에 할 일 줄 (개수 · 크기), 고르는 동안 누르기 = 고르기
expect(find.text('지우기'), findsNothing);
await tester.longPress(find.text('ep2.mkv'));
await tester.pump();
await tester.tap(find.text('ep3.mkv'));
await tester.pump();
expect(opened, hasLength(1), reason: '고르는 중에는 열지 않음');
expect(find.text('2개 · 2B'), findsOneWidget);
expect(find.text('복사 → dest'), findsOneWidget);
await tester.tap(find.text('지우기'));
await tester.pumpAndSettle();
expect(result()!.kind, 'delete');
expect(result()!.paths.map(p.basename), unorderedEquals(['ep2.mkv', 'ep3.mkv']), reason: '고른 것만');
});
testWidgets('폴더를 누르면 그 자리로', (tester) async {
final (_, result) = await show(tester);
await tester.tap(find.text('ep_dir'));
await tester.pumpAndSettle();
expect(result()!.kind, 'reveal');
expect(p.basename(result()!.paths.single), 'ep_dir');
});
});
}