알림 시간 주입: 시험이 알림 시간을 정할 수 있게 (앱은 그대로 10초 - U2)

- notice.dart: appNoticeDuration (10초, 앱의 값) · noticeDuration (읽기) · noticeDurationForTest (시험만, @visibleForTesting)
- 탐색기 점검 시험: 알림이 실제 시간으로 닫히지 않게 두고, 정한 자리 (복사 알림은 선택을 끈 뒤 · 휴지통 알림은 이동을
  누르기 전) 에서 닫는다 - 찍는 때와 10초가 겹쳐 장면 14 · 27 이 실행마다 달라지던 것
- 확인: 같은 판 2회 90/90 픽셀까지 같음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
modular
RyuJongJin 2026-10-23 04:42:59 +09:00
parent e3f8d8dbef
commit 53036633d3
3 changed files with 23 additions and 2 deletions

View File

@ -14,6 +14,7 @@ import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart';
import 'package:jj_mkvmaker/services/platform_services.dart'; import 'package:jj_mkvmaker/services/platform_services.dart';
import 'package:jj_mkvmaker/ui/explorer/job_view.dart' show TransferJobView; import 'package:jj_mkvmaker/ui/explorer/job_view.dart' show TransferJobView;
import 'package:jj_mkvmaker/ui/explorer_page.dart'; import 'package:jj_mkvmaker/ui/explorer_page.dart';
import 'package:jj_mkvmaker/ui/notice.dart' show appNoticeDuration, noticeDurationForTest;
import 'package:jj_mkvmaker/ui/theme.dart'; import 'package:jj_mkvmaker/ui/theme.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
@ -34,6 +35,8 @@ void main() {
t.view.physicalSize = Size(w.toDouble(), h.toDouble()); t.view.physicalSize = Size(w.toDouble(), h.toDouble());
t.view.devicePixelRatio = 1; t.view.devicePixelRatio = 1;
addTearDown(t.view.reset); addTearDown(t.view.reset);
noticeDurationForTest = const Duration(hours: 1);
addTearDown(() => noticeDurationForTest = appNoticeDuration);
await t.runAsync(() => i18n.apply('ko', save: false)); await t.runAsync(() => i18n.apply('ko', save: false));
useTestRecycleBin(); useTestRecycleBin();
// 캡처 비교가 흔들리지 않게 시험 폴더 이름 · 파일 시각을 고정 (관리자 지시 10/22) - 끝나면 지움 // 캡처 비교가 흔들리지 않게 시험 폴더 이름 · 파일 시각을 고정 (관리자 지시 10/22) - 끝나면 지움
@ -97,6 +100,13 @@ void main() {
} }
// 복사 · 이동이 끝나 진행 패널이 다 내려갈 때까지 (실제 시간, 10초까지) - 캡처가 PC 빠르기에 따라 달라지지 않게 // 복사 · 이동이 끝나 진행 패널이 다 내려갈 때까지 (실제 시간, 10초까지) - 캡처가 PC 빠르기에 따라 달라지지 않게
// 알림은 실제 시간 10초 (appNoticeDuration) 로 닫히면 찍는 때와 겹쳐 장면이 실행마다 달라진다 (14 · 27) -
// 이 시험은 저절로 닫히지 않게 두고, 정한 자리에서 "시간이 다 되어 닫힘" 으로 닫는다 (앱의 알림 시간은 그대로)
Future<void> noticeTimesOut() async {
t.state<ScaffoldMessengerState>(find.byType(ScaffoldMessenger).first).removeCurrentSnackBar();
await settle(3);
}
Future<void> jobsGone() async { Future<void> jobsGone() async {
for (var i = 0; i < 100 && find.byType(TransferJobView).evaluate().isNotEmpty; i++) { for (var i = 0; i < 100 && find.byType(TransferJobView).evaluate().isNotEmpty; i++) {
await t.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 100))); await t.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 100)));
@ -186,6 +196,7 @@ void main() {
await step('선택 모드 - 안의 ep01 누르기', () => t.tap(txt('ep01.mkv'))); await step('선택 모드 - 안의 ep01 누르기', () => t.tap(txt('ep01.mkv')));
await step('선택 모드 - Show S01 다시 누르기', () => t.tap(txt('Show S01'))); await step('선택 모드 - Show S01 다시 누르기', () => t.tap(txt('Show S01')));
await step('선택 단추 (끄기)', () => t.tap(tip('선택'))); await step('선택 단추 (끄기)', () => t.tap(tip('선택')));
await noticeTimesOut(); // 복사 알림: 선택을 끈 뒤에는 닫혀 있음
await step('ep03 오른쪽 클릭', () => t.tap(row('ep03.mkv'), buttons: kSecondaryMouseButton)); await step('ep03 오른쪽 클릭', () => t.tap(row('ep03.mkv'), buttons: kSecondaryMouseButton));
await step('메뉴 이름 변경', () => t.tap(find.text('이름 변경').last)); await step('메뉴 이름 변경', () => t.tap(find.text('이름 변경').last));
await step('새 이름 넣고 확인', () async { await step('새 이름 넣고 확인', () async {
@ -204,6 +215,7 @@ void main() {
await step('선택 켜기 2', selectOn); await step('선택 켜기 2', selectOn);
await step('film 표시 단추', () => t.tap(mark('film.mp4'))); await step('film 표시 단추', () => t.tap(mark('film.mp4')));
await step('이동 단추', () => t.tap(tip('이동'))); await step('이동 단추', () => t.tap(tip('이동')));
await noticeTimesOut(); // 휴지통 알림: 이동을 누르기 전에는 닫혀 있음 (이동 창 뒤에는 아직 보임)
await step('이동 확인 누르기', () async { await step('이동 확인 누르기', () async {
if (find.byType(Dialog).evaluate().isNotEmpty) { if (find.byType(Dialog).evaluate().isNotEmpty) {
await okDialog(); await okDialog();

View File

@ -3,14 +3,22 @@ import 'package:flutter/material.dart';
/// 단추가 있는 아래 알림 (SnackBar) 을 띄우는 곳은 이것 하나 (E4 U2, 관리자 승인 10/22). /// 단추가 있는 아래 알림 (SnackBar) 을 띄우는 곳은 이것 하나 (E4 U2, 관리자 승인 10/22).
/// ///
/// 약속 /// 약속
/// - [NoticeLife.timed] (기본): [noticeDuration] 뒤 저절로 닫힌다. 앱 전체 한 값이다. /// - [NoticeLife.timed] (기본): [noticeDuration] (앱은 [appNoticeDuration]) 뒤 저절로 닫힌다. 앱 전체 한 값이다.
/// (Flutter 3.29 부터 단추가 있는 SnackBar 는 기본으로 닫히지 않아, 끝 알림이 다음 일을 하는 내내 아래 줄을 가렸다 - 점검 U2) /// (Flutter 3.29 부터 단추가 있는 SnackBar 는 기본으로 닫히지 않아, 끝 알림이 다음 일을 하는 내내 아래 줄을 가렸다 - 점검 U2)
/// - [NoticeLife.untilClosed]: 사용자가 ✕ 를 누르거나 부르는 쪽이 돌려받은 것으로 닫을 때까지 둔다. /// - [NoticeLife.untilClosed]: 사용자가 ✕ 를 누르거나 부르는 쪽이 돌려받은 것으로 닫을 때까지 둔다.
/// 상태가 남는 중요한 안내 (248 DNS · 198 감지한 언어) 와, 부르는 쪽이 일이 끝나면 닫는 것 (다운로드 준비 · 시작 전 [취소]) 만. /// 상태가 남는 중요한 안내 (248 DNS · 198 감지한 언어) 와, 부르는 쪽이 일이 끝나면 닫는 것 (다운로드 준비 · 시작 전 [취소]) 만.
/// - 두 경우 모두 ✕ 로 바로 닫을 수 있다. [action] 을 누르면 알림이 닫히고 그 일을 한다. /// - 두 경우 모두 ✕ 로 바로 닫을 수 있다. [action] 을 누르면 알림이 닫히고 그 일을 한다.
/// - [clearOthers] 면 앞서 떠 있던 알림을 치우고 바로 보인다 (되돌리기처럼 바로 보여야 하는 것). /// - [clearOthers] 면 앞서 떠 있던 알림을 치우고 바로 보인다 (되돌리기처럼 바로 보여야 하는 것).
/// - [messenger] 가 없으면 아무것도 하지 않고 null. /// - [messenger] 가 없으면 아무것도 하지 않고 null.
const noticeDuration = Duration(seconds: 10); const appNoticeDuration = Duration(seconds: 10);
/// [NoticeLife.timed] 알림이 저절로 닫히기까지. 앱은 늘 [appNoticeDuration] (U2 의 값 - 바꾸는 곳 없음).
/// 화면을 찍는 시험만 [noticeDurationForTest] 로 정한다 (실제 시간 10초가 찍는 때와 겹쳐 장면이 실행마다 달라지지 않게)
Duration get noticeDuration => _noticeDuration;
Duration _noticeDuration = appNoticeDuration;
@visibleForTesting
set noticeDurationForTest(Duration d) => _noticeDuration = d;
enum NoticeLife { timed, untilClosed } enum NoticeLife { timed, untilClosed }

View File

@ -25,6 +25,7 @@ void main() {
expect(find.text('복사했습니다'), findsNothing); expect(find.text('복사했습니다'), findsNothing);
expect(pressed, 0); expect(pressed, 0);
expect(noticeDuration, const Duration(seconds: 10)); expect(noticeDuration, const Duration(seconds: 10));
expect(appNoticeDuration, const Duration(seconds: 10), reason: '앱의 값 (U2) - 시험이 정하지 않으면 이것');
}); });
testWidgets('단추를 누르면 그 일을 하고 닫힘', (tester) async { testWidgets('단추를 누르면 그 일을 하고 닫힘', (tester) async {