From a20a7bfe79b6449a5d54634e1fe523e78d287d3d Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Mon, 12 Oct 2026 01:31:22 +0900 Subject: [PATCH] =?UTF-8?q?=EC=8B=9C=ED=97=98=20=ED=9D=94=EB=93=A4?= =?UTF-8?q?=EB=A6=BC:=20=ED=83=90=EC=83=89=EA=B8=B0=20=EC=8B=9C=ED=97=98?= =?UTF-8?q?=EC=9D=98=20pumpAndSettle=20=EC=9D=84=20calm=20(=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=9D=BD=EA=B8=B0=EA=B0=80=20=EB=81=9D=EB=82=9C=20?= =?UTF-8?q?=EB=92=A4)=20=EC=9C=BC=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- test/batch_test.dart | 4 +- test/explorer_conflict_test.dart | 23 ++++--- test/explorer_drives_test.dart | 13 ++-- test/explorer_parallel_test.dart | 10 +-- test/explorer_steady_test.dart | 7 +- test/explorer_test.dart | 111 +++++++++++++++++-------------- test/explorer_webdav_test.dart | 6 +- 7 files changed, 94 insertions(+), 80 deletions(-) diff --git a/test/batch_test.dart b/test/batch_test.dart index 3ee15dd..0d91cbf 100644 --- a/test/batch_test.dart +++ b/test/batch_test.dart @@ -627,8 +627,8 @@ void main() { }); expect(c.busy, isTrue); await tester.pumpWidget(MaterialApp(home: HomePage(c: c))); - // 첫 그림에서 재어 보고 모자라면 다음 그림부터 두 줄 (첫 그림의 넘침 알림은 그 재기) - tester.takeException(); + // 첫 그림에서 재어 보고 모자라면 다음 그림부터 두 줄 (첫 그림도 넘침 오류 없이 잘라 그림) + expect(tester.takeException(), isNull); await tester.pump(); await tester.pump(); expect(tester.takeException(), isNull, reason: '두 줄로 바뀐 뒤에는 넘침 없음'); diff --git a/test/explorer_conflict_test.dart b/test/explorer_conflict_test.dart index 369d6f3..35cbc3c 100644 --- a/test/explorer_conflict_test.dart +++ b/test/explorer_conflict_test.dart @@ -14,6 +14,7 @@ import 'support/fake_recycle_bin.dart'; import 'support/recycle_leftovers.dart'; import 'support/elevated.dart'; +import 'explorer_test.dart' show calm; /// 48 · 154 · 155: 같은 이름이 있으면 덮어쓰기 · 건너뛰기 · 이름 바꾸기를 고르고, 확인 창에 항목 이름이 보인다 void main() { late Directory tmp; @@ -165,7 +166,7 @@ void main() { await tester.runAsync(() => tester.tap(find.text('doc.txt').first)); await settle(tester, () => false, rounds: 10); await tester.runAsync(() => tester.tap(find.text('삭제').first)); - await tester.pumpAndSettle(); + await calm(tester); await tester.runAsync(() => tester.tap(find.widgetWithText(FilledButton, '휴지통으로'))); await settle(tester, () => find.text('되돌리기').evaluate().isNotEmpty); expect(File(p.join(left, 'doc.txt')).existsSync(), isFalse); @@ -179,7 +180,7 @@ void main() { if (bin != null) expect(bin!.items, isEmpty, reason: '가짜 휴지통에서 되돌림'); // 알림 (10초) 이 시험이 끝난 뒤에 닫히지 않게 정리 ScaffoldMessenger.of(tester.element(find.byType(ExplorerPage))).clearSnackBars(); - await tester.pumpAndSettle(); + await calm(tester); }, skip: !Platform.isWindows); testWidgets('144: 긴 경로는 휴지통에 넣지 못한 이유 한 줄과 [영구 삭제] - 누르면 지운다 · [그대로 두기] 면 남는다', (tester) async { @@ -199,7 +200,7 @@ void main() { await tester.runAsync(() => tester.tap(find.text('deep').first)); await settle(tester, () => false, rounds: 10); await tester.runAsync(() => tester.tap(find.text('삭제').first)); - await tester.pumpAndSettle(); + await calm(tester); await tester.runAsync(() => tester.tap(find.widgetWithText(FilledButton, '휴지통으로'))); await settle(tester, () => find.textContaining('경로가 너무 길어').evaluate().isNotEmpty); } @@ -210,7 +211,7 @@ void main() { expect(find.text('· deep'), findsOneWidget); expect(find.textContaining('영구 삭제로 지울 수 있습니다'), findsNothing, reason: '긴 설명을 항목마다 되풀이하지 않음'); await tester.tap(find.text('그대로 두기')); - await tester.pumpAndSettle(); + await calm(tester); expect(Directory(lp(deep)).existsSync(), isTrue); // 다시 → [영구 삭제] await deleteDeep(); @@ -249,7 +250,7 @@ void main() { expect(find.byType(ExplorerPage), findsOneWidget); expect(find.textContaining(p.join(tmp.path, 'left')), findsNothing); await back(); // 3) 맨 위에서는 화면 닫기 - await tester.pumpAndSettle(); // 닫히는 전환 (가짜 시간을 흘려야 끝난다) + await calm(tester); // 닫히는 전환 (가짜 시간을 흘려야 끝난다) expect(find.byType(ExplorerPage), findsNothing); expect(find.text('앞 화면'), findsOneWidget); }); @@ -287,14 +288,14 @@ void main() { expect(find.descendant(of: find.byType(AlertDialog), matching: find.text('이미 이 폴더에 있습니다')), findsOneWidget); expect(find.textContaining('doc.txt 은(는) 이미 이 폴더에 있어 옮길 것이 없습니다'), findsOneWidget); await tester.tap(find.widgetWithText(FilledButton, '확인')); - await tester.pumpAndSettle(); + await calm(tester); expect(File(p.join(left, 'doc.txt')).existsSync(), isTrue); // 같은 폴더로 [복사] 는 사본을 만들지 묻는다: [취소] → 그대로, [사본 만들기] → "doc (2).txt" await tester.runAsync(() => tester.tap(find.text('복사').first)); await settle(tester, () => find.byType(AlertDialog).evaluate().isNotEmpty, rounds: 30); expect(find.textContaining('같은 폴더에 사본을 만들까요? (이름 (2))'), findsOneWidget); await tester.tap(find.text('취소')); - await tester.pumpAndSettle(); + await calm(tester); expect(File(p.join(left, 'doc (2).txt')).existsSync(), isFalse); await tester.runAsync(() => tester.tap(find.text('복사').first)); await settle(tester, () => find.byType(AlertDialog).evaluate().isNotEmpty, rounds: 30); @@ -346,7 +347,7 @@ void main() { expect(find.text('더 보기'), findsOneWidget); expect(find.text('숨은 항목 표시'), findsNothing, reason: '들어가지 않는 버튼은 줄에 없음'); await tester.tap(find.text('더 보기')); - await tester.pumpAndSettle(); + await calm(tester); expect(find.text('숨은 항목 표시'), findsOneWidget); await tester.runAsync(() => tester.tap(find.text('숨은 항목 표시'))); await settle(tester, () => c.settings.explorerShowHidden != before, rounds: 20); @@ -391,7 +392,7 @@ void main() { await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(c: c)))); await settle(tester, () => find.text('doc.txt').evaluate().isNotEmpty); await tester.tap(find.byTooltip('경로 입력').first); - await tester.pumpAndSettle(); + await calm(tester); await tester.enterText(find.byType(TextField).last, p.join(tmp.path, 'nope')); await tester.runAsync(() => tester.tap(find.widgetWithText(FilledButton, '확인'))); await settle(tester, () => find.text('폴더가 없거나 열 수 없습니다').evaluate().isNotEmpty); @@ -431,7 +432,7 @@ void main() { await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(c: c)))); await settle(tester, () => find.text('doc.txt').evaluate().isNotEmpty); await tester.tap(find.byTooltip('경로 입력').first); - await tester.pumpAndSettle(); + await calm(tester); expect(find.widgetWithText(TextButton, 'WebDAV 서버 추가'), findsNothing); await tester.enterText(find.byType(TextField).last, 'smb://nas/영상'); await tester.runAsync(() => tester.tap(find.widgetWithText(FilledButton, '확인'))); @@ -444,7 +445,7 @@ void main() { expect(find.widgetWithText(AlertDialog, 'WebDAV 서버 추가'), findsOneWidget); expect(find.textContaining('SMB'), findsNothing, reason: '경로 창은 닫힘'); await tester.tap(find.text('취소').last); - await tester.pumpAndSettle(); + await calm(tester); expect(find.byType(AlertDialog), findsNothing); }); diff --git a/test/explorer_drives_test.dart b/test/explorer_drives_test.dart index 2c724a4..1bd65bb 100644 --- a/test/explorer_drives_test.dart +++ b/test/explorer_drives_test.dart @@ -12,6 +12,7 @@ import 'package:jj_mkvmaker/services/platform_services.dart'; import 'package:jj_mkvmaker/ui/explorer_page.dart'; import 'package:path/path.dart' as p; +import 'explorer_test.dart' show calm; void main() { late Directory tmp; late AppController c; @@ -93,10 +94,10 @@ void main() { expect(tester.getRect(find.byWidget(e.widget).first).right, lessThanOrEqualTo(1280)); } await tester.tap(pathBtn.first); - await tester.pumpAndSettle(); + await calm(tester); expect(find.byType(AlertDialog), findsOneWidget); await tester.tap(find.text('취소').last); - await tester.pumpAndSettle(); + await calm(tester); // 정상 드라이브 (T:) 가 빈 드라이브들보다 앞 expect(tester.getTopLeft(find.text('T:').first).dx, lessThan(tester.getTopLeft(find.textContaining('G: ·').first).dx)); // 휠 (위 · 아래) 로 탭 줄이 옆으로 (왼쪽 창의 T: 탭이 왼쪽으로 밀린다) @@ -108,15 +109,15 @@ void main() { expect(tester.getTopLeft(find.text('T:').first).dx, lessThan(before - 10)); // 좁은 창 (위아래 두 창 · 폰): 끝 버튼은 [⋮] 하나로 늘 보임 → 경로 입력 tester.view.physicalSize = const Size(560, 900); - await tester.pumpAndSettle(); + await calm(tester); expect(find.byTooltip('경로 입력 · WebDAV'), findsWidgets); await tester.tap(find.byTooltip('경로 입력 · WebDAV').first); - await tester.pumpAndSettle(); + await calm(tester); await tester.tap(find.text('경로 입력').last); - await tester.pumpAndSettle(); + await calm(tester); expect(find.byType(AlertDialog), findsOneWidget); await tester.tap(find.text('취소').last); - await tester.pumpAndSettle(); + await calm(tester); expect(tester.takeException(), isNull, reason: '넘침 (RenderFlex overflow) 없음'); }); diff --git a/test/explorer_parallel_test.dart b/test/explorer_parallel_test.dart index aa392b2..73f0762 100644 --- a/test/explorer_parallel_test.dart +++ b/test/explorer_parallel_test.dart @@ -11,7 +11,7 @@ import 'package:jj_mkvmaker/services/platform_services.dart'; import 'package:jj_mkvmaker/ui/explorer_page.dart'; import 'package:path/path.dart' as p; -import 'explorer_test.dart' show act, settle; +import 'explorer_test.dart' show act, settle, calm; /// 23: 앞의 복사가 끝나지 않아도 다음 복사를 함께 시작한다 (막지 않음, 진행 막대가 둘) void main() { @@ -161,7 +161,7 @@ void main() { await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(c: c, rsync: true)))); await settle(tester, () => find.text('sub').evaluate().isNotEmpty); await tester.longPress(find.text('sub')); - await tester.pumpAndSettle(); + await calm(tester); for (final t in ['rsync 에 고르기', '열기', '다른 창에서 열기', '이름 변경', '삭제', '정보']) { expect(find.text(t), findsOneWidget, reason: t); } @@ -194,7 +194,7 @@ void main() { await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(c: c)))); await settle(tester, () => find.text('top.txt').evaluate().isNotEmpty); await tester.tap(find.byTooltip('경로 입력').first); - await tester.pumpAndSettle(); + await calm(tester); await tester.enterText(find.byType(TextField).last, p.join(tmp.path, 'none')); await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '확인'))); await settle(tester, () => find.textContaining('폴더가 없거나 열 수 없습니다').evaluate().isNotEmpty, rounds: 20); @@ -232,7 +232,7 @@ void main() { ..createSync(recursive: true) ..writeAsBytesSync([1, 2, 3]); await tester.tap(find.byTooltip('경로 입력').first); - await tester.pumpAndSettle(); + await calm(tester); await tester.enterText(find.byType(TextField).last, made); await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '확인'))); await settle(tester, () => find.text('pg1.png').evaluate().isNotEmpty, rounds: 300); @@ -308,7 +308,7 @@ void main() { await act(tester, () => tester.tap(find.byTooltip(tooltip))); } else { await tester.tap(find.text('더 보기')); - await tester.pumpAndSettle(); + await calm(tester); expect(find.text(label), findsOneWidget, reason: '[더 보기] 메뉴에 지금 배치가 보임: $label'); await act(tester, () => tester.tap(find.text(label))); } diff --git a/test/explorer_steady_test.dart b/test/explorer_steady_test.dart index eb925b6..db28c2e 100644 --- a/test/explorer_steady_test.dart +++ b/test/explorer_steady_test.dart @@ -12,6 +12,7 @@ import 'package:jj_mkvmaker/ui/explorer_page.dart'; import 'package:path/path.dart' as p; import 'support/fake_recycle_bin.dart'; +import 'explorer_test.dart' show calm; /// 180: 복사 · 이동이 시작 · 진행 · 끝나도 목록의 자리와 스크롤은 그대로 (다음 것을 누르려다 엉뚱한 것을 누르지 않게) void main() { late Directory tmp; @@ -62,7 +63,7 @@ void main() { // 왼쪽 목록을 조금 내려 둔다 final list = find.ancestor(of: find.text('f05.txt'), matching: find.byType(Scrollable)).first; await tester.drag(find.text('f05.txt'), const Offset(0, -200)); - await tester.pumpAndSettle(); + await calm(tester); final probe = find.text('f07.txt'); expect(probe, findsOneWidget); final where = tester.getTopLeft(probe); @@ -84,7 +85,7 @@ void main() { await act(tester, () => tester.tap(find.text('새로 고침').first)); } else { await act(tester, () => tester.tap(find.text('복사').first)); - await tester.pumpAndSettle(); + await calm(tester); await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '복사'))); } // 시작 · 진행 · 끝 (진행 막대가 내려갈 때까지) 내내 같은 자리 @@ -126,7 +127,7 @@ void main() { await settle(tester, () => find.text('f05.txt').evaluate().isNotEmpty); final list = find.ancestor(of: find.text('f05.txt'), matching: find.byType(Scrollable)).first; await tester.drag(find.text('f05.txt'), const Offset(0, -200)); - await tester.pumpAndSettle(); + await calm(tester); final at = tester.state(list).position.pixels; expect(at, greaterThan(0)); final w = tester.getSize(list).width; diff --git a/test/explorer_test.dart b/test/explorer_test.dart index 86fc3c0..1873988 100644 --- a/test/explorer_test.dart +++ b/test/explorer_test.dart @@ -112,7 +112,7 @@ void main() { // 길게 누르기 → 메뉴 → 다른 앱으로 열기 (고르기 창) await tester.longPress(find.text('doc.txt')); - await tester.pumpAndSettle(); + await calm(tester); expect(find.text('다른 창으로 복사'), findsOneWidget); expect(find.text('삭제'), findsWidgets); await act(tester, () => tester.tap(find.text('다른 앱으로 열기'))); @@ -123,7 +123,7 @@ void main() { await act(tester, () => tester.tap(find.text('doc.txt'))); await settle(tester, () => false, rounds: 15); await act(tester, () => tester.tap(find.text('복사').first)); - await tester.pumpAndSettle(); + await calm(tester); await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '복사'))); await settle(tester, () => File(p.join(right, 'doc.txt')).existsSync() && find.byType(LinearProgressIndicator).evaluate().isEmpty); expect(File(p.join(right, 'doc.txt')).readAsStringSync(), 'hello'); @@ -138,17 +138,17 @@ void main() { await act(tester, () => tester.tap(find.text('doc.txt'))); await settle(tester, () => false, rounds: 15); await act(tester, () => tester.tap(find.text('복사').first)); - await tester.pumpAndSettle(); + await calm(tester); expect(find.textContaining('방법: robocopy'), findsOneWidget); expect(find.text('환경 설정의 /MOV 는 복사에서는 쓰지 않습니다 (이동은 [이동] 으로)'), findsOneWidget); await act(tester, () => tester.tap(find.text('취소').last)); - await tester.pumpAndSettle(); + await calm(tester); await act(tester, () => tester.tap(find.text('이동').first)); - await tester.pumpAndSettle(); + await calm(tester); expect(find.textContaining('옮길까요?'), findsOneWidget); expect(find.textContaining('복사에서는 쓰지 않습니다'), findsNothing); await act(tester, () => tester.tap(find.text('취소').last)); - await tester.pumpAndSettle(); + await calm(tester); expect(File(p.join(left, 'doc.txt')).existsSync(), isTrue); expect(File(p.join(right, 'doc.txt')).existsSync(), isFalse); }, skip: !Platform.isWindows); @@ -192,17 +192,17 @@ void main() { // 이 배치에도 복사 · 이동: 길게 눌러 [복사] 로 담고 → 다른 폴더를 골라 [붙여넣기] await tester.longPress(find.text('inner.txt')); - await tester.pumpAndSettle(); + await calm(tester); expect(find.text('다른 창으로 복사'), findsNothing); await tester.tap(find.text('복사').last); - await tester.pumpAndSettle(); + await calm(tester); await act(tester, () => tester.tap(find.text('..'))); await settle(tester, () => find.text('doc.txt').evaluate().isNotEmpty); // 73 (관리자): 담아 둔 것이 있으면 [붙여넣기] 는 좁아도 [더 보기] 로 가지 않고 줄에 남는다 expect(find.text('더 보기'), findsOneWidget, reason: '이 너비에서는 줄이 넘친다'); expect(find.text('붙여넣기'), findsOneWidget); await act(tester, () => tester.tap(find.text('붙여넣기'))); - await tester.pumpAndSettle(); + await calm(tester); await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '복사'))); await settle(tester, () => File(p.join(left, 'inner.txt')).existsSync() && find.byType(LinearProgressIndicator).evaluate().isEmpty); expect(File(p.join(left, 'inner.txt')).readAsStringSync(), 'inner'); @@ -220,7 +220,7 @@ void main() { expect(find.text('확장자'), findsOneWidget); // 알림 (복사 끝 등) 이 시험이 끝난 뒤에 닫히지 않게 정리 ScaffoldMessenger.of(tester.element(find.byType(ExplorerPage))).clearSnackBars(); - await tester.pumpAndSettle(); + await calm(tester); }); testWidgets('선택 모드: 파일 누르기 = 선택 · 취소, 폴더 누르기 = 자기 자신과 안의 것 모두 선택 · 다시 누르면 모두 취소', (tester) async { @@ -278,9 +278,9 @@ void main() { await act(tester, () => tester.tap(markOf('sub'))); await settle(tester, () => find.textContaining('3개 표시함').evaluate().isNotEmpty); await act(tester, () => tester.tap(find.text('삭제').first)); - await tester.pumpAndSettle(); + await calm(tester); await tester.tap(find.text('휴지통을 거치지 않고 영구 삭제 (Shift+Delete)')); - await tester.pumpAndSettle(); + await calm(tester); await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '영구 삭제'))); await settle(tester, () => find.textContaining('지우지 못했습니다').evaluate().isNotEmpty); expect(find.text('3개 중 1개를 지우지 못했습니다'), findsOneWidget); @@ -289,43 +289,43 @@ void main() { expect(Directory(p.join(left, 'sub')).existsSync(), isFalse); expect(locked.existsSync(), isTrue); await tester.tap(find.widgetWithText(FilledButton, '확인')); - await tester.pumpAndSettle(); + await calm(tester); }, skip: !Platform.isWindows); testWidgets('73: 즐겨찾기 - 지금 폴더 넣기 (이름 고침) · 한 번에 가기 · 빼기 (폴더는 그대로)', (tester) async { await open(tester); // 창 위의 ★ (왼쪽 창) await act(tester, () => tester.tap(find.byTooltip('즐겨찾기').first)); - await tester.pumpAndSettle(); + await calm(tester); expect(find.text('아직 없습니다. 아래에서 지금 폴더를 넣으세요.'), findsOneWidget); await tester.tap(find.text('지금 폴더 추가')); - await tester.pumpAndSettle(); + await calm(tester); await tester.enterText(find.byType(TextField).last, '왼쪽 폴더'); await tester.tap(find.widgetWithText(FilledButton, '확인')); - await tester.pumpAndSettle(); + await calm(tester); expect([for (final b in c.settings.folderBookmarks) (b.name, b.path)], [('왼쪽 폴더', left)]); expect(find.text('왼쪽 폴더'), findsOneWidget); await tester.tap(find.text('닫기')); - await tester.pumpAndSettle(); + await calm(tester); expect(find.byIcon(Icons.star), findsOneWidget, reason: '즐겨찾기한 폴더를 보는 창의 ★ 는 채움'); // 오른쪽 폴더를 즐겨찾기에 두고 왼쪽 창에서 한 번에 가기 await c.updateSettings((x) => x.folderBookmarks = [...x.folderBookmarks, FolderBookmark(name: 'R', path: right)]); await act(tester, () => tester.tap(find.byTooltip('즐겨찾기').first)); - await tester.pumpAndSettle(); + await calm(tester); await act(tester, () => tester.tap(find.text('R'))); await settle(tester, () => false, rounds: 20); expect(find.byIcon(Icons.star), findsNWidgets(2), reason: '두 창 모두 즐겨찾기한 폴더 (왼쪽 창이 R 로 감)'); // 빼기: 즐겨찾기에서만 (폴더는 그대로) await act(tester, () => tester.tap(find.byTooltip('즐겨찾기').first)); - await tester.pumpAndSettle(); + await calm(tester); await tester.tap(find.byTooltip('즐겨찾기에서 빼기 (폴더는 그대로)').first); - await tester.pumpAndSettle(); + await calm(tester); expect([for (final b in c.settings.folderBookmarks) b.name], ['R']); expect(Directory(left).existsSync(), isTrue); await tester.tap(find.text('닫기')); - await tester.pumpAndSettle(); + await calm(tester); }); testWidgets('73: Rsync 화면 - "왼쪽 + 오른쪽" 짝을 넣고 한 번에 연다', (tester) async { @@ -336,19 +336,19 @@ void main() { await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(c: c, rsync: true)))); await settle(tester, () => find.text('doc.txt').evaluate().isNotEmpty); await act(tester, () => tester.tap(find.byTooltip('즐겨찾기').first)); - await tester.pumpAndSettle(); + await calm(tester); await tester.tap(find.text('지금 짝 추가')); - await tester.pumpAndSettle(); + await calm(tester); await tester.tap(find.widgetWithText(FilledButton, '확인')); - await tester.pumpAndSettle(); + await calm(tester); final pair = c.settings.folderBookmarks.single; expect((pair.path, pair.right, pair.name), (left, right, 'left ⇄ right')); await tester.tap(find.text('닫기')); - await tester.pumpAndSettle(); + await calm(tester); // 반대 짝을 넣어 두고 열면 두 창이 함께 바뀐다 await c.updateSettings((x) => x.folderBookmarks = [...x.folderBookmarks, FolderBookmark(name: '거꾸로', path: right, right: left)]); await act(tester, () => tester.tap(find.byTooltip('즐겨찾기').first)); - await tester.pumpAndSettle(); + await calm(tester); await act(tester, () => tester.tap(find.text('거꾸로'))); await settle(tester, () => c.settings.rsyncPaths.length == 2 && c.settings.rsyncPaths[0] == right && c.settings.rsyncPaths[1] == left, rounds: 100); @@ -368,9 +368,9 @@ void main() { Future tool(String label) async { await tester.tap(find.text('고르기 도구').first); - await tester.pumpAndSettle(); + await calm(tester); await tester.tap(find.textContaining(label).last); - await tester.pumpAndSettle(); + await calm(tester); } // 같은 확장자 모두 (.srt) - 마지막으로 누른 a.srt 기준 @@ -386,7 +386,7 @@ void main() { await tool('패턴으로 고르기'); await tester.enterText(find.byType(TextField).last, '*.MKV; doc.*'); await tester.tap(find.widgetWithText(FilledButton, '고르기')); - await tester.pumpAndSettle(); + await calm(tester); expect(find.text('2개 표시함 · 13B'), findsOneWidget); // 범위: a.srt 를 표시한 뒤 Shift + c.mkv → a.srt · b.srt · c.mkv (보이는 순서) @@ -404,9 +404,9 @@ void main() { await tester.pump(); await act(tester, () => tester.tap(markOf('b.srt'))); await tester.longPress(find.text('doc.txt').first); - await tester.pumpAndSettle(); + await calm(tester); await tester.tap(find.text('여기까지 범위 고르기')); - await tester.pumpAndSettle(); + await calm(tester); expect(find.textContaining('3개 표시함'), findsOneWidget, reason: 'b.srt · c.mkv · doc.txt'); }); @@ -548,14 +548,14 @@ void main() { await settle(tester, () => find.textContaining('2개 표시함').evaluate().isNotEmpty); expect(find.textContaining('2개 표시함'), findsOneWidget); await act(tester, () => tester.tap(find.text('삭제').first)); - await tester.pumpAndSettle(); + await calm(tester); // 지울 항목 이름이 확인 창에 보인다. 65: Windows 는 기본이 휴지통 (시험에서는 영구 삭제를 골라 휴지통을 어지럽히지 않음) expect(find.textContaining('2개 항목을 휴지통으로 보낼까요?'), findsOneWidget); expect(find.widgetWithText(FilledButton, '휴지통으로'), findsOneWidget); expect(find.textContaining('· sub'), findsOneWidget); expect(find.textContaining('· doc.txt'), findsOneWidget); await tester.tap(find.text('휴지통을 거치지 않고 영구 삭제 (Shift+Delete)')); - await tester.pumpAndSettle(); + await calm(tester); expect(find.textContaining('2개 항목을 지울까요? 되돌릴 수 없습니다.'), findsOneWidget); await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '영구 삭제'))); await settle(tester, () => find.text('doc.txt').evaluate().isEmpty); @@ -564,14 +564,14 @@ void main() { // 새 폴더 → 이름 변경 (지운 뒤 창들이 목록을 다시 읽는 동안은 진행 막대가 돌아 pumpAndSettle 이 끝나지 않는다 - 무거울 때 흔들림) await idle(tester); await act(tester, () => tester.tap(find.text('새 폴더').first)); - await tester.pumpAndSettle(); + await calm(tester); await act(tester, () => tester.enterText(find.byType(TextField), 'films')); await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '확인'))); await settle(tester, () => find.text('films').evaluate().isNotEmpty); expect(Directory(p.join(left, 'films')).existsSync(), isTrue); await idle(tester); await act(tester, () => tester.tap(find.text('이름 변경').first)); - await tester.pumpAndSettle(); + await calm(tester); await act(tester, () => tester.enterText(find.byType(TextField), 'movies')); await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '확인'))); await settle(tester, () => find.text('movies').evaluate().isNotEmpty); @@ -581,33 +581,33 @@ void main() { testWidgets('창 배치: 한 창 · 버튼 줄 숨김, 버튼 구성: 버튼 빼기 → 설정에 저장', (tester) async { await open(tester); await tester.tap(find.byTooltip('창 배치 · 버튼 구성')); - await tester.pumpAndSettle(); + await calm(tester); await tester.tap(find.text('창 배치').last); - await tester.pumpAndSettle(); + await calm(tester); await tester.tap(find.widgetWithText(ChoiceChip, '한 창')); await tester.pump(); await tester.tap(find.widgetWithText(ChoiceChip, '숨김')); await tester.pump(); await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '확인'))); - await tester.pumpAndSettle(); + await calm(tester); expect([c.settings.explorerLayout, c.settings.explorerToolbar], ['single', 'hidden']); expect(find.text('상위 폴더'), findsNothing); // 버튼 줄 숨김 expect(find.text('doc.txt'), findsOneWidget); // 한 창 (왼쪽만) // ⋮ 메뉴에서 버튼 구성: "찾기" 빼기 await c.updateSettings((s) => s.explorerToolbar = 'middle'); - await tester.pumpAndSettle(); + await calm(tester); await tester.tap(find.byTooltip('창 배치 · 버튼 구성')); - await tester.pumpAndSettle(); + await calm(tester); await tester.tap(find.text('버튼 구성').last); - await tester.pumpAndSettle(); + await calm(tester); // 처음 값 순서 (자주 쓰는 것부터) 에서 뒤쪽이라 목록을 내려 보이게 한 뒤 await tester.scrollUntilVisible(find.widgetWithText(CheckboxListTile, 'MKV 목록에 추가'), 80, scrollable: find.descendant(of: find.byType(AlertDialog), matching: find.byType(Scrollable)).first); await tester.tap(find.widgetWithText(CheckboxListTile, 'MKV 목록에 추가')); await tester.pump(); await tester.tap(find.widgetWithText(FilledButton, '확인')); - await tester.pumpAndSettle(); + await calm(tester); expect(c.settings.explorerButtons, isNot(contains('addMkv'))); expect(c.settings.explorerButtons, contains('copy')); expect(find.text('MKV 목록에 추가'), findsNothing); @@ -673,7 +673,7 @@ void main() { expect(find.text('$rs/ → $ls/'), findsOneWidget); expect(find.textContaining('-avPog -u'), findsNWidgets(2)); await tester.tap(find.text('취소')); - await tester.pumpAndSettle(); + await calm(tester); expect(c.settings.copyTasks, isEmpty); expect(find.text('원본 파일 지우기 (--remove-source-files)'), findsNothing); // 양쪽에는 없음 // → 는 한 방향 · -u 없이 · 원본 파일 지우기를 고를 수 있다 (고르면 원본 폴더 남김 / 지움) @@ -684,13 +684,13 @@ void main() { expect(find.textContaining('-u'), findsNothing); expect(find.text('빈 폴더 지움 · 원본 폴더는 남김'), findsNothing); await tester.tap(find.text('원본 파일 지우기 (--remove-source-files)')); - await tester.pumpAndSettle(); + await calm(tester); expect(find.textContaining('-avPog --remove-source-files'), findsOneWidget); expect(find.text('빈 폴더 지움 · 원본 폴더는 남김'), findsOneWidget); expect(find.text('빈 폴더 지움 · 원본 폴더도 지움'), findsOneWidget); expect(find.widgetWithText(FilledButton, '이동'), findsOneWidget); await tester.tap(find.text('취소')); - await tester.pumpAndSettle(); + await calm(tester); // 고른 폴더는 기억 (다음에 열 때 그 폴더부터) expect(c.settings.rsyncPaths, [p.join(left, 'sub'), p.join(right, 'rsub')]); @@ -700,7 +700,7 @@ void main() { await act(tester, () => tester.tap(find.text('rsub'))); await settle(tester, () => false, rounds: 5); // 다시 골라 지금 폴더로 await act(tester, () => tester.tap(find.text('새 폴더'))); - await tester.pumpAndSettle(); + await calm(tester); await tester.enterText(find.byType(TextField), '새로만든'); await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '확인'))); // 만든 폴더가 보이고 골라질 때까지 (전체 시험 중 바쁠 때는 고르기가 조금 늦다) @@ -743,10 +743,21 @@ Future doubleTap(WidgetTester tester, Finder f) async { await tester.pump(); } -/// 창들이 목록을 다시 읽는 중 (맨 위 진행 막대) 이 끝날 때까지 실제 시간으로 기다린다. -/// 그사이 pumpAndSettle 을 하면 막대가 계속 움직여, 디스크 읽기가 늦을 때 (전체 시험이 무거울 때) 시간 초과가 난다 -Future idle(WidgetTester tester) => - settle(tester, () => find.byType(LinearProgressIndicator).evaluate().isEmpty && find.byType(CircularProgressIndicator).evaluate().isEmpty); +/// 창들이 목록을 다시 읽는 중 (맨 위 진행 막대 · 도는 표시) 이 끝날 때까지 실제 시간으로 기다린다. +/// 그사이 pumpAndSettle 을 하면 막대가 계속 움직여, 디스크 읽기가 늦을 때 (전체 시험이 무거울 때) 시간 초과가 난다. +/// 값이 있는 막대 (디스크 용량 · 진행률) 는 움직이지 않으므로 세지 않는다 +Future idle(WidgetTester tester) => settle( + tester, + () => find + .byWidgetPredicate((w) => w is ProgressIndicator && w.value == null) + .evaluate() + .isEmpty); + +/// 탐색기 시험의 pumpAndSettle: 목록 읽기가 끝난 뒤에 (경로를 바꾸거나 다시 읽은 뒤 무거울 때 흔들리던 것을 한 번에) +Future calm(WidgetTester tester) async { + await idle(tester); + await tester.pumpAndSettle(); +} /// 실제 파일 작업 (비동기 IO) 이 끝나기를 기다리며 화면을 갱신 // 끝나면 바로 나온다 - 넉넉한 횟수는 전체 시험 중 PC 가 바쁠 때 (실제 파일 입출력이 느려짐) 흔들리지 않게 diff --git a/test/explorer_webdav_test.dart b/test/explorer_webdav_test.dart index e713c43..4ba9f5d 100644 --- a/test/explorer_webdav_test.dart +++ b/test/explorer_webdav_test.dart @@ -13,7 +13,7 @@ import 'package:jj_mkvmaker/services/platform_services.dart'; import 'package:jj_mkvmaker/ui/explorer_page.dart'; import 'package:path/path.dart' as p; -import 'explorer_test.dart' show act, doubleTap, settle; +import 'explorer_test.dart' show act, doubleTap, settle, calm; import 'support/dav_server.dart'; class _Shell extends NoopShell { @@ -76,7 +76,7 @@ void main() { // 서버가 없으면 위쪽 저장 장치 옆에 "WebDAV" 탭 (창마다) expect(find.text('WebDAV'), findsNWidgets(2)); await act(tester, () => tester.tap(find.text('WebDAV').last)); - await tester.pumpAndSettle(); + await calm(tester); expect(find.text('WebDAV 서버 추가'), findsOneWidget); await tester.enterText(find.widgetWithText(TextField, '이름 (탭에 보임, 비우면 주소)'), '집 NAS'); await tester.enterText(find.widgetWithText(TextField, '주소'), server.url); @@ -108,7 +108,7 @@ void main() { await act(tester, () => tester.tap(find.text('doc.txt'))); await settle(tester, () => false, rounds: 15); await act(tester, () => tester.tap(find.text('복사').first)); - await tester.pumpAndSettle(); + await calm(tester); expect(find.textContaining('☁ 집 NAS/원격'), findsOneWidget); await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '복사'))); final up = File(p.join(remote.path, '원격', 'doc.txt'));