import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:image/image.dart' as img; import 'package:jj_mkvmaker/app/ai_local.dart'; import 'package:jj_mkvmaker/app/ai_upscale.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/ai_upscale_ui.dart'; import 'package:path/path.dart' as p; /// 관리자 판단 (10/17, 사용자 질문에 답이 없어 둘 다 넣고 기본 꺼짐): /// (a) 해상도 올리기 배율 "1배 (선명하게)" - 크기 그대로 · 처음 값은 2배 그대로 /// (b) [품질 올리기] - 그림 → 그림 약하게 + 해상도 올리기, 단추는 설정으로 (처음 끔), 새 파일 "이름_q", 모델이 없으면 안내 void main() { late Directory tmp; setUp(() => tmp = Directory.systemTemp.createTempSync('jj_quality_t_')); tearDown(() => tmp.deleteSync(recursive: true)); test('설정: 배율 1 을 받아들이고 처음 값은 2 · 품질 올리기 단추는 처음 끔 · 저장', () { expect(AppSettings().aiUpScale, 2); expect(AppSettings.fromJson({'aiUpScale': 1}).aiUpScale, 1); expect(AppSettings.fromJson({'aiUpScale': 7}).aiUpScale, 2); expect(AppSettings().aiQualityButton, isFalse); expect(AppSettings.fromJson((AppSettings()..aiQualityButton = true).toJson()).aiQualityButton, isTrue); }); test('1배: 4배 결과를 원본 크기로 줄인다 (선명하게만)', () async { final four = img.encodePng(img.Image(width: 40, height: 24)); final out = img.decodePng(await finishUpscale(four, origW: 10, origH: 6, scale: 1, ext: 'png'))!; expect((out.width, out.height), (10, 6)); expect(p.basename(upscaledPath(p.join(tmp.path, 'a.jpg'), scale: 1, ext: 'jpg')), 'a_x1.jpg'); }); test('품질 올리기: 저장 이름 "이름_q" (덮어쓰지 않음) · 그림 → 그림 크기 (64 배수 · 긴 쪽 1024 까지)', () { final src = p.join(tmp.path, 'b.png'); expect(p.basename(upscaledPath(src, scale: 2, ext: 'png', suffix: '_q')), 'b_q.png'); File(p.join(tmp.path, 'b_q.png')).writeAsStringSync('x'); expect(p.basename(upscaledPath(src, scale: 2, ext: 'png', suffix: '_q')), 'b_q (2).png'); expect(qualitySize(800, 600), (832, 576)); expect(qualitySize(4000, 3000), (1024, 768)); expect(qualitySize(100, 50), (256, 256)); expect(qualitySize(0, 0), (512, 512)); }); testWidgets('품질 올리기: AI 그림 모델이 없으면 받으라는 안내 (원본은 그대로)', (tester) async { final input = p.join(tmp.path, 'page.png'); File(input).writeAsBytesSync(img.encodePng(img.Image(width: 8, height: 6))); final before = AiStore.instance; AiStore.instance = AiStore(p.join(tmp.path, 'ai')); // 받은 모델이 없는 곳 addTearDown(() => AiStore.instance = before); final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); await tester.pumpWidget(MaterialApp( home: Scaffold( body: Builder( builder: (ctx) => TextButton( onPressed: () => qualityUp(ctx, c, input: input, nameFrom: input, outDir: tmp.path), child: const Text('품질'), ), ), ), )); await tester.tap(find.text('품질')); await tester.pumpAndSettle(); expect(find.textContaining('AI 그림 모델이 필요합니다'), findsOneWidget); await tester.tap(find.text('닫기')); await tester.pumpAndSettle(); expect(tmp.listSync().map((e) => p.basename(e.path)).where((n) => n.contains('_q')), isEmpty); }); }