JJ_MKVMaker/test/storage_scan_test.dart

152 lines
6.6 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:jj_mkvmaker/core/storage_scan.dart';
import 'package:jj_mkvmaker/ui/storage_page.dart';
import 'package:path/path.dart' as p;
/// 87: 저장 공간 분석 - 폴더마다 크기 (안쪽 합) · 큰 파일 차례, 링크는 세지 않음 (80 과 같게) · 취소, 화면은 들어가기 · 그 자리로
void main() {
late Directory tmp;
setUp(() {
tmp = Directory.systemTemp.createTempSync('jj_scan87_');
File(p.join(tmp.path, 'movies', 'a.mkv'))
..createSync(recursive: true)
..writeAsBytesSync(List.filled(5000, 0));
File(p.join(tmp.path, 'movies', 'old', 'b.mkv'))
..createSync(recursive: true)
..writeAsBytesSync(List.filled(3000, 0));
File(p.join(tmp.path, 'docs', 'c.txt'))
..createSync(recursive: true)
..writeAsBytesSync(List.filled(100, 0));
File(p.join(tmp.path, 'top.bin')).writeAsBytesSync(List.filled(10, 0));
});
tearDown(() => tmp.deleteSync(recursive: true));
test('폴더마다 안쪽 합 · 큰 폴더 차례 · 큰 파일 차례', () async {
final s = await StorageScanner().scan(tmp.path);
expect(s.sizeOf(tmp.path), 8110);
expect(s.filesOf(tmp.path), 4);
expect(s.sizeOf(p.join(tmp.path, 'movies')), 8000);
expect(s.biggestIn(tmp.path).map(p.basename), ['movies', 'docs']);
expect(s.bigFiles.map((f) => f.name).take(3), ['a.mkv', 'b.mkv', 'c.txt']);
});
test('링크 · junction 은 세지 않고 (위 폴더를 가리키는 고리도 끝남) · 취소', () async {
var junctions = 0;
if (Platform.isWindows) {
for (final (link, target) in [(p.join(tmp.path, 'docs', 'loop'), tmp.path), (p.join(tmp.path, 'tomovies'), p.join(tmp.path, 'movies'))]) {
if ((await Process.run('cmd', ['/c', 'mklink', '/J', link, target])).exitCode == 0) junctions++;
}
}
final s = await StorageScanner().scan(tmp.path);
expect(s.sizeOf(tmp.path), 8110, reason: 'junction 이 가리키는 것을 더하지 않는다');
expect(s.links, junctions);
await expectLater(StorageScanner().scan(tmp.path, cancelled: () => true), throwsA(isA<StorageScanCancelled>()));
for (final l in [p.join(tmp.path, 'docs', 'loop'), p.join(tmp.path, 'tomovies')]) {
if (Link(l).existsSync()) Link(l).deleteSync();
}
});
testWidgets('화면: 큰 폴더 → 들어가기 · 위로 · 큰 파일 · [그 자리로] 는 그 경로를 돌려준다', (tester) async {
tester.view.physicalSize = const Size(1000, 900);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
final key = GlobalKey<NavigatorState>();
await tester.pumpWidget(MaterialApp(navigatorKey: key, home: const Scaffold()));
String? picked;
var done = false;
Navigator.of(key.currentContext!)
.push<String>(MaterialPageRoute(builder: (_) => StorageAnalysisPage(root: tmp.path)))
.then((v) {
picked = v;
done = true;
});
for (var i = 0; i < 200 && find.text('movies').evaluate().isEmpty; i++) {
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 20)));
await tester.pump();
}
expect(find.text('movies'), findsOneWidget);
await tester.tap(find.text('movies'));
await tester.pump();
expect(find.text('old'), findsOneWidget, reason: '들어가기');
await tester.tap(find.byTooltip('위 폴더'));
await tester.pump();
await tester.tap(find.text('큰 파일'));
await tester.pump();
expect(find.text('a.mkv'), findsOneWidget);
await tester.tap(find.text('a.mkv'));
await tester.pumpAndSettle();
expect(done, isTrue);
expect(picked, p.join(tmp.path, 'movies', 'a.mkv'));
});
test('5-1 ⑤ 지운 것을 다시 훑지 않고 빼기: 폴더 (그 아래 모두) · 파일 - 위 폴더들의 크기 · 파일 수에서 덜어 냄', () async {
final s = await StorageScanner().scan(tmp.path);
final movies = p.join(tmp.path, 'movies');
s.remove(p.join(movies, 'old'));
expect(s.sizeOf(movies), 5000);
expect(s.filesOf(movies), 1);
expect(s.sizeOf(tmp.path), 5110);
expect(s.biggestIn(movies), isEmpty);
expect(s.bigFiles.map((f) => f.name), isNot(contains('b.mkv')));
s.remove(p.join(movies, 'a.mkv'));
expect((s.sizeOf(movies), s.filesOf(movies)), (0, 0));
expect((s.sizeOf(tmp.path), s.filesOf(tmp.path)), (110, 2));
// 큰 파일 목록에 없는 파일은 알려 준 크기로
s.remove(p.join(tmp.path, 'unknown.bin'), fileSize: 10);
expect(s.sizeOf(tmp.path), 100);
});
testWidgets('5-1 ⑤ 화면: ⋮ [지우기 (확인 · 휴지통)] 는 탐색기의 지우기를 부르고 결과에서 뺌 · 취소면 그대로 · [이동 (붙여넣기로)] 는 담기', (tester) async {
tester.view.physicalSize = const Size(1000, 900);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
final asked = <List<String>>[];
final cut = <List<String>>[];
var confirm = false;
await tester.pumpWidget(MaterialApp(
home: StorageAnalysisPage(
root: tmp.path,
onDelete: (paths) async {
asked.add(paths);
if (!confirm) return const [];
for (final x in paths) {
Directory(x).deleteSync(recursive: true);
}
return paths;
},
onCut: cut.add,
),
));
for (var i = 0; i < 200 && find.text('movies').evaluate().isEmpty; i++) {
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 20)));
await tester.pump();
}
Future<void> menu(String row, String item) async {
await tester.tap(find.descendant(of: find.widgetWithText(ListTile, row), matching: find.byTooltip('더 보기')));
await tester.pumpAndSettle();
await tester.tap(find.text(item).last);
for (var i = 0; i < 10; i++) {
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 20)));
await tester.pump();
}
}
final movies = p.join(tmp.path, 'movies');
// 취소 (탐색기 확인 창에서 [취소]) → 그대로
await menu('movies', '지우기 (확인 · 휴지통)');
expect(asked.single, [movies]);
expect(find.text('movies'), findsOneWidget);
// 확인 → 목록에서 빠지고 위 폴더 크기도 줄어듦
confirm = true;
await menu('movies', '지우기 (확인 · 휴지통)');
expect(find.text('movies'), findsNothing);
expect(find.textContaining('110B'), findsWidgets);
// 이동 (붙여넣기로)
await menu('docs', '이동 (붙여넣기로)');
expect(cut.single, [p.join(tmp.path, 'docs')]);
await tester.pumpWidget(const SizedBox());
});
}