공개페이지
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:lanebow/models/public_event.dart';
|
||||
import 'package:lanebow/models/public_participant.dart';
|
||||
|
||||
void main() {
|
||||
group('PublicEvent.fromJson', () {
|
||||
test('parses minimal and coerces types', () {
|
||||
final e = PublicEvent.fromJson({
|
||||
'id': 10,
|
||||
'title': 'Open',
|
||||
'gameCount': '5',
|
||||
});
|
||||
expect(e.id, '10');
|
||||
expect(e.title, 'Open');
|
||||
expect(e.gameCount, 5);
|
||||
expect(e.description, isNull);
|
||||
});
|
||||
|
||||
test('parses dates and optionals', () {
|
||||
final e = PublicEvent.fromJson({
|
||||
'id': 'A1',
|
||||
'title': 'E',
|
||||
'startDate': '2024-01-01T10:00:00Z',
|
||||
'registrationDeadline': '2024-01-02T10:00:00Z',
|
||||
'participantFee': '10000',
|
||||
'maxParticipants': '30',
|
||||
});
|
||||
expect(e.id, 'A1');
|
||||
expect(e.startDate, isNotNull);
|
||||
expect(e.registrationDeadline, isNotNull);
|
||||
expect(e.participantFee, 10000);
|
||||
expect(e.maxParticipants, 30);
|
||||
});
|
||||
});
|
||||
|
||||
group('PublicParticipant.fromJson', () {
|
||||
test('handles numeric scores and fills length to gameCount', () {
|
||||
final p = PublicParticipant.fromJson({
|
||||
'participantId': 7,
|
||||
'name': 'Kim',
|
||||
'scores': [120, 130],
|
||||
'handicap': 10,
|
||||
}, gameCount: 3);
|
||||
expect(p.participantId, '7');
|
||||
expect(p.name, 'Kim');
|
||||
expect(p.rawScores, [120, 130, null]);
|
||||
expect(p.handicaps, [0, 0, 0]);
|
||||
});
|
||||
|
||||
test('handles object scores with handicap', () {
|
||||
final p = PublicParticipant.fromJson({
|
||||
'participantId': '9',
|
||||
'name': 'Lee',
|
||||
'scores': [
|
||||
{'score': 200, 'handicap': 0},
|
||||
{'score': '150', 'handicap': '5'},
|
||||
null
|
||||
],
|
||||
}, gameCount: 3);
|
||||
expect(p.rawScores, [200, 150, null]);
|
||||
expect(p.handicaps, [0, 5, 0]);
|
||||
});
|
||||
|
||||
test('coerces fields and defaults', () {
|
||||
final p = PublicParticipant.fromJson({
|
||||
'id': '11',
|
||||
'name': null,
|
||||
'average': '192.5',
|
||||
'memberType': 'guest',
|
||||
'status': 'pending',
|
||||
'paymentStatus': 'unpaid',
|
||||
'scores': [],
|
||||
}, gameCount: 2);
|
||||
expect(p.participantId, '11');
|
||||
expect(p.name, '이름없음');
|
||||
expect(p.average, 192.5);
|
||||
expect(p.isGuest, false); // isGuest는 명시 true일 때만 처리
|
||||
expect(p.rawScores, [null, null]);
|
||||
expect(p.handicaps, [0, 0]);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import 'package:lanebow/services/public_event_service.dart';
|
||||
import 'package:lanebow/utils/web_storage.dart';
|
||||
|
||||
class FakeHttp implements PublicHttpClient {
|
||||
Response<dynamic>? lastResponse;
|
||||
Map<String, dynamic>? lastHeaders;
|
||||
String? lastPath;
|
||||
dynamic lastData;
|
||||
|
||||
int statusCode = 200;
|
||||
Map<String, dynamic> responseData = const {'ok': true};
|
||||
|
||||
@override
|
||||
Future<Response<dynamic>> get(String path, {Map<String, dynamic>? headers}) async {
|
||||
lastPath = path;
|
||||
lastData = null;
|
||||
lastHeaders = headers;
|
||||
if (statusCode >= 400) {
|
||||
throw DioException(
|
||||
requestOptions: RequestOptions(path: path),
|
||||
response: Response(requestOptions: RequestOptions(path: path), statusCode: statusCode, data: responseData),
|
||||
type: DioExceptionType.badResponse,
|
||||
);
|
||||
}
|
||||
lastResponse = Response(requestOptions: RequestOptions(path: path), statusCode: statusCode, data: responseData);
|
||||
return lastResponse!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<dynamic>> post(String path, {data, Map<String, dynamic>? headers}) async {
|
||||
lastPath = path;
|
||||
lastData = data;
|
||||
lastHeaders = headers;
|
||||
if (statusCode >= 400) {
|
||||
throw DioException(
|
||||
requestOptions: RequestOptions(path: path),
|
||||
response: Response(requestOptions: RequestOptions(path: path), statusCode: statusCode, data: responseData),
|
||||
type: DioExceptionType.badResponse,
|
||||
);
|
||||
}
|
||||
lastResponse = Response(requestOptions: RequestOptions(path: path), statusCode: statusCode, data: responseData);
|
||||
return lastResponse!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<dynamic>> put(String path, {data, Map<String, dynamic>? headers}) async {
|
||||
lastPath = path;
|
||||
lastData = data;
|
||||
lastHeaders = headers;
|
||||
if (statusCode >= 400) {
|
||||
throw DioException(
|
||||
requestOptions: RequestOptions(path: path),
|
||||
response: Response(requestOptions: RequestOptions(path: path), statusCode: statusCode, data: responseData),
|
||||
type: DioExceptionType.badResponse,
|
||||
);
|
||||
}
|
||||
lastResponse = Response(requestOptions: RequestOptions(path: path), statusCode: statusCode, data: responseData);
|
||||
return lastResponse!;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('PublicEventService.enter', () {
|
||||
test('saves token when response includes token', () async {
|
||||
final fake = FakeHttp()
|
||||
..statusCode = 200
|
||||
..responseData = {'token': 'TKN', 'event': {'id': 1}};
|
||||
final svc = PublicEventService.forTest(fake);
|
||||
final hash = 'abc123';
|
||||
|
||||
// ensure empty
|
||||
WebStorage.removeItem(PublicEventService.tokenKey(hash));
|
||||
|
||||
final data = await svc.enter(hash, password: 'pw');
|
||||
expect(data['token'], 'TKN');
|
||||
expect(WebStorage.getItem(PublicEventService.tokenKey(hash)), 'TKN');
|
||||
expect(fake.lastPath, '/api/public/' + hash);
|
||||
});
|
||||
|
||||
test('removes saved token on 401/403', () async {
|
||||
final fake = FakeHttp()
|
||||
..statusCode = 401
|
||||
..responseData = {'message': 'unauthorized'};
|
||||
final svc = PublicEventService.forTest(fake);
|
||||
final hash = 'abc401';
|
||||
|
||||
// preset token
|
||||
WebStorage.setItem(PublicEventService.tokenKey(hash), 'OLD');
|
||||
|
||||
await expectLater(
|
||||
svc.enter(hash, password: 'wrong'),
|
||||
throwsA(isA<DioException>()),
|
||||
);
|
||||
expect(WebStorage.getItem(PublicEventService.tokenKey(hash)), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('PublicEventService.updateScore', () {
|
||||
test('sends Authorization header when token saved', () async {
|
||||
final fake = FakeHttp()
|
||||
..statusCode = 200
|
||||
..responseData = {'ok': true};
|
||||
final svc = PublicEventService.forTest(fake);
|
||||
final hash = 'hhh';
|
||||
WebStorage.setItem(PublicEventService.tokenKey(hash), 'TKN2');
|
||||
|
||||
await svc.updateScore(
|
||||
hash,
|
||||
participantId: '10',
|
||||
gameNumber: 1,
|
||||
score: 200,
|
||||
handicap: 0,
|
||||
);
|
||||
|
||||
expect(fake.lastPath, '/api/public/' + hash + '/score');
|
||||
expect(fake.lastHeaders?['Authorization'], 'Bearer TKN2');
|
||||
expect(fake.lastData['participantId'], '10');
|
||||
expect(fake.lastData['gameNumber'], 1);
|
||||
expect(fake.lastData['score'], 200);
|
||||
});
|
||||
});
|
||||
|
||||
group('PublicEventService.fetchEvent', () {
|
||||
test('uses GET and Authorization header when token saved', () async {
|
||||
final fake = FakeHttp()
|
||||
..statusCode = 200
|
||||
..responseData = {'event': {'id': 'E1'}};
|
||||
final svc = PublicEventService.forTest(fake);
|
||||
final hash = 'fetch1';
|
||||
WebStorage.setItem(PublicEventService.tokenKey(hash), 'TKF');
|
||||
|
||||
final data = await svc.fetchEvent(hash);
|
||||
expect(data['event']['id'], 'E1');
|
||||
expect(fake.lastPath, '/api/public/' + hash);
|
||||
expect(fake.lastHeaders?['Authorization'], 'Bearer TKF');
|
||||
});
|
||||
});
|
||||
|
||||
group('PublicEventService.enter edge cases', () {
|
||||
test('404 does not remove saved token', () async {
|
||||
final fake = FakeHttp()
|
||||
..statusCode = 404
|
||||
..responseData = {'message': 'not found'};
|
||||
final svc = PublicEventService.forTest(fake);
|
||||
final hash = 'nf404';
|
||||
WebStorage.setItem(PublicEventService.tokenKey(hash), 'KEEP');
|
||||
await expectLater(svc.enter(hash), throwsA(isA<DioException>()));
|
||||
expect(WebStorage.getItem(PublicEventService.tokenKey(hash)), 'KEEP');
|
||||
});
|
||||
});
|
||||
|
||||
group('PublicEventService.addGuest', () {
|
||||
test('sends payload and header', () async {
|
||||
final fake = FakeHttp()
|
||||
..statusCode = 200
|
||||
..responseData = {'ok': true};
|
||||
final svc = PublicEventService.forTest(fake);
|
||||
final hash = 'guest';
|
||||
WebStorage.setItem(PublicEventService.tokenKey(hash), 'TG');
|
||||
|
||||
await svc.addGuest(hash, name: '홍길동', phone: '010', gender: 'M', average: 190);
|
||||
expect(fake.lastPath, '/api/public/' + hash + '/guest');
|
||||
expect(fake.lastHeaders?['Authorization'], 'Bearer TG');
|
||||
expect(fake.lastData['name'], '홍길동');
|
||||
expect(fake.lastData['phone'], '010');
|
||||
expect(fake.lastData['gender'], 'M');
|
||||
expect(fake.lastData['average'], 190);
|
||||
});
|
||||
});
|
||||
|
||||
group('PublicEventService.updateScore errors', () {
|
||||
test('propagates server error 500', () async {
|
||||
final fake = FakeHttp()
|
||||
..statusCode = 500
|
||||
..responseData = {'message': 'oops'};
|
||||
final svc = PublicEventService.forTest(fake);
|
||||
final hash = 'err';
|
||||
WebStorage.setItem(PublicEventService.tokenKey(hash), 'TE');
|
||||
|
||||
await expectLater(
|
||||
svc.updateScore(hash, participantId: 'p1', gameNumber: 1, score: 100),
|
||||
throwsA(isA<DioException>()),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:lanebow/models/public_participant.dart';
|
||||
import 'package:lanebow/models/public_team.dart';
|
||||
import 'package:lanebow/utils/ranking_utils_public.dart';
|
||||
|
||||
void main() {
|
||||
group('PublicRankingUtils', () {
|
||||
test('caps score with handicap at 300 per game and ties rank', () {
|
||||
final p1 = PublicParticipant.fromJson({
|
||||
'participantId': '1',
|
||||
'name': 'A',
|
||||
'scores': [250, 280],
|
||||
'handicap': 0,
|
||||
}, gameCount: 2);
|
||||
final p2 = PublicParticipant.fromJson({
|
||||
'participantId': '2',
|
||||
'name': 'B',
|
||||
'scores': [295, 290],
|
||||
'handicap': 20, // 각 게임 합이 300 넘는 케이스 -> 캡
|
||||
}, gameCount: 2);
|
||||
|
||||
final ranks = PublicRankingUtils.individualRanking([p1, p2]);
|
||||
// p1 totals: 250 + 280 = 530
|
||||
// p2 totals with cap: min(295+20,300)=300 + min(290+20,300)=300 => 600
|
||||
expect(ranks[0]['p'].participantId, '2');
|
||||
expect(ranks[0]['total'], 600);
|
||||
expect(ranks[0]['rank'], 1);
|
||||
expect(ranks[1]['p'].participantId, '1');
|
||||
expect(ranks[1]['total'], 530);
|
||||
expect(ranks[1]['rank'], 2);
|
||||
});
|
||||
|
||||
test('tie produces 1,1,3 ranks', () {
|
||||
final p1 = PublicParticipant.fromJson({
|
||||
'participantId': '1', 'name': 'A', 'scores': [200], 'handicap': 0
|
||||
}, gameCount: 1);
|
||||
final p2 = PublicParticipant.fromJson({
|
||||
'participantId': '2', 'name': 'B', 'scores': [200], 'handicap': 0
|
||||
}, gameCount: 1);
|
||||
final p3 = PublicParticipant.fromJson({
|
||||
'participantId': '3', 'name': 'C', 'scores': [150], 'handicap': 0
|
||||
}, gameCount: 1);
|
||||
final ranks = PublicRankingUtils.individualRanking([p1, p2, p3]);
|
||||
expect(ranks[0]['total'], 200);
|
||||
expect(ranks[1]['total'], 200);
|
||||
expect(ranks[2]['total'], 150);
|
||||
expect(ranks[0]['rank'], 1);
|
||||
expect(ranks[1]['rank'], 1);
|
||||
expect(ranks[2]['rank'], 3);
|
||||
});
|
||||
|
||||
test('team ranking sums member totals and team handicap', () {
|
||||
final team = PublicTeam.fromJson({
|
||||
'teamId': 'T1',
|
||||
'teamNumber': 1,
|
||||
'handicap': 5,
|
||||
'members': [
|
||||
{
|
||||
'participantId': '1',
|
||||
'name': 'A',
|
||||
'scores': [200, {'score': 150, 'handicap': 10}],
|
||||
},
|
||||
{
|
||||
'participantId': '2',
|
||||
'name': 'B',
|
||||
'scores': [100, 120],
|
||||
},
|
||||
]
|
||||
}, gameCount: 2);
|
||||
final ranked = PublicRankingUtils.teamRanking([team]);
|
||||
// member1: 200 + min(150+10,300)=160 => 360
|
||||
// member2: 100 + 120 = 220
|
||||
// team total = 360 + 220 + teamHandicap(5) = 585
|
||||
expect(ranked[0]['total'], 585);
|
||||
expect(ranked[0]['rank'], 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lanebow/screens/public_event/public_event_entry_screen.dart';
|
||||
import 'package:lanebow/screens/public_event/public_event_screen.dart';
|
||||
import 'package:lanebow/services/public_event_service.dart';
|
||||
|
||||
DioException dio404(String path) => DioException(
|
||||
requestOptions: RequestOptions(path: path),
|
||||
response: Response(requestOptions: RequestOptions(path: path), statusCode: 404),
|
||||
type: DioExceptionType.badResponse,
|
||||
);
|
||||
|
||||
class Stub404Service extends PublicEventService {
|
||||
@override
|
||||
Future<Map<String, dynamic>> fetchEvent(String publicHash) async {
|
||||
throw dio404('/api/public/' + publicHash);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('shows 404 message and go to entry button navigates back', (tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: PublicEventScreen(publicHash: 'nope', service: Stub404Service()),
|
||||
));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('이벤트를 찾을 수 없습니다'), findsOneWidget);
|
||||
expect(find.byKey(const Key('go_entry_button')), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byKey(const Key('go_entry_button')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(PublicEventEntryScreen), findsOneWidget);
|
||||
expect(find.text('공개 이벤트 입장'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lanebow/screens/public_event/public_event_entry_screen.dart';
|
||||
import 'package:lanebow/screens/public_event/public_event_screen.dart';
|
||||
import 'package:lanebow/services/public_event_service.dart';
|
||||
|
||||
DioException dio401(String path) => DioException(
|
||||
requestOptions: RequestOptions(path: path),
|
||||
response: Response(requestOptions: RequestOptions(path: path), statusCode: 401),
|
||||
type: DioExceptionType.badResponse,
|
||||
);
|
||||
|
||||
class StubFetch401Service extends PublicEventService {
|
||||
@override
|
||||
Future<Map<String, dynamic>> fetchEvent(String publicHash) async {
|
||||
throw dio401('/api/public/' + publicHash);
|
||||
}
|
||||
}
|
||||
|
||||
class StubUpdate401Service extends PublicEventService {
|
||||
@override
|
||||
Future<Map<String, dynamic>> fetchEvent(String publicHash) async {
|
||||
return {
|
||||
'event': {'id': 'E1', 'title': '테스트 이벤트', 'gameCount': 3, 'status': 'active'},
|
||||
'participants': [
|
||||
{
|
||||
'participantId': 'p1',
|
||||
'name': 'Kim',
|
||||
'scores': [100, null, null],
|
||||
'handicap': 0,
|
||||
},
|
||||
],
|
||||
'teams': [],
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateScore(String publicHash, {required String participantId, required int gameNumber, required int score, int? handicap, dynamic teamNumber}) async {
|
||||
throw dio401('/api/public/' + publicHash + '/score');
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('401 on fetchEvent navigates to entry screen', (tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: PublicEventScreen(publicHash: 'abc', service: StubFetch401Service()),
|
||||
));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(PublicEventEntryScreen), findsOneWidget);
|
||||
expect(find.text('공개 이벤트 입장'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('401 on updateScore navigates to entry screen', (tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: PublicEventScreen(publicHash: 'abc', service: StubUpdate401Service()),
|
||||
));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const Key('participant_tile_p1')), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byKey(const Key('participant_tile_p1')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(find.byKey(const Key('score_input')), '150');
|
||||
await tester.tap(find.byKey(const Key('score_save_button')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(PublicEventEntryScreen), findsOneWidget);
|
||||
expect(find.text('공개 이벤트 입장'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lanebow/screens/public_event/public_event_entry_screen.dart';
|
||||
import 'package:lanebow/services/public_event_service.dart';
|
||||
|
||||
class StubSuccessService extends PublicEventService {
|
||||
@override
|
||||
Future<Map<String, dynamic>> enter(String publicHash, {String? password}) async {
|
||||
// 즉시 성공(토큰 포함)
|
||||
return {'token': 'TKN', 'event': {'id': 'E1'}};
|
||||
}
|
||||
}
|
||||
|
||||
class StubNeedPasswordThenSuccessService extends PublicEventService {
|
||||
bool first = true;
|
||||
@override
|
||||
Future<Map<String, dynamic>> enter(String publicHash, {String? password}) async {
|
||||
if (first) {
|
||||
first = false;
|
||||
throw Exception('401 need password');
|
||||
}
|
||||
if (password == 'pw') {
|
||||
return {'token': 'TKN2', 'event': {'id': 'E2'}};
|
||||
}
|
||||
throw Exception('wrong password');
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('auto enter success navigates to PublicEventScreen', (tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: PublicEventEntryScreen(publicHash: 'auto', service: StubSuccessService()),
|
||||
));
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('공개 이벤트'), findsOneWidget);
|
||||
expect(find.text('공개 이벤트 입장'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('needs password then success on submit', (tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: PublicEventEntryScreen(publicHash: 'needpw', service: StubNeedPasswordThenSuccessService()),
|
||||
));
|
||||
|
||||
// 초기 자동 진입 실패 → 비밀번호 UI 노출
|
||||
await tester.pump();
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const Key('public_password_field')), findsOneWidget);
|
||||
|
||||
// 비밀번호 입력 후 제출
|
||||
await tester.enterText(find.byKey(const Key('public_password_field')), 'pw');
|
||||
await tester.tap(find.byKey(const Key('public_enter_submit')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('공개 이벤트'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lanebow/screens/public_event/public_event_screen.dart';
|
||||
import 'package:lanebow/services/public_event_service.dart';
|
||||
|
||||
class StubFinalService extends PublicEventService {
|
||||
@override
|
||||
Future<Map<String, dynamic>> fetchEvent(String publicHash) async {
|
||||
return {
|
||||
'event': {'id': 'E1', 'title': '최종 결과 테스트', 'gameCount': 3, 'status': 'completed'},
|
||||
'participants': [
|
||||
{
|
||||
'participantId': 'p1',
|
||||
'name': 'Kim',
|
||||
'scores': [140, 150, 0],
|
||||
'handicap': 0,
|
||||
},
|
||||
{
|
||||
'participantId': 'p2',
|
||||
'name': 'Lee',
|
||||
'scores': [200, 100, 0],
|
||||
'handicap': 0,
|
||||
},
|
||||
{
|
||||
'participantId': 'p3',
|
||||
'name': 'Park',
|
||||
'scores': [200, 100, 0],
|
||||
'handicap': 0,
|
||||
},
|
||||
],
|
||||
'teams': [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('완료 상태에서 최종 결과 섹션 렌더와 동순위 처리, 평균 1자리', (tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: PublicEventScreen(publicHash: 'H', service: StubFinalService()),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// 최종 결과 섹션 존재 (스크롤 필요 시 스크롤)
|
||||
final scrollable = find.byType(Scrollable).first;
|
||||
await tester.scrollUntilVisible(find.byKey(const Key('section_final_results')), 300, scrollable: scrollable);
|
||||
expect(find.byKey(const Key('section_final_results')), findsOneWidget);
|
||||
|
||||
// 각 행 존재
|
||||
expect(find.byKey(const Key('result_row_p1')), findsOneWidget);
|
||||
expect(find.byKey(const Key('result_row_p2')), findsOneWidget);
|
||||
expect(find.byKey(const Key('result_row_p3')), findsOneWidget);
|
||||
|
||||
// 총점: p2/p3=300, p1=300 (동률), 동순위 1,1,3
|
||||
expect(find.byKey(const Key('result_rank_p2')), findsOneWidget);
|
||||
expect(find.byKey(const Key('result_rank_p3')), findsOneWidget);
|
||||
expect(find.byKey(const Key('result_rank_p1')), findsOneWidget);
|
||||
|
||||
final r2 = tester.widget<Text>(find.byKey(const Key('result_rank_p2'))).data;
|
||||
final r3 = tester.widget<Text>(find.byKey(const Key('result_rank_p3'))).data;
|
||||
final r1 = tester.widget<Text>(find.byKey(const Key('result_rank_p1'))).data;
|
||||
|
||||
expect(r2, '1');
|
||||
expect(r3, '1');
|
||||
expect(r1, '3');
|
||||
|
||||
// 평균 표기 1자리 확인: 0점도 입력으로 카운트됨 → p2: (200+100+0)/3 = 100.0
|
||||
expect(find.textContaining('평균 100.0'), findsWidgets);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lanebow/screens/public_event/public_event_screen.dart';
|
||||
import 'package:lanebow/services/public_event_service.dart';
|
||||
|
||||
class StubInlineService extends PublicEventService {
|
||||
int fetchCount = 0;
|
||||
int updateCount = 0;
|
||||
final List<Map<String, dynamic>> calls = [];
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> fetchEvent(String publicHash) async {
|
||||
fetchCount++;
|
||||
return {
|
||||
'event': {'id': 'E1', 'title': '인라인 저장 테스트', 'gameCount': 3, 'status': 'active'},
|
||||
'participants': [
|
||||
{
|
||||
'participantId': 'p1',
|
||||
'name': 'Kim',
|
||||
'scores': [null, null, null],
|
||||
'handicap': 0,
|
||||
},
|
||||
],
|
||||
'teams': [],
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateScore(String publicHash, {required String participantId, required int gameNumber, required int score, int? handicap, teamNumber}) async {
|
||||
updateCount++;
|
||||
calls.add({'pid': participantId, 'g': gameNumber, 's': score});
|
||||
// simulate small delay
|
||||
await Future<void>.delayed(const Duration(milliseconds: 1));
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('3자리 입력 즉시 저장, 디바운스 저장, 범위 밖 값은 저장 안함', (tester) async {
|
||||
final stub = StubInlineService();
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: PublicEventScreen(publicHash: 'H', service: stub),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// cell p1 game2 존재 확인
|
||||
final cellG2 = find.byKey(const Key('cell_p1_2'));
|
||||
expect(cellG2, findsOneWidget);
|
||||
|
||||
// 3자리 입력 즉시 저장
|
||||
await tester.enterText(cellG2, '150');
|
||||
// allow microtasks
|
||||
await tester.pump(const Duration(milliseconds: 10));
|
||||
expect(stub.updateCount, 1);
|
||||
expect(stub.calls.last['s'], 150);
|
||||
|
||||
// 1자리 입력 후 디바운스 200ms 후 저장
|
||||
await tester.enterText(cellG2, '2');
|
||||
await tester.pump(const Duration(milliseconds: 250));
|
||||
expect(stub.updateCount, 2);
|
||||
expect(stub.calls.last['s'], 2);
|
||||
|
||||
// 범위 밖 값은 저장 안함 (350)
|
||||
await tester.enterText(cellG2, '350');
|
||||
await tester.pump(const Duration(milliseconds: 250));
|
||||
expect(stub.updateCount, 2);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lanebow/screens/public_event/public_event_screen.dart';
|
||||
import 'package:lanebow/services/public_event_service.dart';
|
||||
|
||||
class StubJoinService extends PublicEventService {
|
||||
int fetchCount = 0;
|
||||
bool addGuestCalled = false;
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> fetchEvent(String publicHash) async {
|
||||
fetchCount++;
|
||||
return {
|
||||
'event': {'id': 'E1', 'title': 'Join 테스트', 'gameCount': 3, 'status': 'ready'},
|
||||
'participants': [
|
||||
{
|
||||
'participantId': 'p1',
|
||||
'name': 'Kim',
|
||||
'scores': [null, null, null],
|
||||
'handicap': 0,
|
||||
},
|
||||
],
|
||||
'teams': [],
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> addGuest(String publicHash, {required String name, String? phone, String? gender, num? average}) async {
|
||||
addGuestCalled = true;
|
||||
await Future<void>.delayed(const Duration(milliseconds: 10));
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('join button opens dialog, saves, and reloads (fetch called twice)', (tester) async {
|
||||
final stub = StubJoinService();
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: PublicEventScreen(publicHash: 'HASH', service: stub),
|
||||
));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(stub.fetchCount, 1);
|
||||
expect(find.byKey(const Key('join_button')), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byKey(const Key('join_button')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('참가 신청(게스트)'), findsOneWidget);
|
||||
|
||||
await tester.enterText(find.byKey(const Key('join_name_input')), 'New Guest');
|
||||
await tester.tap(find.byKey(const Key('join_save_button')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(stub.addGuestCalled, isTrue);
|
||||
// reload triggered
|
||||
expect(stub.fetchCount, 2);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lanebow/main.dart' as app;
|
||||
|
||||
void main() {
|
||||
testWidgets('navigates to PublicEventEntryScreen with /p/:hash', (tester) async {
|
||||
// MyApp은 내부에서 routes/onGenerateRoute를 정의하므로 직접 runApp
|
||||
await tester.pumpWidget(const app.MyApp());
|
||||
|
||||
// 초기 홈이 뜬 후, 라우트 푸시
|
||||
final nav = app.navigatorKey.currentState!;
|
||||
nav.pushNamed('/p/abc123');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('공개 이벤트 입장'), findsOneWidget);
|
||||
expect(find.textContaining('abc123'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lanebow/screens/public_event/public_event_screen.dart';
|
||||
import 'package:lanebow/services/public_event_service.dart';
|
||||
|
||||
class StubColorService extends PublicEventService {
|
||||
@override
|
||||
Future<Map<String, dynamic>> fetchEvent(String publicHash) async {
|
||||
return {
|
||||
'event': {'id': 'E1', 'title': '색상/상태 테스트', 'gameCount': 3, 'status': 'active'},
|
||||
'participants': [
|
||||
{
|
||||
'participantId': 'p1',
|
||||
'name': 'Kim',
|
||||
'scores': [null, null, null],
|
||||
'handicap': 0,
|
||||
},
|
||||
],
|
||||
'teams': [],
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateScore(String publicHash, {required String participantId, required int gameNumber, required int score, int? handicap, teamNumber}) async {
|
||||
// no-op
|
||||
await Future<void>.delayed(const Duration(milliseconds: 1));
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('200 입력 시 색상 빨강으로 표시', (tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: PublicEventScreen(publicHash: 'H', service: StubColorService()),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// 초기에는 회색 점
|
||||
final colorDot = find.byKey(const Key('color_p1_1'));
|
||||
expect(colorDot, findsOneWidget);
|
||||
|
||||
// 200 입력
|
||||
final cell = find.byKey(const Key('cell_p1_1'));
|
||||
await tester.enterText(cell, '200');
|
||||
await tester.pump(const Duration(milliseconds: 20));
|
||||
|
||||
// 색상 컨테이너를 가져와서 색상을 확인
|
||||
final container = tester.widget<Container>(colorDot);
|
||||
final deco = container.decoration as BoxDecoration;
|
||||
expect((deco.color as Color), equals(Colors.red));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lanebow/screens/public_event/public_event_screen.dart';
|
||||
import 'package:lanebow/services/public_event_service.dart';
|
||||
|
||||
class StubDialogService extends PublicEventService {
|
||||
String? lastHash;
|
||||
String? lastParticipantId;
|
||||
int? lastGameNumber;
|
||||
int? lastScore;
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> fetchEvent(String publicHash) async {
|
||||
return {
|
||||
'event': {'id': 'E1', 'title': '다이얼로그 테스트', 'gameCount': 3, 'status': 'active'},
|
||||
'participants': [
|
||||
{
|
||||
'participantId': 'p1',
|
||||
'name': 'Kim',
|
||||
'scores': [null, null, null],
|
||||
'handicap': 0,
|
||||
},
|
||||
],
|
||||
'teams': [],
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateScore(String publicHash, {required String participantId, required int gameNumber, required int score, int? handicap, dynamic teamNumber}) async {
|
||||
lastHash = publicHash;
|
||||
lastParticipantId = participantId;
|
||||
lastGameNumber = gameNumber;
|
||||
lastScore = score;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('game select and validation works in score dialog', (tester) async {
|
||||
final stub = StubDialogService();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: PublicEventScreen(publicHash: 'hash-validate', service: stub),
|
||||
));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const Key('participant_tile_p1')), findsOneWidget);
|
||||
|
||||
// Open dialog
|
||||
await tester.tap(find.byKey(const Key('participant_tile_p1')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Select game 2
|
||||
await tester.tap(find.byKey(const Key('game_select')));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('게임 2').last);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Enter invalid value
|
||||
await tester.enterText(find.byKey(const Key('score_input')), '350');
|
||||
await tester.tap(find.byKey(const Key('score_save_button')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('0~300'), findsOneWidget);
|
||||
|
||||
// Enter valid value
|
||||
await tester.enterText(find.byKey(const Key('score_input')), '180');
|
||||
await tester.tap(find.byKey(const Key('score_save_button')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Saved and called with game 2
|
||||
expect(stub.lastHash, 'hash-validate');
|
||||
expect(stub.lastParticipantId, 'p1');
|
||||
expect(stub.lastGameNumber, 2);
|
||||
expect(stub.lastScore, 180);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lanebow/screens/public_event/public_event_screen.dart';
|
||||
import 'package:lanebow/services/public_event_service.dart';
|
||||
|
||||
class StubScoreService extends PublicEventService {
|
||||
String? lastHash;
|
||||
String? lastParticipantId;
|
||||
int? lastGameNumber;
|
||||
int? lastScore;
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> fetchEvent(String publicHash) async {
|
||||
return {
|
||||
'event': {'id': 'E1', 'title': '테스트 이벤트', 'gameCount': 3, 'status': 'active'},
|
||||
'participants': [
|
||||
{
|
||||
'participantId': 'p1',
|
||||
'name': 'Kim',
|
||||
'scores': [100, null, null],
|
||||
'handicap': 0,
|
||||
},
|
||||
],
|
||||
'teams': [],
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateScore(String publicHash, {required String participantId, required int gameNumber, required int score, int? handicap, dynamic teamNumber}) async {
|
||||
lastHash = publicHash;
|
||||
lastParticipantId = participantId;
|
||||
lastGameNumber = gameNumber;
|
||||
lastScore = score;
|
||||
await Future<void>.delayed(const Duration(milliseconds: 10));
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('tap participant -> edit game1 score -> calls updateScore and shows snackbar', (tester) async {
|
||||
final stub = StubScoreService();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: PublicEventScreen(publicHash: 'hash123', service: stub),
|
||||
));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const Key('participant_tile_p1')), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byKey(const Key('participant_tile_p1')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// 입력 후 저장
|
||||
await tester.enterText(find.byKey(const Key('score_input')), '199');
|
||||
await tester.tap(find.byKey(const Key('score_save_button')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// 스낵바 문구 확인
|
||||
expect(find.text('점수가 저장되었습니다'), findsOneWidget);
|
||||
|
||||
// 서비스 호출 확인
|
||||
expect(stub.lastHash, 'hash123');
|
||||
expect(stub.lastParticipantId, 'p1');
|
||||
expect(stub.lastGameNumber, 1);
|
||||
expect(stub.lastScore, 199);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lanebow/screens/public_event/public_event_screen.dart';
|
||||
import 'package:lanebow/services/public_event_service.dart';
|
||||
|
||||
class StubFetchService extends PublicEventService {
|
||||
@override
|
||||
Future<Map<String, dynamic>> fetchEvent(String publicHash) async {
|
||||
return {
|
||||
'event': {'id': 'E100', 'title': '오픈 리그'},
|
||||
'participants': [
|
||||
{'participantId': '1', 'name': 'Kim'},
|
||||
{'participantId': '2', 'name': 'Lee'},
|
||||
],
|
||||
'teams': [
|
||||
{'teamId': 'T1'},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('renders title and counts from fetchEvent', (tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: PublicEventScreen(publicHash: 'abc', service: StubFetchService()),
|
||||
));
|
||||
|
||||
// initial loading
|
||||
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const Key('event_title')), findsOneWidget);
|
||||
expect(find.text('오픈 리그'), findsOneWidget);
|
||||
expect(find.byKey(const Key('participants_count')), findsOneWidget);
|
||||
final pc = tester.widget<Text>(find.byKey(const Key('participants_count')));
|
||||
expect(pc.data, '총 2명');
|
||||
expect(find.byKey(const Key('teams_count')), findsOneWidget);
|
||||
expect(find.text('총 1팀'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lanebow/screens/public_event/public_event_screen.dart';
|
||||
import 'package:lanebow/services/public_event_service.dart';
|
||||
|
||||
class StubStateService extends PublicEventService {
|
||||
final String status;
|
||||
StubStateService(this.status);
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> fetchEvent(String publicHash) async {
|
||||
return {
|
||||
'event': {'id': 'E1', 'title': '상태 테스트', 'gameCount': 3, 'status': status},
|
||||
'participants': [
|
||||
{
|
||||
'participantId': 'p1',
|
||||
'name': 'Kim',
|
||||
'scores': [null, null, null],
|
||||
'handicap': 0,
|
||||
},
|
||||
],
|
||||
'teams': [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('ready: shows join button, blocks score dialog', (tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: PublicEventScreen(publicHash: 'H', service: StubStateService('ready')),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const Key('join_button')), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byKey(const Key('participant_tile_p1')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// dialog should NOT appear, snackbar appears
|
||||
expect(find.byType(AlertDialog), findsNothing);
|
||||
expect(find.textContaining('불가'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('active: shows join button, allows score dialog', (tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: PublicEventScreen(publicHash: 'H', service: StubStateService('active')),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const Key('join_button')), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byKey(const Key('participant_tile_p1')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(AlertDialog), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('completed: hides join button, blocks score dialog', (tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: PublicEventScreen(publicHash: 'H', service: StubStateService('completed')),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const Key('join_button')), findsNothing);
|
||||
|
||||
await tester.tap(find.byKey(const Key('participant_tile_p1')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(AlertDialog), findsNothing);
|
||||
expect(find.textContaining('불가'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lanebow/screens/public_event/public_event_screen.dart';
|
||||
import 'package:lanebow/services/public_event_service.dart';
|
||||
|
||||
class StubTeamsService extends PublicEventService {
|
||||
@override
|
||||
Future<Map<String, dynamic>> fetchEvent(String publicHash) async {
|
||||
return {
|
||||
'event': {'id': 'E1', 'title': '팀/미배정 테스트', 'gameCount': 3, 'status': 'active'},
|
||||
'participants': [
|
||||
{
|
||||
'participantId': 'p1',
|
||||
'name': 'Kim',
|
||||
'scores': [100, 100, 100],
|
||||
'handicap': 0,
|
||||
},
|
||||
{
|
||||
'participantId': 'p2',
|
||||
'name': 'Lee',
|
||||
'scores': [120, 110, 90],
|
||||
'handicap': 0,
|
||||
},
|
||||
],
|
||||
'teams': [
|
||||
{
|
||||
'teamId': 'T1',
|
||||
'teamNumber': 1,
|
||||
'handicap': 0,
|
||||
'members': [
|
||||
{
|
||||
'participantId': 'p1',
|
||||
'name': 'Kim',
|
||||
'scores': [100, 100, 100],
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('팀 순위 배지와 미배정 목록 렌더', (tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: PublicEventScreen(publicHash: 'H', service: StubTeamsService()),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// 팀 섹션 (스크롤 필요 시 스크롤)
|
||||
final scrollable = find.byType(Scrollable).first;
|
||||
await tester.scrollUntilVisible(find.byKey(const Key('section_teams')), 300, scrollable: scrollable);
|
||||
expect(find.byKey(const Key('section_teams')), findsOneWidget);
|
||||
expect(find.byKey(const Key('team_rank_1')), findsOneWidget);
|
||||
|
||||
// 미배정: p2가 표시되어야 함 (필요 시 더 스크롤)
|
||||
await tester.scrollUntilVisible(find.byKey(const Key('section_unassigned')), 300, scrollable: scrollable);
|
||||
expect(find.byKey(const Key('section_unassigned')), findsOneWidget);
|
||||
expect(find.byKey(const Key('unassigned_count')), findsOneWidget);
|
||||
await tester.scrollUntilVisible(find.byKey(const Key('unassigned_p2')), 300, scrollable: scrollable);
|
||||
expect(find.byKey(const Key('unassigned_p2')), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lanebow/screens/public_event/public_event_screen.dart';
|
||||
import 'package:lanebow/services/public_event_service.dart';
|
||||
|
||||
class StubTotalsService extends PublicEventService {
|
||||
@override
|
||||
Future<Map<String, dynamic>> fetchEvent(String publicHash) async {
|
||||
return {
|
||||
'event': {'id': 'E1', 'title': '합계/평균 테스트', 'gameCount': 3},
|
||||
'participants': [
|
||||
{
|
||||
'participantId': 'p1',
|
||||
'name': 'Kim',
|
||||
'handicap': 10,
|
||||
'scores': [100, 120, 130], // per-game hc 없음 → 기본 hc=10 적용
|
||||
},
|
||||
{
|
||||
'participantId': 'p2',
|
||||
'name': 'Lee',
|
||||
'handicap': 0,
|
||||
'scores': [200, null, 50], // null은 미집계
|
||||
}
|
||||
],
|
||||
'teams': [
|
||||
{
|
||||
'teamId': 'T1',
|
||||
'teamNumber': 1,
|
||||
'handicap': 0,
|
||||
'members': [
|
||||
{
|
||||
'participantId': 'm1',
|
||||
'name': 'A',
|
||||
'scores': [100, 100, 100]
|
||||
},
|
||||
{
|
||||
'participantId': 'm2',
|
||||
'name': 'B',
|
||||
'scores': [50, null, 50]
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('renders participant totals and team total/avg', (tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: PublicEventScreen(publicHash: 'hash-totals', service: StubTotalsService()),
|
||||
));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// 참가자 합계 검증
|
||||
// Kim: (100+10) + (120+10) + (130+10) = 380 → 300 cap 없음 → 380
|
||||
expect(find.textContaining('총 380'), findsOneWidget);
|
||||
// Lee: (200+0) + (50+0) = 250
|
||||
expect(find.textContaining('총 250'), findsOneWidget);
|
||||
|
||||
// 팀 합계/평균 검증
|
||||
// team members total: A:300, B:100 → 합 400, 팀 hc 0 → 총 400, 인원2 → 평균 200.0
|
||||
expect(find.textContaining('총 400'), findsOneWidget);
|
||||
expect(find.textContaining('평균 200.0'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user