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/app/settings.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/platform_services.dart'; import 'package:jj_mkvmaker/ui/explorer_keys.dart'; import 'package:jj_mkvmaker/ui/explorer_keys_page.dart'; import 'package:jj_mkvmaker/ui/explorer_page.dart'; /// X-plore 비교 5-1 ⑥ (관리자 지시 10/21): 파일 탐색기 단축키 바꾸기 - 처음은 83 과 같은 키 그대로, 바꾼 것만 설정에 void main() { const k = LogicalKeyboardKey.keyK; List texts(List l) => l.map(keyText).toList(); test('바꾼 것이 없으면 (처음) 83 의 키 그대로 - Total Commander / 그 밖', () { final tc = effectiveExplorerKeys(tc: true, overrides: {}); expect(texts(tc['copy']!), ['F5']); expect(texts(tc['move']!), ['F6']); expect(texts(tc['newFolder']!), ['F7']); expect(texts(tc['delete']!), ['Delete', 'F8']); expect(texts(tc['refresh']!), ['Ctrl+R']); expect(texts(tc['mark']!), ['Space']); expect(texts(tc['markNext']!), ['Insert']); expect(texts(tc['quickLook']!), ['F3']); final win = effectiveExplorerKeys(tc: false, overrides: {}); expect(texts(win['refresh']!), ['F5']); expect(texts(win['clipCopy']!), ['Ctrl+C']); expect(texts(win['paste']!), ['Ctrl+V']); expect(texts(win['selectAll']!), ['Ctrl+A']); expect(texts(win['quickLook']!), ['Space']); expect(win['copy'], isNull, reason: 'Windows 모양은 F5 가 새로 고침 (복사 키 없음)'); expect(texts(win['deletePermanent']!), ['Shift+Delete']); expect(texts(win['back']!), ['Alt+Arrow Left']); }); test('바꾸기: 그 동작은 새 키만 · 겹치는 다른 동작은 그 키를 잃음 · \'\' 는 없음 · 틀린 값은 처음 키 · 저장 꼴 왕복', () { final spec = keySpec(const SingleActivator(k, control: true, shift: true)); expect(parseKeySpec(spec), isA().having((a) => keyText(a), 'text', 'Ctrl+Shift+K')); final a = effectiveExplorerKeys(tc: false, overrides: {'newFolder': spec, 'paste': '', 'rename': 'zzz'}); expect(texts(a['newFolder']!), ['Ctrl+Shift+K']); expect(a['paste'], isEmpty); expect(texts(a['rename']!), ['F2'], reason: '틀린 값은 처음 키'); // F5 를 복사에 주면 새로 고침은 F5 를 잃음 final b = effectiveExplorerKeys(tc: false, overrides: {'copy': keySpec(const SingleActivator(LogicalKeyboardKey.f5))}); expect(texts(b['copy']!), ['F5']); expect(b['refresh'], isEmpty); expect(keyConflict('copy', const SingleActivator(LogicalKeyboardKey.f5), tc: false, overrides: {}), 'refresh'); expect(keyConflict('copy', const SingleActivator(LogicalKeyboardKey.escape), tc: false, overrides: {}), 'escape'); expect(keyConflict('copy', const SingleActivator(k, control: true), tc: false, overrides: {}), isNull); // 설정 저장 · 읽기 · 틀린 꼴은 비움 final back = AppSettings.fromJson((AppSettings()..explorer.keyOverrides = {'copy': spec}).toJson()); expect(back.explorer.keyOverrides, {'copy': spec}); expect(AppSettings.fromJson({'explorerKeyOverrides': 'x'}).explorer.keyOverrides, isEmpty); expect(AppSettings().explorer.keyOverrides, isEmpty); }); late Directory tmp; setUp(() => tmp = Directory.systemTemp.createTempSync('jj_keys_')); tearDown(() => tmp.deleteSync(recursive: true)); AppController ctl() => AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); testWidgets('탐색기: 바꾼 키 (새 폴더 = Ctrl+Shift+K) 를 누르면 그 동작', (tester) async { tester.view.physicalSize = const Size(1400, 900); tester.view.devicePixelRatio = 1; addTearDown(tester.view.reset); final c = ctl(); c.settings ..explorer.paths = [tmp.path, tmp.path] ..explorer.style = 'windows' ..explorer.keyOverrides = {'newFolder': keySpec(const SingleActivator(k, control: true, shift: true))}; await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(c: c)))); for (var i = 0; i < 20; i++) { await tester.runAsync(() => Future.delayed(const Duration(milliseconds: 30))); await tester.pump(); } await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft); await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft); await tester.sendKeyEvent(k); await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft); await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft); // 전체 실행에서는 목록 읽기 빙글이가 남아 pumpAndSettle 이 끝나지 않을 수 있다 - 창이 뜰 때까지만 for (var i = 0; i < 100 && find.widgetWithText(AlertDialog, '새 폴더').evaluate().isEmpty; i++) { await tester.pump(const Duration(milliseconds: 50)); } expect(find.widgetWithText(AlertDialog, '새 폴더'), findsOneWidget, reason: '바꾼 키로 새 폴더 창'); await tester.tap(find.text('취소').last); await tester.pump(const Duration(milliseconds: 300)); await tester.pumpWidget(const SizedBox()); await tester.pump(const Duration(seconds: 1)); }); testWidgets('설정 화면: [바꾸기] 에서 누른 키 저장 · 겹치면 알림 · [처음 값으로] · [단축키 없음]', (tester) async { tester.view.physicalSize = const Size(1000, 1600); tester.view.devicePixelRatio = 1; addTearDown(tester.view.reset); final c = ctl()..settings.explorer.style = 'windows'; await tester.pumpWidget(MaterialApp(home: ExplorerKeysPage(c: c))); Future change(String label) async { await tester.tap(find.descendant(of: find.widgetWithText(ListTile, label), matching: find.text('바꾸기'))); await tester.pumpAndSettle(); } await change('다른 창으로 복사'); await tester.sendKeyEvent(LogicalKeyboardKey.f5); await tester.pump(); expect(find.textContaining('이미 "새로 고침" 에 쓰는 키입니다'), findsOneWidget); await tester.tap(find.text('저장')); await tester.pumpAndSettle(); expect(c.settings.explorer.keyOverrides['copy'], keySpec(const SingleActivator(LogicalKeyboardKey.f5))); expect(find.descendant(of: find.widgetWithText(ListTile, '새로 고침'), matching: find.text('(없음)')), findsOneWidget, reason: '겹친 동작은 키를 잃음'); // 처음 값으로 await tester.tap(find.descendant(of: find.widgetWithText(ListTile, '다른 창으로 복사'), matching: find.text('처음 값으로'))); await tester.pumpAndSettle(); expect(c.settings.explorer.keyOverrides, isEmpty); // 단축키 없음 await change('붙여넣기'); await tester.tap(find.text('단축키 없음')); await tester.pumpAndSettle(); expect(c.settings.explorer.keyOverrides['paste'], ''); // Esc 는 저장할 수 없음 await change('삭제'); await tester.sendKeyEvent(LogicalKeyboardKey.escape); await tester.pump(); if (find.byType(AlertDialog).evaluate().isNotEmpty) { expect(tester.widget(find.widgetWithText(FilledButton, '저장')).onPressed, isNull); } }); }