JJ_MKVMaker/test/explorer_panes_test.dart

255 lines
15 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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));
});
}