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: '시간이 지난 뒤의 ".." 한 번은 그대로 위로'); }); }