통합 시험 4개: E6 (Windows 처음 모양 TC) 뒤로 실패하던 것 - X-plore 를 직접 고르고, TC 로 쓰는 화면은 TC 단계를 더함

- windows_b6_input · rsync_page: 시험이 X-plore 모양을 직접 고름 (TC 는 폴더를 [이름], 파일은 이름 · 확장자를 다른 칸에 보여 이름 글을 찾지 못했다)
- windows_move_left_shot · windows_rsync_narrow_shot: X-plore 시험은 그대로 두고, Total Commander 모양으로 같은 일을 보는 단계를 더함
  (관리자 10/24: 사용자가 TC 로 쓰므로). move_left TC: [D] 고르기 → F6 이동 → 알림 → Enter 로 원본 D 에 들어가 남은 것만.
  rsync_narrow TC: [sub] · [rsub] 고르기 → 좌 → 우 확인 창. 캡처 이름은 …_tc_…

확인: analyze 0 · b6_input 2 ×2 · move_left 12 ×2 · rsync_narrow 12 ×2 · rsync_page 3 ×3
(rsync_page 1회째는 시작 4초 만에 시험 앱이 끝나 did not complete - 시험 논리와 무관, 그 뒤 3회 모두 통과)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
modular
RyuJongJin 2026-10-24 05:49:46 +09:00
parent 8b8cb23fb9
commit f9d4257c0d
4 changed files with 65 additions and 22 deletions

View File

@ -124,6 +124,7 @@ void main() {
await tester.runAsync(findBundledRsync); await tester.runAsync(findBundledRsync);
final c = AppController(PlatformServices.create()); final c = AppController(PlatformServices.create());
c.settings c.settings
..explorer.style = 'xplore' // 이 시험은 X-plore 모양 (E6 부터 Windows 처음 값은 Total Commander - 폴더는 [이름], 파일은 이름 · 확장자가 다른 칸)
..copy.rsyncPaths = [tmp.path, tmp.path] ..copy.rsyncPaths = [tmp.path, tmp.path]
..copy.rsyncOptions = '-avPog --delete --exclude=*.log --exclude=build/ --exclude=/sub/deep/'; ..copy.rsyncOptions = '-avPog --delete --exclude=*.log --exclude=build/ --exclude=/sub/deep/';
final key = GlobalKey(); final key = GlobalKey();
@ -194,6 +195,7 @@ void main() {
await tester.runAsync(findBundledRsync); await tester.runAsync(findBundledRsync);
final c = AppController(PlatformServices.create()); final c = AppController(PlatformServices.create());
c.settings c.settings
..explorer.style = 'xplore' // 이 시험은 X-plore 모양 (E6 부터 Windows 처음 값은 Total Commander - 폴더는 [이름], 파일은 이름 · 확장자가 다른 칸)
..copy.rsyncPaths = [tmp.path, tmp.path] ..copy.rsyncPaths = [tmp.path, tmp.path]
..copy.liveSyncPairs = [LiveSyncPair(cDir, d, method: 'rsync', delete: true)]; ..copy.liveSyncPairs = [LiveSyncPair(cDir, d, method: 'rsync', delete: true)];
final key = GlobalKey(); final key = GlobalKey();

View File

@ -47,6 +47,7 @@ void main() {
..writeAsStringSync('x'); ..writeAsStringSync('x');
c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
c.settings c.settings
..explorer.style = 'xplore' // 이 시험은 X-plore 모양 (E6 부터 Windows 처음 값은 Total Commander - 폴더는 [이름], 파일은 이름 · 확장자가 다른 칸)
..explorer.paths = [left, right] ..explorer.paths = [left, right]
..explorer.dropAction = 'ask'; ..explorer.dropAction = 'ask';
}); });

View File

