// subtitle_editor_panes.dart 약속 시험: 실제 SubtitleEditorController 로 조각의 입력 · 출력 (편집기 · 콜백) · 오류 표시를 본다. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:jj_mkvmaker/app/subtitle_editor_controller.dart'; import 'package:jj_mkvmaker/core/srt.dart'; import 'package:jj_mkvmaker/ui/subtitle_editor_panes.dart'; SubtitleEditorController _editor() => SubtitleEditorController([ Cue(const Duration(seconds: 1), const Duration(seconds: 2), '첫 줄'), Cue(const Duration(seconds: 3), const Duration(seconds: 4), '둘째 줄'), ], saveCharset: 'UTF-8'); Widget _host(Widget child) => MaterialApp(home: Scaffold(body: child)); void main() { testWidgets('SubtitleCueTable: 번호를 누르면 onSelect · 시간 고치기 · 틀린 형식 · 줄 추가 · 삭제는 편집기로', (tester) async { tester.view.physicalSize = const Size(1200, 700); tester.view.devicePixelRatio = 1; addTearDown(tester.view.reset); final e = _editor(); final active = ValueNotifier(null); final picked = []; await tester.pumpWidget(_host(ListenableBuilder( listenable: e, builder: (_, _) => SubtitleCueTable(editor: e, active: active, onSelect: picked.add)))); await tester.tap(find.text('2')); expect(picked, [e.cues[1]]); await tester.enterText(find.text('00:00:01,000'), '00:00:01,500'); expect(e.cues[0].start, const Duration(milliseconds: 1500)); await tester.enterText(find.text('00:00:03,000'), '말도 안 됨'); await tester.pump(); expect(find.text('형식 오류'), findsOneWidget); expect(e.cues[1].start, const Duration(seconds: 3)); await tester.tap(find.byTooltip('아래에 줄 추가').first); await tester.pump(); expect(e.cues.length, 3); await tester.tap(find.byTooltip('줄 삭제').last); await tester.pump(); expect(e.cues.length, 2); }); testWidgets('SubtitleTransportBar: 고른 줄이 없으면 줄 단추 꺼짐 · 누르면 콜백', (tester) async { // 줄 단추가 모두 보이는 넓은 화면 (좁으면 밀어서 본다) tester.view.physicalSize = const Size(1800, 600); tester.view.devicePixelRatio = 1; addTearDown(tester.view.reset); final calls = []; Widget bar(bool sel) => _host(SubtitleTransportBar( position: ValueNotifier(const Duration(seconds: 5)), duration: const Duration(minutes: 1), playing: false, rate: 1.0, hasSelection: sel, onSeekBy: (ms) => calls.add('seek $ms'), onPlayPause: () => calls.add('play'), onRate: (r) => calls.add('rate $r'), onInsert: () => calls.add('insert'), onPlaySelected: () => calls.add('playSel'), onSetStart: () => calls.add('start'), onSetEnd: () => calls.add('end'), onAlign: () => calls.add('align'), )); await tester.pumpWidget(bar(false)); expect(find.text('00:00:05,000 / 00:01:00,000'), findsOneWidget); expect(tester.widget(find.widgetWithText(OutlinedButton, '시작 = 현재')).onPressed, isNull); await tester.tap(find.byIcon(Icons.replay_5)); await tester.tap(find.byIcon(Icons.play_circle)); await tester.tap(find.text('+ 여기에 새 줄')); await tester.pumpWidget(bar(true)); await tester.tap(find.text('끝 = 현재')); await tester.tap(find.text('1.0x')); await tester.pumpAndSettle(); await tester.tap(find.text('2.0x').last); await tester.pumpAndSettle(); expect(calls, ['seek -5000', 'play', 'insert', 'end', 'rate 2.0']); }); testWidgets('SubtitleVideoPane: 지금 위치의 줄 · 재생 오류를 겹쳐 보인다', (tester) async { final e = _editor(); final pos = ValueNotifier(const Duration(milliseconds: 1200)); await tester.pumpWidget(_host(SubtitleVideoPane(view: const SizedBox(), editor: e, position: pos, error: '열 수 없음'))); expect(find.text('첫 줄'), findsOneWidget); expect(find.textContaining('열 수 없음'), findsOneWidget); pos.value = const Duration(milliseconds: 2500); await tester.pump(); expect(find.text('첫 줄'), findsNothing); }); }