208 lines
10 KiB
Dart
208 lines
10 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/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 'support/fake_recycle_bin.dart';
|
|
|
|
import 'support/explorer_harness.dart' show calm;
|
|
import 'support/test_explorer_device.dart';
|
|
/// 180: 복사 · 이동이 시작 · 진행 · 끝나도 목록의 자리와 스크롤은 그대로 (다음 것을 누르려다 엉뚱한 것을 누르지 않게)
|
|
void main() {
|
|
late TestExplorerDevice device;
|
|
setUp(() => device = TestExplorerDevice());
|
|
late Directory tmp;
|
|
late String left, right;
|
|
late AppController c;
|
|
|
|
setUp(() {
|
|
useTestRecycleBin();
|
|
tmp = Directory.systemTemp.createTempSync('jj_steady_');
|
|
left = p.join(tmp.path, 'left');
|
|
right = p.join(tmp.path, 'right');
|
|
for (var i = 0; i < 60; i++) {
|
|
File(p.join(left, 'f${i.toString().padLeft(2, '0')}.txt'))
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('x' * 2000);
|
|
}
|
|
Directory(right).createSync();
|
|
// 저장 장치는 시험 폴더 하나 (실제 Temp 폴더의 많은 항목 사이에서 찾지 않게)
|
|
device.volumes = () => [(tmp.path, 'T:')];
|
|
c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService(), shell: NoopShell()));
|
|
c.settings
|
|
..explorerPaths = [left, right]
|
|
..copyMethodFile = 'builtin'
|
|
..copyMethodFolder = 'builtin';
|
|
});
|
|
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));
|
|
}
|
|
}
|
|
});
|
|
|
|
for (final layout in ['dual', 'split']) {
|
|
for (final selecting in [false, true]) {
|
|
testWidgets('180: 복사해도 자리 · 스크롤 그대로 ($layout${selecting ? ' · 선택 모드' : ''})', (tester) async {
|
|
c.settings.explorerLayout = layout;
|
|
tester.view.physicalSize = const Size(1280, 800);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.reset);
|
|
await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(device: device, c: c))));
|
|
await settle(tester, () => find.text('f05.txt').evaluate().isNotEmpty);
|
|
// 왼쪽 목록을 조금 내려 둔다
|
|
final list = find.ancestor(of: find.text('f05.txt'), matching: find.byType(Scrollable)).first;
|
|
await tester.drag(find.text('f05.txt'), const Offset(0, -200));
|
|
await calm(tester);
|
|
final probe = find.text('f07.txt');
|
|
expect(probe, findsOneWidget);
|
|
final where = tester.getTopLeft(probe);
|
|
final at = tester.state<ScrollableState>(list).position.pixels;
|
|
expect(at, greaterThan(0));
|
|
if (selecting) {
|
|
await act(tester, () => tester.tap(find.text('선택').first));
|
|
await act(tester, () => tester.tap(markOf('f09.txt')));
|
|
} else {
|
|
await act(tester, () => tester.tap(find.text('f09.txt')));
|
|
}
|
|
await settle(tester, () => false, rounds: 10);
|
|
final before = tester.getTopLeft(probe);
|
|
expect(before.dy, where.dy, reason: '고르기만 해서는 움직이지 않음');
|
|
final split = layout == 'split';
|
|
if (split) {
|
|
// "폴더 + 파일 목록" 은 다른 창이 없어 복사는 붙여넣기로 - 같은 다시 읽기를 [새로 고침] 으로
|
|
File(p.join(left, 'zz_new.txt')).writeAsStringSync('n');
|
|
await act(tester, () => tester.tap(find.text('새로 고침').first));
|
|
} else {
|
|
await act(tester, () => tester.tap(find.text('복사').first));
|
|
await calm(tester);
|
|
await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '복사')));
|
|
}
|
|
// 시작 · 진행 · 끝 (진행 막대가 내려갈 때까지) 내내 같은 자리
|
|
final seen = <Offset>{};
|
|
for (var i = 0; i < 300; i++) {
|
|
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 20)));
|
|
await tester.pump();
|
|
if (probe.evaluate().isEmpty) {
|
|
seen.add(const Offset(-1, -1));
|
|
} else {
|
|
seen.add(tester.getTopLeft(probe));
|
|
}
|
|
if ((split || File(p.join(right, 'f09.txt')).existsSync()) && find.byType(LinearProgressIndicator).evaluate().isEmpty && i > 100) break;
|
|
}
|
|
if (!split) expect(File(p.join(right, 'f09.txt')).existsSync(), isTrue);
|
|
expect(seen, {before}, reason: '목록 자리가 그대로');
|
|
expect(tester.state<ScrollableState>(list).position.pixels, at, reason: '스크롤 그대로');
|
|
if (selecting) expect(find.text('선택 끝'), findsWidgets, reason: '선택 모드는 그대로 (표시만 지움)');
|
|
if (selecting) {
|
|
// Esc 로 바로 나온다
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
|
|
await tester.pump();
|
|
expect(find.text('선택 끝'), findsNothing);
|
|
}
|
|
ScaffoldMessenger.of(tester.element(find.byType(Scaffold).first)).clearSnackBars();
|
|
await tester.pump();
|
|
});
|
|
}
|
|
}
|
|
|
|
testWidgets('180: 창 크기가 바뀌어 두 창이 가로 ↔ 세로로 바뀌어도 스크롤 그대로', (tester) async {
|
|
c.settings
|
|
..explorerLayout = 'dual'
|
|
..explorerOrientation = 'auto';
|
|
tester.view.physicalSize = const Size(1280, 800);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.reset);
|
|
await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(device: device, c: c))));
|
|
await settle(tester, () => find.text('f05.txt').evaluate().isNotEmpty);
|
|
final list = find.ancestor(of: find.text('f05.txt'), matching: find.byType(Scrollable)).first;
|
|
await tester.drag(find.text('f05.txt'), const Offset(0, -200));
|
|
await calm(tester);
|
|
final at = tester.state<ScrollableState>(list).position.pixels;
|
|
expect(at, greaterThan(0));
|
|
final w = tester.getSize(list).width;
|
|
// 세로로 (위 · 아래 두 창)
|
|
tester.view.physicalSize = const Size(900, 1200);
|
|
await settle(tester, () => false, rounds: 10);
|
|
expect(tester.getSize(find.ancestor(of: find.text('f07.txt'), matching: find.byType(Scrollable)).first).width, isNot(w), reason: '배치가 바뀜');
|
|
expect(tester.state<ScrollableState>(find.ancestor(of: find.text('f07.txt'), matching: find.byType(Scrollable)).first).position.pixels, at);
|
|
// 다시 가로로
|
|
tester.view.physicalSize = const Size(1280, 800);
|
|
await settle(tester, () => false, rounds: 10);
|
|
expect(tester.state<ScrollableState>(find.ancestor(of: find.text('f07.txt'), matching: find.byType(Scrollable)).first).position.pixels, at);
|
|
});
|
|
|
|
testWidgets('183: [새로 고침] 은 두 창 모두 (같은 폴더를 보는 다른 창에 밖에서 지운 파일이 남지 않게)', (tester) async {
|
|
c.settings
|
|
..explorerLayout = 'dual'
|
|
..explorerPaths = [left, left];
|
|
tester.view.physicalSize = const Size(1280, 800);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.reset);
|
|
await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(device: device, c: c))));
|
|
await settle(tester, () => find.text('f03.txt').evaluate().length == 2);
|
|
expect(find.text('f03.txt'), findsNWidgets(2));
|
|
await tester.runAsync(() => File(p.join(left, 'f03.txt')).delete());
|
|
await act(tester, () => tester.tap(find.text('새로 고침').first));
|
|
await settle(tester, () => find.text('f03.txt').evaluate().isEmpty);
|
|
expect(find.text('f03.txt'), findsNothing);
|
|
});
|
|
|
|
testWidgets('232: 앱으로 돌아오면 보이는 폴더를 다시 읽는다 (다른 앱에서 지우고 · 만든 것이 바로 보이게)', (tester) async {
|
|
c.settings
|
|
..explorerLayout = 'dual'
|
|
..explorerPaths = [left, left];
|
|
tester.view.physicalSize = const Size(1280, 800);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.reset);
|
|
await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(device: device, c: c))));
|
|
await settle(tester, () => find.text('f03.txt').evaluate().length == 2);
|
|
// 다른 앱으로 갔다가 (그사이 밖에서 지우고 만듦)
|
|
tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.inactive);
|
|
tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.hidden);
|
|
await tester.runAsync(() async {
|
|
await File(p.join(left, 'f03.txt')).delete();
|
|
await File(p.join(left, '0_made_outside.txt')).writeAsString('m'); // 맨 위에 보이게 (목록이 길다)
|
|
});
|
|
await settle(tester, () => false, rounds: 5);
|
|
expect(find.text('f03.txt'), findsNWidgets(2), reason: '돌아오기 전에는 그대로');
|
|
// 돌아옴
|
|
tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.inactive);
|
|
tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed);
|
|
await settle(tester, () => find.text('f03.txt').evaluate().isEmpty && find.text('0_made_outside.txt').evaluate().length == 2);
|
|
expect(find.text('f03.txt'), findsNothing);
|
|
expect(find.text('0_made_outside.txt'), findsNWidgets(2));
|
|
});
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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));
|
|
}
|