@ -3,6 +3,7 @@ import 'dart:ui' as ui;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'package:jj_mkvmaker/app/app_controller.dart'; import 'package:jj_mkvmaker/app/app_controller.dart';
@ -26,9 +27,12 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
final out = Platform.environment['JJ_SHOT_DIR'] ?? ''; final out = Platform.environment['JJ_SHOT_DIR'] ?? '';
// 두 모양: X-plore (예전 캡처 그대로) · Total Commander (Windows 처음 값 - 관리자 10/24: 사용자가 TC 로 쓰므로 같은 일을 TC 로도)
for (final style in ['xplore', 'totalcmd']) {
final tc = style == 'totalcmd';
for (final lang in ['ko', 'en', 'ja']) { for (final lang in ['ko', 'en', 'ja']) {
for (final (w, h) in [(960, 600), (1400, 800)]) { for (final (w, h) in [(960, 600), (1400, 800)]) {
testWidgets('264 · 265 이동 뒤 $lang ${w}x$h', (t) async { testWidgets('264 · 265 이동 뒤 $style $lang ${w}x$h', (t) async {
t.view.physicalSize = Size(w.toDouble(), h.toDouble()); t.view.physicalSize = Size(w.toDouble(), h.toDouble());
t.view.devicePixelRatio = 1; t.view.devicePixelRatio = 1;
addTearDown(t.view.reset); addTearDown(t.view.reset);
@ -47,6 +51,7 @@ void main() {
..writeAsStringSync('NEWER'); ..writeAsStringSync('NEWER');
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
c.settings c.settings
..explorer.style = style
..explorer.paths = [left, right] ..explorer.paths = [left, right]
..copy.conflict = 'overwrite'; ..copy.conflict = 'overwrite';
final root = p.rootPrefix(tmp.path); final root = p.rootPrefix(tmp.path);
@ -71,28 +76,56 @@ void main() {
final img = await t.runAsync(() => b.toImage()); final img = await t.runAsync(() => b.toImage());
final bytes = await t.runAsync(() => img!.toByteData(format: ui.ImageByteFormat.png)); final bytes = await t.runAsync(() => img!.toByteData(format: ui.ImageByteFormat.png));
Directory(out).createSync(recursive: true); Directory(out).createSync(recursive: true);
File(p.join(out, 'moveleft_${name}_${lang}_$w.png')).writeAsBytesSync(bytes!.buffer.asUint8List()); File(p.join(out, 'moveleft_${tc ? 'tc_' : ''}${name}_${lang}_$w.png')).writeAsBytesSync(bytes!.buffer.asUint8List());
} }
await settle(() => find.text('D').evaluate().length >= 2); if (!tc) {
// 왼쪽 D 를 눌러 펼치고 (고른 것) 안의 add.txt 가 보이게 await settle(() => find.text('D').evaluate().length >= 2);
await t.tap(find.text('D').first); // 왼쪽 D 를 눌러 펼치고 (고른 것) 안의 add.txt 가 보이게
await settle(() => find.text('add.txt').evaluate().isNotEmpty); await t.tap(find.text('D').first);
await shot('1_before'); await settle(() => find.text('add.txt').evaluate().isNotEmpty);
await t.tap(find.text(tr('이동')).first); await shot('1_before');
await settle(() => find.widgetWithText(FilledButton, tr('이동')).evaluate().isNotEmpty); await t.tap(find.text(tr('이동')).first);
await t.tap(find.widgetWithText(FilledButton, tr('이동'))); await settle(() => find.widgetWithText(FilledButton, tr('이동')).evaluate().isNotEmpty);
await settle(() => find.byType(LinearProgressIndicator).evaluate().isEmpty && find.byType(SnackBar).evaluate().isNotEmpty); await t.tap(find.widgetWithText(FilledButton, tr('이동')));
await settle(() => false, rounds: 10); // 알림이 다 올라오게 await settle(() => find.byType(LinearProgressIndicator).evaluate().isEmpty && find.byType(SnackBar).evaluate().isNotEmpty);
expect(File(p.join(right, 'D', 'add.txt')).existsSync(), isTrue); await settle(() => false, rounds: 10); // 알림이 다 올라오게
expect(File(p.join(d, 'same.txt')).readAsStringSync(), 'old', reason: '받는 쪽이 더 새것이라 원본에 남음'); expect(File(p.join(right, 'D', 'add.txt')).existsSync(), isTrue);
expect(find.textContaining(trf('{0}개는 원본에 남았습니다 (건너뜀).', [1])), findsOneWidget, reason: '264: 알림에'); expect(File(p.join(d, 'same.txt')).readAsStringSync(), 'old', reason: '받는 쪽이 더 새것이라 원본에 남음');
expect(find.text('add.txt'), findsNothing, reason: '265: 원본 D 를 다시 읽음'); expect(find.textContaining(trf('{0}개는 원본에 남았습니다 (건너뜀).', [1])), findsOneWidget, reason: '264: 알림에');
expect(find.text('same.txt'), findsOneWidget); expect(find.text('add.txt'), findsNothing, reason: '265: 원본 D 를 다시 읽음');
expect(find.text('same.txt'), findsOneWidget);
} else {
// TC: 폴더는 [D], 파일은 이름 · 확장자가 다른 칸 (좁으면 한 칸) - 이름 글로 찾는다
Finder named(String stem, String ext) =>
find.byWidgetPredicate((w) => w is Text && (w.data == stem || w.data == '$stem.$ext'));
await settle(() => find.text('[D]').evaluate().length >= 2);
// 왼쪽 [D] 를 한 번 눌러 고르고 (TC: 한 번 = 고르기) F6 = 이동
await t.tap(find.text('[D]').first);
await settle(() => false, rounds: 5);
await shot('1_before');
await t.sendKeyEvent(LogicalKeyboardKey.f6);
await settle(() => find.widgetWithText(FilledButton, tr('이동')).evaluate().isNotEmpty);
await t.tap(find.widgetWithText(FilledButton, tr('이동')));
await settle(() => find.byType(LinearProgressIndicator).evaluate().isEmpty && find.byType(SnackBar).evaluate().isNotEmpty);
await settle(() => false, rounds: 10); // 알림이 다 올라오게
expect(File(p.join(right, 'D', 'add.txt')).existsSync(), isTrue);
expect(File(p.join(d, 'same.txt')).readAsStringSync(), 'old', reason: '받는 쪽이 더 새것이라 원본에 남음');
expect(find.textContaining(trf('{0}개는 원본에 남았습니다 (건너뜀).', [1])), findsOneWidget, reason: '264: 알림에');
// 265: 원본 D 로 들어가면 (TC: Enter = 열기) 옮겨진 것은 없고 남은 것만
ScaffoldMessenger.of(t.element(find.byType(ExplorerPage))).clearSnackBars();
await t.tap(find.text('[D]').first);
await settle(() => false, rounds: 5);
await t.sendKeyEvent(LogicalKeyboardKey.enter);
await settle(() => named('same', 'txt').evaluate().isNotEmpty);
expect(named('add', 'txt'), findsNothing, reason: '265: 원본 D 를 다시 읽음');
expect(named('same', 'txt'), findsOneWidget);
}
await shot('2_after'); await shot('2_after');
ScaffoldMessenger.of(t.element(find.byType(ExplorerPage))).clearSnackBars(); ScaffoldMessenger.of(t.element(find.byType(ExplorerPage))).clearSnackBars();
await settle(() => false, rounds: 20); await settle(() => false, rounds: 20);
}); });
} }
} }
}
} }

