import 'dart:io'; import 'package:archive/archive_io.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:jj_mkvmaker/app/app_controller.dart'; import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart'; import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart'; import 'package:jj_mkvmaker/services/platform_services.dart'; import 'package:jj_mkvmaker/ui/archive_ui.dart'; import 'package:path/path.dart' as p; /// 86: 압축 풀기 · ZIP 묶기 화면 흐름 - 확인 창에 막은 경로 · 링크, 취소는 아무 일 없음, 묶기는 만든 ZIP void main() { late Directory tmp; late AppController c; setUp(() { tmp = Directory.systemTemp.createTempSync('jj_arch_ui_'); c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); }); tearDown(() => tmp.deleteSync(recursive: true)); Future wait(WidgetTester tester, bool Function() done) async { for (var i = 0; i < 200 && !done(); i++) { await tester.runAsync(() => Future.delayed(const Duration(milliseconds: 20))); await tester.pump(); } } Future Function(Future Function(BuildContext))> host(WidgetTester tester) async { tester.view.physicalSize = const Size(1000, 900); tester.view.devicePixelRatio = 1; addTearDown(tester.view.reset); final key = GlobalKey(); await tester.pumpWidget(MaterialApp(navigatorKey: key, home: const Scaffold())); return (Future Function(BuildContext) f) => f(key.currentContext!); } testWidgets('풀기: 확인 창에 막은 경로 · 건너뛸 링크를 보이고, 풀면 대상 안에만', (tester) async { final a = Archive() ..addFile(ArchiveFile.string('ok.txt', 'ok')) ..addFile(ArchiveFile.string('../evil.txt', 'evil')) ..addFile(ArchiveFile.string('lnk', '../x')..mode = 0xA1FF); final zip = p.join(tmp.path, 'bad.zip'); File(zip).writeAsBytesSync(ZipEncoder().encode(a)); final run = await host(tester); String? out; var finished = false; run((ctx) => extractArchiveFlow(ctx, c, zip)).then((v) { out = v; finished = true; }); await wait(tester, () => find.text('풀기').evaluate().isNotEmpty); expect(find.textContaining('밖으로 나가는 경로라 풀지 않습니다 (1개)'), findsOneWidget); expect(find.textContaining('링크 1개는 만들지 않고 건너뜁니다'), findsOneWidget); await tester.tap(find.widgetWithText(FilledButton, '풀기')); await wait(tester, () => finished); expect(out, p.join(tmp.path, 'bad')); expect(File(p.join(tmp.path, 'bad', 'ok.txt')).readAsStringSync(), 'ok'); expect(File(p.join(tmp.path, 'evil.txt')).existsSync(), isFalse); }); testWidgets('풀기 [취소] 는 아무 일 없음 · 7z 를 풀 수 없는 기기면 다른 앱 안내 (막지 않음)', (tester) async { final zip = p.join(tmp.path, 'a.zip'); File(zip).writeAsBytesSync(ZipEncoder().encode(Archive()..addFile(ArchiveFile.string('a.txt', 'a')))); final run = await host(tester); var finished = false; run((ctx) => extractArchiveFlow(ctx, c, zip)).then((_) => finished = true); await wait(tester, () => find.text('풀기').evaluate().isNotEmpty); await tester.tap(find.text('취소')); await wait(tester, () => finished); expect(Directory(p.join(tmp.path, 'a')).existsSync(), isFalse); if (bundledSevenZip() == null) { final sz = File(p.join(tmp.path, 'x.7z'))..writeAsStringSync('7z'); finished = false; run((ctx) => extractArchiveFlow(ctx, c, sz.path)).then((_) => finished = true); await wait(tester, () => find.text('다른 앱으로 열기').evaluate().isNotEmpty); expect(find.textContaining('다른 앱으로 열어 주세요'), findsOneWidget); await tester.tap(find.text('닫기')); await wait(tester, () => finished); } }); testWidgets('ZIP 으로 묶기: 이름 · 압축 정도 (기억) → 만든 ZIP', (tester) async { final src = Directory(p.join(tmp.path, 'photos'))..createSync(); File(p.join(src.path, 'a.txt')).writeAsStringSync('A'); final run = await host(tester); String? made; var finished = false; run((ctx) => createZipFlow(ctx, c, [src.path], dir: tmp.path)).then((v) { made = v; finished = true; }); await wait(tester, () => find.text('묶기').evaluate().isNotEmpty); expect(find.text('만들 파일: photos.zip'), findsOneWidget); await tester.tap(find.text('작게')); await tester.pump(); await tester.tap(find.widgetWithText(FilledButton, '묶기')); await wait(tester, () => finished); expect(made, p.join(tmp.path, 'photos.zip')); expect(c.settings.zipLevel, 9, reason: '고른 압축 정도를 기억'); expect(File(made!).lengthSync() > 0, isTrue); }); }