import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:provider/provider.dart'; import 'package:lanebow/screens/club/event_details_screen.dart'; import 'package:lanebow/models/event_model.dart'; import 'package:lanebow/models/participant_model.dart'; import 'package:lanebow/models/score_model.dart'; import 'package:lanebow/models/member_model.dart'; import 'package:lanebow/models/club_model.dart'; import 'package:lanebow/models/team_model.dart'; import 'package:lanebow/services/event_service.dart'; import 'package:lanebow/services/club_service.dart'; import 'package:lanebow/services/member_service.dart'; import 'package:lanebow/widgets/loading_indicator.dart'; // 간단한 테스트 더블 구현 (ChangeNotifier 유지) class FakeEventService extends EventService { @override List participants = []; @override List scores = []; @override Future> fetchEventParticipants(String eventId) async { return participants; } @override Future> fetchEventScores(String eventId) async { return scores; } @override Future> fetchEventTeams(String eventId) async { // 팀 기능은 테스트에서 필수가 아니므로 빈 리스트를 즉시 반환하여 네트워크 의존성을 제거 return []; } } class FakeClubService extends ClubService { Club? club; @override Future fetchClub(String clubId) async { return club ?? Club(name: '클럽', id: clubId); } } class FakeMemberService extends MemberService { List membersByIds = []; @override Future> fetchMembersByIds(String clubId, List ids) async { return membersByIds; } @override Future setClubId(String clubId) async { // no-op: 테스트에서는 내부 상태 주입만 필요하며 네트워크 호출 불필요 return; } @override Future fetchClubMembers() async { // no-op: 테스트에서는 네트워크 호출 없이 즉시 완료 return; } } void main() { TestWidgetsFlutterBinding.ensureInitialized(); // ChangeNotifier 서브타입에 Provider.value 사용 허용(자동 업데이트 불필요한 테스트 시나리오) Provider.debugCheckInvalidValueType = null; group('EventDetailsScreen', () { late FakeEventService eventService; late FakeClubService clubService; late FakeMemberService memberService; final event = Event( id: 'e1', clubId: 'c1', title: '테스트 이벤트', description: '설명입니다', startDate: DateTime(2025, 1, 2, 19, 30), endDate: DateTime(2025, 1, 2, 22, 00), location: '강남볼링장', type: '정기전', status: '진행중', maxParticipants: 24, currentParticipants: 2, gameCount: 3, participantFee: 10000, registrationDeadline: DateTime(2024, 12, 31, 23, 59), publicHash: 'hash123', accessPassword: '1234', isActive: true, ); setUp(() { eventService = FakeEventService(); clubService = FakeClubService(); memberService = FakeMemberService(); // Participants eventService.participants = [ Participant( id: 'p1', eventId: 'e1', memberId: 'm1', registeredAt: DateTime(2025, 1, 1), ), Participant( id: 'p2', eventId: 'e1', memberId: 'm2', registeredAt: DateTime(2025, 1, 1), ), ]; // Scores eventService.scores = [ Score( id: 's1', eventId: 'e1', memberId: 'm1', clubId: 'c1', frames: const [100, 100, 100, 100, 100], totalScore: 500, handicap: 0, date: DateTime(2025, 1, 2), ), ]; // Club clubService.club = Club(id: 'c1', name: '볼링클럽', ownerId: 'u1'); // Members memberService.membersByIds = [ Member( id: 'm1', userId: 'u1', clubId: 'c1', name: '철수', email: 'a@b.com', isActive: true, gender: 'M', birthDate: DateTime(1990, 1, 1), ), Member( id: 'm2', userId: 'u2', clubId: 'c1', name: '영희', email: 'c@d.com', isActive: true, gender: 'F', birthDate: DateTime(1992, 1, 1), ), ]; }); Widget wrapWithProviders(Widget child) { return MultiProvider( providers: [ Provider.value(value: eventService), Provider.value(value: clubService), Provider.value(value: memberService), ], child: MaterialApp(home: child), ); } testWidgets('로딩 후 기본 정보 탭 렌더링 및 핵심 정보 표시', (tester) async { await tester.pumpWidget(wrapWithProviders(EventDetailsScreen(event: event))); // 초기 로딩 표시 expect(find.byType(LoadingIndicator), findsOneWidget); // async 로딩/초기 빌드가 끝나도록 약간 더 대기 (무한 대기 방지) await tester.pump(const Duration(milliseconds: 200)); await tester.pump(const Duration(milliseconds: 200)); // AppBar 제목 expect(find.text('테스트 이벤트'), findsOneWidget); // 탭 존재 확인 expect(find.text('기본 정보'), findsOneWidget); expect(find.text('참가자'), findsOneWidget); expect(find.text('점수'), findsOneWidget); expect(find.text('팀'), findsOneWidget); // 기본 정보 섹션이 렌더링되고 공유 버튼이 존재 expect(find.byIcon(Icons.share), findsOneWidget); // 장소/날짜와 같은 텍스트 일부가 표시되는지(정확한 포맷 대신 포함 여부만 확인) expect(find.textContaining('강남'), findsWidgets); }); testWidgets('참가자 탭: 참가자 수와 리스트 표시', (tester) async { await tester.pumpWidget(wrapWithProviders(EventDetailsScreen(event: event))); await tester.pump(const Duration(milliseconds: 50)); await tester.pump(const Duration(milliseconds: 50)); // 참가자 탭으로 이동: 탭 터치 대신 TabBarView를 드래그로 넘김(제스처 경로 단순화) await tester.drag(find.byType(TabBarView), const Offset(-400, 0)); await tester.pump(const Duration(milliseconds: 250)); await tester.pump(const Duration(milliseconds: 250)); // 리스트가 렌더링되는지(정확한 항목 텍스트 대신 리스트 존재 확인) expect(find.byType(ListView), findsWidgets); }); }); }