View File

@ -26,9 +26,14 @@ void main() {
setUp(() => device = TestExplorerDevice()); setUp(() => device = TestExplorerDevice());
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_rsync_narrow'); final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_rsync_narrow');
// 두 모양: X-plore (예전 캡처 그대로) · Total Commander (Windows 처음 값 - 관리자 10/24: 사용자가 TC 로 쓰므로 같은 일을 TC 로도)
for (final style in ['xplore', 'totalcmd']) {
final tc = style == 'totalcmd';
// TC 모양은 폴더를 [이름] 으로 보인다
String dir(String n) => tc ? '[$n]' : n;
for (final lang in ['ko', 'en', 'ja']) { for (final lang in ['ko', 'en', 'ja']) {
for (final (w, h) in [(412, 900), (960, 600)]) { for (final (w, h) in [(412, 900), (960, 600)]) {
testWidgets('좁은 Rsync 화면 $lang ${w}x$h', (t) async { testWidgets('좁은 Rsync 화면 $style $lang ${w}x$h', (t) async {
t.view.physicalSize = Size(w.toDouble(), h.toDouble()); t.view.physicalSize = Size(w.toDouble(), h.toDouble());
t.view.devicePixelRatio = 1; t.view.devicePixelRatio = 1;
addTearDown(t.view.reset); addTearDown(t.view.reset);
@ -50,6 +55,7 @@ void main() {
device.volumes = () => [(tmp.path, 'T:')]; device.volumes = () => [(tmp.path, 'T:')];
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
c.settings c.settings
..explorer.style = style
..explorer.orientation = 'side' ..explorer.orientation = 'side'
..copy.rsyncPaths = [l, r]; ..copy.rsyncPaths = [l, r];
final shotKey = GlobalKey(); final shotKey = GlobalKey();
@ -73,20 +79,20 @@ void main() {
final img = await t.runAsync(() => b.toImage()); final img = await t.runAsync(() => b.toImage());
final bytes = await t.runAsync(() => img!.toByteData(format: ui.ImageByteFormat.png)); final bytes = await t.runAsync(() => img!.toByteData(format: ui.ImageByteFormat.png));
Directory(out).createSync(recursive: true); Directory(out).createSync(recursive: true);
File(p.join(out, 'rsync_narrow_${name}_${lang}_$w.png')).writeAsBytesSync(bytes!.buffer.asUint8List()); File(p.join(out, 'rsync_narrow_${tc ? 'tc_' : ''}${name}_${lang}_$w.png')).writeAsBytesSync(bytes!.buffer.asUint8List());
} }
await settle(20); await settle(20);
final narrow = find.text('${tr('오른쪽')} · R').evaluate().isNotEmpty; final narrow = find.text('${tr('오른쪽')} · R').evaluate().isNotEmpty;
await t.tap(find.text('sub').hitTestable()); await t.tap(find.text(dir('sub')).hitTestable());
await settle(); await settle();
if (narrow) { if (narrow) {
await t.tap(find.text('${tr('오른쪽')} · R')); await t.tap(find.text('${tr('오른쪽')} · R'));
await settle(15); await settle(15);
expect(find.text('rsub').hitTestable(), findsOneWidget, reason: '[오른쪽] 탭을 처음 보면 지금 폴더'); expect(find.text(dir('rsub')).hitTestable(), findsOneWidget, reason: '[오른쪽] 탭을 처음 보면 지금 폴더');
await shot('1_right_tab'); await shot('1_right_tab');
} }
await t.tap(find.text('rsub').hitTestable()); await t.tap(find.text(dir('rsub')).hitTestable());
await settle(); await settle();
await t.tap(find.text(tr('좌 → 우')).first); await t.tap(find.text(tr('좌 → 우')).first);
await settle(25); await settle(25);
@ -97,4 +103,5 @@ void main() {
}); });
} }
} }
}
} }