JJ_MKVMaker/test/explorer_select_contract_te...

118 lines
5.1 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/core/explorer/tree_rows.dart';
import 'package:jj_mkvmaker/core/file_ops.dart' show FileEntry;
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_controller.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:path/path.dart' as p;
/// E5d-2 약속 시험: lib/ui/explorer/explorer_select.dart 맨 위 약속 (실제 시험 폴더 · Host 는 실제 화면).
void main() {
late Directory tmp;
late String root, show, ep1, ep2;
setUp(() {
tmp = Directory.systemTemp.createTempSync('jj_sel_ui_');
root = tmp.path;
show = p.join(root, 'Show');
Directory(show).createSync();
ep1 = p.join(show, 'ep1.mkv');
ep2 = p.join(show, 'ep2.mkv');
File(ep1).writeAsStringSync('1');
File(ep2).writeAsStringSync('2');
});
tearDown(() => tmp.deleteSync(recursive: true));
var now = DateTime(2026, 10, 22, 9);
Future<(ExplorerSelect, ExplorerPane, List<String>)> open(WidgetTester t, {bool rsync = false}) async {
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
final key = GlobalKey<_HostState>();
await t.pumpWidget(MaterialApp(home: _Host(key: key)));
final ctl = ExplorerController(c, rsync: rsync, host: key.currentState!)..local = [(root, 'T:')];
final opened = <String>[];
final sel = ExplorerSelect(ctl, clock: () => now, openFile: (path, _) async => opened.add(path));
addTearDown(() {
sel.dispose();
ctl.dispose();
});
final q = ctl.panes[0];
await t.runAsync(() => ctl.goTo(q, root));
return (sel, q, opened);
}
TreeRow rowOf(String path, {bool dir = false}) => TreeRow(FileEntry(path, isDir: dir, modified: DateTime(0)), 1);
testWidgets('폴더 한 번 = 펼침 · 다시 = 접힘 (두 번 누르기의 두 번째는 아님) · 파일 한 번 = 지금 줄 · 두 번 = 열기', (t) async {
final (sel, q, opened) = await open(t);
await t.runAsync(() => sel.onTap(q, rowOf(show, dir: true)));
expect(q.expanded.contains(show), isTrue);
now = now.add(const Duration(milliseconds: 100));
await t.runAsync(() => sel.onTap(q, rowOf(show, dir: true)));
expect(q.expanded.contains(show), isTrue, reason: '두 번 누르기의 두 번째');
now = now.add(const Duration(seconds: 1));
await t.runAsync(() => sel.onTap(q, rowOf(show, dir: true)));
expect(q.expanded.contains(show), isFalse, reason: '규칙 3');
await t.runAsync(() => sel.onTap(q, rowOf(show, dir: true)));
await t.runAsync(() => sel.onTap(q, rowOf(ep1)));
expect([q.focused, opened], [ep1, <String>[]]);
await t.runAsync(() => sel.onOpen(q, rowOf(ep1)));
expect(opened, [ep1]);
});
testWidgets('선택 모드: 파일 고름 · 폴더 = 자기 + 안 · 범위 · 뒤집기 · 끄면 모두 풀림', (t) async {
final (sel, q, _) = await open(t);
sel.setSelecting(true);
await t.runAsync(() => sel.onTap(q, rowOf(show, dir: true)));
expect([sel.isMarked(q, ep1), q.expanded.contains(show)], [true, true]);
await t.runAsync(() => sel.onTap(q, rowOf(show, dir: true)));
expect(q.sel.isEmpty, isTrue);
await t.runAsync(() => sel.toggleMark(q, FileEntry(ep1, isDir: false, modified: DateTime(0))));
expect(sel.canRange(q, FileEntry(ep2, isDir: false, modified: DateTime(0))), isTrue);
sel.markRange(q, FileEntry(ep2, isDir: false, modified: DateTime(0)));
expect(q.sel.marked.length, 2);
sel.invertMarks(q, show);
expect(q.sel.isEmpty, isTrue);
sel.markWhere(q, show, (e) => e.path.endsWith('2.mkv'));
expect(q.sel.marked, [ep2]);
sel.setSelecting(false);
expect([sel.selecting, q.sel.isEmpty], [false, true]);
});
testWidgets('Rsync: 폴더만 하나 · 고르면 펼침', (t) async {
final (sel, q, _) = await open(t, rsync: true);
await t.runAsync(() => sel.onTap(q, rowOf(ep1)));
expect(q.sel.isEmpty, isTrue);
await t.runAsync(() => sel.onTap(q, rowOf(show, dir: true)));
expect([q.sel.marked, q.expanded.contains(show)], [
[show],
true
]);
});
}
class _Host extends StatefulWidget {
const _Host({super.key});
@override
State<_Host> createState() => _HostState();
}
class _HostState extends State<_Host> implements ExplorerHost {
@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
Widget build(BuildContext context) => const Scaffold(body: SizedBox());
}