207 lines
7.0 KiB
Dart
207 lines
7.0 KiB
Dart
import 'package:fake_cloud_firestore/fake_cloud_firestore.dart';
|
|
import 'package:fancake/core/data/backend.dart';
|
|
import 'package:fancake/core/theme/app_theme.dart';
|
|
import 'package:fancake/features/home/widgets/home_sections.dart';
|
|
import 'package:fancake/features/campaign/campaign_create_screen.dart';
|
|
import 'package:fancake/features/campaign/campaign_list_screen.dart';
|
|
import 'package:fancake/features/home/home_data.dart';
|
|
import 'package:fancake/features/home/home_repository_firestore.dart';
|
|
import 'package:fancake/features/home/home_screen.dart';
|
|
import 'package:fancake/features/quest/quest_data.dart';
|
|
import 'package:firebase_auth_mocks/firebase_auth_mocks.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import 'eclipse_stubs.dart';
|
|
|
|
/// 실기기 판정(2026-10-04) HOME 결함 — HOT ARCHIVE 진입 · 브리핑 · 인기 K-INSIDE · 상태 표시줄.
|
|
|
|
const _uid = 'u-home';
|
|
|
|
class _Backend extends Backend {
|
|
_Backend(FakeFirebaseFirestore db)
|
|
: super(
|
|
firestore: db,
|
|
auth: MockFirebaseAuth(signedIn: true, mockUser: MockUser(uid: _uid)),
|
|
);
|
|
|
|
@override
|
|
Future<Map<String, dynamic>> call(
|
|
String name, [
|
|
Map<String, dynamic> payload = const {},
|
|
]) async => {'code': 'ABCDEFGH23'};
|
|
}
|
|
|
|
/// 모금 중 캠페인이 없는 HOME.
|
|
class _NoCampaignRepo extends SampleHomeRepository {
|
|
const _NoCampaignRepo();
|
|
|
|
@override
|
|
Stream<PlanetGrowth> watchGrowth(String fandomId) => const Stream.empty();
|
|
|
|
@override
|
|
Future<HomeSnapshot> load() async {
|
|
final base = await const SampleHomeRepository().load();
|
|
return HomeSnapshot(
|
|
popBalance: base.popBalance,
|
|
liveTicker: base.liveTicker,
|
|
growth: base.growth,
|
|
quests: base.quests,
|
|
briefings: base.briefings,
|
|
campaigns: const [],
|
|
insides: base.insides,
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _pump(WidgetTester tester, HomeRepository repo) async {
|
|
tester.view.physicalSize = const Size(1080, 2400);
|
|
tester.view.devicePixelRatio = 2;
|
|
addTearDown(tester.view.reset);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: buildAppTheme(),
|
|
home: MediaQuery(
|
|
// 노치 단말 — 상태 표시줄 47.
|
|
data: const MediaQueryData(
|
|
size: Size(540, 1200),
|
|
padding: EdgeInsets.only(top: 47),
|
|
),
|
|
child: HomeScreen(
|
|
repository: repo,
|
|
questRepository: const SampleQuestRepository(),
|
|
eclipse: const NoEclipseRepository(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
Finder get _hotArchive => find.byWidgetPredicate(
|
|
(w) => w is HomeSectionHeader && w.title == 'HOT ARCHIVE',
|
|
);
|
|
|
|
void main() {
|
|
setUp(() => SharedPreferences.setMockInitialValues({}));
|
|
|
|
group('HOT ARCHIVE — 모금 중 캠페인 0건(명세 §2.114-6)', () {
|
|
testWidgets('구획과 「더 보기」가 남아 캠페인 목록으로 간다', (tester) async {
|
|
await _pump(tester, const _NoCampaignRepo());
|
|
await tester.scrollUntilVisible(
|
|
_hotArchive,
|
|
220,
|
|
scrollable: find.byType(Scrollable).first,
|
|
);
|
|
await tester.ensureVisible(_hotArchive);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(
|
|
find.descendant(of: _hotArchive, matching: find.text('더 보기')),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(CampaignListScreen), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('카드 자리의 「새 캠페인」이 개설 화면을 연다', (tester) async {
|
|
await _pump(tester, const _NoCampaignRepo());
|
|
final create = find.text('새 캠페인');
|
|
await tester.scrollUntilVisible(
|
|
create,
|
|
220,
|
|
scrollable: find.byType(Scrollable).first,
|
|
);
|
|
await tester.tap(create);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(CampaignCreateScreen), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
testWidgets('스크롤하면 상태 표시줄 자리를 가린다(머리글 겹침 방지)', (tester) async {
|
|
await _pump(tester, const _NoCampaignRepo());
|
|
final scrim = find.byKey(const ValueKey('home-status-scrim'));
|
|
// 맨 위에서는 히어로 배경이 상태 표시줄 뒤까지 보여야 한다.
|
|
expect(scrim, findsNothing);
|
|
await tester.drag(find.byType(Scrollable).first, const Offset(0, -400));
|
|
await tester.pumpAndSettle();
|
|
expect(scrim, findsOneWidget);
|
|
expect(tester.getSize(scrim).height, 47);
|
|
await tester.drag(find.byType(Scrollable).first, const Offset(0, 2000));
|
|
await tester.pumpAndSettle();
|
|
expect(scrim, findsNothing);
|
|
});
|
|
|
|
group('FirestoreHomeRepository', () {
|
|
late FakeFirebaseFirestore db;
|
|
setUp(() async {
|
|
db = FakeFirebaseFirestore();
|
|
await db.doc('users/$_uid').set({'fandomId': 'fanuzu-demo'});
|
|
});
|
|
|
|
Future<void> brief(String id, String section, String status) =>
|
|
db.doc('briefings/$id').set({
|
|
'fandomId': 'fanuzu-demo',
|
|
'dayKey': kstDayKey(),
|
|
'status': status,
|
|
'section': section,
|
|
'rank': 1,
|
|
'headline': id,
|
|
'sourceName': '출처',
|
|
'sourceUrl': 'https://example.com/s',
|
|
'articleUrl': 'https://example.com/a',
|
|
'aiSummary': '요약',
|
|
'sourceAt': DateTime(2026, 10, 4),
|
|
});
|
|
|
|
test('브리핑 section 은 서버 값(News·SNS)만 그린다 — 시연 시드 소문자 결함', () async {
|
|
await brief('news', 'News', 'published');
|
|
await brief('sns', 'SNS', 'published');
|
|
await brief('draft', 'News', 'draft');
|
|
// 수정 전 시드(`tools/demo/fs.mjs`)가 쓰던 값 — 앱은 모르는 구획이라 버린다.
|
|
await brief('lower', 'news', 'published');
|
|
final home = await FirestoreHomeRepository(_Backend(db)).load();
|
|
expect(
|
|
home.briefings.map((b) => b.headline),
|
|
unorderedEquals(['news', 'sns']),
|
|
);
|
|
});
|
|
|
|
test('인기 K-INSIDE 는 미해결 먼저, 그 안에서 현상금 큰 순(명세 §2.114-7)', () async {
|
|
final at = DateTime(2026, 10, 4);
|
|
final rows = {
|
|
'new-0-answers': ('open', 100, at.add(const Duration(hours: 1))),
|
|
'solved-big': ('accepted', 900, at),
|
|
'open-500': ('open', 500, at),
|
|
'open-300': ('open', 300, at),
|
|
'open-200': ('open', 200, at),
|
|
};
|
|
for (final MapEntry(key: id, value: (status, bounty, createdAt))
|
|
in rows.entries) {
|
|
await db.doc('questions/$id').set({
|
|
'title': id,
|
|
'status': status,
|
|
'bountyPop': bounty,
|
|
'createdAt': createdAt,
|
|
});
|
|
}
|
|
final home = await FirestoreHomeRepository(_Backend(db)).load();
|
|
expect(home.insides.map((i) => i.title), [
|
|
'open-500',
|
|
'open-300',
|
|
'open-200',
|
|
]);
|
|
});
|
|
|
|
test('미해결이 3건보다 적으면 해결된 고액 질문으로 채운다', () {
|
|
expect(
|
|
popularInsides([
|
|
{'title': 'a', 'status': 'accepted'},
|
|
{'title': 'b', 'status': 'open'},
|
|
{'title': 'c', 'status': 'expired'},
|
|
]).map((q) => q['title']),
|
|
['b', 'a', 'c'],
|
|
);
|
|
});
|
|
});
|
|
}
|