앱 단위·화면·통합 테스트

This commit is contained in:
Sn-Kinos
2026-10-04 01:43:10 +09:00
parent de44739e63
commit 4f6e1eac61
63 changed files with 14700 additions and 230 deletions
+99
View File
@@ -0,0 +1,99 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:fake_cloud_firestore/fake_cloud_firestore.dart';
import 'package:firebase_auth_mocks/firebase_auth_mocks.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fancake/core/data/backend.dart';
import 'package:fancake/features/eclipse/eclipse_data.dart';
import 'package:fancake/features/eclipse/eclipse_repository_firestore.dart';
void main() {
test('추가 스냅샷 없이 마감시각에 위험 상태가 끝난다', () async {
final db = FakeFirebaseFirestore();
final repo = FirestoreEclipseRepository(
Backend(firestore: db, auth: MockFirebaseAuth()),
);
await db.doc('fandoms/bts/eclipseRounds/one').set({
'status': 'active',
'startAt': Timestamp.now(),
'endsAt': Timestamp.fromDate(
DateTime.now().add(const Duration(milliseconds: 150)),
),
});
final states = <EclipseState?>[];
final sub = repo.watch(fandomId: 'bts').listen(states.add);
addTearDown(sub.cancel);
await Future<void>.delayed(const Duration(milliseconds: 20));
expect(states.last?.eclipseId, 'one');
await Future<void>.delayed(const Duration(milliseconds: 200));
expect(states.last, isNull);
});
test('활성 회차의 성공·종료·새 회차를 샤드 구독 중에도 반영한다', () async {
final db = FakeFirebaseFirestore();
final repo = FirestoreEclipseRepository(
Backend(firestore: db, auth: MockFirebaseAuth()),
);
final doc = db.doc('fandoms/bts/eclipseRounds/one');
final data = {
'status': 'active',
'startAt': Timestamp.now(),
'endsAt': Timestamp.fromDate(
DateTime.now().add(const Duration(hours: 1)),
),
'targetPop': 60, // 옛 필드는 목표 인원으로 재해석하지 않는다.
'target': 2,
'defended': false,
};
await doc.set(data);
await doc.collection('shards').doc('0').set({'defense': 600, 'defenseCount': 25, 'qualifiedParticipants': 1});
final states = <EclipseState?>[];
final sub = repo.watch(fandomId: 'bts').listen(states.add);
addTearDown(sub.cancel);
Future<void> flush() =>
Future<void>.delayed(const Duration(milliseconds: 20));
await flush();
expect(states.last?.defense, 1);
expect(states.last?.targetDefense, 2);
expect(states.last?.progress, 0.5);
await doc.update({'defended': true});
await flush();
expect(states.last?.defended, true);
await doc.update({'status': 'ended'});
await flush();
expect(states.last, isNull);
await db.doc('fandoms/bts/eclipseRounds/two').set(data);
await flush();
expect(states.last?.eclipseId, 'two');
await doc.collection('shards').doc('0').update({'defense': 99});
await flush();
expect(states.last?.eclipseId, 'two');
});
test('방어 화면 구독은 마감 뒤에도 같은 회차의 서버 결과를 받는다', () async {
final db = FakeFirebaseFirestore();
final repo = FirestoreEclipseRepository(Backend(firestore: db, auth: MockFirebaseAuth()));
final ref = db.doc('fandoms/bts/eclipseRounds/final');
await ref.set({
'status': 'active', 'startAt': Timestamp.now(),
'endsAt': Timestamp.fromDate(DateTime.now().add(const Duration(milliseconds: 150))),
'target': 2, 'qualifiedParticipants': 1,
});
final results = <EclipseState?>[];
final banner = <EclipseState?>[];
final sub = repo.watch(fandomId: 'bts', includeResults: true).listen(results.add);
final active = repo.watch(fandomId: 'bts').listen(banner.add);
addTearDown(sub.cancel);
addTearDown(active.cancel);
await Future<void>.delayed(const Duration(milliseconds: 200));
expect(banner.last, isNull);
expect(results.last?.eclipseId, 'final');
expect(results.last?.canDefend, isFalse);
expect(results.last?.result, isNull);
await ref.update({'status': 'ended', 'result': 'failed'});
await Future<void>.delayed(const Duration(milliseconds: 20));
expect(results.last?.result, EclipseResult.failure);
expect(banner.last, isNull);
await ref.update({'status': 'cancelled', 'result': 'cancelled_error'});
await Future<void>.delayed(const Duration(milliseconds: 20));
expect(results.last?.result, EclipseResult.cancelled);
});
}