diff --git a/integration_test/windows_ftp_shot_test.dart b/integration_test/windows_ftp_shot_test.dart new file mode 100644 index 0000000..af05fb7 --- /dev/null +++ b/integration_test/windows_ftp_shot_test.dart @@ -0,0 +1,94 @@ +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/i18n_controller.dart'; +import 'package:jj_mkvmaker/core/ftp.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/ftp_settings.dart'; +import 'package:jj_mkvmaker/ui/theme.dart'; +import 'package:path/path.dart' as p; + +/// 관리자 지시 (10/18): FTP · FTPS 화면을 PNG 로 (JJ_SHOT_DIR) - 서버 추가 창 (FTPS 처음 값 · 평문 경고) · 인증서 지문 확인 창 · 설정 목록. +/// 크기 412 · 960, 언어 ko · en · ja. 설정은 메모리에만. 실행: flutter test integration_test\windows_ftp_shot_test.dart -d windows +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_ftp'); + for (final lang in ['ko', 'en', 'ja']) { + for (final (w, h) in [(412, 900), (960, 600)]) { + testWidgets('FTP 화면 $lang ${w}x$h', (t) async { + t.view.physicalSize = Size(w.toDouble(), h.toDouble()); + t.view.devicePixelRatio = 1; + addTearDown(t.view.reset); + await t.runAsync(() => i18n.apply(lang, save: false)); + addTearDown(() => i18n.apply('ko', save: false)); + final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.ftpServers = [ + const FtpServer(id: 'f1', name: 'NAS FTPS', host: '192.168.0.15', user: 'jj'), + const FtpServer(id: 'f2', name: '옛 공유기', host: '192.168.0.1', security: 'none'), + ]; + final navKey = GlobalKey(); + final shotKey = GlobalKey(); + await t.pumpWidget(RepaintBoundary( + key: shotKey, + child: MaterialApp( + theme: buildTheme(), + debugShowCheckedModeBanner: false, + navigatorKey: navKey, + home: Scaffold(body: SingleChildScrollView(child: FtpSettings(c: c)))), + )); + BuildContext ctx() => navKey.currentContext!; + Future settle([int n = 8]) async { + for (var i = 0; i < n; i++) { + await t.runAsync(() => Future.delayed(const Duration(milliseconds: 60))); + await t.pump(); + } + } + + Future shot(String name) async { + await settle(3); + final b = shotKey.currentContext!.findRenderObject()! as RenderRepaintBoundary; + final img = await t.runAsync(() => b.toImage()); + final bytes = await t.runAsync(() => img!.toByteData(format: ui.ImageByteFormat.png)); + Directory(out).createSync(recursive: true); + File(p.join(out, 'ftp_${name}_${lang}_$w.png')).writeAsBytesSync(bytes!.buffer.asUint8List()); + } + + Future closeAll() async { + navKey.currentState!.popUntil((r) => r.isFirst); + await settle(4); + } + + await settle(); + await shot('list'); + // 서버 추가 창 (처음 값 FTPS 명시) + editFtpServer(ctx(), c); + await settle(); + await shot('add'); + // 평문 FTP 를 고르면 빨간 경고 + await t.tap(find.textContaining(RegExp('FTP \\(|平文|明文|Plain')).last); + await settle(); + await t.scrollUntilVisible(find.byType(TextField).at(2), 100, scrollable: find.byType(Scrollable).last); + await settle(); + await shot('add_plain'); + await closeAll(); + // 자체 서명 인증서 확인 창 + askTrustFtpCert( + ctx(), + 'NAS FTPS', + const FtpCertException('x', FtpFailure.certUnknown, + fingerprint: 'SHA256:E8:A8:51:BE:6E:78:58:B6:AC:53:03:6D:CB:CE:31:7A:C9:55:D2:55:BA:33:4D:DE:4B:CF:16:4B:6F:C4:90:C1', + subject: '/CN=ds1815')); + await settle(); + await shot('cert'); + await closeAll(); + }); + } + } +} diff --git a/lib/ui/ftp_settings.dart b/lib/ui/ftp_settings.dart index acbd980..cfd4ebc 100644 --- a/lib/ui/ftp_settings.dart +++ b/lib/ui/ftp_settings.dart @@ -207,7 +207,8 @@ Future editFtpServer(BuildContext context, AppController c, {FtpServ child: TextField( controller: host, keyboardType: TextInputType.url, - decoration: InputDecoration(labelText: tr('서버 주소'), helperText: tr('예: nas.local · 192.168.0.10 · 100.x.y.z (Tailscale)')), + decoration: InputDecoration( + labelText: tr('서버 주소'), helperText: tr('예: nas.local · 192.168.0.10 · 100.x.y.z (Tailscale)'), helperMaxLines: 3), onChanged: (_) => set(() {}), ), ), @@ -265,7 +266,8 @@ Future editFtpServer(BuildContext context, AppController c, {FtpServ ), TextField( controller: root, - decoration: InputDecoration(labelText: tr('시작 폴더 (비우면 로그인한 곳)'), helperText: tr('예: /volume1/video · 탭의 맨 위가 됩니다')), + decoration: InputDecoration( + labelText: tr('시작 폴더 (비우면 로그인한 곳)'), helperText: tr('예: /volume1/video · 탭의 맨 위가 됩니다'), helperMaxLines: 3), ), if (security != 'none') ...[ const SizedBox(height: 6),