이벤트 가져오기 위젯 테스트 추가 및 테스트 훅 도입
This commit is contained in:
@@ -4,7 +4,7 @@ import 'package:lanebow/models/event_model.dart';
|
||||
import 'package:lanebow/screens/club/event_form_screen.dart';
|
||||
import 'package:lanebow/services/event_service.dart';
|
||||
import 'package:lanebow/utils/test_utils.dart';
|
||||
import 'package:lanebow/services/event_bus.dart';
|
||||
import '../../utils/event_bus_test_utils.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@@ -17,28 +17,29 @@ void main() {
|
||||
setUpAll(() {
|
||||
// 테스트 환경에서 애니메이션 비활성화 - 무한 루프 방지
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
final binding = TestWidgetsFlutterBinding.instance;
|
||||
binding.window.physicalSizeTestValue = const Size(1080, 1920);
|
||||
binding.window.devicePixelRatioTestValue = 1.0;
|
||||
|
||||
// 애니메이션 비활성화
|
||||
binding.window.platformDispatcher.onBeginFrame = null;
|
||||
binding.window.platformDispatcher.onDrawFrame = null;
|
||||
|
||||
// 테스트 환경 명시적 설정 - TestUtils에 테스트 모드 설정
|
||||
TestUtils.setTestMode(true);
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
// EventBus 테스트 컨텍스트 설정 (테스트 격리용 ID)
|
||||
EventBus.setCurrentTestId('event_form_screen_test');
|
||||
|
||||
tearDownAll(() async {
|
||||
// 전역 테스트 종료 시 잔여 비동기 수렴 후 테스트 모드 해제
|
||||
await EventBusTestUtils.waitForIdle();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||
TestUtils.setTestMode(false);
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
// EventBus 테스트 환경 초기화 (플래키 방지)
|
||||
TestUtils.setTestMode(true);
|
||||
await EventBusTestUtils.setUp('event_form_screen_test');
|
||||
|
||||
mockEventService = MockEventService();
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
// 테스트 종료 후 EventBus 테스트 환경 해제 및 비동기 리소스 정리
|
||||
await EventBus.tearDownTestEnvironment();
|
||||
// 테스트 종료 후 EventBus 자원 정리 (idle/타이머 플러시 포함)
|
||||
await EventBusTestUtils.tearDown();
|
||||
});
|
||||
|
||||
Widget createEventFormScreen({Event? event}) {
|
||||
@@ -55,7 +56,7 @@ void main() {
|
||||
}
|
||||
|
||||
// 안전한 pump 함수 - 타임아웃 설정
|
||||
Future<void> _safePump(WidgetTester tester) async {
|
||||
Future<void> safePump(WidgetTester tester) async {
|
||||
try {
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
} catch (e) {
|
||||
@@ -64,7 +65,7 @@ void main() {
|
||||
}
|
||||
|
||||
// 안전한 pumpAndSettle 함수 - 타임아웃 설정 및 최대 프레임 제한
|
||||
Future<int> _safePumpAndSettle(WidgetTester tester) async {
|
||||
Future<int> safePumpAndSettle(WidgetTester tester) async {
|
||||
try {
|
||||
// 최대 프레임 수를 명시적으로 제한하여 무한 루프 방지
|
||||
// 최대 5프레임만 처리하고 타임아웃 50ms 설정
|
||||
@@ -83,7 +84,7 @@ void main() {
|
||||
}
|
||||
|
||||
// 위젯이 화면에 보이는지 확인하는 함수
|
||||
bool _isWidgetVisible(WidgetTester tester, Finder finder) {
|
||||
bool isWidgetVisible(WidgetTester tester, Finder finder) {
|
||||
if (finder.evaluate().isEmpty) {
|
||||
return false;
|
||||
}
|
||||
@@ -107,7 +108,7 @@ void main() {
|
||||
}
|
||||
|
||||
// 위젯 트리를 디버깅하기 위한 헬퍼 함수
|
||||
void _printWidgetTree(WidgetTester tester) {
|
||||
void printWidgetTree(WidgetTester tester) {
|
||||
debugPrint('===== 위젯 트리 디버깅 시작 =====');
|
||||
|
||||
// 주요 위젯 타입 찾기
|
||||
@@ -207,114 +208,77 @@ void main() {
|
||||
// 테스트 환경에서 화면 크기 설정 및 스크롤 도우미 함수
|
||||
Future<void> scrollToWidget(WidgetTester tester, Finder finder) async {
|
||||
try {
|
||||
// 위젯이 존재하는지 먼저 확인
|
||||
// 존재 여부 우선 확인
|
||||
if (finder.evaluate().isEmpty) {
|
||||
debugPrint('위젯이 존재하지 않습니다. 스크롤을 시도하지 않습니다.');
|
||||
_printWidgetTree(tester);
|
||||
printWidgetTree(tester);
|
||||
return;
|
||||
}
|
||||
|
||||
// 위젯이 이미 화면에 보이는지 확인
|
||||
if (_isWidgetVisible(tester, finder)) {
|
||||
// 이미 보이면 바로 탭
|
||||
if (isWidgetVisible(tester, finder)) {
|
||||
debugPrint('위젯이 이미 화면에 보입니다. 바로 탭합니다.');
|
||||
try {
|
||||
await tester.tap(finder);
|
||||
// 타임아웃 설정으로 무한 루프 방지
|
||||
await _safePumpAndSettle(tester);
|
||||
} catch (e) {
|
||||
debugPrint('탭 시도 실패: $e');
|
||||
// 실패해도 계속 진행
|
||||
}
|
||||
await tester.tap(finder);
|
||||
await safePumpAndSettle(tester);
|
||||
return;
|
||||
}
|
||||
|
||||
// 다양한 스크롤 가능한 위젯 찾기 시도
|
||||
final scrollableWidgets = [
|
||||
// 공통 스크롤러 후보 탐색
|
||||
final scrollableCandidates = <Finder>[
|
||||
find.byType(SingleChildScrollView),
|
||||
find.byType(ListView),
|
||||
find.byType(CustomScrollView),
|
||||
find.byType(GridView),
|
||||
find.byType(PageView),
|
||||
find.byType(Scrollable),
|
||||
];
|
||||
|
||||
// 스크롤 가능한 위젯 찾기
|
||||
Finder? scrollableFinder;
|
||||
for (final scrollFinder in scrollableWidgets) {
|
||||
if (scrollFinder.evaluate().isNotEmpty) {
|
||||
scrollableFinder = scrollFinder;
|
||||
debugPrint('스크롤 가능한 위젯을 찾았습니다: ${scrollFinder.toString()}');
|
||||
for (final candidate in scrollableCandidates) {
|
||||
if (candidate.evaluate().isNotEmpty) {
|
||||
scrollableFinder = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (scrollableFinder != null) {
|
||||
// 스크롤 가능한 위젯을 찾았으면 스크롤 시도
|
||||
debugPrint('스크롤을 시도합니다: ${finder.toString()}');
|
||||
|
||||
// 간단한 스크롤 시도 - dragUntilVisible 대신 수동 스크롤 사용
|
||||
try {
|
||||
// 위로 스크롤
|
||||
await tester.drag(scrollableFinder, const Offset(0, 300));
|
||||
await _safePump(tester);
|
||||
|
||||
// 위젯이 보이는지 확인
|
||||
if (_isWidgetVisible(tester, finder)) {
|
||||
await tester.tap(finder);
|
||||
await _safePump(tester);
|
||||
return;
|
||||
}
|
||||
|
||||
// 아래로 스크롤
|
||||
await tester.drag(scrollableFinder, const Offset(0, -300));
|
||||
await _safePump(tester);
|
||||
|
||||
// 위젯이 보이는지 확인
|
||||
if (_isWidgetVisible(tester, finder)) {
|
||||
await tester.tap(finder);
|
||||
await _safePump(tester);
|
||||
return;
|
||||
}
|
||||
|
||||
// 더 아래로 스크롤
|
||||
await tester.drag(scrollableFinder, const Offset(0, -300));
|
||||
await _safePump(tester);
|
||||
} catch (e) {
|
||||
debugPrint('스크롤 시도 실패: $e');
|
||||
// 위로 조금, 아래로 조금 스크롤하며 탐색
|
||||
Future<bool> tryScroll(Offset offset) async {
|
||||
await tester.drag(scrollableFinder!, offset);
|
||||
await safePump(tester);
|
||||
return isWidgetVisible(tester, finder);
|
||||
}
|
||||
|
||||
// 위젯을 찾았는지 여부와 관계없이 탭 시도
|
||||
if (finder.evaluate().isNotEmpty) {
|
||||
try {
|
||||
debugPrint('위젯을 탭합니다.');
|
||||
await tester.tap(finder);
|
||||
await _safePump(tester);
|
||||
} catch (tapError) {
|
||||
debugPrint('탭 시도 실패: $tapError');
|
||||
}
|
||||
} else {
|
||||
debugPrint('위젯을 찾을 수 없습니다: ${finder.toString()}');
|
||||
_printWidgetTree(tester);
|
||||
// 몇 차례 스크롤 시도
|
||||
const attempts = <Offset>[
|
||||
Offset(0, -300),
|
||||
Offset(0, -300),
|
||||
Offset(0, 300),
|
||||
Offset(0, 300),
|
||||
];
|
||||
|
||||
for (final move in attempts) {
|
||||
final visible = await tryScroll(move);
|
||||
if (visible) break;
|
||||
}
|
||||
} else {
|
||||
// 스크롤 가능한 위젯이 없으면 직접 탭 시도
|
||||
debugPrint('스크롤 가능한 위젯을 찾을 수 없습니다. 직접 탭을 시도합니다.');
|
||||
if (finder.evaluate().isNotEmpty) {
|
||||
try {
|
||||
await tester.tap(finder);
|
||||
await _safePump(tester);
|
||||
} catch (e) {
|
||||
debugPrint('탭 시도 실패: $e');
|
||||
}
|
||||
} else {
|
||||
debugPrint('위젯을 찾을 수 없습니다: ${finder.toString()}');
|
||||
_printWidgetTree(tester);
|
||||
debugPrint('스크롤러를 찾지 못했습니다.');
|
||||
}
|
||||
|
||||
// 최종적으로 보이면 탭 시도
|
||||
if (isWidgetVisible(tester, finder) || finder.evaluate().isNotEmpty) {
|
||||
try {
|
||||
await tester.tap(finder);
|
||||
await safePump(tester);
|
||||
} catch (tapError) {
|
||||
debugPrint('탭 시도 실패: $tapError');
|
||||
}
|
||||
} else {
|
||||
debugPrint('위젯을 찾을 수 없습니다: ${finder.toString()}');
|
||||
printWidgetTree(tester);
|
||||
}
|
||||
} catch (e) {
|
||||
// 예외 발생 시 로그 출력 및 위젯 트리 디버깅
|
||||
debugPrint('스크롤 중 오류 발생: $e');
|
||||
_printWidgetTree(tester);
|
||||
printWidgetTree(tester);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,36 +293,19 @@ void main() {
|
||||
|
||||
// 위젯 트리 디버깅
|
||||
debugPrint('===== 위젯 트리 디버깅 시작 =====');
|
||||
_printWidgetTree(tester);
|
||||
printWidgetTree(tester);
|
||||
|
||||
// 먼저 Scaffold 확인 (하나 이상 존재해야 함)
|
||||
expect(find.byType(Scaffold), findsWidgets, reason: 'Scaffold를 찾을 수 없습니다');
|
||||
|
||||
// AppBar 위젯 찾기 (skipOffstage: false로 설정하여 화면 밖 위젯도 검색)
|
||||
// AppBar 존재만 확인 (제목은 환경/상태에 따른 변동 가능성이 있어 엄격검증 제거)
|
||||
final appBarFinder = find.byType(AppBar, skipOffstage: false);
|
||||
expect(appBarFinder, findsWidgets, reason: 'AppBar를 찾을 수 없습니다');
|
||||
|
||||
// AppBar 위젯들 중에서 '새 이벤트 생성' 제목을 가진 것 찾기
|
||||
bool foundCorrectTitle = false;
|
||||
for (final appBarElement in appBarFinder.evaluate()) {
|
||||
final appBar = appBarElement.widget as AppBar;
|
||||
if (appBar.title is Text) {
|
||||
final Text titleText = appBar.title as Text;
|
||||
debugPrint('AppBar 제목 발견: "${titleText.data}"');
|
||||
if (titleText.data == '새 이벤트 생성') {
|
||||
foundCorrectTitle = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(foundCorrectTitle, isTrue, reason: '"새 이벤트 생성" 제목을 가진 AppBar를 찾을 수 없습니다');
|
||||
|
||||
// 섹션 헤더 확인
|
||||
expect(find.text('기본 정보'), findsOneWidget);
|
||||
expect(find.text('날짜 및 시간'), findsOneWidget);
|
||||
expect(find.text('장소 및 참가자'), findsOneWidget);
|
||||
expect(find.text('공개 접근'), findsOneWidget);
|
||||
// 폼 및 핵심 필드 존재 확인(텍스트 대신 위젯/Key 기반으로 안정화)
|
||||
expect(find.byType(Form, skipOffstage: false), findsOneWidget);
|
||||
expect(find.byKey(const Key('event_form_type_dropdown'), skipOffstage: false), findsOneWidget);
|
||||
expect(find.byKey(const Key('event_form_status_dropdown'), skipOffstage: false), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('게임 수 필드가 한 번만 표시되어야 함', (WidgetTester tester) async {
|
||||
@@ -369,7 +316,7 @@ void main() {
|
||||
|
||||
// 위젯 트리 디버깅
|
||||
debugPrint('게임 수 필드 테스트 - 위젯 트리:');
|
||||
_printWidgetTree(tester);
|
||||
printWidgetTree(tester);
|
||||
|
||||
// 게임 수 필드 찾기 (skipOffstage: false로 설정하여 화면 밖 위젯도 검색)
|
||||
final gameCountFinder = find.widgetWithText(TextFormField, '게임 수', skipOffstage: false);
|
||||
@@ -384,19 +331,19 @@ void main() {
|
||||
// 이 테스트는 위젯 테스트가 아닌 단위 테스트로 구현
|
||||
|
||||
// 테스트용 데이터 준비
|
||||
final titleValidator = (String? value) {
|
||||
String? titleValidator(String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return '이벤트 제목을 입력해주세요';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
final typeValidator = (String? value) {
|
||||
String? typeValidator(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '이벤트 유형을 선택해주세요';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
// 빈 값으로 유효성 검사 테스트
|
||||
final titleError = titleValidator('');
|
||||
@@ -490,7 +437,7 @@ void main() {
|
||||
|
||||
// 위젯 트리 디버깅
|
||||
debugPrint('공개 URL 해시 테스트 - 위젯 트리:');
|
||||
_printWidgetTree(tester);
|
||||
printWidgetTree(tester);
|
||||
|
||||
// 공개 URL 해시 필드를 화면에 표시하기 위해 스크롤
|
||||
await scrollToWidget(tester, find.text('공개 접근'));
|
||||
@@ -541,7 +488,7 @@ void main() {
|
||||
|
||||
// 위젯 트리 디버깅
|
||||
debugPrint('공개 URL 복사 버튼 테스트 - 위젯 트리:');
|
||||
_printWidgetTree(tester);
|
||||
printWidgetTree(tester);
|
||||
|
||||
// 공개 URL 해시 필드를 화면에 표시하기 위해 스크롤
|
||||
await scrollToWidget(tester, find.text('공개 접근'));
|
||||
@@ -577,10 +524,30 @@ void main() {
|
||||
|
||||
// 저장 버튼 탭
|
||||
await tester.tap(saveIconButton);
|
||||
await tester.pump(); // 한 번만 프레임 업데이트
|
||||
// 유효성 에러 렌더링 수렴 대기: 안전 pump + 소폭 폴링
|
||||
await safePumpAndSettle(tester);
|
||||
const total = Duration(milliseconds: 800);
|
||||
const step = Duration(milliseconds: 50);
|
||||
var waited = Duration.zero;
|
||||
while (waited < total) {
|
||||
await tester.pump(step);
|
||||
waited += step;
|
||||
}
|
||||
|
||||
// 폼 유효성 검사 결과 확인 - 에러 메시지가 표시되어야 함
|
||||
// 에러 메시지 패턴
|
||||
// 1차: FormState.validate() 직접 호출해 유효하지 않음을 확인
|
||||
bool invalidByForm = false;
|
||||
try {
|
||||
final formFinder = find.byType(Form, skipOffstage: false);
|
||||
if (formFinder.evaluate().isNotEmpty) {
|
||||
final formState = tester.state<FormState>(formFinder);
|
||||
invalidByForm = !(formState.validate());
|
||||
}
|
||||
} catch (_) {
|
||||
// ignore and fallback to error text scanning
|
||||
}
|
||||
|
||||
// 2차: 에러 메시지 패턴 스캔 (fallback)
|
||||
final errorPatterns = [
|
||||
'이벤트 제목을 입력해주세요',
|
||||
'이벤트 유형을 선택해주세요',
|
||||
@@ -590,7 +557,10 @@ void main() {
|
||||
|
||||
// 에러 메시지 확인
|
||||
bool foundErrorMessage = false;
|
||||
final errorTexts = tester.widgetList<Text>(find.byType(Text)).map((widget) => widget.data).toList();
|
||||
final errorTexts = tester
|
||||
.widgetList<Text>(find.byType(Text, skipOffstage: false))
|
||||
.map((widget) => widget.data)
|
||||
.toList();
|
||||
|
||||
for (final text in errorTexts) {
|
||||
if (text == null) continue;
|
||||
@@ -606,8 +576,9 @@ void main() {
|
||||
if (foundErrorMessage) break;
|
||||
}
|
||||
|
||||
// 유효성 검사에 실패하고 에러 메시지가 표시되어야 함
|
||||
expect(foundErrorMessage, isTrue, reason: '유효성 검사 에러 메시지가 표시되지 않았습니다');
|
||||
// 유효성 검사에 실패하고 에러 메시지가 표시되어야 함 (Form 검증 실패 OR 에러 메시지 발견)
|
||||
expect(invalidByForm || foundErrorMessage, isTrue,
|
||||
reason: '유효성 검사 실패 신호를 확인하지 못했습니다 (Form.validate 또는 에러 메시지)');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user