import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:jj_mkvmaker/app/app_controller.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/explorer/explorer_controller.dart'; import 'package:jj_mkvmaker/ui/explorer/explorer_host.dart'; import 'package:jj_mkvmaker/ui/explorer/explorer_pane.dart'; import 'package:path/path.dart' as p; /// E5d-1 약속 시험: lib/ui/explorer/explorer_controller.dart · explorer_host.dart 맨 위 약속. /// Host 는 실제 화면이 해 준다 (실제 ScaffoldMessenger 의 알림 · 실제 다음 그리기). 폴더는 실제 시험 폴더, 설정은 메모리에만. void main() { late Directory tmp; late String a, b, show; setUp(() { tmp = Directory.systemTemp.createTempSync('jj_ctl_'); a = p.join(tmp.path, 'A'); b = p.join(tmp.path, 'B'); show = p.join(a, 'Show'); Directory(show).createSync(recursive: true); Directory(b).createSync(); File(p.join(show, 'ep1.mkv')).writeAsStringSync('1'); File(p.join(a, 'big.bin')).writeAsBytesSync(List.filled(3000, 0)); File(p.join(a, 'small.txt')).writeAsStringSync('s'); }); tearDown(() => tmp.deleteSync(recursive: true)); Future<(ExplorerController, _HostState)> open(WidgetTester t) async { final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); final key = GlobalKey<_HostState>(); await t.pumpWidget(MaterialApp(home: _Host(key: key))); final ctl = ExplorerController(c, rsync: false, host: key.currentState!); ctl.local = [(a, 'A:'), (b, 'B:')]; addTearDown(ctl.dispose); return (ctl, key.currentState!); } testWidgets('이동: 장치부터 펼치고 읽음 · 지금 폴더 · 기억 · 스크롤 부탁 · 뒤로/앞으로', (t) async { final (ctl, host) = await open(t); final q = ctl.panes[0]; await t.runAsync(() => ctl.goTo(q, show)); expect(q.root, a); expect(q.current, show); expect(q.expanded, containsAll([a, show])); expect([for (final e in q.cache[show]!) p.basename(e.path)], ['ep1.mkv']); expect(host.revealed, [show], reason: 'Host 에 스크롤 부탁'); expect(ctl.c.settings.explorerPaths.first, show, reason: '마지막 폴더 기억'); await t.runAsync(() => ctl.goTo(q, b)); expect(q.root, b, reason: '다른 장치'); await t.runAsync(() => ctl.travel(q, forward: false)); expect(q.current, show); await t.runAsync(() => ctl.travel(q, forward: true)); expect(q.current, b); }); testWidgets('뒤로 갈 곳이 지워졌으면 내역에서 빼고 실제 알림으로 까닭 · 정렬 바꾸기 · 숨기기 토글', (t) async { final (ctl, host) = await open(t); final q = ctl.panes[0]; await t.runAsync(() => ctl.goTo(q, show)); await t.runAsync(() => ctl.goTo(q, b)); Directory(show).deleteSync(recursive: true); await t.runAsync(() => ctl.travel(q, forward: false)); await t.pump(); expect(q.current, b); expect(find.textContaining('내역에서 뺐습니다'), findsOneWidget, reason: '실제 ScaffoldMessenger 알림'); await t.runAsync(() => ctl.goTo(q, a)); await t.runAsync(() => ctl.setSort(a, 'size', true)); expect(p.basename(q.cache[a]!.where((e) => !e.isDir).first.path), 'big.bin'); expect(ctl.sortOf(a), ('size', true)); var n = 0; ctl.addListener(() => n++); ctl.toggleAppHidden(); expect([ctl.showAppHidden, n], [true, 1]); expect(host.alive, isTrue); }); testWidgets('화면 밖 글: 경로 보이기 · 서버 목록 · 장치 맞추기', (t) async { final (ctl, _) = await open(t); expect(ctl.volumeOf(p.join(b, 'x')), b); expect(ctl.volumes.take(2).map((v) => v.$2), ['A:', 'B:']); expect(ctl.displayPath(show), Platform.isAndroid ? 'A: › Show' : show); }); } /// 실제 화면이 해 주는 Host (실제 ScaffoldMessenger 알림 · 실제 다음 그리기) class _Host extends StatefulWidget { const _Host({super.key}); @override State<_Host> createState() => _HostState(); } class _HostState extends State<_Host> implements ExplorerHost { final revealed = []; @override bool get alive => mounted; @override void snack(String text) => ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(text))); @override void reveal(ExplorerPane pane, String path) => revealed.add(path); @override void afterFrame(VoidCallback f) => WidgetsBinding.instance.addPostFrameCallback((_) => f()); @override Widget build(BuildContext context) => const Scaffold(body: SizedBox()); }