296 lines
9.8 KiB
Dart
296 lines
9.8 KiB
Dart
import 'dart:async';
|
|
import 'package:fancake/core/data/planet_visual_state.dart';
|
|
import 'package:fancake/core/data/backend.dart';
|
|
import 'package:fancake/features/planet/planet_repository_firestore.dart';
|
|
import 'package:fancake/features/planet/planet_detail_screen.dart';
|
|
import 'package:fake_cloud_firestore/fake_cloud_firestore.dart';
|
|
import 'package:firebase_auth_mocks/firebase_auth_mocks.dart';
|
|
import 'eclipse_stubs.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:fancake/features/planet/planet_data.dart';
|
|
import 'package:fancake/features/planet/planet_history_screen.dart';
|
|
|
|
class _Backend extends Backend {
|
|
_Backend()
|
|
: super(
|
|
firestore: FakeFirebaseFirestore(),
|
|
auth: MockFirebaseAuth(
|
|
mockUser: MockUser(uid: 'reader'),
|
|
signedIn: true,
|
|
),
|
|
);
|
|
Map<String, dynamic>? payload;
|
|
Map<String, dynamic> response = {
|
|
'items': [
|
|
{
|
|
'id': 'one',
|
|
'kind': 'evolution',
|
|
'occurredAt': '2026-09-22T00:00:00Z',
|
|
'fromStage': 1,
|
|
'toStage': 2,
|
|
'participationConfirmed': 'true',
|
|
},
|
|
],
|
|
'nextCursor': null,
|
|
};
|
|
@override
|
|
Future<Map<String, dynamic>> call(
|
|
String name, [
|
|
Map<String, dynamic> data = const {},
|
|
]) async {
|
|
expect(name, 'fnPlanetHistory');
|
|
payload = data;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
class _PersonalHistory extends SamplePlanetRepository {
|
|
final scopes = <bool>[];
|
|
@override
|
|
Future<PlanetHistoryPage> history({
|
|
String? cursor,
|
|
bool personal = false,
|
|
}) async {
|
|
scopes.add(personal);
|
|
return PlanetHistoryPage(
|
|
items: [
|
|
PlanetHistoryEntry(
|
|
id: 'one',
|
|
kind: personal ? PlanetHistoryKind.zap : PlanetHistoryKind.evolution,
|
|
occurredAt: DateTime.utc(2026, 9, 22),
|
|
fromStage: personal ? null : 1,
|
|
toStage: personal ? null : 2,
|
|
amount: personal ? 37 : null,
|
|
),
|
|
],
|
|
nextCursor: personal ? null : 'older',
|
|
);
|
|
}
|
|
}
|
|
|
|
class _History extends SamplePlanetRepository {
|
|
final cursors = <String?>[];
|
|
@override
|
|
Future<PlanetHistoryPage> history({
|
|
String? cursor,
|
|
bool personal = false,
|
|
}) async {
|
|
cursors.add(cursor);
|
|
if (cursors.length == 2) throw StateError('private details');
|
|
return PlanetHistoryPage(
|
|
items: [
|
|
PlanetHistoryEntry(
|
|
id: cursor ?? 'first',
|
|
kind: PlanetHistoryKind.evolution,
|
|
occurredAt: DateTime.utc(2026, 9, 22),
|
|
fromStage: 1,
|
|
toStage: 2,
|
|
participationConfirmed: cursor == null,
|
|
),
|
|
],
|
|
nextCursor: cursor == null ? 'next' : null,
|
|
);
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
testWidgets('내 활동 전환은 이전 목록과 커서를 지우고 실제 성장량을 표시한다', (tester) async {
|
|
final repository = _PersonalHistory();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: PlanetHistoryScreen(repository: repository, fandomName: '팬덤'),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('내 활동'));
|
|
await tester.pumpAndSettle();
|
|
expect(repository.scopes, [false, true]);
|
|
expect(find.text('Stage 1 → 2 진화'), findsNothing);
|
|
expect(find.text('ZAP 기여'), findsOneWidget);
|
|
expect(find.textContaining('성장에 37 POP 반영'), findsOneWidget);
|
|
expect(find.textContaining('이 진화에 함께했습니다'), findsNothing);
|
|
expect(find.text('이전 기록 더 보기'), findsNothing);
|
|
});
|
|
|
|
testWidgets('서버 Stage 상승 완료에서 현재 행성의 역사 화면을 연다', (tester) async {
|
|
const repository = SamplePlanetRepository();
|
|
final states = StreamController<PlanetVisualState>.broadcast();
|
|
addTearDown(states.close);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: PlanetDetailScreen(
|
|
repository: repository,
|
|
eclipse: const NoEclipseRepository(),
|
|
watchPlanet: (_) => states.stream,
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
states.add(const PlanetVisualState(stage: 1));
|
|
await tester.pumpAndSettle();
|
|
states.add(const PlanetVisualState(stage: 2));
|
|
await tester.pump();
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 4));
|
|
await tester.tap(find.text('PASSPORT 기록 보기'));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
tester
|
|
.widget<PlanetHistoryScreen>(find.byType(PlanetHistoryScreen))
|
|
.repository,
|
|
same(repository),
|
|
);
|
|
await tester.pumpWidget(const SizedBox());
|
|
});
|
|
|
|
test('실제 저장소는 대상 팬덤·커서만 요청하고 불완전한 응답을 빈 기록으로 숨기지 않는다', () async {
|
|
final backend = _Backend();
|
|
final repository = FirestorePlanetRepository(backend, fandomId: 'target');
|
|
final page = await repository.history(cursor: 'previous');
|
|
expect(backend.payload, {'fandomId': 'target', 'cursor': 'previous'});
|
|
expect(page.items.single.participationConfirmed, isFalse);
|
|
backend.response = {
|
|
'items': [
|
|
{'id': 'bad'},
|
|
],
|
|
'nextCursor': null,
|
|
};
|
|
await expectLater(repository.history(), throwsStateError);
|
|
backend.response = {
|
|
'items': [
|
|
{
|
|
'id': 'zap',
|
|
'kind': 'zap',
|
|
'occurredAt': '2026-09-22T00:00:00Z',
|
|
'amount': 37,
|
|
'participationConfirmed': true,
|
|
},
|
|
],
|
|
'nextCursor': null,
|
|
};
|
|
final personal = await repository.history(personal: true);
|
|
expect(backend.payload, {'fandomId': 'target', 'scope': 'personal'});
|
|
expect(personal.items.single.amount, 37);
|
|
expect(personal.items.single.participationConfirmed, isFalse);
|
|
// PASSPORT Stamp 는 내 기록에서만 받는다(FX §5.2 · §7.2).
|
|
backend.response = {
|
|
'items': [
|
|
{
|
|
'id': 'e',
|
|
'kind': 'eclipse_success',
|
|
'occurredAt': '2026-09-23T00:00:00Z',
|
|
},
|
|
{
|
|
'id': 'v',
|
|
'kind': 'emblem_evolution',
|
|
'occurredAt': '2026-09-22T00:00:00Z',
|
|
'toStage': 3,
|
|
},
|
|
{
|
|
'id': 'f',
|
|
'kind': 'badge_founder100',
|
|
'occurredAt': '2026-09-21T00:00:00Z',
|
|
'memberNo': 7,
|
|
},
|
|
],
|
|
'nextCursor': null,
|
|
};
|
|
final stamps = await repository.history(personal: true);
|
|
expect(stamps.items.map((i) => i.kind), [
|
|
PlanetHistoryKind.eclipseSuccess,
|
|
PlanetHistoryKind.emblemEvolution,
|
|
PlanetHistoryKind.founderBadge,
|
|
]);
|
|
expect(stamps.items[1].toStage, 3);
|
|
expect(stamps.items[2].memberNo, 7);
|
|
await expectLater(repository.history(), throwsStateError);
|
|
});
|
|
|
|
testWidgets('행성 상세에서 같은 저장소의 팬덤 역사를 연다', (tester) async {
|
|
const repository = SamplePlanetRepository();
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: PlanetDetailScreen(
|
|
repository: repository,
|
|
eclipse: NoEclipseRepository(),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byTooltip('더 보기'));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('PASSPORT · 팬덤 역사'));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
tester
|
|
.widget<PlanetHistoryScreen>(find.byType(PlanetHistoryScreen))
|
|
.repository,
|
|
same(repository),
|
|
);
|
|
});
|
|
|
|
testWidgets('확정된 개인 참여만 표시하고 다음 페이지 실패는 같은 커서로 재시도한다', (tester) async {
|
|
final repository = _History();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: PlanetHistoryScreen(repository: repository, fandomName: '팬덤'),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(find.textContaining('이 진화에 함께했습니다'), findsOneWidget);
|
|
await tester.tap(find.text('이전 기록 더 보기'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('기록을 불러오지 못했습니다.'), findsOneWidget);
|
|
expect(find.text('아직 기록된 팬덤 역사가 없습니다.'), findsNothing);
|
|
expect(find.textContaining('private details'), findsNothing);
|
|
await tester.tap(find.text('다시 시도'));
|
|
await tester.pumpAndSettle();
|
|
expect(repository.cursors, [null, 'next', 'next']);
|
|
expect(find.text('Stage 1 → 2 진화'), findsNWidgets(2));
|
|
expect(find.textContaining('이 진화에 함께했습니다'), findsOneWidget);
|
|
expect(find.text('이전 기록 더 보기'), findsNothing);
|
|
});
|
|
|
|
testWidgets('조회 성공의 빈 기록을 오류와 구분한다', (tester) async {
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: PlanetHistoryScreen(
|
|
repository: SamplePlanetRepository(),
|
|
fandomName: '팬덤',
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('아직 기록된 팬덤 역사가 없습니다.'), findsOneWidget);
|
|
expect(find.text('다시 시도'), findsNothing);
|
|
});
|
|
|
|
test('UX·FX §7.2 — 진화 · Eclipse 성공 · 기념 Badge 만 역사적 사건', () {
|
|
expect(PlanetHistoryKind.values.where((k) => k.isHistoric).toSet(), {
|
|
PlanetHistoryKind.evolution,
|
|
PlanetHistoryKind.eclipseSuccess,
|
|
PlanetHistoryKind.emblemEvolution,
|
|
PlanetHistoryKind.founderBadge,
|
|
});
|
|
});
|
|
|
|
testWidgets('역사적 사건 줄에만 「역사적 사건」 표지가 붙는다', (tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: PlanetHistoryScreen(
|
|
repository: _PersonalHistory(),
|
|
fandomName: '팬덤',
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Stage 1 → 2 진화'), findsOneWidget);
|
|
expect(find.text('역사적 사건'), findsOneWidget);
|
|
await tester.tap(find.text('내 활동'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('ZAP 기여'), findsOneWidget);
|
|
expect(find.text('역사적 사건'), findsNothing);
|
|
});
|
|
}
|