diff --git a/assets/l10n/en.json b/assets/l10n/en.json index eb6ed4d..034a7de 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -2023,6 +2023,7 @@ "열 수 없습니다: {0}": "Cannot open: {0}", "열 수 없음": "Cannot open", "열기": "Open", + "열기 (폴더는 들어가기)": "Open (enter folders)", "열기 · 재생": "Open · Play", "열어 본 때: {0}": "Accessed: {0}", "영구 삭제": "Delete permanently", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index 5f45383..3d69a59 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -2023,6 +2023,7 @@ "열 수 없습니다: {0}": "開けません: {0}", "열 수 없음": "開けません", "열기": "開く", + "열기 (폴더는 들어가기)": "開く (フォルダーは中へ)", "열기 · 재생": "開く・再生", "열어 본 때: {0}": "アクセス日時: {0}", "영구 삭제": "完全に削除", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index 31497c8..e5ab4b9 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -2023,6 +2023,7 @@ "열 수 없습니다: {0}": "无法打开: {0}", "열 수 없음": "无法打开", "열기": "打开", + "열기 (폴더는 들어가기)": "打开 (文件夹则进入)", "열기 · 재생": "打开 · 播放", "열어 본 때: {0}": "访问时间: {0}", "영구 삭제": "永久删除", diff --git a/integration_test/explorer_use_check_test.dart b/integration_test/explorer_use_check_test.dart index 881ea95..8c8cb70 100644 --- a/integration_test/explorer_use_check_test.dart +++ b/integration_test/explorer_use_check_test.dart @@ -120,12 +120,26 @@ void main() { await settle(3); } + // 복사 · 이동 · 지우기는 받는 폴더의 시각을 지금으로 바꾼다 - TC 는 폴더 날짜 칸이 있어 다시 고정하고 새로 읽는다 (Ctrl+R) + Future fixDirTimes() async { + await t.runAsync(() => Process.run('powershell', [ + '-NoProfile', + '-Command', + "Get-ChildItem -LiteralPath '${tmp.path}' -Recurse -Directory | ForEach-Object { \$_.LastWriteTime = [datetime]'2026-10-22 09:00' }", + ])); + await t.sendKeyDownEvent(LogicalKeyboardKey.controlLeft); + await t.sendKeyEvent(LogicalKeyboardKey.keyR); + await t.sendKeyUpEvent(LogicalKeyboardKey.controlLeft); + await settle(); + } + Future jobsGone() async { for (var i = 0; i < 100 && find.byType(TransferJobView).evaluate().isNotEmpty; i++) { await t.runAsync(() => Future.delayed(const Duration(milliseconds: 100))); await t.pump(const Duration(milliseconds: 100)); } await settle(); + if (style == 'totalcmd') await fixDirTimes(); } // 한 단계: 실패해도 다음 단계로 (못 찾은 것 자체가 점검 결과) @@ -270,25 +284,11 @@ void main() { }); // TC 전용 (관리자 지시 10/23): 목록 들어가고 나오기 (..) · 아래 단추 줄 F5 복사 · F6 이동 · F8 삭제 · 지운 뒤 초점 if (style == 'totalcmd') { - // 복사 · 이동 · 지우기는 받는 폴더의 시각을 지금으로 바꾼다 - TC 는 폴더 날짜 칸이 있어 다시 고정하고 새로 읽는다 (Ctrl+R) - Future fixDirTimes() async { - await t.runAsync(() => Process.run('powershell', [ - '-NoProfile', - '-Command', - "Get-ChildItem -LiteralPath '${tmp.path}' -Recurse -Directory | ForEach-Object { \$_.LastWriteTime = [datetime]'2026-10-22 09:00' }", - ])); - await t.sendKeyDownEvent(LogicalKeyboardKey.controlLeft); - await t.sendKeyEvent(LogicalKeyboardKey.keyR); - await t.sendKeyUpEvent(LogicalKeyboardKey.controlLeft); - await settle(); - } - Future fkey(String tip) async { await t.tap(find.byTooltip(tip)); await settle(); if (find.byType(Dialog).evaluate().isNotEmpty) await okDialog(); - await jobsGone(); - await fixDirTimes(); + await jobsGone(); // TC 는 jobsGone 이 폴더 시각을 다시 고정한다 } await noticeTimesOut(); @@ -307,18 +307,26 @@ void main() { await settle(); } }); - // 들어가기 · 나오기 (".." 두 번 누르기는 단위 약속 시험 explorer_tc_look_test 가 본다 - 이 실제 시간 시험에서는 두 번 누르기가 - // 열기로 잡히지 않았다): 줄 메뉴 [열기] 로 들어가고, 위 폴더 키 (Backspace) 로 나온다 - await step('TC - [Docs] 메뉴 열기 (들어가기)', () async { - await t.tap(txt('Docs'), buttons: kSecondaryMouseButton); + // 들어가기 · 나오기 (관리자 10/23: TC 는 한 번 = 고르기, 두 번 · Enter = 열기). 자리를 한 번만 재고 실제 시간 60ms 사이로 두 번 + Future doubleTapOn(Finder f) async { + final at = t.getCenter(f); + await t.tapAt(at); + await t.runAsync(() => Future.delayed(const Duration(milliseconds: 60))); + await t.tapAt(at); await settle(); - await t.tap(find.text('열기').last); - await settle(); - }); - await step('TC - Backspace (나오기)', () async { - await t.sendKeyEvent(LogicalKeyboardKey.backspace); + } + + await step('TC - [Docs] 한 번 (고르기만)', () async { + await t.tap(txt('Docs')); + await settle(10); + }); + await step('TC - Enter (들어가기)', () async { + await t.sendKeyEvent(LogicalKeyboardKey.enter); await settle(); }); + await step('TC - .. 두 번 (나오기)', () => doubleTapOn(leftOf(find.text('..')))); + await step('TC - [Docs] 두 번 (들어가기)', () => doubleTapOn(txt('Docs'))); + await step('TC - .. 두 번 (다시 나오기)', () => doubleTapOn(leftOf(find.text('..')))); await step('TC - notes 고르고 F5 복사', () async { await t.tap(txt('notes.txt')); await settle(10); // 한 번 누르기는 두 번 누르기 기다림 (약 300ms) 뒤에 처리된다 @@ -338,6 +346,21 @@ void main() { }); files('TC 단계 뒤'); } + // 관리자 10/23: "폴더 + 파일 목록" 배치 - 목록의 폴더는 한 번에 열리고, 습관처럼 곧바로 누른 두 번째 누르기는 버린다 (도로 나오지 않음) + if (style == 'xplore' && w >= 960) { + await step('목록 배치 - 폴더 + 파일 목록으로', () async { + await c.updateSettings((x) => x.explorer.layout = 'split'); + await settle(20); + }); + await step('목록 배치 - 오른쪽 목록 Movies 한 번 + 곧바로 두 번째', () async { + final row = find.text('Movies').last; // 오른쪽 목록의 줄 + final at = t.getCenter(row); + await t.tapAt(at); + await t.runAsync(() => Future.delayed(const Duration(milliseconds: 60))); + await t.tapAt(at); + await settle(20); + }); + } // 끝 상태: 실제 파일 final left = [ for (final e in tmp.listSync(recursive: true)) diff --git a/lib/core/selection.dart b/lib/core/selection.dart index d438fac..e11516e 100644 --- a/lib/core/selection.dart +++ b/lib/core/selection.dart @@ -65,27 +65,32 @@ class ClickPolicy { /// 두 번째 누르기로 방금 펼친 것을 접지 않게). /// 사용자 설정 (처음 값은 규칙대로): /// [tapOpens] 파일 한 번 누르기 = 바로 실행 (설정 "바로 열기", 이때 두 번 누르기는 받지 않음) · - /// [retapCollapses] 펼친 폴더를 다시 누르면 접기 (끄면 [ClickAction.expand] - 화면은 펼친 폴더를 지금 폴더로만) + /// [retapCollapses] 펼친 폴더를 다시 누르면 접기 (끄면 [ClickAction.expand] - 화면은 펼친 폴더를 지금 폴더로만) · + /// [doubleOpens] 폴더 · ".." 도 한 번 = 고르기, 두 번 = 열기 (Total Commander 모양 - 한 번에 열면 두 번 누른 사람은 두 번째 누르기가 + /// 같은 자리의 ".." 에 맞아 도로 나왔다, 관리자 10/23) static ClickAction decide(Press press, ItemKind kind, {required bool selectMode, bool expanded = false, bool list = false, bool repeat = false, bool tapOpens = false, - bool retapCollapses = true}) { + bool retapCollapses = true, + bool doubleOpens = false}) { final real = kind == ItemKind.file || kind == ItemKind.folder; switch (press) { case Press.longPress: case Press.secondary: return real ? ClickAction.menu : ClickAction.none; case Press.doubleTap: - // 두 번 = 실행 (파일만, 선택 모드 밖). 폴더 · 선택 모드 · 바로 열기는 한 번 누르기로 이미 처리됨 + // 두 번 = 실행 (파일만, 선택 모드 밖). 폴더 · 선택 모드 · 바로 열기는 한 번 누르기로 이미 처리됨. + // [doubleOpens] 면 폴더 · ".." 도 두 번 = 열기 + if (doubleOpens && !selectMode && (kind == ItemKind.folder || kind == ItemKind.up)) return ClickAction.open; return kind == ItemKind.file && !selectMode && !tapOpens ? ClickAction.open : ClickAction.none; case Press.tap: - if (kind == ItemKind.up) return ClickAction.open; + if (kind == ItemKind.up) return doubleOpens ? ClickAction.focus : ClickAction.open; if (selectMode && real) return ClickAction.toggleMark; if (kind == ItemKind.file) return tapOpens ? ClickAction.open : ClickAction.focus; - if (list && kind == ItemKind.folder) return ClickAction.open; + if (list && kind == ItemKind.folder) return doubleOpens ? ClickAction.focus : ClickAction.open; if (!expanded || !retapCollapses) return ClickAction.expand; return repeat ? ClickAction.none : ClickAction.collapse; } diff --git a/lib/ui/explorer/explorer_select.dart b/lib/ui/explorer/explorer_select.dart index d63132c..52ec630 100644 --- a/lib/ui/explorer/explorer_select.dart +++ b/lib/ui/explorer/explorer_select.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart' show kDoubleTapTimeout; +import 'package:flutter/scheduler.dart' show SchedulerBinding; import 'package:flutter/services.dart' show HardwareKeyboard; import '../../app/archive_browse.dart'; @@ -9,6 +10,7 @@ import '../../core/explorer/tree_rows.dart'; import '../../core/file_ops.dart' show FileEntry, isSameOrInside, samePath; import '../../core/selection.dart'; import '../../core/vfs.dart' show isDav, vDirname; +import '../explorer_look.dart' show ExplorerStyle; import '../file_error.dart' show explainFileError; import 'explorer_controller.dart'; import 'explorer_pane.dart'; @@ -50,6 +52,9 @@ class ExplorerSelect extends ChangeNotifier { ctl.update(); } + /// TC 모양의 목록: 폴더 · ".." 도 두 번 · Enter 로 연다 ([ClickPolicy.decide] 의 doubleOpens) + bool doubleOpens({required bool list}) => list && !ctl.rsync && ctl.look == ExplorerStyle.totalcmd; + /// 줄의 종류 ([ClickPolicy] 에 넘김) static ItemKind kindOf(TreeRow row) => row.isUp ? ItemKind.up @@ -73,6 +78,12 @@ class ExplorerSelect extends ChangeNotifier { if (!selecting) setSelecting(true); return toggleMark(pane, e); } + // 관리자 10/23: 목록에서 한 번 눌러 폴더를 연 직후, 두 번 누르기 시간 안에 온 다음 누르기는 버린다 - 습관처럼 두 번 누르면 + // 두 번째 누르기가 같은 자리에 온 ".." · 다른 줄에 맞아 도로 나오거나 엉뚱한 것을 열었다. 마지막 틀 시각으로 잰다 (누르기의 잉크가 + // 틀을 그려 누를 때마다 새로 정해진다 · 시험은 시험 시간) + final frame = SchedulerBinding.instance.currentSystemFrameTimeStamp; + final opened = _listOpenedAt; + if (list && opened != null && frame - opened < kDoubleTapTimeout) return; final now = clock(); final last = _lastFolderTap; _lastFolderTap = (e.path, now); @@ -83,10 +94,15 @@ class ExplorerSelect extends ChangeNotifier { list: list, repeat: repeat, tapOpens: !selectMode, - retapCollapses: ctl.c.settings.explorer.retapCollapse); + retapCollapses: ctl.c.settings.explorer.retapCollapse, + doubleOpens: doubleOpens(list: list)); + if (list && act == ClickAction.open && (row.isUp || e.isDir)) _listOpenedAt = frame; await _act(pane, row, act, list: list); } + /// 목록에서 한 번 누르기로 폴더 · ".." 를 연 틀 시각 (그 직후의 두 번째 누르기를 버리려고) + Duration? _listOpenedAt; + Future _act(ExplorerPane pane, TreeRow row, ClickAction act, {bool list = false}) async { final e = row.entry; switch (act) { diff --git a/lib/ui/explorer/key_flow.dart b/lib/ui/explorer/key_flow.dart index 82f1f24..fb3307d 100644 --- a/lib/ui/explorer/key_flow.dart +++ b/lib/ui/explorer/key_flow.dart @@ -5,7 +5,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../core/file_ops.dart' show samePath; -import '../../core/vfs.dart' show vDirname; +import '../../core/vfs.dart' show vDirname, vIsDirSync; +import '../explorer_look.dart' show ExplorerStyle; import '../explorer_keys.dart' show defaultExplorerKeys, effectiveExplorerKeys; import 'explorer_buttons.dart'; import 'explorer_controller.dart'; @@ -105,6 +106,17 @@ class ExplorerInput { // 75: 지금 창의 뒤로 · 앞으로 'back': () => ctl.travel(ctl.panes[ctl.active], forward: false), 'forward': () => ctl.travel(ctl.panes[ctl.active], forward: true), + // TC 모양 Enter: 지금 줄을 연다 - 폴더 (".." 은 위 폴더) 는 들어가기, 파일은 열기 + 'open': () { + final pane = ctl.pane; + final f = pane.focused; + if (f == null) return; + if (vIsDirSync(f)) { + ctl.goTo(pane, f); + } else { + flows.opener.open(context, f, pane: pane); + } + }, 'switchPane': () { ctl ..active = dual() ? 1 - ctl.active : 0 @@ -117,7 +129,7 @@ class ExplorerInput { if (const {'delete', 'deletePermanent', 'rename', 'refresh', 'filter', 'up', 'back', 'forward', 'switchPane'}.contains(e.key)) e.key: e.value, } - : effectiveExplorerKeys(tc: _tcKeys, overrides: ctl.c.settings.explorer.keyOverrides); + : effectiveExplorerKeys(tc: _tcKeys, tcLook: ctl.look == ExplorerStyle.totalcmd, overrides: ctl.c.settings.explorer.keyOverrides); return { for (final e in keys.entries) if (cb[e.key] != null) diff --git a/lib/ui/explorer/row_view.dart b/lib/ui/explorer/row_view.dart index 593df58..4b2e9ff 100644 --- a/lib/ui/explorer/row_view.dart +++ b/lib/ui/explorer/row_view.dart @@ -237,7 +237,8 @@ class ExplorerRowView extends StatelessWidget { onTap: () => sel.onTap(pane, row, list: list), // 선택 모드: 두 번 누르면 열기 (바로 열기 모드는 두 번 누르기를 받지 않음 - 한 번 누르기가 늦어지지 않게) // 폴더는 한 번 누르기로 바로 펼치므로 두 번 누르기를 받지 않는다 (기다리지 않게) - onDoubleTap: ClickPolicy.decide(Press.doubleTap, ExplorerSelect.kindOf(row), selectMode: sel.selecting, tapOpens: !sel.selectMode) == + onDoubleTap: ClickPolicy.decide(Press.doubleTap, ExplorerSelect.kindOf(row), + selectMode: sel.selecting, tapOpens: !sel.selectMode, doubleOpens: sel.doubleOpens(list: list)) == ClickAction.open ? () => sel.onOpen(pane, row, list: list) : null, diff --git a/lib/ui/explorer_keys.dart b/lib/ui/explorer_keys.dart index 1afcf11..e6c4b1f 100644 --- a/lib/ui/explorer_keys.dart +++ b/lib/ui/explorer_keys.dart @@ -8,7 +8,7 @@ import '../l10n/tr.dart'; /// 바꾼 키가 다른 동작의 키와 같으면 바꾼 쪽이 이기고 그 동작은 그 키를 잃는다 (설정 화면이 먼저 알린다). Esc 는 바꾸지 않는다. const explorerKeyActions = [ 'copy', 'move', 'newFolder', 'delete', 'deletePermanent', 'rename', 'refresh', 'quickLook', // - 'clipCopy', 'clipCut', 'paste', 'selectAll', 'mark', 'markNext', 'filter', 'up', 'back', 'forward', 'switchPane', + 'clipCopy', 'clipCut', 'paste', 'selectAll', 'mark', 'markNext', 'filter', 'up', 'back', 'forward', 'switchPane', 'open', ]; /// 동작 이름 (화면 글) @@ -32,11 +32,14 @@ String explorerKeyLabel(String id) => switch (id) { 'back' => tr('뒤로'), 'forward' => tr('앞으로'), 'switchPane' => tr('다른 창으로 옮겨 가기'), + 'open' => tr('열기 (폴더는 들어가기)'), _ => id, }; -/// 처음 키. [tc] Total Commander 키 (규칙 1, 10/22: Windows 는 스타일과 상관없이 TC 키 + Ctrl+C · X · V · A) -Map> defaultExplorerKeys({required bool tc}) => { +/// 처음 키. [tc] Total Commander 키 (규칙 1, 10/22: Windows 는 스타일과 상관없이 TC 키 + Ctrl+C · X · V · A). +/// [tcLook] Total Commander 모양 - Enter = 열기 (관리자 10/23, 다른 모양의 Enter 는 그대로 - 처음 키 없음) +Map> defaultExplorerKeys({required bool tc, bool tcLook = false}) => { + if (tcLook) 'open': [const SingleActivator(LogicalKeyboardKey.enter), const SingleActivator(LogicalKeyboardKey.numpadEnter)], 'delete': [const SingleActivator(LogicalKeyboardKey.delete), if (tc) const SingleActivator(LogicalKeyboardKey.f8)], 'deletePermanent': [const SingleActivator(LogicalKeyboardKey.delete, shift: true)], 'rename': [const SingleActivator(LogicalKeyboardKey.f2)], @@ -93,8 +96,8 @@ String keyText(SingleActivator a) { } /// 바꾼 것을 넣은 동작마다의 키. 바꾼 키는 다른 동작에서 뺀다 -Map> effectiveExplorerKeys({required bool tc, required Map overrides}) { - final out = {for (final e in defaultExplorerKeys(tc: tc).entries) e.key: [...e.value]}; +Map> effectiveExplorerKeys({required bool tc, bool tcLook = false, required Map overrides}) { + final out = {for (final e in defaultExplorerKeys(tc: tc, tcLook: tcLook).entries) e.key: [...e.value]}; final mine = {}; for (final e in overrides.entries) { if (!explorerKeyActions.contains(e.key)) continue; @@ -115,9 +118,9 @@ Map> effectiveExplorerKeys({required bool tc, requ } /// [action] 에 [key] 를 주면 키를 잃는 다른 동작 (없으면 null). Esc 는 쓸 수 없음 ('escape') -String? keyConflict(String action, SingleActivator key, {required bool tc, required Map overrides}) { +String? keyConflict(String action, SingleActivator key, {required bool tc, bool tcLook = false, required Map overrides}) { if (key.trigger == LogicalKeyboardKey.escape) return 'escape'; - final now = effectiveExplorerKeys(tc: tc, overrides: overrides); + final now = effectiveExplorerKeys(tc: tc, tcLook: tcLook, overrides: overrides); for (final e in now.entries) { if (e.key != action && e.value.any((x) => sameKey(x, key))) return e.key; } diff --git a/lib/ui/explorer_keys_page.dart b/lib/ui/explorer_keys_page.dart index a4cef9f..80c79ee 100644 --- a/lib/ui/explorer_keys_page.dart +++ b/lib/ui/explorer_keys_page.dart @@ -34,7 +34,7 @@ class ExplorerKeysPage extends StatelessWidget { listenable: c, builder: (context, _) { final ov = c.settings.explorer.keyOverrides; - final keys = effectiveExplorerKeys(tc: _tc, overrides: ov); + final keys = effectiveExplorerKeys(tc: _tc, tcLook: _tc, overrides: ov); return Scaffold( appBar: AppBar( title: Text(tr('파일 탐색기 단축키')), @@ -101,7 +101,7 @@ class _KeyCaptureDialogState extends State<_KeyCaptureDialog> { @override Widget build(BuildContext context) { final k = _key; - final clash = k == null ? null : keyConflict(widget.action, k, tc: widget.tc, overrides: widget.c.settings.explorer.keyOverrides); + final clash = k == null ? null : keyConflict(widget.action, k, tc: widget.tc, tcLook: widget.tc, overrides: widget.c.settings.explorer.keyOverrides); return AlertDialog( title: Text(trf('{0} - 단축키', [explorerKeyLabel(widget.action)])), content: Focus( diff --git a/test/explorer_tc_look_test.dart b/test/explorer_tc_look_test.dart index 805636e..24c89d5 100644 --- a/test/explorer_tc_look_test.dart +++ b/test/explorer_tc_look_test.dart @@ -1,6 +1,7 @@ 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'; @@ -98,4 +99,37 @@ void main() { 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: '시간이 지난 뒤의 ".." 한 번은 그대로 위로'); + }); } diff --git a/test/selection_contract_test.dart b/test/selection_contract_test.dart index 007d2a5..ea315f9 100644 --- a/test/selection_contract_test.dart +++ b/test/selection_contract_test.dart @@ -35,6 +35,16 @@ void main() { } test('사용자 설정: 바로 열기 · 다시 눌러 접지 않기', () { expect(ClickPolicy.decide(Press.tap, ItemKind.file, selectMode: false, tapOpens: true), ClickAction.open); + // TC 모양 (doubleOpens, 관리자 10/23): 폴더 · ".." 도 한 번 = 고르기, 두 번 = 열기 (선택 모드는 그대로 표시) + expect(ClickPolicy.decide(Press.tap, ItemKind.folder, selectMode: false, list: true, doubleOpens: true), ClickAction.focus); + expect(ClickPolicy.decide(Press.tap, ItemKind.up, selectMode: false, list: true, doubleOpens: true), ClickAction.focus); + expect(ClickPolicy.decide(Press.doubleTap, ItemKind.folder, selectMode: false, list: true, doubleOpens: true), ClickAction.open); + expect(ClickPolicy.decide(Press.doubleTap, ItemKind.up, selectMode: false, list: true, doubleOpens: true), ClickAction.open); + expect(ClickPolicy.decide(Press.tap, ItemKind.folder, selectMode: true, list: true, doubleOpens: true), ClickAction.toggleMark); + expect(ClickPolicy.decide(Press.doubleTap, ItemKind.folder, selectMode: true, list: true, doubleOpens: true), ClickAction.none); + // 그 밖의 목록 (폴더 + 파일 목록) 은 한 번 = 열기 그대로 + expect(ClickPolicy.decide(Press.tap, ItemKind.folder, selectMode: false, list: true), ClickAction.open); + expect(ClickPolicy.decide(Press.doubleTap, ItemKind.folder, selectMode: false, list: true), ClickAction.none); expect(ClickPolicy.decide(Press.doubleTap, ItemKind.file, selectMode: false, tapOpens: true), ClickAction.none); expect(ClickPolicy.decide(Press.tap, ItemKind.file, selectMode: true, tapOpens: true), ClickAction.toggleMark); expect(ClickPolicy.decide(Press.tap, ItemKind.folder, selectMode: false, expanded: true, retapCollapses: false),