JJ_MKVMaker/test/support/explorer_harness.dart

148 lines
6.6 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/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));
}