import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get_it/get_it.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; /// 테스트용 GetIt 인스턴스 초기화 GetIt setupTestGetIt() { final getIt = GetIt.instance; // 기존 등록된 서비스들 모두 제거 getIt.reset(); return getIt; } /// 테스트용 위젯 래퍼 /// 모든 위젯 테스트에서 필요한 기본 설정을 제공 class TestWidgetWrapper extends StatelessWidget { final Widget child; final List? providers; final NavigatorObserver? navigatorObserver; final Map? routes; final String? initialRoute; const TestWidgetWrapper({ Key? key, required this.child, this.providers, this.navigatorObserver, this.routes, this.initialRoute, }) : super(key: key); @override Widget build(BuildContext context) { Widget wrappedChild = MaterialApp( title: 'Test App', theme: ThemeData( primarySwatch: Colors.blue, useMaterial3: true, ), localizationsDelegates: const [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: const [ Locale('ko', 'KR'), Locale('en', 'US'), ], home: Scaffold(body: child), routes: routes ?? {}, initialRoute: initialRoute, navigatorObservers: navigatorObserver != null ? [navigatorObserver!] : [], ); // Provider가 있는 경우 래핑 if (providers != null && providers!.isNotEmpty) { return MultiProvider( providers: providers!, child: wrappedChild, ); } return wrappedChild; } } /// 위젯을 테스트 환경에서 펌프하는 헬퍼 함수 Future pumpTestWidget( WidgetTester tester, Widget widget, { List? providers, NavigatorObserver? navigatorObserver, Map? routes, String? initialRoute, }) async { await tester.pumpWidget( TestWidgetWrapper( child: widget, providers: providers, navigatorObserver: navigatorObserver, routes: routes, initialRoute: initialRoute, ), ); } /// 비동기 작업을 기다리고 위젯을 리빌드하는 헬퍼 Future pumpAndSettleWithTimeout( WidgetTester tester, { Duration timeout = const Duration(seconds: 10), }) async { await tester.pump(); await tester.pumpAndSettle(timeout); } /// TextField에 텍스트를 입력하는 헬퍼 Future enterTextByLabel( WidgetTester tester, String label, String text, ) async { final textFieldFinder = find.ancestor( of: find.text(label), matching: find.byType(TextFormField), ); if (textFieldFinder.evaluate().isEmpty) { // 라벨로 찾지 못한 경우, 가까운 TextFormField 찾기 final labelWidget = find.text(label); final textField = find.byType(TextFormField).first; await tester.enterText(textField, text); } else { await tester.enterText(textFieldFinder, text); } } /// 버튼을 찾고 탭하는 헬퍼 Future tapButtonByText( WidgetTester tester, String buttonText, ) async { final buttonFinder = find.widgetWithText(ElevatedButton, buttonText); if (buttonFinder.evaluate().isEmpty) { // ElevatedButton이 아닌 경우 다른 버튼 타입 시도 final textButtonFinder = find.widgetWithText(TextButton, buttonText); if (textButtonFinder.evaluate().isNotEmpty) { await tester.tap(textButtonFinder); return; } final outlinedButtonFinder = find.widgetWithText(OutlinedButton, buttonText); if (outlinedButtonFinder.evaluate().isNotEmpty) { await tester.tap(outlinedButtonFinder); return; } // 아무 버튼도 찾지 못한 경우 텍스트만으로 시도 await tester.tap(find.text(buttonText)); } else { await tester.tap(buttonFinder); } } /// 스낵바 메시지 검증 헬퍼 void expectSnackBar(WidgetTester tester, String message) { expect( find.descendant( of: find.byType(SnackBar), matching: find.text(message), ), findsOneWidget, ); } /// 로딩 인디케이터 검증 헬퍼 void expectLoading(WidgetTester tester, {bool isLoading = true}) { expect( find.byType(CircularProgressIndicator), isLoading ? findsOneWidget : findsNothing, ); } /// 에러 메시지 검증 헬퍼 void expectErrorMessage(WidgetTester tester, String errorMessage) { expect(find.text(errorMessage), findsOneWidget); } /// 화면 전환 대기 헬퍼 Future waitForNavigation(WidgetTester tester) async { await tester.pump(); await tester.pump(const Duration(milliseconds: 300)); // 애니메이션 대기 } /// 다이얼로그 검증 헬퍼 void expectDialog(WidgetTester tester, {String? title, String? content}) { expect(find.byType(Dialog), findsOneWidget); if (title != null) { expect( find.descendant( of: find.byType(Dialog), matching: find.text(title), ), findsOneWidget, ); } if (content != null) { expect( find.descendant( of: find.byType(Dialog), matching: find.text(content), ), findsOneWidget, ); } } /// 다이얼로그 닫기 헬퍼 Future closeDialog(WidgetTester tester) async { // 다이얼로그 외부 탭하여 닫기 await tester.tapAt(const Offset(10, 10)); await tester.pump(); } /// 스크롤하여 위젯 찾기 헬퍼 Future scrollUntilVisible( WidgetTester tester, Finder finder, { double delta = 300, int maxScrolls = 10, Finder? scrollable, }) async { final scrollableFinder = scrollable ?? find.byType(Scrollable).first; for (int i = 0; i < maxScrolls; i++) { if (finder.evaluate().isNotEmpty) { return; } await tester.drag(scrollableFinder, Offset(0, -delta)); await tester.pump(); } } /// 테이블이나 리스트에서 특정 행 찾기 헬퍼 Finder findRowContaining(String text) { return find.ancestor( of: find.text(text), matching: find.byType(Row), ); } /// 폼 필드 검증 헬퍼 void expectFormFieldError(WidgetTester tester, String fieldLabel, String errorText) { final formField = find.ancestor( of: find.text(fieldLabel), matching: find.byType(TextFormField), ); final errorFinder = find.descendant( of: formField, matching: find.text(errorText), ); expect(errorFinder, findsOneWidget); } /// 드롭다운 선택 헬퍼 Future selectDropdownItem( WidgetTester tester, String dropdownLabel, String itemText, ) async { // 드롭다운 찾기 final dropdown = find.ancestor( of: find.text(dropdownLabel), matching: find.byType(DropdownButtonFormField), ); // 드롭다운 열기 await tester.tap(dropdown); await tester.pump(); // 아이템 선택 await tester.tap(find.text(itemText).last); await tester.pump(); } /// 날짜 선택 헬퍼 Future selectDate( WidgetTester tester, String dateFieldLabel, DateTime date, ) async { // 날짜 필드 탭 final dateField = find.ancestor( of: find.text(dateFieldLabel), matching: find.byType(TextFormField), ); await tester.tap(dateField); await tester.pump(); // 날짜 선택 (간단한 구현, 실제로는 더 복잡할 수 있음) await tester.tap(find.text(date.day.toString())); await tester.pump(); // 확인 버튼 탭 await tester.tap(find.text('확인')); await tester.pump(); }