JJ_MKVMaker/test/explorer_parallel_test.dart

238 lines
13 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/app/settings.dart';
import 'package:jj_mkvmaker/core/file_ops.dart' show uncRoot;
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:jj_mkvmaker/ui/explorer_page.dart';
import 'package:path/path.dart' as p;
import 'explorer_test.dart' show act, settle;
/// 23: 앞의 복사가 끝나지 않아도 다음 복사를 함께 시작한다 (막지 않음, 진행 막대가 둘)
void main() {
testWidgets('복사 중에 다른 파일 복사 → 둘 다 진행 · 둘 다 끝남', (tester) async {
final tmp = Directory.systemTemp.createTempSync('jj_parallel_');
addTearDown(() => tmp.deleteSync(recursive: true));
final left = p.join(tmp.path, 'left'), right = p.join(tmp.path, 'right');
File(p.join(left, 'big.bin'))
..createSync(recursive: true)
..writeAsBytesSync(List.filled(3 << 20, 7));
File(p.join(left, 'doc.txt')).writeAsStringSync('hello');
Directory(right).createSync();
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
c.settings
..explorerPaths = [left, right]
..copyBandwidthKBps = 512; // 큰 파일이 6초쯤 걸리게
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))));
await settle(tester, () => find.text('doc.txt').evaluate().isNotEmpty);
Future<void> copy(String name) async {
await act(tester, () => tester.tap(find.text(name).first));
await settle(tester, () => false, rounds: 15);
await act(tester, () => tester.tap(find.text('복사').first));
await tester.pump(const Duration(milliseconds: 300));
await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '복사')));
await tester.pump(const Duration(milliseconds: 300));
}
await copy('big.bin');
await settle(tester, () => find.textContaining('복사하는 중').evaluate().isNotEmpty, rounds: 30);
await copy('doc.txt');
expect(find.text('앞의 복사 · 이동이 끝난 뒤에 하세요.'), findsNothing);
// 둘이 함께 돈다 (진행 막대 두 줄)
expect(find.textContaining('복사하는 중'), findsNWidgets(2));
await settle(tester, () => File(p.join(right, 'doc.txt')).existsSync(), rounds: 100);
await settle(tester,
() => File(p.join(right, 'big.bin')).existsSync() && find.byType(LinearProgressIndicator).evaluate().isEmpty,
rounds: 400);
expect(File(p.join(right, 'big.bin')).lengthSync(), 3 << 20);
expect(File(p.join(right, 'doc.txt')).readAsStringSync(), 'hello');
});
testWidgets('25: Rsync 화면 폴더도 길게 누르면 메뉴 (고르기 · 열기 · 이름 변경 · 삭제 · 정보)', (tester) async {
final tmp = Directory.systemTemp.createTempSync('jj_rsync_menu_');
addTearDown(() => tmp.deleteSync(recursive: true));
final l = p.join(tmp.path, 'L'), r = p.join(tmp.path, 'R');
Directory(p.join(l, 'sub')).createSync(recursive: true);
Directory(p.join(r, 'rsub')).createSync(recursive: true);
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
c.settings
..explorerLayout = 'single'
..rsyncPaths = [l, r];
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, rsync: true))));
await settle(tester, () => find.text('sub').evaluate().isNotEmpty);
await tester.longPress(find.text('sub'));
await tester.pumpAndSettle();
for (final t in ['rsync 에 고르기', '열기', '다른 창에서 열기', '이름 변경', '삭제', '정보']) {
expect(find.text(t), findsOneWidget, reason: t);
}
expect(find.text('다른 앱으로 열기'), findsNothing); // 파일 메뉴는 없음
await act(tester, () => tester.tap(find.text('rsync 에 고르기')));
await settle(tester, () => false, rounds: 5);
expect(find.byTooltip('고르기 취소'), findsOneWidget);
});
test('37: 네트워크 공유 경로의 맨 위', () {
expect(uncRoot(r'\\NAS\영상\드라마\1화.mkv'), r'\\NAS\영상');
expect(uncRoot(r'\\NAS\영상'), r'\\NAS\영상');
});
testWidgets('37: 경로를 직접 넣어 가기 · 없는 경로는 알림', (tester) async {
final tmp = Directory.systemTemp.createTempSync('jj_enter_path_');
addTearDown(() => tmp.deleteSync(recursive: true));
final l = p.join(tmp.path, 'L'), deep = p.join(l, 'a', 'b');
File(p.join(deep, 'deep.txt'))
..createSync(recursive: true)
..writeAsStringSync('d');
File(p.join(l, 'top.txt')).writeAsStringSync('t');
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
c.settings
..explorerLayout = 'single'
..explorerPaths = [l, l];
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))));
await settle(tester, () => find.text('top.txt').evaluate().isNotEmpty);
await tester.tap(find.byTooltip('경로 입력').first);
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField).last, p.join(tmp.path, 'none'));
await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '확인')));
await settle(tester, () => find.textContaining('폴더가 없거나 열 수 없습니다').evaluate().isNotEmpty, rounds: 20);
expect(find.textContaining('폴더가 없거나 열 수 없습니다'), findsOneWidget);
await tester.tap(find.byTooltip('경로 입력').first);
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField).last, deep);
await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '확인')));
await settle(tester, () => find.text('deep.txt').evaluate().isNotEmpty);
expect(find.text('deep.txt'), findsOneWidget);
});
testWidgets('122: 앱 밖에서 만든 폴더도 경로 입력으로 가면 새로 읽어 펼치고 화면에 보인다', (tester) async {
final tmp = Directory.systemTemp.createTempSync('jj_enter_fresh_');
addTearDown(() => tmp.deleteSync(recursive: true));
final dl = p.join(tmp.path, 'Download');
// 앞쪽에 파일이 많아 새 폴더가 화면 아래로 밀리는 Download
for (var i = 0; i < 60; i++) {
File(p.join(dl, 'a$i.txt'))
..createSync(recursive: true)
..writeAsStringSync('$i');
}
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
c.settings
..explorerLayout = 'single'
..explorerPaths = [dl, dl];
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))));
await settle(tester, () => find.text('a0.txt').evaluate().isNotEmpty);
// 앱이 Download 를 읽은 뒤에 다른 앱이 만든 폴더 (이름 순으로 맨 뒤)
final made = p.join(dl, 'zz_jjsup');
File(p.join(made, 'pg1.png'))
..createSync(recursive: true)
..writeAsBytesSync([1, 2, 3]);
await tester.tap(find.byTooltip('경로 입력').first);
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField).last, made);
await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '확인')));
await settle(tester, () => find.text('pg1.png').evaluate().isNotEmpty, rounds: 300);
expect(find.text('zz_jjsup'), findsOneWidget);
expect(find.text('pg1.png').hitTestable(), findsOneWidget, reason: '그 자리로 스크롤되어 보임');
});
testWidgets('38: 좁고 낮은 창 (접은 폴드 · 위아래 두 창) 에서 안내가 있어도 목록이 보인다', (tester) async {
final tmp = Directory.systemTemp.createTempSync('jj_notice_');
addTearDown(() => tmp.deleteSync(recursive: true));
final l = p.join(tmp.path, 'L');
for (var i = 0; i < 4; i++) {
File(p.join(l, 'part$i.mkv.jjpart'))
..createSync(recursive: true)
..writeAsStringSync('x')
..setLastModifiedSync(DateTime.now().subtract(const Duration(hours: 1)));
}
File(p.join(l, 'zz_file.txt')).writeAsStringSync('t');
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
c.settings
..explorerLayout = 'dual'
..explorerOrientation = 'stacked'
..explorerPaths = [l, l];
tester.view.physicalSize = const Size(420, 640);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(c: c))));
await settle(tester, () => find.textContaining('만들다 만 파일').evaluate().isNotEmpty);
expect(tester.takeException(), isNull); // 넘침 (overflow) 없음
// 목록 (창마다) 이 밀려 사라지지 않고 높이가 남아 있다
final lists = find.byWidgetPredicate((w) => w is ListView && w.scrollDirection == Axis.vertical);
expect(lists, findsWidgets);
for (final e in lists.evaluate()) {
expect(tester.getSize(find.byWidget(e.widget)).height, greaterThan(80));
}
});
test('118: 두 창 배치 처음 값은 좌우 · 예전 "화면 모양 따라" 는 한 번 좌우로 (알림) · 고른 위아래는 그대로', () {
expect(AppSettings().explorerOrientation, 'side');
expect(AppSettings.fromJson({}).explorerOrientation, 'side');
final old = AppSettings.fromJson({'explorerOrientation': 'auto'});
expect(old.explorerOrientation, 'side');
expect(old.migrated, contains('explorerOrientation'));
final stacked = AppSettings.fromJson({'explorerOrientation': 'stacked'});
expect(stacked.explorerOrientation, 'stacked');
expect(stacked.migrated, isNot(contains('explorerOrientation')));
// 옮긴 뒤 사용자가 다시 '화면 모양 따라' 를 고르면 그대로
final again = AppSettings.fromJson((AppSettings()..explorerOrientation = 'auto').toJson());
expect(again.explorerOrientation, 'auto');
expect(again.migrated, isEmpty);
});
testWidgets('118: 가운데 버튼 [좌우 ⇆ / 위아래 ⇅] - 한 번 눌러 바로 바뀜 · 한 창이면 두 창 (좌우) 으로', (tester) async {
final tmp = Directory.systemTemp.createTempSync('jj_orient_');
addTearDown(() => tmp.deleteSync(recursive: true));
final l = p.join(tmp.path, 'L');
File(p.join(l, 'a.txt'))
..createSync(recursive: true)
..writeAsStringSync('a');
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
c.settings
..explorerLayout = 'single'
..explorerPaths = [l, l];
// 폰 세로처럼 좁고 긴 화면
tester.view.physicalSize = const Size(500, 1000);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(c: c))));
await settle(tester, () => find.text('a.txt').evaluate().isNotEmpty);
await act(tester, () => tester.tap(find.byTooltip('두 창으로 보기 (좌우)')));
await settle(tester, () => false, rounds: 5);
expect((c.settings.explorerLayout, c.settings.explorerOrientation), ('dual', 'side'));
expect(find.byTooltip('창 배치: 좌우 (누르면 위아래)'), findsOneWidget, reason: '폰 세로에서도 좌우');
expect(find.text('a.txt'), findsNWidgets(2));
final left = tester.getCenter(find.text('a.txt').first), right = tester.getCenter(find.text('a.txt').last);
expect(right.dx, greaterThan(left.dx));
await act(tester, () => tester.tap(find.byTooltip('창 배치: 좌우 (누르면 위아래)')));
await settle(tester, () => false, rounds: 5);
expect(c.settings.explorerOrientation, 'stacked');
expect(find.byTooltip('창 배치: 위아래 (누르면 좌우)'), findsOneWidget);
final top = tester.getCenter(find.text('a.txt').first), bottom = tester.getCenter(find.text('a.txt').last);
expect(bottom.dy, greaterThan(top.dy));
// 위아래면 버튼 줄이 가로로 밀어 보는 줄 - 보이게 한 뒤 누른다
await tester.ensureVisible(find.byTooltip('창 배치: 위아래 (누르면 좌우)'));
await tester.pump();
await act(tester, () => tester.tap(find.byTooltip('창 배치: 위아래 (누르면 좌우)')));
await settle(tester, () => false, rounds: 5);
expect(c.settings.explorerOrientation, 'side');
});
}