194 lines
8.4 KiB
Dart
194 lines
8.4 KiB
Dart
import 'dart:async';
|
|
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/transfer_job.dart';
|
|
import 'package:jj_mkvmaker/core/file_ops.dart' show samePath;
|
|
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/explorer_buttons.dart';
|
|
import 'package:jj_mkvmaker/ui/explorer/explorer_controller.dart';
|
|
import 'package:jj_mkvmaker/ui/explorer/explorer_flows.dart';
|
|
import 'package:jj_mkvmaker/ui/explorer/explorer_host.dart';
|
|
import 'package:jj_mkvmaker/ui/explorer/explorer_pane.dart';
|
|
import 'package:jj_mkvmaker/ui/explorer/explorer_select.dart';
|
|
import 'package:jj_mkvmaker/ui/explorer/marks_bar.dart';
|
|
import 'package:jj_mkvmaker/ui/explorer/toolbar.dart';
|
|
import 'package:jj_mkvmaker/ui/explorer/transfer_panel.dart';
|
|
import 'package:path/path.dart' as p;
|
|
|
|
/// E5d-4 약속 시험: explorer_buttons · toolbar · marks_bar · transfer_panel · job_view 맨 위 약속
|
|
/// (실제 시험 폴더 · 실제 화면 · 실제 복사 작업. 화면의 일 [ExplorerCommands] 은 이 시험 화면이 받아 적는다)
|
|
void main() {
|
|
late Directory tmp;
|
|
late String root, show, ep1;
|
|
setUp(() {
|
|
tmp = Directory.systemTemp.createTempSync('jj_btn_ui_');
|
|
root = tmp.path;
|
|
show = p.join(root, 'Show');
|
|
Directory(show).createSync();
|
|
ep1 = p.join(show, 'ep1.mkv');
|
|
File(ep1).writeAsStringSync('1');
|
|
File(p.join(show, 'ep2.mkv')).writeAsStringSync('2');
|
|
});
|
|
tearDown(() => tmp.deleteSync(recursive: true));
|
|
|
|
Future<(ExplorerButtons, ExplorerController, ExplorerSelect, _HostState)> open(WidgetTester t) async {
|
|
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
|
final key = GlobalKey<_HostState>();
|
|
await t.pumpWidget(MaterialApp(home: _Host(key: key)));
|
|
final host = key.currentState!;
|
|
final ctl = ExplorerController(c, rsync: false, host: host)..local = [(root, 'T:')];
|
|
final sel = ExplorerSelect(ctl, clock: DateTime.now, openFile: (_, _) async {});
|
|
addTearDown(() {
|
|
sel.dispose();
|
|
ctl.dispose();
|
|
});
|
|
await t.runAsync(() => ctl.goTo(ctl.panes[0], show));
|
|
final flows = ExplorerFlows(ctl, sel, androidTrash: () => false, otherDirOf: (_) => null, otherDir: () => null, twoPanes: () => false);
|
|
addTearDown(flows.dispose);
|
|
final b = ExplorerButtons(ctl, sel, host, flows);
|
|
return (b, ctl, sel, host);
|
|
}
|
|
|
|
testWidgets('버튼: 상위 폴더 · 선택 · 새 폴더 (실제로 만들고 그 줄로) · 한 창 복사 = 담기 · 길게 = 짝 동작 (영구 삭제 창)', (t) async {
|
|
final (b, ctl, sel, _) = await open(t);
|
|
final q = ctl.pane;
|
|
await t.runAsync(() => b.press(ExplorerButton.select));
|
|
expect(sel.selecting, isTrue);
|
|
var made0 = false;
|
|
b.press(ExplorerButton.newFolder).then((_) => made0 = true);
|
|
await t.pump();
|
|
await t.enterText(find.byType(TextField), 'New');
|
|
await t.tap(find.text('확인'));
|
|
for (var i = 0; i < 20 && !made0; i++) {
|
|
await t.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 30)));
|
|
await t.pump(const Duration(milliseconds: 100));
|
|
}
|
|
final made = p.join(show, 'New');
|
|
expect([Directory(made).existsSync(), samePath(q.focused!, made)], [true, true]);
|
|
await t.runAsync(() => sel.toggleMark(q, sel.shown(q, show).firstWhere((e) => samePath(e.path, ep1))));
|
|
await t.runAsync(() => b.press(ExplorerButton.copy));
|
|
expect([b.flows.transfers.clip.items, b.flows.transfers.clip.move], [
|
|
[ep1],
|
|
false
|
|
]);
|
|
await t.runAsync(() => b.longPress(ExplorerButton.copy));
|
|
expect(b.flows.transfers.clip.move, isTrue, reason: '복사 길게 = 이동');
|
|
unawaited(b.longPress(ExplorerButton.delete));
|
|
await t.pump();
|
|
expect([find.text('영구 삭제').evaluate().isNotEmpty, b.flows.deletes.shiftDelete], [true, false]);
|
|
await t.tap(find.text('취소'));
|
|
await t.pump();
|
|
expect(File(ep1).existsSync(), isTrue, reason: '취소하면 그대로');
|
|
await t.runAsync(() => b.longPress(ExplorerButton.refresh));
|
|
await t.runAsync(() => b.press(ExplorerButton.up));
|
|
expect(samePath(q.current, root), isTrue);
|
|
await t.runAsync(() => b.press(ExplorerButton.up));
|
|
expect(samePath(q.current, root), isTrue, reason: '맨 위에서는 그대로');
|
|
expect([ExplorerButtons.longPressLabel(ExplorerButton.copy), ExplorerButtons.longPressLabel(ExplorerButton.up)], ['이동', null]);
|
|
});
|
|
|
|
testWidgets('버튼 줄: 누르기 · 못 누르는 버튼 · 넘치면 [더 보기] (고정한 것은 줄에)', (t) async {
|
|
final pressed = <ExplorerButton>[];
|
|
Future<void> bar(double height) async {
|
|
await t.pumpWidget(MaterialApp(
|
|
home: Scaffold(
|
|
body: Align(
|
|
alignment: Alignment.topLeft,
|
|
child: SizedBox(
|
|
height: height,
|
|
child: ExplorerToolbar(
|
|
buttons: const [ExplorerButton.copy, ExplorerButton.move, ExplorerButton.paste, ExplorerButton.delete, ExplorerButton.orient],
|
|
vertical: true,
|
|
onPressed: pressed.add,
|
|
enabled: (b) => b != ExplorerButton.paste,
|
|
selected: (_) => false,
|
|
))))));
|
|
}
|
|
|
|
await bar(600);
|
|
await t.tap(find.text('복사'));
|
|
await t.tap(find.text('붙여넣기'));
|
|
expect([pressed, find.text('더 보기').evaluate().isEmpty], [
|
|
[ExplorerButton.copy],
|
|
true
|
|
]);
|
|
await bar(200);
|
|
expect([find.text('더 보기').evaluate().length, find.byIcon(Icons.swap_horiz).evaluate().length], [1, 1]);
|
|
});
|
|
|
|
testWidgets('표시 막대: 수 · 모두 선택 · 표시 지우기 · 선택 끝', (t) async {
|
|
final (_, ctl, sel, host) = await open(t);
|
|
final q = ctl.pane;
|
|
sel.setSelecting(true);
|
|
await t.runAsync(() => sel.toggleMark(q, sel.shown(q, show).first));
|
|
host.show(() => ExplorerMarksBar(sel: sel, pane: q), sel);
|
|
await t.pump();
|
|
expect(find.textContaining('1개 표시함'), findsOneWidget);
|
|
await t.tap(find.text('모두 선택'));
|
|
await t.pump();
|
|
expect(find.textContaining('2개 표시함'), findsOneWidget);
|
|
await t.tap(find.text('표시 지우기'));
|
|
await t.pump();
|
|
expect(q.sel.isEmpty, isTrue);
|
|
await t.tap(find.text('선택 끝'));
|
|
expect(sel.selecting, isFalse);
|
|
});
|
|
|
|
testWidgets('진행 패널: 실제 복사 작업 · 끝나면 완료 · 숨기면 작은 표시', (t) async {
|
|
final (_, ctl, _, host) = await open(t);
|
|
final dest = p.join(root, 'Out');
|
|
Directory(dest).createSync();
|
|
final job = TransferJob(sources: [ep1], dest: dest, move: false);
|
|
await t.runAsync(job.run);
|
|
expect(File(p.join(dest, 'ep1.mkv')).existsSync(), isTrue);
|
|
host.show(() => ExplorerTransferPanel(c: ctl.c, jobs: [job], rsync: false), ctl.c);
|
|
await t.pump();
|
|
expect(find.textContaining('완료'), findsOneWidget);
|
|
await t.runAsync(() => ctl.c.updateSettings((s) => s.explorerProgressHidden = true));
|
|
await t.pump();
|
|
expect([find.byType(TransferProgressChip).evaluate().length, find.textContaining('완료').evaluate().length], [1, 0]);
|
|
});
|
|
}
|
|
|
|
class _Host extends StatefulWidget {
|
|
const _Host({super.key});
|
|
@override
|
|
State<_Host> createState() => _HostState();
|
|
}
|
|
|
|
/// 실제 화면: ExplorerHost · ExplorerCommands 를 이 화면이 맡는다
|
|
class _HostState extends State<_Host> implements ExplorerHost, ExplorerCommands {
|
|
Widget Function()? child;
|
|
|
|
/// [build] 로 그리고, [changes] 가 바뀔 때마다 다시 그린다
|
|
void show(Widget Function() build, Listenable changes) {
|
|
changes.addListener(() {
|
|
if (mounted) setState(() {});
|
|
});
|
|
setState(() => child = build);
|
|
}
|
|
|
|
@override
|
|
bool get alive => mounted;
|
|
@override
|
|
void snack(String text) => ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(text)));
|
|
@override
|
|
void reveal(ExplorerPane pane, String path) {}
|
|
@override
|
|
void afterFrame(VoidCallback f) => WidgetsBinding.instance.addPostFrameCallback((_) => f());
|
|
|
|
@override
|
|
String? get otherDir => null;
|
|
@override
|
|
bool get dual => false;
|
|
|
|
|
|
@override
|
|
Widget build(BuildContext context) => Scaffold(body: child?.call() ?? const SizedBox());
|
|
}
|