148 lines
6.6 KiB
Dart
148 lines
6.6 KiB
Dart
import 'dart:io';
|
||
|
||
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/app_shell.dart';
|
||
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_clock_keys.dart';
|
||
import 'fake_recycle_bin.dart';
|
||
import 'test_explorer_device.dart';
|
||
|
||
/// E5e: 탐색기 화면 시험의 공용 준비 (예전 explorer_test.dart 의 setUp · tearDown · open · 도우미).
|
||
/// [useExplorerFixture] 를 main 맨 앞에서 부르면 시험마다 시험 폴더 (left: doc.txt · sub/inner.txt, right: 빈 폴더) 와
|
||
/// 앱 (다른 앱으로 열기를 적기만 하는 셸, 저장소 없음) 을 새로 만들고 끝나면 지운다. 휴지통은 가짜 (사용자 휴지통에 닿지 않게)
|
||
|
||
/// 다른 앱으로 열기를 기록만 하는 셸 (실제로 프로그램을 띄우지 않게)
|
||
class ExplorerShell extends NoopShell {
|
||
final opened = <(String, bool)>[];
|
||
final scanned = <List<String>>[];
|
||
@override
|
||
Future<void> mediaScan(List<String> paths) async => scanned.add(paths);
|
||
@override
|
||
Future<bool> openWith(String path, {bool choose = false}) async {
|
||
opened.add((path, choose));
|
||
return true;
|
||
}
|
||
}
|
||
|
||
Directory? _tmp;
|
||
String? _left, _right;
|
||
ExplorerShell? _shell;
|
||
TestExplorerDevice? _device;
|
||
AppController? _c;
|
||
|
||
/// 이 시험의 시험 폴더 · 왼쪽 · 오른쪽 창 폴더 · 셸 · 앱
|
||
Directory get tmp => _tmp!;
|
||
String get left => _left!;
|
||
String get right => _right!;
|
||
ExplorerShell get shell => _shell!;
|
||
AppController get c => _c!;
|
||
|
||
/// 이 시험의 기기 (저장 장치 · 드라이브 · Android 갈래) - [open] 이 화면에 넘긴다
|
||
TestExplorerDevice get device => _device!;
|
||
|
||
void useExplorerFixture() {
|
||
setUp(() {
|
||
_tmp = Directory.systemTemp.createTempSync('jj_explorer_');
|
||
_left = p.join(tmp.path, 'left');
|
||
_right = p.join(tmp.path, 'right');
|
||
File(p.join(left, 'doc.txt'))
|
||
..createSync(recursive: true)
|
||
..writeAsStringSync('hello');
|
||
File(p.join(left, 'sub', 'inner.txt'))
|
||
..createSync(recursive: true)
|
||
..writeAsStringSync('inner');
|
||
Directory(right).createSync();
|
||
_shell = ExplorerShell();
|
||
_device = TestExplorerDevice();
|
||
// 휴지통: 기본은 가짜 (이 시험들은 영구 삭제를 고르지만 혹시라도 사용자 휴지통에 닿지 않게)
|
||
useTestRecycleBin(device);
|
||
_c = AppController(PlatformServices(
|
||
mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService(), shell: shell));
|
||
c.settings.explorer.paths = [left, right];
|
||
c.settings.explorer.style = 'xplore'; // E6: 이 시험들은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander
|
||
});
|
||
// Windows: 방금 닫은 파일 (65 의 잠금 시험) 이나 색인 · 백신이 잠깐 잡고 있으면 지우기가 실패한다 - 될 때까지 기다린다
|
||
tearDown(() async {
|
||
for (var i = 0;; i++) {
|
||
try {
|
||
if (tmp.existsSync()) tmp.deleteSync(recursive: true);
|
||
return;
|
||
} on FileSystemException {
|
||
if (i >= 50) rethrow;
|
||
await Future<void>.delayed(const Duration(milliseconds: 100));
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/// 탐색기를 1600 × 900 으로 열고 왼쪽 창 목록이 보일 때까지 기다린다 (지금 시각은 정한 시각)
|
||
Future<void> open(WidgetTester tester) async {
|
||
tester.view.physicalSize = const Size(1600, 900);
|
||
tester.view.devicePixelRatio = 1;
|
||
addTearDown(tester.view.reset);
|
||
await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(c: c, clock: () => testNow, device: device))));
|
||
await settle(tester, () => find.text('doc.txt').evaluate().isNotEmpty);
|
||
}
|
||
|
||
/// [name] 줄의 "표시" 동그라미 버튼
|
||
Finder markOf(String name) => find.descendant(
|
||
of: find.ancestor(of: find.text(name), matching: find.byType(Material)).first,
|
||
matching: find.byTooltip('표시 (여러 개 고르기)'),
|
||
);
|
||
|
||
/// 누르기 등을 실제 비동기 구역에서 (그 때 시작한 파일 작업이 테스트의 가짜 시간에 묶이지 않게)
|
||
Future<void> act(WidgetTester tester, Future<void> Function() f) async {
|
||
await tester.runAsync(f);
|
||
await tester.pump();
|
||
}
|
||
|
||
/// [name] 트리 줄의 › (펼치기) 화살표
|
||
Finder chevronOf(String name) => find.descendant(
|
||
of: find.ancestor(of: find.text(name), matching: find.byType(InkWell)).first,
|
||
matching: find.byIcon(Icons.chevron_right),
|
||
);
|
||
|
||
/// 두 번 누르기 (실제 비동기 구역에서: 그때 시작한 파일 작업이 테스트의 가짜 시간에 묶이지 않게)
|
||
Future<void> doubleTap(WidgetTester tester, Finder f) async {
|
||
// 흔들림: 두 누름 사이를 실제 시간 (80ms) 으로 쉬면 무거울 때 두 번 누르기 판정 (300ms) 을 넘겨 한 번씩 두 번으로 됐다 -
|
||
// 시험의 가짜 시간으로 50ms. 그 뒤 시작한 파일 작업은 settle (실제 시간) 이 기다린다
|
||
await tester.tap(f);
|
||
await tester.pump(const Duration(milliseconds: 50));
|
||
await tester.tap(f);
|
||
await tester.pump();
|
||
}
|
||
|
||
/// 창들이 목록을 다시 읽는 중 (맨 위 진행 막대 · 도는 표시) 이 끝날 때까지 실제 시간으로 기다린다.
|
||
/// 그사이 pumpAndSettle 을 하면 막대가 계속 움직여, 디스크 읽기가 늦을 때 (전체 시험이 무거울 때) 시간 초과가 난다.
|
||
/// 값이 있는 막대 (디스크 용량 · 진행률) 는 움직이지 않으므로 세지 않는다
|
||
Future<void> idle(WidgetTester tester) => settle(
|
||
tester,
|
||
() => find
|
||
.byWidgetPredicate((w) => w is ProgressIndicator && w.value == null)
|
||
.evaluate()
|
||
.isEmpty);
|
||
|
||
/// 탐색기 시험의 pumpAndSettle: 목록 읽기가 끝난 뒤에 (경로를 바꾸거나 다시 읽은 뒤 무거울 때 흔들리던 것을 한 번에)
|
||
Future<void> calm(WidgetTester tester) async {
|
||
await idle(tester);
|
||
await tester.pumpAndSettle();
|
||
}
|
||
|
||
/// 실제 파일 작업 (비동기 IO) 이 끝나기를 기다리며 화면을 갱신
|
||
// 끝나면 바로 나온다 - 넉넉한 횟수는 전체 시험 중 PC 가 바쁠 때 (실제 파일 입출력이 느려짐) 흔들리지 않게
|
||
Future<void> settle(WidgetTester tester, bool Function() done, {int rounds = 300}) async {
|
||
for (var i = 0; i < rounds; i++) {
|
||
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 30)));
|
||
await tester.pump();
|
||
if (done()) break;
|
||
}
|
||
await tester.pump(const Duration(milliseconds: 300));
|
||
}
|