255 lines
15 KiB
Dart
255 lines
15 KiB
Dart
import 'dart:io';
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter_test/flutter_test.dart';
|
||
import 'package:jj_mkvmaker/app/settings.dart';
|
||
import 'package:jj_mkvmaker/ui/explorer_page.dart';
|
||
import 'package:jj_mkvmaker/ui/file_tools.dart' show HexViewerPage;
|
||
import 'package:image/image.dart' as img;
|
||
import 'package:jj_mkvmaker/ui/reader_page.dart' show ReaderPage;
|
||
import 'package:path/path.dart' as p;
|
||
import 'support/explorer_clock_keys.dart';
|
||
import 'support/explorer_harness.dart';
|
||
|
||
/// E5e: 예전 explorer_test.dart 에서 나눈 탐색기 화면 시험 - 두 창 누르기 · 한 창 갈 곳 · 슬라이드쇼 · 해시 · 경로 막대 · 길게 = 짝 · 다시 누르면 접기 · 트리 맨 위 항목 · 밀어서 창 바꾸기
|
||
void main() {
|
||
useExplorerFixture();
|
||
|
||
testWidgets('두 창: 폴더 한 번 = 펼치기 · 파일 한 번 = 선택 · 두 번 = 실행 · 길게 = 메뉴 · 다른 창으로 복사', (tester) async {
|
||
await open(tester);
|
||
// 두 창 + 가운데 버튼 줄. 선택 표시는 선택 모드에서만 (규칙 4, 10/22 - 설정으로 늘 보이게 할 수 있음)
|
||
expect(find.text('상위 폴더'), findsOneWidget);
|
||
expect(find.text('doc.txt'), findsOneWidget);
|
||
expect(find.byTooltip('표시 (여러 개 고르기)'), findsNothing);
|
||
// 모니터링 · rsync 는 Rsync 화면에만
|
||
expect(find.text('모니터링'), findsNothing);
|
||
expect(find.text('좌 → 우'), findsNothing);
|
||
|
||
// 폴더를 한 번 누르면 바로 펼친다. 규칙 3 (10/22): 조금 뒤 다시 누르면 접고, 또 누르면 펼친다
|
||
// (예전 189 "다시 눌러도 접지 않음" 은 규칙 3 으로 바뀜. 두 번 누르기의 두 번째는 접지 않음 - 규칙 3 시험)
|
||
await act(tester, () => tester.tap(find.text('sub')));
|
||
await settle(tester, () => find.text('inner.txt').evaluate().isNotEmpty);
|
||
expect(find.text('inner.txt'), findsOneWidget);
|
||
testNow = testNow.add(const Duration(seconds: 1)); // 두 번 누르기 시간이 지난 뒤
|
||
await act(tester, () => tester.tap(find.text('sub')));
|
||
await settle(tester, () => find.text('inner.txt').evaluate().isEmpty);
|
||
expect(find.text('inner.txt'), findsNothing, reason: '규칙 3: 다시 누르면 접힘');
|
||
testNow = testNow.add(const Duration(seconds: 1));
|
||
await act(tester, () => tester.tap(find.text('sub')));
|
||
await settle(tester, () => find.text('inner.txt').evaluate().isNotEmpty);
|
||
expect(find.text('inner.txt'), findsOneWidget, reason: '또 누르면 펼침');
|
||
await act(tester, () => tester.tap(find.descendant(
|
||
of: find.ancestor(of: find.text('sub'), matching: find.byType(InkWell)).first, matching: find.byIcon(Icons.expand_more))));
|
||
await settle(tester, () => find.text('inner.txt').evaluate().isEmpty);
|
||
expect(find.text('inner.txt'), findsNothing, reason: '화살표로 접는다');
|
||
// › 화살표로 펼친다
|
||
await act(tester, () => tester.tap(chevronOf('sub')));
|
||
await settle(tester, () => find.text('inner.txt').evaluate().isNotEmpty);
|
||
expect(find.text('inner.txt'), findsOneWidget);
|
||
|
||
// 파일을 한 번 누르면 고르기만 (실행 안 함)
|
||
await act(tester, () => tester.tap(find.text('doc.txt')));
|
||
await settle(tester, () => false, rounds: 15);
|
||
expect(shell.opened, isEmpty);
|
||
// 두 번 누르면 기본 앱으로 실행
|
||
await doubleTap(tester, find.text('doc.txt'));
|
||
await settle(tester, () => shell.opened.isNotEmpty);
|
||
expect(shell.opened.last, (p.join(left, 'doc.txt'), false));
|
||
|
||
// 길게 누르기 → 메뉴 → 다른 앱으로 열기 (고르기 창)
|
||
await tester.longPress(find.text('doc.txt'));
|
||
await calm(tester);
|
||
// 77: 복사 · 이동 · 이름 변경 · 삭제는 메뉴 위 아이콘 줄 (두 창이면 복사는 다른 창으로)
|
||
expect(find.byTooltip('다른 창으로 복사'), findsOneWidget);
|
||
expect(find.text('삭제'), findsWidgets);
|
||
await act(tester, () => tester.tap(find.text('다른 앱으로 열기')));
|
||
await settle(tester, () => shell.opened.length == 2);
|
||
expect(shell.opened.last, (p.join(left, 'doc.txt'), true));
|
||
|
||
// doc.txt 를 고른 채 [복사] → 확인 → 오른쪽 창 폴더에 생긴다
|
||
await act(tester, () => tester.tap(find.text('doc.txt')));
|
||
await settle(tester, () => false, rounds: 15);
|
||
await act(tester, () => tester.tap(find.text('복사').first));
|
||
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');
|
||
expect(File(p.join(left, 'doc.txt')).existsSync(), isTrue);
|
||
});
|
||
|
||
testWidgets('X-plore a: 한 창 + "숨은 다른 창으로" - 위에 갈 곳 줄 · [복사] 는 그 폴더로 · [지금 폴더로] 로 바꾸기 (처음 값은 담아 두기)', (tester) async {
|
||
expect(AppSettings().explorerOnePaneCopy, 'clip', reason: '처음 값은 지금 그대로');
|
||
c.settings
|
||
..explorerLayout = 'single'
|
||
..explorerOnePaneCopy = 'other';
|
||
await open(tester);
|
||
expect(find.textContaining('갈 곳: '), findsOneWidget);
|
||
expect(find.textContaining('갈 곳: right'), findsOneWidget, reason: '숨은 창의 폴더 이름 (전체 경로는 풍선 도움말)');
|
||
await act(tester, () => tester.tap(find.text('doc.txt')));
|
||
await settle(tester, () => false, rounds: 15);
|
||
await act(tester, () => tester.tap(find.text('복사').first));
|
||
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');
|
||
// [지금 폴더로] → 갈 곳이 지금 보는 폴더 (left)
|
||
await act(tester, () => tester.tap(find.text('지금 폴더로')));
|
||
await settle(tester, () => c.settings.explorerPaths[1] == left, rounds: 50);
|
||
expect(c.settings.explorerPaths[1], left);
|
||
ScaffoldMessenger.of(tester.element(find.byType(ExplorerPage))).clearSnackBars();
|
||
await calm(tester);
|
||
await tester.pump(const Duration(seconds: 2));
|
||
});
|
||
|
||
testWidgets('X-plore 맞추기 (관리자 지시 10/20): 폴더 [사진 슬라이드쇼] - 그 폴더 그림을 [계속 보기] 로 열기 · 그림이 없으면 알림', (tester) async {
|
||
for (final n in ['b.png', 'a.png']) {
|
||
File(p.join(left, 'sub', n)).writeAsBytesSync(img.encodePng(img.Image(width: 20, height: 30)));
|
||
}
|
||
Directory(p.join(left, 'empty')).createSync();
|
||
await open(tester);
|
||
await tester.longPress(find.text('sub'));
|
||
await calm(tester);
|
||
await act(tester, () => tester.tap(find.text('사진 슬라이드쇼')));
|
||
await settle(tester, () => find.byType(ReaderPage).evaluate().isNotEmpty);
|
||
expect(find.text('1 / 2'), findsOneWidget);
|
||
expect(find.text('a.png'), findsOneWidget, reason: '지금 정렬 순서 (이름) 의 첫 그림부터');
|
||
expect(find.byTooltip('계속 보기 멈추기'), findsOneWidget, reason: '[계속 보기] 를 켠 채 시작');
|
||
await act(tester, () async => tester.state<NavigatorState>(find.byType(Navigator).first).pop());
|
||
await calm(tester);
|
||
// 그림이 없는 폴더
|
||
await tester.longPress(find.text('empty'));
|
||
await calm(tester);
|
||
await act(tester, () => tester.tap(find.text('사진 슬라이드쇼')));
|
||
await settle(tester, () => find.text('이 폴더에는 그림이 없습니다').evaluate().isNotEmpty);
|
||
expect(find.byType(ReaderPage), findsNothing);
|
||
ScaffoldMessenger.of(tester.element(find.byType(ExplorerPage))).clearSnackBars();
|
||
await calm(tester);
|
||
});
|
||
|
||
testWidgets('X-plore P3: 길게 누르기 [해시] (MD5 · SHA-256) · 폴더 [폴더 속성] · 빠른 보기 [16진]', (tester) async {
|
||
await open(tester);
|
||
// 해시: doc.txt = "hello"
|
||
await tester.longPress(find.text('doc.txt'));
|
||
await calm(tester);
|
||
await act(tester, () => tester.tap(find.text('해시 (MD5 · SHA-256)')));
|
||
await settle(tester, () => find.textContaining('2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824').evaluate().isNotEmpty);
|
||
expect(find.textContaining('5d41402abc4b2a76b9719d911017c592'), findsOneWidget, reason: 'MD5');
|
||
await act(tester, () => tester.tap(find.text('닫기').last));
|
||
await calm(tester);
|
||
// 폴더 속성: sub 안에 inner.txt 하나
|
||
await tester.longPress(find.text('sub'));
|
||
await calm(tester);
|
||
await act(tester, () => tester.tap(find.text('폴더 속성')));
|
||
await settle(tester, () => find.text('파일 1개 · 폴더 0개').evaluate().isNotEmpty);
|
||
await act(tester, () => tester.tap(find.text('닫기').last));
|
||
await calm(tester);
|
||
// 빠른 보기 → [16진]
|
||
await tester.longPress(find.text('doc.txt'));
|
||
await calm(tester);
|
||
await act(tester, () => tester.tap(find.text('미리 보기')));
|
||
await settle(tester, () => find.text('16진').evaluate().isNotEmpty);
|
||
await act(tester, () => tester.tap(find.text('16진')));
|
||
await settle(tester, () => find.byType(HexViewerPage).evaluate().isNotEmpty);
|
||
expect(find.byType(HexViewerPage), findsOneWidget);
|
||
// 16진 보기가 앞부분을 다 읽어 파일을 놓을 때까지 (실제 시간) 기다린 뒤 닫는다 - 열린 파일이 남으면 시험 폴더를 못 지운다
|
||
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 300)));
|
||
await tester.pump();
|
||
await act(tester, () => tester.pageBack());
|
||
await calm(tester);
|
||
});
|
||
|
||
testWidgets('X-plore 경로 막대: 위 막대의 경로를 누르면 지금 폴더 · 위 폴더들 (창의 맨 위까지) - 고르면 그리로', (tester) async {
|
||
await open(tester);
|
||
await act(tester, () => tester.tap(find.text('sub')));
|
||
await settle(tester, () => find.text('inner.txt').evaluate().isNotEmpty);
|
||
await act(tester, () => tester.tap(find.byTooltip('위 폴더로 가기')));
|
||
await calm(tester);
|
||
final items = find.byType(PopupMenuItem<String>);
|
||
final n = items.evaluate().length;
|
||
expect(n, greaterThanOrEqualTo(2), reason: 'sub · 그 위 폴더들 (창의 맨 위까지)');
|
||
expect(tester.widget<PopupMenuItem<String>>(items.first).enabled, isFalse, reason: '지금 폴더는 고를 수 없음');
|
||
expect(tester.widget<PopupMenuItem<String>>(items.at(1)).value, left, reason: '바로 위 = left');
|
||
await act(tester, () => tester.tap(items.at(1)));
|
||
await settle(tester, () => false, rounds: 15);
|
||
// 위로 갔으면 다시 열 때 하나 적다 (지금 폴더 = left)
|
||
await act(tester, () => tester.tap(find.byTooltip('위 폴더로 가기')));
|
||
await calm(tester);
|
||
expect(find.byType(PopupMenuItem<String>), findsNWidgets(n - 1));
|
||
});
|
||
|
||
testWidgets('X-plore b: 가운데 버튼 길게 누르기 = 짝 동작 - [복사] 길게 = 이동 창 · [삭제] 길게 = 영구 삭제 창', (tester) async {
|
||
await open(tester);
|
||
await act(tester, () => tester.tap(find.text('doc.txt')));
|
||
await settle(tester, () => false, rounds: 15);
|
||
await tester.longPress(find.text('복사').first);
|
||
await calm(tester);
|
||
expect(find.widgetWithText(FilledButton, '이동'), findsOneWidget, reason: '복사 길게 = 이동');
|
||
await tester.tap(find.text('취소').last);
|
||
await calm(tester);
|
||
await tester.longPress(find.text('삭제').first);
|
||
await calm(tester);
|
||
expect(find.widgetWithText(FilledButton, '영구 삭제'), findsOneWidget, reason: '삭제 길게 = 영구 삭제로 묻기');
|
||
await tester.tap(find.text('취소').last);
|
||
await calm(tester);
|
||
expect(File(p.join(left, 'doc.txt')).existsSync(), isTrue);
|
||
});
|
||
|
||
testWidgets('규칙 3: 펼친 폴더를 다시 누르면 접힘 · 두 번 누르기의 두 번째는 접지 않음 (처음 값 켬)', (tester) async {
|
||
expect(AppSettings().explorerRetapCollapse, isTrue, reason: '규칙 3 (10/22)');
|
||
await open(tester);
|
||
await act(tester, () => tester.tap(find.text('sub')));
|
||
await settle(tester, () => find.text('inner.txt').evaluate().isNotEmpty);
|
||
expect(find.text('inner.txt'), findsOneWidget);
|
||
// 두 번 누르기 시간 안의 두 번째 누르기 - 접지 않음
|
||
testNow = testNow.add(const Duration(milliseconds: 100));
|
||
await act(tester, () => tester.tap(find.text('sub')));
|
||
await settle(tester, () => false, rounds: 15);
|
||
expect(find.text('inner.txt'), findsOneWidget, reason: '두 번 누르기의 두 번째');
|
||
// 시간이 지난 뒤 다시 누름 - 접힘
|
||
testNow = testNow.add(const Duration(seconds: 1));
|
||
await act(tester, () => tester.tap(find.text('sub')));
|
||
await settle(tester, () => find.text('inner.txt').evaluate().isEmpty);
|
||
expect(find.text('inner.txt'), findsNothing);
|
||
});
|
||
|
||
testWidgets('X-plore c: X-plore 스타일 트리 맨 위에 다른 저장 장치 · 즐겨찾기 - 누르면 그곳으로, 즐겨찾기는 펼침', (tester) async {
|
||
device.volumes = () => [(left, 'L:'), (right, 'R:')];
|
||
c.settings
|
||
..explorerStyle = 'xplore'
|
||
..folderBookmarks = [FolderBookmark(name: '내 즐겨찾기', path: p.join(left, 'sub'))];
|
||
await open(tester);
|
||
final otherRoot = find.byKey(ValueKey<String>('k1:$right'));
|
||
expect(otherRoot, findsWidgets, reason: '왼쪽 창 트리에 R: (다른 저장 장치) 줄');
|
||
final favs = find.byKey(const ValueKey<String>('k2:\u0000favorites'));
|
||
expect(favs, findsWidgets);
|
||
expect(find.text('내 즐겨찾기'), findsNothing, reason: '접힌 즐겨찾기');
|
||
await act(tester, () => tester.tap(favs.first));
|
||
await calm(tester);
|
||
expect(find.text('내 즐겨찾기'), findsOneWidget);
|
||
await act(tester, () => tester.tap(find.text('내 즐겨찾기')));
|
||
await settle(tester, () => c.settings.explorerPaths.first == p.join(left, 'sub'), rounds: 80);
|
||
expect(c.settings.explorerPaths.first, p.join(left, 'sub'));
|
||
await act(tester, () => tester.tap(find.byKey(ValueKey<String>('k1:$right')).first));
|
||
await settle(tester, () => c.settings.explorerPaths.first == right, rounds: 80);
|
||
expect(c.settings.explorerPaths.first, right, reason: '다른 저장 장치 줄 = 그 저장 장치로');
|
||
// 다른 스타일은 그대로 (맨 위 항목 없음)
|
||
await tester.runAsync(() => c.updateSettings((x) => x.explorerStyle = 'windows'));
|
||
await calm(tester);
|
||
expect(find.byKey(const ValueKey<String>('k2:\u0000favorites')), findsNothing);
|
||
});
|
||
|
||
testWidgets('X-plore ⚖: 탐색기 안에서 옆으로 밀기 = 창 바꾸기 (한 창이면 숨은 창과 바꿈)', (tester) async {
|
||
expect(AppSettings().explorerSwipePanes, isFalse, reason: '처음 값은 끔 - 화면 가운데를 밀면 화면 이동 (10/8 사용자 결정)');
|
||
c.settings.explorerSwipePanes = true;
|
||
c.settings.explorerLayout = 'single';
|
||
await open(tester);
|
||
expect(c.settings.explorerPaths, [left, right]);
|
||
// 화면 가운데 높이에서 (위 · 아래 가장자리 1/5 는 밀기로 보지 않는다 - SwipeNav)
|
||
await tester.flingFrom(tester.getCenter(find.byType(ExplorerPage)), const Offset(-400, 0), 1500);
|
||
await settle(tester, () => c.settings.explorerPaths.first == right, rounds: 60);
|
||
expect(c.settings.explorerPaths, [right, left], reason: '숨은 창이 보이는 창으로');
|
||
ScaffoldMessenger.of(tester.element(find.byType(ExplorerPage))).clearSnackBars();
|
||
await tester.pump(const Duration(seconds: 1));
|
||
});
|
||
}
|