173 lines
9.4 KiB
Dart
173 lines
9.4 KiB
Dart
import 'dart:io';
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter/services.dart';
|
||
import 'package:flutter_test/flutter_test.dart';
|
||
import 'package:jj_mkvmaker/app/app_controller.dart';
|
||
import 'package:jj_mkvmaker/app/download_manager.dart';
|
||
import 'package:jj_mkvmaker/app/settings.dart';
|
||
import 'package:jj_mkvmaker/core/models.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/platform_services.dart';
|
||
import 'package:archive/archive_io.dart';
|
||
import 'package:jj_mkvmaker/core/file_ops.dart';
|
||
import 'package:jj_mkvmaker/ui/archive_ui.dart';
|
||
import 'package:jj_mkvmaker/ui/batch_rename_dialog.dart';
|
||
import 'package:jj_mkvmaker/ui/downloads_page.dart';
|
||
import 'package:jj_mkvmaker/ui/explorer_drag.dart';
|
||
import 'package:jj_mkvmaker/ui/explorer_page.dart';
|
||
import 'package:jj_mkvmaker/ui/explorer_search.dart';
|
||
import 'package:jj_mkvmaker/ui/home_page.dart';
|
||
import 'package:jj_mkvmaker/ui/monitor_page.dart';
|
||
import 'package:jj_mkvmaker/ui/quick_look.dart';
|
||
import 'package:jj_mkvmaker/ui/settings_page.dart';
|
||
import 'package:jj_mkvmaker/ui/storage_page.dart';
|
||
import 'package:path/path.dart' as p;
|
||
|
||
/// 253: 글자를 크게 (기기 설정 글꼴 크기 · 접근성) 해도 화면이 넘치지 않는다 - 줄 높이가 고정이던 목록이 넘쳤다
|
||
void main() {
|
||
late Directory tmp;
|
||
setUp(() => tmp = Directory.systemTemp.createTempSync('jj_big_'));
|
||
tearDown(() => tmp.deleteSync(recursive: true));
|
||
|
||
final pages = <String, Widget Function(AppController, DownloadManager)>{
|
||
'MKV': (c, d) => HomePage(c: c),
|
||
'파일 탐색기': (c, d) => ExplorerPage(c: c),
|
||
'Rsync': (c, d) => ExplorerPage(c: c, rsync: true),
|
||
'모니터링': (c, d) => MonitorPage(c: c),
|
||
'다운로드': (c, d) => DownloadsPage(d: d),
|
||
'환경 설정': (c, d) => SettingsPage(c: c),
|
||
};
|
||
for (final MapEntry(key: name, value: page) in pages.entries) {
|
||
for (final size in [const Size(412, 900), const Size(900, 412), const Size(1280, 800)]) {
|
||
testWidgets('253: $name (${size.width.round()}×${size.height.round()}, 글자 1.6배) 넘치지 않는다', (tester) async {
|
||
tester.view.physicalSize = size;
|
||
tester.view.devicePixelRatio = 1;
|
||
addTearDown(tester.view.reset);
|
||
const pp = MethodChannel('plugins.flutter.io/path_provider');
|
||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(pp, (_) async => tmp.path);
|
||
addTearDown(() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(pp, null));
|
||
File(p.join(tmp.path, '아주 긴 이름의 동영상 파일 - 시험용 2026.mp4')).writeAsStringSync('v');
|
||
Directory(p.join(tmp.path, 'sub')).createSync();
|
||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||
c.settings
|
||
..explorer.paths = [tmp.path, tmp.path]
|
||
..copy.rsyncPaths = [tmp.path, tmp.path]
|
||
..copy.tasks = [CopyTask(id: '1', sources: [tmp.path], dest: p.join(tmp.path, 'sub'), method: 'rsync', options: '-av')]
|
||
..copy.liveSyncPairs = [LiveSyncPair(tmp.path, p.join(tmp.path, 'x'))];
|
||
c.videos.add(VideoItem(p.join(tmp.path, '아주 긴 이름의 동영상 파일 - 시험용 2026.mp4')));
|
||
c.selected = c.videos.first;
|
||
final d = DownloadManager(backends: const [], settings: () => c.settings, readClipboard: () async => null);
|
||
addTearDown(d.dispose);
|
||
await tester.runAsync(() => tester.pumpWidget(MaterialApp(
|
||
builder: (ctx, child) => MediaQuery(data: MediaQuery.of(ctx).copyWith(textScaler: const TextScaler.linear(1.6)), child: child!),
|
||
home: page(c, d))));
|
||
for (var i = 0; i < 15; i++) {
|
||
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 30)));
|
||
await tester.pump();
|
||
}
|
||
expect(tester.takeException(), isNull);
|
||
// 화면을 내리고 남은 타이머 (탐색기 목록 읽기 등) 가 끝나게
|
||
await tester.pumpWidget(const SizedBox());
|
||
await tester.pump(const Duration(seconds: 1));
|
||
});
|
||
}
|
||
}
|
||
|
||
// 관리자 지시 8 (묶음6 릴리스 전 점검): 새 창 · 화면도 폭 412 · 글자 1.6배에서 넘치지 않는다
|
||
for (final size in [const Size(412, 900), const Size(900, 412)]) {
|
||
testWidgets('253: 묶음6 새 창 (78 · 79 · 81 · 82 · 85 · 86 · 87, ${size.width.round()}×${size.height.round()}, 글자 1.6배) 넘치지 않는다',
|
||
(tester) async {
|
||
tester.view.physicalSize = size;
|
||
tester.view.devicePixelRatio = 1;
|
||
addTearDown(tester.view.reset);
|
||
final show = Directory(p.join(tmp.path, '아주 긴 이름의 폴더 - 시험용', 'Show'))..createSync(recursive: true);
|
||
final eps = [
|
||
for (final n in ['아주 긴 이름의 동영상 파일 - 시험용 2026 ep01.mkv', 'ep02.mkv'])
|
||
(File(p.join(show.path, n))..writeAsBytesSync(List.filled(3000, 0))).path,
|
||
];
|
||
File(p.join(show.path, 'ep01.srt')).writeAsStringSync('1\n00:00:01,000 --> 00:00:02,000\n안녕\n');
|
||
final zip = p.join(tmp.path, '아주 긴 이름의 압축 파일 - 시험용.zip');
|
||
File(zip).writeAsBytesSync(ZipEncoder().encode(Archive()
|
||
..addFile(ArchiveFile.string('ok/a.txt', 'a'))
|
||
..addFile(ArchiveFile.string('../evil.txt', 'x'))
|
||
..addFile(ArchiveFile.string('CON.txt', 'x'))
|
||
..addFile(ArchiveFile.string('lnk', '../x')..mode = 0xA1FF)));
|
||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||
final nav = GlobalKey<NavigatorState>();
|
||
await tester.pumpWidget(MaterialApp(
|
||
navigatorKey: nav,
|
||
builder: (ctx, child) => MediaQuery(data: MediaQuery.of(ctx).copyWith(textScaler: const TextScaler.linear(1.6)), child: child!),
|
||
home: const Scaffold(body: SizedBox.expand())));
|
||
BuildContext ctx() => nav.currentContext!;
|
||
Future<void> settle([int n = 10]) async {
|
||
for (var i = 0; i < n; i++) {
|
||
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 30)));
|
||
// 화면 · 창이 열리는 움직임이 끝나게 (시험 시계를 앞으로)
|
||
await tester.pump(const Duration(milliseconds: 60));
|
||
}
|
||
}
|
||
|
||
Future<void> check(String what) async {
|
||
await settle();
|
||
expect(tester.takeException(), isNull, reason: what);
|
||
// 넘침 오류 없이 옆으로 밀려나는 것도 본다: 누르는 것 (단추 · 고르기 · 입력 칸) 은 화면 폭 안에
|
||
final hit = find.byWidgetPredicate((w) =>
|
||
w is ButtonStyleButton || w is IconButton || w is Checkbox || w is DropdownButton || w is TextField || w is ChoiceChip || w is Radio);
|
||
for (final e in hit.evaluate()) {
|
||
final r = tester.getRect(find.byElementPredicate((x) => x == e));
|
||
expect(r.left >= -0.5 && r.right <= size.width + 0.5, isTrue, reason: '$what: ${e.widget.runtimeType} $r');
|
||
}
|
||
nav.currentState!.popUntil((r) => r.isFirst);
|
||
await settle(4);
|
||
}
|
||
|
||
showBatchRename(ctx(), eps, isDir: (_) => false);
|
||
await check('78 이름 일괄');
|
||
nav.currentState!.push(MaterialPageRoute<void>(
|
||
builder: (_) => SearchResultsPage(
|
||
root: tmp.path, cond: const SearchConditions(name: 'ep', minBytes: 1024), showHidden: false, otherDir: tmp.path, onOpen: (_) async {})));
|
||
await settle(15);
|
||
await tester.tap(find.byType(Checkbox).first);
|
||
await check('79 찾기 결과 (고른 뒤 아래 줄)');
|
||
final list = await tester.runAsync(() => listEntries(show.path));
|
||
showDialog<void>(
|
||
context: ctx(),
|
||
builder: (_) => QuickLook(
|
||
entries: list!,
|
||
index: list.indexWhere((e) => e.name == 'ep01.srt'),
|
||
probe: (_) async => const MediaInfo(streams: []),
|
||
thumbnail: (_) async => null,
|
||
imageExts: const ['jpg'],
|
||
onOpen: (_) {}));
|
||
await check('81 빠른 보기');
|
||
askDropAction(ctx(), eps, tmp.path);
|
||
await check('82 놓기 묻기');
|
||
nav.currentState!.push(MaterialPageRoute<void>(
|
||
builder: (_) => Scaffold(
|
||
body: Align(
|
||
alignment: Alignment.bottomRight,
|
||
child: TransferProgressChip(running: 0, failed: true, onTap: () {})))));
|
||
await check('85 멈춤 표시');
|
||
extractArchiveFlow(ctx(), c, zip, otherDir: show.path);
|
||
await settle(20);
|
||
// 글자가 크면 창 아래쪽이라 내려서 누른다 (창은 스크롤)
|
||
await tester.ensureVisible(find.byType(DropdownButton<NameConflict>));
|
||
await tester.pump();
|
||
// 고르기 상자가 화면 안에 다 있어야 한다 (넘침 오류 없이 화면 밖으로 밀려났었다)
|
||
expect(tester.getRect(find.byType(DropdownButton<NameConflict>)).right, lessThanOrEqualTo(size.width));
|
||
await tester.tap(find.byType(DropdownButton<NameConflict>));
|
||
await settle(4);
|
||
await tester.tap(find.text('덮어쓰기').last);
|
||
await check('86 풀기 확인 (경고 줄 · 덮어쓰기)');
|
||
createZipFlow(ctx(), c, eps, dir: show.path, otherDir: tmp.path);
|
||
await check('86 ZIP 묶기');
|
||
nav.currentState!.push(MaterialPageRoute<void>(builder: (_) => StorageAnalysisPage(root: tmp.path)));
|
||
await settle(20);
|
||
await tester.tap(find.byType(ChoiceChip).last);
|
||
await check('87 저장 공간 분석 (큰 파일)');
|
||
});
|
||
}
|
||
}
|