import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:jj_mkvmaker/core/file_ops.dart'; import 'package:jj_mkvmaker/core/models.dart'; import 'package:jj_mkvmaker/ui/quick_look.dart'; import 'package:path/path.dart' as p; /// 81: 빠른 보기 - 글 파일은 앞 64KB (깨지면 "미리 볼 수 없음"), ← → 로 앞 · 다음, Space 로 닫기, [열기] void main() { late Directory tmp; setUp(() => tmp = Directory.systemTemp.createTempSync('jj_ql81_')); tearDown(() => tmp.deleteSync(recursive: true)); test('글 앞부분: UTF-8 · CP949 .smi · UTF-16 (BOM) .srt · UTF-8 BOM .txt 를 읽고, 글이 아니면 null · 64KB 끝에서 잘린 한 글자는 괜찮음', () async { final a = File(p.join(tmp.path, 'a.txt'))..writeAsStringSync('안녕 hello'); expect(await readTextHead(a.path), '안녕 hello'); // 81 보충 (15 지적): 한국어 자막에 흔한 CP949 · 메모장의 UTF-16 · UTF-8 BOM final b = File(p.join(tmp.path, 'b.smi'))..writeAsBytesSync([0xBE, 0xC8, 0xB3, 0xE7, 0x20, 0xC0, 0xDA, 0xB8, 0xB7]); // CP949 "안녕 자막" expect(await readTextHead(b.path), '안녕 자막'); final u16 = [0xFF, 0xFE]; for (final r in '자막 1'.codeUnits) { u16.addAll([r & 0xFF, r >> 8]); } final s = File(p.join(tmp.path, 's.srt'))..writeAsBytesSync(u16); expect(await readTextHead(s.path), '자막 1'); final bom = File(p.join(tmp.path, 'bom.txt'))..writeAsBytesSync([0xEF, 0xBB, 0xBF, ...utf8.encode('hi 안녕')]); expect(await readTextHead(bom.path), 'hi 안녕'); final bin = File(p.join(tmp.path, 'bin.txt'))..writeAsBytesSync([0x00, 0x01, 0x02, 0x00, 0xFF]); expect(await readTextHead(bin.path), isNull, reason: '글이 아님'); // 64KB 경계에 3바이트 글자가 걸리게 final c = File(p.join(tmp.path, 'c.txt'))..writeAsStringSync('${'x' * (64 * 1024 - 1)}가나다'); final head = await readTextHead(c.path); expect(head, isNotNull); expect(head!.length, 64 * 1024 - 1); }); testWidgets('← → 로 앞 · 다음 (보이는 차례), Space 로 닫기, [열기]', (tester) async { tester.view.physicalSize = const Size(1000, 800); tester.view.devicePixelRatio = 1; addTearDown(tester.view.reset); final now = DateTime(2026, 10, 17); final entries = [ FileEntry(p.join(tmp.path, 'one.bin'), isDir: false, size: 10, modified: now), FileEntry(p.join(tmp.path, 'two.bin'), isDir: false, size: 20, modified: now), ]; final opened = []; final nav = GlobalKey(); await tester.pumpWidget(MaterialApp(navigatorKey: nav, home: const Scaffold())); showDialog( context: nav.currentContext!, builder: (_) => QuickLook( entries: entries, index: 0, probe: (_) async => const MediaInfo(streams: []), thumbnail: (_) async => null, imageExts: const ['jpg'], onOpen: (e) => opened.add(e.name), ), ); await tester.pumpAndSettle(); expect(find.text('one.bin'), findsOneWidget); expect(find.text('1/2'), findsOneWidget); await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); await tester.pumpAndSettle(); expect(find.text('two.bin'), findsOneWidget); await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); await tester.pumpAndSettle(); expect(find.text('2/2'), findsOneWidget, reason: '끝에서 더 가지 않음'); await tester.tap(find.text('열기 · 재생')); await tester.pumpAndSettle(); expect(opened, ['two.bin']); expect(find.byType(QuickLook), findsNothing); showDialog( context: nav.currentContext!, builder: (_) => QuickLook( entries: entries, index: 1, probe: (_) async => const MediaInfo(streams: []), thumbnail: (_) async => null, imageExts: const ['jpg'], onOpen: (_) {}, ), ); await tester.pumpAndSettle(); await tester.sendKeyEvent(LogicalKeyboardKey.space); await tester.pumpAndSettle(); expect(find.byType(QuickLook), findsNothing, reason: 'Space 로 닫기'); }); }