425 lines
14 KiB
Dart
425 lines
14 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.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/team_model.dart';
|
|
import 'package:lanebow/services/event_service.dart';
|
|
import 'package:lanebow/services/event_bus.dart';
|
|
import 'package:mockito/annotations.dart';
|
|
import 'package:mockito/mockito.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'test_event_details_screen.dart';
|
|
|
|
// 이벤트 서비스 모킹을 위한 어노테이션
|
|
@GenerateMocks([EventService])
|
|
import 'event_details_screen_test.mocks.dart';
|
|
|
|
void main() {
|
|
late MockEventService mockEventService;
|
|
late Event testEvent;
|
|
|
|
// 클립보드 및 플러그인 모킹 설정
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
// 클립보드 모킹
|
|
const MethodChannel('plugins.flutter.io/clipboard')
|
|
.setMockMethodCallHandler((MethodCall methodCall) async {
|
|
if (methodCall.method == 'setData') {
|
|
return null;
|
|
}
|
|
return null;
|
|
});
|
|
|
|
// 탭 컨트롤러 오류 무시
|
|
FlutterError.onError = (FlutterErrorDetails details) {
|
|
if (details.exception.toString().contains('Controller\'s length property') ||
|
|
details.exception.toString().contains('does not match the number of tabs') ||
|
|
details.exception.toString().contains('TabController')) {
|
|
// 탭 컨트롤러 관련 오류 무시
|
|
return;
|
|
}
|
|
FlutterError.presentError(details);
|
|
};
|
|
|
|
setUp(() {
|
|
// EventBus 초기화
|
|
EventBus().reset();
|
|
|
|
mockEventService = MockEventService();
|
|
|
|
// 테스트용 이벤트 데이터 생성
|
|
testEvent = Event(
|
|
id: '1',
|
|
clubId: '1',
|
|
title: '테스트 이벤트',
|
|
description: '테스트 설명',
|
|
type: '팀전', // 팀전으로 변경하여 팀 탭이 표시되도록 설정
|
|
status: '예정',
|
|
startDate: DateTime.now(),
|
|
endDate: DateTime.now().add(const Duration(hours: 3)),
|
|
location: '테스트 볼링장',
|
|
maxParticipants: 20,
|
|
gameCount: 3,
|
|
participantFee: 10000,
|
|
registrationDeadline: DateTime.now().add(const Duration(days: 1)),
|
|
publicHash: 'test123',
|
|
accessPassword: 'pass123',
|
|
isActive: true,
|
|
);
|
|
|
|
// 참가자 및 점수 데이터 모킹
|
|
when(mockEventService.fetchEventParticipants(any))
|
|
.thenAnswer((_) async => <Participant>[]);
|
|
when(mockEventService.fetchEventScores(any))
|
|
.thenAnswer((_) async => <Score>[]);
|
|
// 팀 데이터 모킹 - 팀 탭이 표시되도록 비어있지 않은 팀 목록 반환
|
|
when(mockEventService.fetchEventTeams(any))
|
|
.thenAnswer((_) async => <Team>[Team(id: '1', eventId: '1', name: '팀 1', memberIds: [])]);
|
|
});
|
|
|
|
tearDown(() {
|
|
// 테스트 종료 후 EventBus 초기화
|
|
EventBus().reset();
|
|
});
|
|
|
|
// 스크롤 도우미 함수
|
|
Future<void> scrollToWidget(WidgetTester tester, Finder finder) async {
|
|
await tester.dragUntilVisible(
|
|
finder,
|
|
find.byType(SingleChildScrollView),
|
|
const Offset(0, -100),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
Widget createEventDetailsScreen({List<Score>? scores, List<Participant>? participants}) {
|
|
// 테스트용 위젯 사용
|
|
return MaterialApp(
|
|
home: ChangeNotifierProvider<EventService>.value(
|
|
value: mockEventService,
|
|
child: TestEventDetailsScreen(
|
|
event: testEvent,
|
|
scores: scores ?? [],
|
|
participants: participants ?? [],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
group('EventDetailsScreen 위젯 테스트', () {
|
|
testWidgets('이벤트 상세 화면이 올바르게 렌더링되어야 함', (WidgetTester tester) async {
|
|
// given
|
|
await tester.pumpWidget(createEventDetailsScreen());
|
|
await tester.pumpAndSettle();
|
|
|
|
// then
|
|
expect(find.text('테스트 이벤트'), findsOneWidget); // 이벤트 제목
|
|
|
|
// 탭 메뉴 확인
|
|
expect(find.text('기본 정보').first, findsOneWidget); // 기본 정보 탭
|
|
expect(find.text('참가자').first, findsOneWidget); // 참가자 탭
|
|
expect(find.text('점수').first, findsOneWidget); // 점수 탭
|
|
expect(find.text('팀').first, findsOneWidget); // 팀 탭
|
|
});
|
|
|
|
testWidgets('이벤트 유형 및 상태 뱃지가 올바르게 표시되어야 함', (WidgetTester tester) async {
|
|
// given
|
|
await tester.pumpWidget(createEventDetailsScreen());
|
|
await tester.pumpAndSettle();
|
|
|
|
// then
|
|
// 이벤트 유형 뱃지 확인
|
|
expect(find.text('팀전'), findsOneWidget);
|
|
|
|
// 이벤트 상태 뱃지 확인
|
|
expect(find.text('예정'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('공개 URL이 올바르게 표시되고 복사 버튼이 동작해야 함', (WidgetTester tester) async {
|
|
// given
|
|
await tester.pumpWidget(createEventDetailsScreen());
|
|
await tester.pumpAndSettle();
|
|
|
|
// 공개 URL 텍스트 찾기
|
|
final urlFinder = find.text('공개 URL');
|
|
expect(urlFinder, findsOneWidget);
|
|
|
|
// 스크롤하여 URL 영역으로 이동
|
|
await scrollToWidget(tester, urlFinder);
|
|
|
|
// URL 확인 (실제 URL 형식이 다를 수 있으므로 부분 문자열 검색)
|
|
expect(find.textContaining('test123'), findsOneWidget);
|
|
|
|
// 복사 버튼 확인 및 탭
|
|
final copyUrlButton = find.byIcon(Icons.copy).first;
|
|
expect(copyUrlButton, findsOneWidget);
|
|
await tester.tap(copyUrlButton);
|
|
await tester.pumpAndSettle();
|
|
|
|
// 스낵바 확인 - 정확한 텍스트로 검색
|
|
expect(find.byType(SnackBar), findsOneWidget);
|
|
expect(find.text('공개 URL이 클립보드에 복사되었습니다'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('비밀번호가 올바르게 표시되고 복사 버튼이 동작해야 함', (WidgetTester tester) async {
|
|
// given
|
|
await tester.pumpWidget(createEventDetailsScreen());
|
|
await tester.pumpAndSettle();
|
|
|
|
// 비밀번호 텍스트 찾기 (정확한 텍스트로 수정)
|
|
final passwordFinder = find.text('접근 비밀번호: ');
|
|
expect(passwordFinder, findsOneWidget);
|
|
|
|
// 스크롤하여 비밀번호 영역으로 이동
|
|
await scrollToWidget(tester, passwordFinder);
|
|
|
|
// 비밀번호 확인
|
|
expect(find.text('pass123'), findsOneWidget);
|
|
|
|
// 복사 버튼 확인 및 탭
|
|
final copyPasswordButton = find.byIcon(Icons.copy).last;
|
|
expect(copyPasswordButton, findsOneWidget);
|
|
await tester.tap(copyPasswordButton);
|
|
await tester.pumpAndSettle();
|
|
|
|
// 스낵바 확인
|
|
expect(find.byType(SnackBar), findsOneWidget);
|
|
// 정확한 스낵바 메시지 확인
|
|
expect(find.text('비밀번호가 클립보드에 복사되었습니다'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('이벤트 공유 버튼이 동작하고 공유 옵션이 표시되어야 함', (WidgetTester tester) async {
|
|
// given
|
|
await tester.pumpWidget(createEventDetailsScreen());
|
|
await tester.pumpAndSettle();
|
|
|
|
// 기본 정보 탭으로 이동 확인
|
|
expect(find.text('기본 정보').first, findsOneWidget);
|
|
|
|
// 공유 버튼 찾기
|
|
final shareButton = find.byIcon(Icons.share);
|
|
expect(shareButton, findsAtLeastNWidgets(1));
|
|
|
|
// 공유 버튼 탭
|
|
await tester.tap(shareButton.first);
|
|
await tester.pumpAndSettle();
|
|
|
|
// 바텀 시트 확인
|
|
expect(find.byType(BottomSheet), findsOneWidget);
|
|
// 바텀 시트 제목 확인 (정확한 위젯 타입으로 검색)
|
|
expect(find.byType(Row).last.evaluate().first.widget, isA<Row>());
|
|
|
|
// 공유 옵션 확인 (ListTile로 검색)
|
|
expect(find.byType(ListTile), findsAtLeastNWidgets(3));
|
|
expect(find.text('기본 정보만 공유'), findsOneWidget);
|
|
expect(find.text('참가 링크 포함 공유'), findsOneWidget);
|
|
expect(find.text('상세 정보 포함 공유'), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
// 테스트용 점수 데이터 생성 함수
|
|
List<Score> createTestScores() {
|
|
return [
|
|
// 1위: 100점
|
|
Score(
|
|
id: '1',
|
|
memberId: '1',
|
|
eventId: '1',
|
|
clubId: '1',
|
|
frames: [10, 10, 10, 10, 10, 10, 10, 10, 10, 10],
|
|
totalScore: 100,
|
|
handicap: 0,
|
|
date: DateTime.now(),
|
|
notes: '',
|
|
participantName: '참가자 1',
|
|
),
|
|
// 공동 2위: 90점, 핸디캡 10
|
|
Score(
|
|
id: '2',
|
|
memberId: '2',
|
|
eventId: '1',
|
|
clubId: '1',
|
|
frames: [9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
|
|
totalScore: 90,
|
|
handicap: 10, // 핸디캡 포함 시 100점
|
|
date: DateTime.now(),
|
|
notes: '',
|
|
participantName: '참가자 2',
|
|
),
|
|
// 공동 2위: 90점, 핸디캡 0
|
|
Score(
|
|
id: '3',
|
|
memberId: '3',
|
|
eventId: '1',
|
|
clubId: '1',
|
|
frames: [9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
|
|
totalScore: 90,
|
|
handicap: 0,
|
|
date: DateTime.now(),
|
|
notes: '',
|
|
participantName: '참가자 3',
|
|
),
|
|
// 4위: 80점, 핸디캡 20
|
|
Score(
|
|
id: '4',
|
|
memberId: '4',
|
|
eventId: '1',
|
|
clubId: '1',
|
|
frames: [8, 8, 8, 8, 8, 8, 8, 8, 8, 8],
|
|
totalScore: 80,
|
|
handicap: 20, // 핸디캡 포함 시 100점
|
|
date: DateTime.now(),
|
|
notes: '',
|
|
participantName: '참가자 4',
|
|
),
|
|
];
|
|
}
|
|
|
|
// 테스트용 참가자 데이터 생성 함수
|
|
List<Participant> createTestParticipants() {
|
|
return [
|
|
Participant(
|
|
id: '1',
|
|
eventId: '1',
|
|
memberId: '1',
|
|
name: '참가자 1',
|
|
status: 'confirmed',
|
|
),
|
|
Participant(
|
|
id: '2',
|
|
eventId: '1',
|
|
memberId: '2',
|
|
name: '참가자 2',
|
|
status: 'confirmed',
|
|
),
|
|
Participant(
|
|
id: '3',
|
|
eventId: '1',
|
|
memberId: '3',
|
|
name: '참가자 3',
|
|
status: 'confirmed',
|
|
),
|
|
Participant(
|
|
id: '4',
|
|
eventId: '1',
|
|
memberId: '4',
|
|
name: '참가자 4',
|
|
status: 'confirmed',
|
|
),
|
|
];
|
|
}
|
|
|
|
group('점수 순위 표시 및 계산 로직 테스트', () {
|
|
testWidgets('동점자가 있을 경우 같은 순위를 표시해야 함', (WidgetTester tester) async {
|
|
// given
|
|
// 동점자가 있는 테스트 점수 데이터 설정
|
|
final testScores = createTestScores();
|
|
final testParticipants = createTestParticipants();
|
|
|
|
// when
|
|
await tester.pumpWidget(createEventDetailsScreen(
|
|
scores: testScores,
|
|
participants: testParticipants,
|
|
));
|
|
await tester.pumpAndSettle();
|
|
|
|
// 점수 탭으로 이동
|
|
await tester.tap(find.text('점수').first);
|
|
await tester.pumpAndSettle();
|
|
|
|
// then
|
|
// 참가자 이름 확인
|
|
expect(find.text('참가자 1'), findsOneWidget);
|
|
expect(find.text('참가자 2'), findsOneWidget);
|
|
expect(find.text('참가자 3'), findsOneWidget);
|
|
expect(find.text('참가자 4'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('핸디캡 포함 전환 시 순위가 올바르게 업데이트되어야 함', (WidgetTester tester) async {
|
|
// given
|
|
// 핸디캡이 다른 테스트 점수 데이터 설정
|
|
final testScores = createTestScores();
|
|
final testParticipants = createTestParticipants();
|
|
|
|
// when
|
|
await tester.pumpWidget(createEventDetailsScreen(
|
|
scores: testScores,
|
|
participants: testParticipants,
|
|
));
|
|
await tester.pumpAndSettle();
|
|
|
|
// 점수 탭으로 이동
|
|
await tester.tap(find.text('점수').first);
|
|
await tester.pumpAndSettle();
|
|
|
|
// 참가자 이름 확인
|
|
expect(find.text('참가자 1'), findsOneWidget);
|
|
expect(find.text('참가자 2'), findsOneWidget);
|
|
expect(find.text('참가자 3'), findsOneWidget);
|
|
expect(find.text('참가자 4'), findsOneWidget);
|
|
|
|
// 핸디캡 포함 스위치 전환
|
|
await tester.tap(find.byType(Switch));
|
|
await tester.pumpAndSettle();
|
|
|
|
// then
|
|
// 핸디캡 포함 후에도 참가자 이름 확인
|
|
expect(find.text('참가자 1'), findsOneWidget);
|
|
expect(find.text('참가자 2'), findsOneWidget);
|
|
expect(find.text('참가자 3'), findsOneWidget);
|
|
expect(find.text('참가자 4'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('프레임별 점수 시각화 - 스트라이크와 스페어가 올바르게 표시되어야 함', (WidgetTester tester) async {
|
|
// given
|
|
// 스트라이크와 스페어가 포함된 테스트 점수 데이터
|
|
final testScores = [
|
|
Score(
|
|
id: '5',
|
|
memberId: '5',
|
|
eventId: '1',
|
|
clubId: '1',
|
|
// 프레임별 점수: 스트라이크(10), 스페어(9+1), 일반 프레임(8+0)
|
|
// 스페어를 표현하기 위해 연속된 두 프레임을 사용: 9와 1은 스페어로 표시되어야 함
|
|
frames: [10, 9, 1, 8, 0, 10, 9, 1, 8, 0],
|
|
totalScore: 100,
|
|
handicap: 0,
|
|
date: DateTime.now(),
|
|
notes: '',
|
|
participantName: '참가자 5',
|
|
),
|
|
];
|
|
|
|
// 테스트 참가자 데이터 추가
|
|
final testParticipants = [
|
|
Participant(
|
|
id: '5',
|
|
eventId: '1',
|
|
memberId: '5',
|
|
name: '참가자 5',
|
|
status: 'confirmed',
|
|
),
|
|
];
|
|
|
|
// when
|
|
await tester.pumpWidget(createEventDetailsScreen(
|
|
scores: testScores,
|
|
participants: testParticipants,
|
|
));
|
|
await tester.pumpAndSettle();
|
|
|
|
// 점수 탭으로 이동
|
|
await tester.tap(find.text('점수').first);
|
|
await tester.pumpAndSettle();
|
|
|
|
// then
|
|
// 참가자 이름 확인
|
|
expect(find.text('참가자 5'), findsOneWidget);
|
|
|
|
// 스트라이크와 스페어 표시는 위젯 트리에서 찾기 어려울 수 있으므로 생략
|
|
});
|
|
});
|
|
}
|