끝난 이클립스 합류 문구 · HOME 헤더 POP · 다국어 HOME 레이아웃 · 캠페인 표시 수정
This commit is contained in:
@@ -196,7 +196,7 @@ void main() {
|
||||
final db = await _seed();
|
||||
final snapshot = await FirestoreHomeRepository(_backend(db)).load();
|
||||
|
||||
expect(snapshot.popBalance, 12540);
|
||||
expect(snapshot.popBalance, 12840, reason: '총 보유 = 일반 12,540 + 기여 300');
|
||||
expect(snapshot.liveTicker, contains('버프'));
|
||||
expect(snapshot.growth.fandomName, 'BTS');
|
||||
// 설명 프레임 `2097:16391` — 인사 줄은 로그인 사용자 기준. 팬덤의 직전 기여자가 아니다.
|
||||
@@ -734,6 +734,21 @@ void main() {
|
||||
expect(again.first.title, '글 0');
|
||||
});
|
||||
|
||||
test('HOME 헤더 POP 은 총 보유(일반 + 기여 + 보류) — POP 내역 · 퀘스트 목록과 같은 값(179:29099 · 10/8 r11)', () async {
|
||||
final db = await _seed();
|
||||
await db.doc('users/$_uid/wallet/main').set({
|
||||
'activityBalance': 12130,
|
||||
'supportBalance': 330,
|
||||
'pendingActivity': 20,
|
||||
});
|
||||
final backend = _backend(db);
|
||||
final home = FirestoreHomeRepository(backend);
|
||||
expect((await home.load()).popBalance, 12480);
|
||||
expect(await home.watchPopBalance().first, 12480);
|
||||
expect(await FirestoreQuestRepository(backend).popBalance(), 12480);
|
||||
expect((await FirestoreMyRepository(backend).profile()).balancePop, 12480);
|
||||
});
|
||||
|
||||
test('다른 팬덤 글은 섞이지 않는다', () async {
|
||||
final db = await seedPlanet();
|
||||
await db.collection('posts').add({
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'package:fancake/core/ui/clipboard_copy.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
/// 링크 복사 — Android 13+ 는 시스템이 「클립보드에 복사했어요」를 띄우므로 앱 토스트를 생략한다(10/8 r11 3-8).
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
const channel = MethodChannel('kr.co.fanuzu.app/device');
|
||||
final messenger =
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
|
||||
|
||||
tearDown(() {
|
||||
messenger.setMockMethodCallHandler(channel, null);
|
||||
resetClipboardSdkCache();
|
||||
});
|
||||
|
||||
for (final (sdk, toasts) in [(33, 0), (34, 0), (32, 1), (null, 1)]) {
|
||||
test('Android API $sdk → 앱 토스트 $toasts 회', () async {
|
||||
messenger.setMockMethodCallHandler(channel, (_) async => sdk);
|
||||
var count = 0;
|
||||
await copyToClipboard('https://fanuzu.app/c/1', () => count++);
|
||||
expect(count, toasts);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -275,7 +275,10 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('홈 라이브 띠가 라이브 이벤트를 그린다', (tester) async {
|
||||
final repo = FakeEclipseRepository();
|
||||
// 방어 합류는 진행 중인 회차에서만 띠에 오른다(r11 2-5).
|
||||
final repo = FakeEclipseRepository(
|
||||
state: eclipseFixture(startAt: DateTime(2026, 9, 13), defense: 1),
|
||||
);
|
||||
addTearDown(repo.dispose);
|
||||
tester.view.physicalSize = const Size(1080, 2400);
|
||||
tester.view.devicePixelRatio = 2;
|
||||
@@ -293,16 +296,16 @@ void main() {
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// 이벤트가 없으면 스냅샷의 폴백 문구를 그린다.
|
||||
expect(find.textContaining('아이템 사용으로 버프가 발동'), findsOneWidget);
|
||||
// 합류 이벤트가 아직 없으면 발생 소식을 그린다.
|
||||
expect(find.text('BTS 행성에 이클립스가 발생했어요!'), findsOneWidget);
|
||||
|
||||
repo.pushEvents([
|
||||
LiveEvent(id: 'e1', nickname: '무민', defense: 20, at: DateTime(2026, 9, 14)),
|
||||
]);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// 이벤트가 오면 그 문구가 폴백을 대체한다.
|
||||
expect(find.textContaining('아이템 사용으로 버프가 발동'), findsNothing);
|
||||
// 이벤트가 오면 그 문구가 발생 소식을 대체한다.
|
||||
expect(find.text('BTS 행성에 이클립스가 발생했어요!'), findsNothing);
|
||||
expect(
|
||||
find.text('[LIVE] 무민님이 이클립스 방어에 합류했습니다!'),
|
||||
findsOneWidget,
|
||||
|
||||
@@ -83,6 +83,20 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
test('캠페인 알림은 문서 ID 앞머리로 달성 · 환불 · 취소 · 결과보고를 가른다(10/8 r11)', () {
|
||||
String label(String id) => AppNotification(
|
||||
id: id,
|
||||
kind: NotificationKind.campaign,
|
||||
title: 't',
|
||||
at: DateTime(2026, 10, 8),
|
||||
).sourceLabel;
|
||||
expect(label('campaignSuccess_c1'), '캠페인 달성');
|
||||
expect(label('campaignRefund_tx1'), '캠페인 환불');
|
||||
expect(label('campaignCancel_c1'), '캠페인 취소');
|
||||
expect(label('campaignReport_c1_r1'), '결과 보고');
|
||||
expect(label('n1'), '캠페인 달성', reason: '모르는 앞머리는 종류 이름');
|
||||
});
|
||||
|
||||
group('알림 화면 (F0 · 229:11265)', () {
|
||||
testWidgets('날짜 머리글과 항목을 함께 보여 준다', (tester) async {
|
||||
// 표본은 DateTime.now() 기준이라 자정 전후로 「오늘/어제」가 갈린다.
|
||||
|
||||
@@ -267,6 +267,28 @@ void main() {
|
||||
expect(find.widgetWithText(FilledButton, '기여하기'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('모금 중이 아닌 타인 캠페인(달성 · 미달 환불 · 결과보고 · 취소)에는 기여하기 버튼이 없다(10/8 r11)', (tester) async {
|
||||
for (final (status, shown) in [
|
||||
(CampaignStatus.funding, findsOneWidget),
|
||||
(CampaignStatus.success, findsNothing),
|
||||
(CampaignStatus.failed, findsNothing),
|
||||
(CampaignStatus.reported, findsNothing),
|
||||
(CampaignStatus.cancelled, findsNothing),
|
||||
]) {
|
||||
await pumpWide(
|
||||
tester,
|
||||
CampaignDetailScreen(
|
||||
key: ValueKey(status),
|
||||
campaignId: 'c1',
|
||||
repository: _Mine(status, supporters: 1, owned: false),
|
||||
myPop: 0,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.widgetWithText(FilledButton, '기여하기'), shown, reason: status.name);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('타인 캠페인에는 기여하기 버튼이 있다', (tester) async {
|
||||
await pumpWide(tester, CampaignDetailScreen(campaignId: 'c1'));
|
||||
await tester.pumpAndSettle();
|
||||
@@ -529,10 +551,11 @@ void main() {
|
||||
|
||||
/// 내 캠페인 상세 — 상태 · 후원 수를 바꿔 철회 버튼을 본다(B-58).
|
||||
class _Mine extends SampleCampaignRepository {
|
||||
_Mine(this.status, {this.supporters = 0});
|
||||
_Mine(this.status, {this.supporters = 0, this.owned = true});
|
||||
|
||||
final CampaignStatus status;
|
||||
final int supporters;
|
||||
final bool owned;
|
||||
final withdrawn = <String>[];
|
||||
|
||||
@override
|
||||
@@ -545,7 +568,7 @@ class _Mine extends SampleCampaignRepository {
|
||||
createdAt: c.createdAt,
|
||||
status: withdrawn.isEmpty ? status : CampaignStatus.withdrawn,
|
||||
goalPop: c.goalPop, raisedPop: supporters == 0 ? 0 : 10,
|
||||
supporters: supporters, mine: true,
|
||||
supporters: supporters, mine: owned,
|
||||
likes: 0, comments: 0, views: 0, shares: 0,
|
||||
),
|
||||
mediaLabels: base.mediaLabels,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:fancake/core/ui/clipboard_copy.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
@@ -240,8 +241,18 @@ void main() {
|
||||
null,
|
||||
),
|
||||
);
|
||||
expect(await shareProfile(profile, '문구 복사'), isTrue);
|
||||
expect(clip, profileShareText(profile));
|
||||
// Android 12 이하는 앱이 알리고(true), 13+ 는 시스템이 알려 앱 토스트를 생략한다(false).
|
||||
const device = MethodChannel('kr.co.fanuzu.app/device');
|
||||
addTearDown(() {
|
||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(device, null);
|
||||
resetClipboardSdkCache();
|
||||
});
|
||||
for (final (sdk, announce) in [(32, true), (33, false)]) {
|
||||
resetClipboardSdkCache();
|
||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(device, (_) async => sdk);
|
||||
expect(await shareProfile(profile, '문구 복사'), announce);
|
||||
expect(clip, profileShareText(profile));
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('티어 카드를 누르면 행성별 티어로 넘어간다', (tester) async {
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import 'package:fancake/core/i18n/app_locale.dart';
|
||||
import 'package:fancake/core/theme/app_theme.dart';
|
||||
import 'package:fancake/features/home/home_data.dart';
|
||||
import 'package:fancake/features/home/widgets/home_hero.dart';
|
||||
import 'package:fancake/features/home/widgets/home_weekly.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
/// 긴 언어(es · ja)에서 HOME 이 깨지지 않는다 — 10/8 r11 실기기(Flip3) 관찰.
|
||||
void main() {
|
||||
tearDown(() => appL10n = koL10n);
|
||||
|
||||
Future<void> pump(WidgetTester tester, Locale locale, Widget child,
|
||||
{double width = 360}) async {
|
||||
tester.view.physicalSize = Size(width * 3, 2400);
|
||||
tester.view.devicePixelRatio = 3;
|
||||
addTearDown(tester.view.reset);
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: buildAppTheme(),
|
||||
locale: locale,
|
||||
supportedLocales: appLocales,
|
||||
localizationsDelegates: const [
|
||||
RemoteL10nDelegate({}),
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
home: Scaffold(body: child),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
const growth = PlanetGrowth(
|
||||
fandomName: '오로라',
|
||||
contributorName: 'Kinos',
|
||||
stage: 2,
|
||||
contributedPop: 0,
|
||||
growthPercent: 1,
|
||||
currentPop: 189,
|
||||
requiredPop: 16575,
|
||||
);
|
||||
|
||||
testWidgets('es 주간 카드 「Mis POP」 라벨이 한 글자씩 줄바꿈되지 않는다', (tester) async {
|
||||
final weekly = HomeWeekly.from(
|
||||
fandomId: 'f',
|
||||
snapshots: const [],
|
||||
fandom: const {},
|
||||
myWeekPop: 250,
|
||||
wallet: const {'activityBalance': 12130, 'supportBalance': 330},
|
||||
);
|
||||
await pump(tester, const Locale('es'), HomeWeeklyCard(weekly: weekly));
|
||||
expect(tester.takeException(), isNull);
|
||||
// 한 줄 = 글자 13 × 행간 18/13.
|
||||
expect(tester.getSize(find.text('Mis POP')).height, 18);
|
||||
});
|
||||
|
||||
testWidgets('es 인사 줄은 「De Kinos」 — 「aporta:」 오역이 아니다', (tester) async {
|
||||
await pump(tester, const Locale('es'), const HomeHero(growth: growth));
|
||||
expect(find.text('De '), findsOneWidget);
|
||||
expect(find.text('Kinos'), findsOneWidget);
|
||||
expect(find.textContaining('aporta'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('es 성장 말풍선이 성장률 글자를 가리지 않는다', (tester) async {
|
||||
await pump(tester, const Locale('es'), const HomeHero(growth: growth),
|
||||
width: 540);
|
||||
final rate = tester.getRect(find.text('Crecimiento del planeta 1%'));
|
||||
final bubble = tester.getRect(find.text('Ver condiciones'));
|
||||
expect(bubble.left, greaterThanOrEqualTo(rate.right));
|
||||
});
|
||||
|
||||
testWidgets('ja 제목이 미니맵 아래로 들어가지 않는다', (tester) async {
|
||||
await pump(
|
||||
tester,
|
||||
const Locale('ja'),
|
||||
const HomeHero(
|
||||
growth: growth,
|
||||
miniMap: SizedBox(width: HomeHero.miniMapSize, height: 90),
|
||||
),
|
||||
);
|
||||
expect(tester.takeException(), isNull);
|
||||
final title = tester.getRect(find.text('みんなで育てるファンダム惑星'));
|
||||
expect(title.right, lessThanOrEqualTo(360 - HomeHero.miniMapSize));
|
||||
});
|
||||
}
|
||||
@@ -166,7 +166,7 @@ void main() {
|
||||
for (final title in [
|
||||
'TODAY’S QUEST',
|
||||
'팬덤행성 인큐베이터',
|
||||
'아티스트 소식',
|
||||
'AI Daily Briefing',
|
||||
'HOT ARCHIVE',
|
||||
'인기 K-INSIDE',
|
||||
]) {
|
||||
@@ -296,7 +296,7 @@ void main() {
|
||||
/// *문구도 노출하지 않음)*. 서버가 당일 게시분을 0 건으로 주는 상황이다.
|
||||
testWidgets('브리핑이 0 건이면 구획 제목까지 숨긴다', (tester) async {
|
||||
await _pump(tester, repo: const _BriefingRepo([]));
|
||||
expect(find.text('아티스트 소식'), findsNothing);
|
||||
expect(find.text('AI Daily Briefing'), findsNothing);
|
||||
// 빈 상태 문구도 두지 않는다 — 아래 구획은 그대로 있어야 한다.
|
||||
await tester.scrollUntilVisible(
|
||||
find.text('HOT ARCHIVE'),
|
||||
@@ -309,7 +309,7 @@ void main() {
|
||||
testWidgets('브리핑 헤더의 「공유」는 브리핑 공유 카드를 연다(F8 · 별첨 1 ③-5)', (tester) async {
|
||||
await _pump(tester, repo: _BriefingRepo([_brief('공유할 한 줄')]));
|
||||
final header = find.byWidgetPredicate(
|
||||
(w) => w is HomeSectionHeader && w.title == '아티스트 소식',
|
||||
(w) => w is HomeSectionHeader && w.title == 'AI Daily Briefing',
|
||||
);
|
||||
await tester.scrollUntilVisible(find.text('공유할 한 줄'), 220,
|
||||
scrollable: find.byType(Scrollable).first);
|
||||
@@ -328,7 +328,7 @@ void main() {
|
||||
220,
|
||||
scrollable: find.byType(Scrollable).first,
|
||||
);
|
||||
expect(find.text('아티스트 소식'), findsOneWidget);
|
||||
expect(find.text('AI Daily Briefing'), findsOneWidget);
|
||||
expect(find.text('오늘 게시된 한 줄'), findsOneWidget);
|
||||
});
|
||||
|
||||
|
||||
@@ -298,5 +298,43 @@ void main() {
|
||||
expect(zap.balance, 100);
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
});
|
||||
|
||||
testWidgets('HOME 반영 문구는 행성 상자 위쪽 — 아래 성장 카드(행성 이름 · 말풍선)를 덮지 않는다(10/8 r11)', (tester) async {
|
||||
final zap = ZapController(SampleGalaxyRepository(), onFailure: (_) {});
|
||||
addTearDown(zap.dispose);
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: ListenableBuilder(
|
||||
listenable: zap,
|
||||
builder: (_, _) => ZapFeedback(
|
||||
fandomName: 'BTS',
|
||||
quest: zap.questReaction,
|
||||
touchSerial: zap.touchSerial,
|
||||
pending: zap.pending > 0 || zap.sending,
|
||||
result: zap.result,
|
||||
source: Offset.zero,
|
||||
target: const Offset(200, 400),
|
||||
radius: 80,
|
||||
messageAboveTarget: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
final reacting = zap.react();
|
||||
await tester.pump(const Duration(milliseconds: 400));
|
||||
await reacting;
|
||||
await tester.pump();
|
||||
final text = find.text('퀘스트 완료가 BTS 성장에 반영되었습니다');
|
||||
expect(text, findsOneWidget);
|
||||
// 행성 상자(반 변 80 / 0.4 / 2 = 100) 윗변 y300 에서 시작해 행성 중심(y400) 위에서 끝난다.
|
||||
final box = tester.getRect(
|
||||
find.ancestor(of: text, matching: find.byType(DecoratedBox)).first,
|
||||
);
|
||||
expect(box.top, 300);
|
||||
expect(box.bottom, lessThan(400));
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -134,6 +134,35 @@ void main() {
|
||||
final l = line(eclipse: eclipseFixture(startAt: start), hero: true)!;
|
||||
expect(l.hero, isFalse);
|
||||
});
|
||||
test('끝난 회차의 방어 합류는 띠에 남지 않는다 (r11 2-5)', () {
|
||||
expect(line(events: [join])!.text, 'fallback', reason: '진행 중 회차 없음');
|
||||
expect(
|
||||
line(
|
||||
eclipse: eclipseFixture(
|
||||
startAt: start,
|
||||
defense: 1,
|
||||
result: EclipseResult.failure,
|
||||
),
|
||||
events: [join],
|
||||
)!.text,
|
||||
'fallback',
|
||||
reason: '판정 끝난 회차',
|
||||
);
|
||||
expect(
|
||||
line(
|
||||
eclipse: eclipseFixture(startAt: DateTime.now(), defense: 1),
|
||||
events: [
|
||||
LiveEvent(
|
||||
id: 'old',
|
||||
nickname: '팬',
|
||||
at: DateTime.now().subtract(const Duration(minutes: 40)),
|
||||
),
|
||||
],
|
||||
)!.text,
|
||||
'BTS 행성에 이클립스가 발생했어요!',
|
||||
reason: '지난 회차의 합류',
|
||||
);
|
||||
});
|
||||
test('아무것도 없으면 폴백', () {
|
||||
expect(line()!.text, 'fallback');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user