퀘스트 제목 다국어 · HOME 다시 읽기 · 내 퀘스트 검토 중·반려됨 구분
This commit is contained in:
@@ -10,6 +10,7 @@ import 'core/i18n/app_locale.dart';
|
|||||||
import 'core/ui/adaptive_planet_effects.dart';
|
import 'core/ui/adaptive_planet_effects.dart';
|
||||||
import 'core/push/push_messaging.dart';
|
import 'core/push/push_messaging.dart';
|
||||||
import 'core/router/app_router.dart';
|
import 'core/router/app_router.dart';
|
||||||
|
import 'features/home/home_screen.dart' show homeRefreshSignal;
|
||||||
import 'core/theme/app_theme.dart';
|
import 'core/theme/app_theme.dart';
|
||||||
import 'features/auth/auth_repository.dart';
|
import 'features/auth/auth_repository.dart';
|
||||||
import 'features/campaign/campaign_detail_screen.dart';
|
import 'features/campaign/campaign_detail_screen.dart';
|
||||||
@@ -188,6 +189,8 @@ class _FancakeAppState extends State<FancakeApp> with WidgetsBindingObserver {
|
|||||||
/// 포그라운드 수신 — 시스템 알림이 뜨지 않으므로 앱이 직접 보여 준다.
|
/// 포그라운드 수신 — 시스템 알림이 뜨지 않으므로 앱이 직접 보여 준다.
|
||||||
/// 탭하면 알림을 눌렀을 때와 같은 곳으로 간다.
|
/// 탭하면 알림을 눌렀을 때와 같은 곳으로 간다.
|
||||||
void _showInApp(PushNotice notice) {
|
void _showInApp(PushNotice notice) {
|
||||||
|
// 타임어택 발동 · 퀘스트 완료 등 — HOME 카드가 바로 따라오게 다시 읽힌다.
|
||||||
|
homeRefreshSignal.value++;
|
||||||
if (!notice.hasText) return;
|
if (!notice.hasText) return;
|
||||||
_messengerKey.currentState
|
_messengerKey.currentState
|
||||||
?..hideCurrentSnackBar()
|
?..hideCurrentSnackBar()
|
||||||
|
|||||||
@@ -169,6 +169,23 @@ extension SnapshotReader on Map<String, dynamic> {
|
|||||||
return value is String && value.isNotEmpty ? value : fallback;
|
return value is String && value.isNotEmpty ? value : fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 다국어 필드 — 문자열이거나 언어별 지도(`{ko: …, en: …}`, 운영 Quest 제목 등).
|
||||||
|
/// 지도면 지금 앱 언어(`zh-CN` · `zh`) → ko → en → 아무 값 순으로 고른다.
|
||||||
|
String textOr(String key, [String fallback = '']) {
|
||||||
|
final value = this[key];
|
||||||
|
if (value is! Map) return strOr(key, fallback);
|
||||||
|
final map = Map<String, dynamic>.from(value);
|
||||||
|
final tag = appL10n.localeName.replaceAll('_', '-');
|
||||||
|
for (final k in [tag, tag.split('-').first, 'ko', 'en']) {
|
||||||
|
final v = map.strOr(k);
|
||||||
|
if (v.isNotEmpty) return v;
|
||||||
|
}
|
||||||
|
return map.values.whereType<String>().firstWhere(
|
||||||
|
(v) => v.isNotEmpty,
|
||||||
|
orElse: () => fallback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
bool boolOr(String key, [bool fallback = false]) {
|
bool boolOr(String key, [bool fallback = false]) {
|
||||||
final value = this[key];
|
final value = this[key];
|
||||||
return value is bool ? value : fallback;
|
return value is bool ? value : fallback;
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ GoRouter buildRouter(AuthRepository auth, {Repositories? repositories}) {
|
|||||||
final linker = repos.linker(auth.providers);
|
final linker = repos.linker(auth.providers);
|
||||||
return GoRouter(
|
return GoRouter(
|
||||||
initialLocation: appTabs.first.path,
|
initialLocation: appTabs.first.path,
|
||||||
|
observers: [HomeRefreshObserver()],
|
||||||
refreshListenable: auth,
|
refreshListenable: auth,
|
||||||
redirect: (context, state) {
|
redirect: (context, state) {
|
||||||
final at = state.matchedLocation;
|
final at = state.matchedLocation;
|
||||||
@@ -151,6 +152,7 @@ GoRouter buildRouter(AuthRepository auth, {Repositories? repositories}) {
|
|||||||
builder: (_, _, shell) => AppShell(shell: shell, flags: repos.flags),
|
builder: (_, _, shell) => AppShell(shell: shell, flags: repos.flags),
|
||||||
branches: [
|
branches: [
|
||||||
StatefulShellBranch(
|
StatefulShellBranch(
|
||||||
|
observers: [HomeRefreshObserver()],
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: appTabs[0].path,
|
path: appTabs[0].path,
|
||||||
@@ -259,11 +261,15 @@ class AppShell extends StatelessWidget {
|
|||||||
child: NavigationBar(
|
child: NavigationBar(
|
||||||
// 숨긴 탭에 머무는 동안(딥링크 등)에는 선택 표시를 첫 탭으로 둔다.
|
// 숨긴 탭에 머무는 동안(딥링크 등)에는 선택 표시를 첫 탭으로 둔다.
|
||||||
selectedIndex: selected < 0 ? 0 : selected,
|
selectedIndex: selected < 0 ? 0 : selected,
|
||||||
onDestinationSelected: (i) => shell.goBranch(
|
onDestinationSelected: (i) {
|
||||||
|
// 다른 탭에서 HOME 으로 돌아오면 다시 읽는다(HOME 은 탭 전환으로 다시 만들어지지 않는다).
|
||||||
|
if (branches[i] == 0 && shell.currentIndex != 0) homeRefreshSignal.value++;
|
||||||
|
shell.goBranch(
|
||||||
branches[i],
|
branches[i],
|
||||||
// initialLocation: true 를 주면 이미 열려 있는 탭을 다시 누를 때 루트로 되돌아간다.
|
// initialLocation: true 를 주면 이미 열려 있는 탭을 다시 누를 때 루트로 되돌아간다.
|
||||||
initialLocation: branches[i] == shell.currentIndex,
|
initialLocation: branches[i] == shell.currentIndex,
|
||||||
),
|
);
|
||||||
|
},
|
||||||
destinations: [
|
destinations: [
|
||||||
for (final tab in tabs)
|
for (final tab in tabs)
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import 'package:flutter/painting.dart' show Color;
|
|||||||
import 'home_data.dart';
|
import 'home_data.dart';
|
||||||
import '../inside/inside_repository_firestore.dart' show insideQuestionFrom;
|
import '../inside/inside_repository_firestore.dart' show insideQuestionFrom;
|
||||||
import '../quest/quest_data.dart' show QuestType;
|
import '../quest/quest_data.dart' show QuestType;
|
||||||
import '../quest/quest_repository_firestore.dart' show openTimeAttack, questToday;
|
import '../quest/quest_repository_firestore.dart' show openTimeAttack, questTitle, questToday;
|
||||||
import '../../core/i18n/app_locale.dart';
|
import '../../core/i18n/app_locale.dart';
|
||||||
|
|
||||||
/// 홈의 실 서버 구현 — 화면 대조 원장 F1.
|
/// 홈의 실 서버 구현 — 화면 대조 원장 F1.
|
||||||
@@ -426,7 +426,7 @@ class FirestoreHomeRepository implements HomeRepository {
|
|||||||
);
|
);
|
||||||
final started = count > 0;
|
final started = count > 0;
|
||||||
return QuestSummary(
|
return QuestSummary(
|
||||||
title: quest.strOr('title'),
|
title: questTitle(quest),
|
||||||
type: QuestType.parse(quest.strOr('type')),
|
type: QuestType.parse(quest.strOr('type')),
|
||||||
category: QuestType.parse(quest.strOr('type')).label,
|
category: QuestType.parse(quest.strOr('type')).label,
|
||||||
rewardPop: openTimeAttack(quest)?.rewardPop ??
|
rewardPop: openTimeAttack(quest)?.rewardPop ??
|
||||||
|
|||||||
@@ -57,6 +57,19 @@ import 'widgets/briefing_share.dart';
|
|||||||
///
|
///
|
||||||
/// 값은 [HomeRepository] 가 준다. 지금은 [SampleHomeRepository] 라 서버 없이 뜨고,
|
/// 값은 [HomeRepository] 가 준다. 지금은 [SampleHomeRepository] 라 서버 없이 뜨고,
|
||||||
/// 5주차 Firestore 연동 때 구현체만 바꾼다.
|
/// 5주차 Firestore 연동 때 구현체만 바꾼다.
|
||||||
|
/// HOME 을 다시 읽으라는 신호 — 포그라운드 Push 수신 · 위에 띄운 화면이 닫힘 · HOME 탭 복귀.
|
||||||
|
/// HOME 은 진입 때 한 번 읽어서 타임어택 발동 Push · 후원 뒤에도 카드가 낡아 있었다(10/6 실기기).
|
||||||
|
/// ponytail: 어느 화면이 닫혀도 HOME 을 한 번 다시 읽는다. 읽기 비용이 문제면 HOME 이 보일 때만.
|
||||||
|
final ValueNotifier<int> homeRefreshSignal = ValueNotifier(0);
|
||||||
|
|
||||||
|
/// 페이지(시트 · 대화상자 제외)가 닫히면 [homeRefreshSignal] 을 울린다. 루트 · HOME 탭 내비게이터에 하나씩.
|
||||||
|
class HomeRefreshObserver extends NavigatorObserver {
|
||||||
|
@override
|
||||||
|
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
|
||||||
|
if (route is PageRoute) homeRefreshSignal.value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
class HomeScreen extends StatefulWidget {
|
||||||
const HomeScreen({
|
const HomeScreen({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -97,7 +110,7 @@ class HomeScreen extends StatefulWidget {
|
|||||||
State<HomeScreen> createState() => _HomeScreenState();
|
State<HomeScreen> createState() => _HomeScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomeScreenState extends State<HomeScreen> {
|
class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
||||||
late Future<HomeSnapshot> _snapshot;
|
late Future<HomeSnapshot> _snapshot;
|
||||||
Future<GalaxySnapshot>? _galaxy;
|
Future<GalaxySnapshot>? _galaxy;
|
||||||
ZapController? _zap;
|
ZapController? _zap;
|
||||||
@@ -113,13 +126,22 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
homeRefreshSignal.removeListener(_refresh);
|
||||||
_zap?.dispose();
|
_zap?.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
if (state == AppLifecycleState.resumed) _refresh();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
homeRefreshSignal.addListener(_refresh);
|
||||||
_snapshot = widget.repository.load();
|
_snapshot = widget.repository.load();
|
||||||
if (widget.galaxyRepository case final repository?) {
|
if (widget.galaxyRepository case final repository?) {
|
||||||
_galaxy = repository.load();
|
_galaxy = repository.load();
|
||||||
@@ -156,6 +178,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _refresh() async {
|
Future<void> _refresh() async {
|
||||||
|
if (!mounted) return;
|
||||||
final next = widget.repository.load();
|
final next = widget.repository.load();
|
||||||
setState(() => _snapshot = next);
|
setState(() => _snapshot = next);
|
||||||
await next;
|
await next;
|
||||||
@@ -176,7 +199,8 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
body: FutureBuilder<HomeSnapshot>(
|
body: FutureBuilder<HomeSnapshot>(
|
||||||
future: _snapshot,
|
future: _snapshot,
|
||||||
builder: (context, snap) {
|
builder: (context, snap) {
|
||||||
if (snap.connectionState != ConnectionState.done) {
|
// 다시 읽는 동안은 앞 데이터를 그대로 둔다 — 화면이 스피너로 깜빡이지 않게.
|
||||||
|
if (snap.connectionState != ConnectionState.done && !snap.hasData) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
if (snap.hasError) {
|
if (snap.hasError) {
|
||||||
|
|||||||
@@ -332,12 +332,17 @@ class QuestCard {
|
|||||||
this.adMultiplier = 2,
|
this.adMultiplier = 2,
|
||||||
this.ugcAdPop = 10,
|
this.ugcAdPop = 10,
|
||||||
this.endsAt,
|
this.endsAt,
|
||||||
|
this.publishState = '',
|
||||||
});
|
});
|
||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
final String title;
|
final String title;
|
||||||
final String categoryName;
|
final String categoryName;
|
||||||
|
|
||||||
|
/// 내가 만든 Quest 의 공개 상태(`quests.status`) — `review` · `published` · `rejected` 등.
|
||||||
|
/// 「내 퀘스트」 카드가 검수 중 · 반려를 공개된 것과 구분해 보인다(10/6 실기기 4-2).
|
||||||
|
final String publishState;
|
||||||
|
|
||||||
/// 검증 경로를 고르는 유형. 표본 데이터는 지정하지 않아 [QuestType.unknown] 이다.
|
/// 검증 경로를 고르는 유형. 표본 데이터는 지정하지 않아 [QuestType.unknown] 이다.
|
||||||
final QuestType type;
|
final QuestType type;
|
||||||
final QuestAuthor author;
|
final QuestAuthor author;
|
||||||
@@ -393,6 +398,7 @@ class QuestCard {
|
|||||||
adMultiplier: adMultiplier,
|
adMultiplier: adMultiplier,
|
||||||
ugcAdPop: ugcAdPop,
|
ugcAdPop: ugcAdPop,
|
||||||
endsAt: endsAt,
|
endsAt: endsAt,
|
||||||
|
publishState: publishState,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ class FirestoreQuestRepository implements QuestRepository {
|
|||||||
|
|
||||||
return QuestCard(
|
return QuestCard(
|
||||||
id: id,
|
id: id,
|
||||||
title: _title(quest),
|
title: questTitle(quest),
|
||||||
categoryName: type.label,
|
categoryName: type.label,
|
||||||
type: type,
|
type: type,
|
||||||
author: authorUid.isEmpty ? QuestAuthor.admin : QuestAuthor.user,
|
author: authorUid.isEmpty ? QuestAuthor.admin : QuestAuthor.user,
|
||||||
@@ -203,22 +203,11 @@ class FirestoreQuestRepository implements QuestRepository {
|
|||||||
ugcAdPop: policy.intOr('adRewardPop', 10),
|
ugcAdPop: policy.intOr('adRewardPop', 10),
|
||||||
// 열린 TIME ATTACK 회차의 마감. 있으면 카드 · 상세가 남은 시간을 세고 참여는 회차로 간다.
|
// 열린 TIME ATTACK 회차의 마감. 있으면 카드 · 상세가 남은 시간을 세고 참여는 회차로 간다.
|
||||||
endsAt: openTimeAttack(quest)?.endsAt,
|
endsAt: openTimeAttack(quest)?.endsAt,
|
||||||
|
publishState: authorUid == uid ? quest.strOr('status') : '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// 다국어 제목 — 설계서 §2.8 은 `title`(다국어)이라고만 적었다.
|
|
||||||
/// 문자열과 지도(map) 둘 다 들어올 수 있어 둘 다 받는다.
|
|
||||||
String _title(Map<String, dynamic> quest) {
|
|
||||||
final raw = quest['title'];
|
|
||||||
if (raw is String) return raw;
|
|
||||||
if (raw is Map) {
|
|
||||||
final map = Map<String, dynamic>.from(raw);
|
|
||||||
return map.strOr('ko', map.strOr('en'));
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Map<String, dynamic>> _policy() async {
|
Future<Map<String, dynamic>> _policy() async {
|
||||||
final snap = await _backend.db.doc('config/policy').get();
|
final snap = await _backend.db.doc('config/policy').get();
|
||||||
return snap.data() ?? const <String, dynamic>{};
|
return snap.data() ?? const <String, dynamic>{};
|
||||||
@@ -635,7 +624,7 @@ class FirestoreQuestRepository implements QuestRepository {
|
|||||||
}
|
}
|
||||||
return MusicShakeSetup(
|
return MusicShakeSetup(
|
||||||
videoId: videoId,
|
videoId: videoId,
|
||||||
title: link.strOr('title', _title(quest)),
|
title: link.strOr('title', questTitle(quest)),
|
||||||
bpm: bpm.toDouble(),
|
bpm: bpm.toDouble(),
|
||||||
segments: segments,
|
segments: segments,
|
||||||
windows: ShakeWindows.from(await _policy()),
|
windows: ShakeWindows.from(await _policy()),
|
||||||
@@ -792,7 +781,14 @@ List<int> _stepTiers(Map<String, dynamic> policy) {
|
|||||||
/// 열린 TIME ATTACK 회차 — 원 Quest 문서의 `timeAttack`(10/3 통합회신: 별도 유형이 아니라
|
/// 열린 TIME ATTACK 회차 — 원 Quest 문서의 `timeAttack`(10/3 통합회신: 별도 유형이 아니라
|
||||||
/// 기존 Quest 에 붙는 제한시간 모드). 창(`activeFrom` ~ `activeTo`, 서버 시각) 밖이면 null.
|
/// 기존 Quest 에 붙는 제한시간 모드). 창(`activeFrom` ~ `activeTo`, 서버 시각) 밖이면 null.
|
||||||
/// 화면 표시용이다 — 참여 수락은 서버(`fn-quest-timeattack`)가 서버 시각으로 다시 판정한다.
|
/// 화면 표시용이다 — 참여 수락은 서버(`fn-quest-timeattack`)가 서버 시각으로 다시 판정한다.
|
||||||
({String roundId, DateTime endsAt, int rewardPop})? openTimeAttack(
|
/// 카드 · 상세에 보일 Quest 제목. 열린 TIME ATTACK 회차면 회차 제목(`timeAttack.title`)이 먼저다.
|
||||||
|
/// 운영 Quest 제목은 언어별 지도라 [SnapshotReader.textOr] 로 읽는다(설계서 §2.8 `title` 다국어).
|
||||||
|
String questTitle(Map<String, dynamic> quest) {
|
||||||
|
final round = openTimeAttack(quest)?.title ?? '';
|
||||||
|
return round.isNotEmpty ? round : quest.textOr('title');
|
||||||
|
}
|
||||||
|
|
||||||
|
({String roundId, DateTime endsAt, int rewardPop, String title})? openTimeAttack(
|
||||||
Map<String, dynamic> quest,
|
Map<String, dynamic> quest,
|
||||||
) {
|
) {
|
||||||
final raw = quest['timeAttack'];
|
final raw = quest['timeAttack'];
|
||||||
@@ -807,5 +803,6 @@ List<int> _stepTiers(Map<String, dynamic> policy) {
|
|||||||
roundId: round.strOr('roundId'),
|
roundId: round.strOr('roundId'),
|
||||||
endsAt: to.toDate(),
|
endsAt: to.toDate(),
|
||||||
rewardPop: round.intOr('rewardPop'),
|
rewardPop: round.intOr('rewardPop'),
|
||||||
|
title: round.textOr('title'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -232,7 +232,14 @@ class QuestCardTile extends StatelessWidget {
|
|||||||
height: 1,
|
height: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(card.stage.buttonLabel),
|
// 내가 만든 Quest 가 검수 중 · 반려면 「자세히」 자리에 상태를 적는다(누르면 상세).
|
||||||
|
child: Text(switch (card) {
|
||||||
|
QuestCard(stage: QuestStage.mine, publishState: 'review') =>
|
||||||
|
context.l10n.questReviewButton,
|
||||||
|
QuestCard(stage: QuestStage.mine, publishState: 'rejected') =>
|
||||||
|
context.l10n.questRejectedButton,
|
||||||
|
_ => card.stage.buttonLabel,
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import 'package:fancake/features/quest/quest_data.dart';
|
||||||
|
import 'package:fancake/features/quest/widgets/quest_card_tile.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
/// 10/6 실기기 4-2 — 「내 퀘스트」 카드가 검수 중 · 반려를 공개된 것과 구분한다.
|
||||||
|
void main() {
|
||||||
|
QuestCard mine(String state) => QuestCard(
|
||||||
|
id: 'q',
|
||||||
|
title: '걷기',
|
||||||
|
categoryName: '건강',
|
||||||
|
author: QuestAuthor.user,
|
||||||
|
stage: QuestStage.mine,
|
||||||
|
goal: 1000,
|
||||||
|
progress: 0,
|
||||||
|
rewardPop: 10,
|
||||||
|
participants: 0,
|
||||||
|
publishState: state,
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<void> pump(WidgetTester t, QuestCard card) => t.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: QuestCardTile(card: card, onOpen: () {}, onAction: () {}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets('검수 중 · 반려 · 공개', (t) async {
|
||||||
|
await pump(t, mine('review'));
|
||||||
|
expect(find.text('검토 중'), findsOneWidget);
|
||||||
|
await pump(t, mine('rejected'));
|
||||||
|
expect(find.text('반려됨'), findsOneWidget);
|
||||||
|
await pump(t, mine('published'));
|
||||||
|
expect(find.text(QuestStage.mine.buttonLabel), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:fancake/features/quest/quest_data.dart';
|
import 'package:fancake/features/quest/quest_data.dart';
|
||||||
import 'package:fancake/features/quest/quest_repository_firestore.dart' show questToday;
|
import 'package:cloud_firestore/cloud_firestore.dart' show Timestamp;
|
||||||
|
import 'package:fancake/features/quest/quest_repository_firestore.dart' show questTitle, questToday;
|
||||||
import 'package:fancake/features/quest/widgets/quest_card_tile.dart' show questImage;
|
import 'package:fancake/features/quest/widgets/quest_card_tile.dart' show questImage;
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
@@ -50,4 +51,19 @@ void main() {
|
|||||||
);
|
);
|
||||||
expect(questImage(card), 'assets/figma/quest-img-bolt.png');
|
expect(questImage(card), 'assets/figma/quest-img-bolt.png');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 10/6 실기기 — 운영 Quest 제목은 언어별 지도라 HOME 카드 제목이 비었다. 타임어택은 회차 제목이 먼저다.
|
||||||
|
test('Quest 제목 — 문자열 · 언어별 지도(앱 언어 → ko) · 열린 타임어택 회차 제목', () {
|
||||||
|
expect(questTitle({'title': '만보기'}), '만보기');
|
||||||
|
expect(questTitle({'title': {'en': 'Stream', 'ko': '스트리밍'}}), '스트리밍');
|
||||||
|
expect(questTitle({'title': {'en': 'Stream'}}), 'Stream');
|
||||||
|
final round = {
|
||||||
|
'roundId': 'r1',
|
||||||
|
'title': '긴급 돌발',
|
||||||
|
'activeFrom': Timestamp.fromDate(DateTime.now().subtract(const Duration(minutes: 5))),
|
||||||
|
'activeTo': Timestamp.fromDate(DateTime.now().add(const Duration(minutes: 5))),
|
||||||
|
};
|
||||||
|
expect(questTitle({'title': {'ko': '스트리밍'}, 'timeAttack': round}), '긴급 돌발');
|
||||||
|
expect(questTitle({'title': {'ko': '스트리밍'}, 'timeAttack': {...round, 'title': ''}}), '스트리밍');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user