import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show LogicalKeyboardKey;
import 'package:flutter_test/flutter_test.dart';
import 'package:jj_mkvmaker/ui/explorer/tc_key_bar.dart';
import 'package:jj_mkvmaker/ui/explorer_page.dart';
import 'package:path/path.dart' as p;
import 'support/explorer_clock_keys.dart';
import 'support/explorer_harness.dart';
/// E6 약속: Total Commander 모양 - 창마다 지금 폴더의 목록 ("..", 폴더는 [이름]) · 아래 F3~F8 단추 줄 (키와 같은 일) ·
/// 창 위 남은 공간. X-plore 모양에는 단추 줄 · 남은 공간 줄이 없다 (모양만 다름)
void main() {
useExplorerFixture();
testWidgets('목록: 폴더를 두 번 누르면 들어가고 ".." 로 나온다 · 폴더는 [이름]', (t) async {
c.settings.explorer.style = 'totalcmd';
await open(t);
// TC 는 이름 · 확장자를 나눠 보인다 (doc · txt) - 하네스의 open 은 'doc.txt' 를 기다리므로 여기서 다시 기다린다
await settle(t, () => find.text('[sub]').evaluate().isNotEmpty);
expect(find.text('[sub]'), findsWidgets, reason: '폴더는 [이름] (TC)');
expect([find.text('doc').evaluate().length, find.text('txt').evaluate().length], [1, 1], reason: '파일은 이름 · 확장자 칸');
expect(find.text('inner'), findsNothing, reason: '트리처럼 펼치지 않는다');
await doubleTap(t, find.text('[sub]').first);
await settle(t, () => find.text('inner').evaluate().isNotEmpty);
expect(find.text('..'), findsWidgets);
await doubleTap(t, find.text('..').first);
await settle(t, () => find.text('[sub]').evaluate().isNotEmpty);
expect(find.text('inner'), findsNothing);
});
testWidgets('아래 단추 줄 F3~F8 · F7 은 새 폴더 창 (가운데 [새 폴더] 와 같음) · 창 위 남은 공간', (t) async {
c.settings.explorer.style = 'totalcmd';
device.space = (_) => (107374182400, 536870912000); // 100 GB / 500 GB
await open(t);
expect(find.byType(TcKeyBar), findsOneWidget);
for (final k in TcKey.values) {
expect(find.byTooltip('${k.key} ${k.label}'), findsOneWidget);
}
expect(find.textContaining('남은 100'), findsWidgets);
await t.tap(find.byTooltip('F7 새 폴더'));
await t.pumpAndSettle();
expect(find.byType(Dialog), findsOneWidget, reason: '새 폴더 이름 창');
});
testWidgets('X-plore 모양에는 아래 단추 줄 · 남은 공간 줄이 없다', (t) async {
device.space = (_) => (107374182400, 536870912000);
await open(t); // 하네스는 X-plore
expect(find.byType(TcKeyBar), findsNothing);
expect(find.textContaining('남은 100'), findsNothing);
});
testWidgets('좁은 창 (412 폭 두 창, 창마다 200 아래): 이름 먼저 · 크기 · 날짜는 이름 아래 · 끌기 손잡이 숨김 (X-plore · TC 같은 줄 부품)', (t) async {
for (final style in ['xplore', 'totalcmd']) {
c.settings.explorer.style = style;
t.view.physicalSize = const Size(412, 900);
t.view.devicePixelRatio = 1;
addTearDown(t.view.reset);
await t.runAsync(() => t.pumpWidget(MaterialApp(home: ExplorerPage(c: c, clock: () => testNow, device: device))));
const name = 'doc.txt'; // TC 도 좁은 줄은 확장자 칸이 없어 이름에 붙인다 (관리자 10/23)
await settle(t, () => find.text(name).evaluate().isNotEmpty);
// X-plore (줄 52) 는 크기 · 날짜가 이름 아래 한 줄, TC (줄 26) 는 두 줄이 들어가지 않아 이름만
expect(find.textContaining('5B · '), style == 'xplore' ? findsWidgets : findsNothing, reason: '$style: 이름 아래 크기 · 날짜');
expect(t.takeException(), isNull, reason: '$style: 넘침 없음');
expect(find.text('
'), findsNothing, reason: '$style: 열 (크기 칸) 대신 이름 먼저');
// 선택 모드에서 표시해도 좁은 창에는 끌기 손잡이 (≡) 가 없다
await act(t, () => t.tap(find.byWidgetPredicate((w) => w is Tooltip && (w.message ?? '').startsWith('선택')).first));
await calm(t);
final mark = find.byTooltip('표시 (여러 개 고르기)');
if (mark.evaluate().isNotEmpty) {
await act(t, () => t.tap(mark.first));
await calm(t);
}
expect(find.byIcon(Icons.drag_indicator), findsNothing, reason: '$style: 좁은 창은 끌기 손잡이 숨김');
await t.pumpWidget(const SizedBox());
}
});
testWidgets('지운 뒤 초점: 지운 줄의 다음 줄로 (TC 목록 · F8)', (t) async {
File(p.join(left, 'e.txt')).writeAsStringSync('e');
c.settings.explorer.style = 'totalcmd';
await open(t);
await settle(t, () => find.text('[sub]').evaluate().isNotEmpty);
await act(t, () => t.tap(find.text('doc').first));
await settle(t, () => false, rounds: 15);
// 아래 단추 줄의 [F8 삭제] (키 F8 과 같은 일) → [휴지통으로]
await act(t, () => t.tap(find.byTooltip('F8 삭제')));
await calm(t);
expect(find.text('휴지통으로 보내기'), findsOneWidget);
await act(t, () => t.tap(find.widgetWithText(FilledButton, '휴지통으로')));
await settle(t, () => !File(p.join(left, 'doc.txt')).existsSync() && find.byType(SnackBar).evaluate().isNotEmpty);
await calm(t);
expect(File(p.join(left, 'doc.txt')).existsSync(), isFalse);
// 차례: [sub] · doc · e - doc 을 지우면 다음 줄 e 가 지금 줄
expect(find.text('doc'), findsNothing);
final focusedRow = find.byWidgetPredicate((w) => w is Material && w.shape is Border);
expect(find.descendant(of: focusedRow, matching: find.text('e')), findsOneWidget, reason: '지운 줄의 다음 줄 (e.txt) 이 지금 줄 (왼쪽 강조 막대)');
});
testWidgets('TC: 폴더 한 번 = 고르기 (들어가지 않음) · Enter = 들어가기 · ".." 두 번 = 나오기 (관리자 10/23)', (t) async {
c.settings.explorer.style = 'totalcmd';
await open(t);
await settle(t, () => find.text('[sub]').evaluate().isNotEmpty);
await act(t, () => t.tap(find.text('[sub]').first));
await settle(t, () => false, rounds: 15);
expect(find.text('inner'), findsNothing, reason: '한 번 누르기는 고르기만');
expect(find.text('[sub]'), findsWidgets);
await act(t, () => t.sendKeyEvent(LogicalKeyboardKey.enter));
await settle(t, () => find.text('inner').evaluate().isNotEmpty);
expect(find.text('inner'), findsOneWidget, reason: 'Enter 로 들어감');
await doubleTap(t, find.text('..').first);
await settle(t, () => find.text('[sub]').evaluate().isNotEmpty);
expect(find.text('inner'), findsNothing, reason: '".." 두 번으로 나옴');
});
testWidgets('폴더 + 파일 목록: 한 번 눌러 폴더를 연 직후 두 번째 누르기는 버린다 (도로 나오지 않음) - 한 번 열기는 그대로', (t) async {
c.settings.explorer.layout = 'split'; // 하네스는 X-plore
await open(t);
final list = find.text('sub').last; // 오른쪽 목록의 줄
await act(t, () => t.tap(list));
await t.pump(const Duration(milliseconds: 50));
// 습관처럼 두 번째 누르기: 같은 자리 (열린 폴더의 ".." 자리)
await act(t, () => t.tapAt(t.getCenter(list)));
await settle(t, () => find.text('inner.txt').evaluate().isNotEmpty);
expect(find.text('inner.txt'), findsWidgets, reason: '한 번 누르기로 열렸고 두 번째 누르기에 도로 나오지 않음');
await calm(t);
await t.pump(const Duration(seconds: 1));
await act(t, () => t.tap(find.text('..').last));
await settle(t, () => find.text('inner.txt').evaluate().isEmpty);
expect(find.text('inner.txt'), findsNothing, reason: '시간이 지난 뒤의 ".." 한 번은 그대로 위로');
});
}