From af77dc56d2cc6e5b91b5b1574a040204b5f287fb Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Wed, 21 Oct 2026 12:21:45 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BA=A1=EC=B2=98=20=EC=8B=9C=ED=97=98:=20253?= =?UTF-8?q?=20=C2=B7=20255=20=C2=B7=20256=20-=20=ED=81=B0=20=EA=B8=80?= =?UTF-8?q?=EC=9E=90=20=C2=B7=20=ED=8F=B0=20=ED=81=AC=EA=B8=B0=EC=97=90?= =?UTF-8?q?=EC=84=9C=206=EA=B0=9C=20=ED=99=94=EB=A9=B4=20=EC=8B=A4?= =?UTF-8?q?=EC=A0=9C=20=EA=B8=80=EA=BC=B4=EB=A1=9C=20(=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EC=A7=80=EC=8B=9C=2010/21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - windows_bigtext_shot_test: test/large_text_test 의 화면 6개 (MKV · 파일 탐색기 · Rsync · 모니터링 · 다운로드 · 환경 설정) 를 실제 글꼴로, 글자 1.6배 (412×900 · 900×412 · 1280×800) · 글자 1.0 (412×900 · 900×412) × ko · ja = 60개. 넘침 없음 · 누르는 것이 화면 폭 안 (가로로 넘기는 막대 안은 뺌). MKV 는 동영상 5개 · 작업 기록 3줄 (256: 폰 가로에서 기록을 켜도 목록이 3줄 보임) - 정식 판 후보 1b31609 로 60개 통과 (release\test\shots_v20261020_001 bigtext_*) Co-Authored-By: Claude Opus 5.5 --- .../windows_bigtext_shot_test.dart | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 integration_test/windows_bigtext_shot_test.dart diff --git a/integration_test/windows_bigtext_shot_test.dart b/integration_test/windows_bigtext_shot_test.dart new file mode 100644 index 0000000..b3d50c1 --- /dev/null +++ b/integration_test/windows_bigtext_shot_test.dart @@ -0,0 +1,117 @@ +import 'dart:io'; +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:jj_mkvmaker/app/app_controller.dart'; +import 'package:jj_mkvmaker/app/download_manager.dart'; +import 'package:jj_mkvmaker/app/i18n_controller.dart'; +import 'package:jj_mkvmaker/app/settings.dart'; +import 'package:jj_mkvmaker/core/models.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/downloads_page.dart'; +import 'package:jj_mkvmaker/ui/explorer_page.dart'; +import 'package:jj_mkvmaker/ui/home_page.dart'; +import 'package:jj_mkvmaker/ui/monitor_page.dart'; +import 'package:jj_mkvmaker/ui/settings_page.dart'; +import 'package:jj_mkvmaker/ui/theme.dart'; +import 'package:path/path.dart' as p; + +import 'support/temp_dir.dart'; + +/// 253 · 255 · 256 (관리자 지시 10/21): test/large_text_test 의 화면들을 실제 글꼴로 - 글자 1.6배 (412×900 · 900×412 · 1280×800) 와 +/// 폰 크기 글자 1.0 (412×900 세로 · 900×412 가로) 에서 넘치지 않고, 누르는 것 (단추 · 고르기 · 입력 칸) 이 화면 폭 안에 있다. ko · ja +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + final out = Platform.environment['JJ_SHOT_DIR'] ?? ''; + final pages = { + 'mkv': (c, d) => HomePage(c: c), + 'explorer': (c, d) => ExplorerPage(c: c), + 'rsync': (c, d) => ExplorerPage(c: c, rsync: true), + 'monitor': (c, d) => MonitorPage(c: c), + 'downloads': (c, d) => DownloadsPage(d: d), + 'settings': (c, d) => SettingsPage(c: c), + }; + final cases = [ + for (final s in [const Size(412, 900), const Size(900, 412), const Size(1280, 800)]) (s, 1.6), + for (final s in [const Size(412, 900), const Size(900, 412)]) (s, 1.0), + ]; + for (final lang in ['ko', 'ja']) { + for (final MapEntry(key: name, value: page) in pages.entries) { + for (final (size, scale) in cases) { + final tag = '${name}_${lang}_${size.width.round()}x${size.height.round()}_s$scale'; + testWidgets('253 · 255 · 256 $tag', (t) async { + t.view.physicalSize = size; + t.view.devicePixelRatio = 1; + addTearDown(t.view.reset); + await t.runAsync(() => i18n.apply(lang, save: false)); + addTearDown(() => i18n.apply('ko', save: false)); + final tmp = testTempDir('jj_bigtext_'); + appTempInto(tmp); + final video = p.join(tmp.path, '아주 긴 이름의 동영상 파일 - 시험용 2026.mp4'); + File(video).writeAsStringSync('v'); + Directory(p.join(tmp.path, 'sub')).createSync(); + final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings + ..explorerPaths = [tmp.path, tmp.path] + ..rsyncPaths = [tmp.path, tmp.path] + ..copyTasks = [CopyTask(id: '1', sources: [tmp.path], dest: p.join(tmp.path, 'sub'), method: 'rsync', options: '-av')] + ..liveSyncPairs = [LiveSyncPair(tmp.path, p.join(tmp.path, 'x'))]; + c.videos.add(VideoItem(video)); + // 256: 폰 가로에서 작업 기록을 켜도 목록이 여러 줄 보이는지 - 동영상 5개 · 기록 몇 줄 + for (var i = 2; i <= 5; i++) { + final f = p.join(tmp.path, '에피소드 0$i.mp4'); + File(f).writeAsStringSync('v'); + c.videos.add(VideoItem(f)); + } + for (var i = 1; i <= 3; i++) { + c.note('시험 기록 $i'); + } + c.selected = c.videos.first; + final d = DownloadManager(backends: const [], settings: () => c.settings, readClipboard: () async => null); + addTearDown(d.dispose); + final shotKey = GlobalKey(); + await t.pumpWidget(RepaintBoundary( + key: shotKey, + child: MaterialApp( + theme: buildTheme(), + debugShowCheckedModeBanner: false, + builder: (ctx, child) => MediaQuery(data: MediaQuery.of(ctx).copyWith(textScaler: TextScaler.linear(scale)), child: child!), + home: page(c, d), + ), + )); + for (var i = 0; i < 15; i++) { + await t.runAsync(() => Future.delayed(const Duration(milliseconds: 40))); + await t.pump(); + } + if (out.isNotEmpty) { + final r = shotKey.currentContext!.findRenderObject()! as RenderRepaintBoundary; + final img = await t.runAsync(() => r.toImage()); + final bytes = await t.runAsync(() => img!.toByteData(format: ui.ImageByteFormat.png)); + Directory(out).createSync(recursive: true); + File(p.join(out, 'bigtext_$tag.png')).writeAsBytesSync(bytes!.buffer.asUint8List()); + } + expect(t.takeException(), isNull, reason: '$tag 넘침'); + // 넘침 오류 없이 옆으로 밀려나는 것도 본다: 누르는 것은 화면 폭 안 (가로로 넘기는 막대 안의 것은 빼고) + final hit = find.byWidgetPredicate((w) => + w is ButtonStyleButton || w is IconButton || w is Checkbox || w is DropdownButton || w is TextField || w is ChoiceChip || w is Radio); + final off = []; + for (final e in hit.evaluate()) { + final inRow = e.findAncestorWidgetOfExactType()?.scrollDirection == Axis.horizontal || + e.findAncestorWidgetOfExactType()?.scrollDirection == Axis.horizontal; + if (inRow) continue; + final r = t.getRect(find.byElementPredicate((x) => x == e)); + if (r.left < -0.5 || r.right > size.width + 0.5) off.add('${e.widget.runtimeType} $r'); + } + expect(off, isEmpty, reason: '$tag: 화면 밖 ${off.join(' · ')}'); + await t.pumpWidget(const SizedBox()); + await t.pump(const Duration(seconds: 1)); + }); + } + } + } +}