260 lines
8.5 KiB
Dart
260 lines
8.5 KiB
Dart
import 'package:fancake/core/ui/app_icons.dart';
|
|
import 'package:fancake/features/notification/notification_screen.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
/// F0 공통 — 화면 대조 원장 2화면.
|
|
/// Bottom Navigation(`350:215853`)은 5탭 셸이라 `app_smoke_test` 가 검증한다.
|
|
|
|
Future<void> pumpWide(WidgetTester tester, Widget child) async {
|
|
tester.view.physicalSize = const Size(1080, 2400);
|
|
tester.view.devicePixelRatio = 3.0;
|
|
addTearDown(tester.view.reset);
|
|
await tester.pumpWidget(MaterialApp(home: child));
|
|
await tester.pump();
|
|
}
|
|
|
|
void main() {
|
|
group('날짜 머리글 (F0 · 518:88353)', () {
|
|
final now = DateTime(2026, 9, 1, 12);
|
|
|
|
test('오늘과 어제만 이름으로 부른다', () {
|
|
expect(notificationDayLabel(DateTime(2026, 9, 1, 3), now: now), '오늘');
|
|
expect(notificationDayLabel(DateTime(2026, 8, 31, 23), now: now), '어제');
|
|
expect(
|
|
notificationDayLabel(DateTime(2026, 8, 30), now: now),
|
|
'2026.08.30',
|
|
);
|
|
});
|
|
|
|
test('생성일시는 명세 형식을 그대로 쓴다', () {
|
|
expect(
|
|
notificationTimestamp(DateTime(2026, 1, 2, 3, 4)),
|
|
'2026.01.02 03:04',
|
|
);
|
|
});
|
|
});
|
|
|
|
group('날짜 그룹화', () {
|
|
final now = DateTime(2026, 9, 1, 12);
|
|
|
|
test('날짜로 묶고 최신이 앞에 온다', () {
|
|
final items = [
|
|
AppNotification(
|
|
id: 'a',
|
|
kind: NotificationKind.like,
|
|
title: 'a',
|
|
at: DateTime(2026, 8, 30, 9),
|
|
),
|
|
AppNotification(
|
|
id: 'b',
|
|
kind: NotificationKind.like,
|
|
title: 'b',
|
|
at: DateTime(2026, 9, 1, 9),
|
|
),
|
|
AppNotification(
|
|
id: 'c',
|
|
kind: NotificationKind.like,
|
|
title: 'c',
|
|
at: DateTime(2026, 9, 1, 11),
|
|
),
|
|
];
|
|
final groups = groupNotifications(items, now: now);
|
|
expect(groups.map((g) => g.label).toList(), ['오늘', '2026.08.30']);
|
|
expect(groups.first.items.map((i) => i.id).toList(), ['c', 'b']);
|
|
});
|
|
});
|
|
|
|
group('종류별 이동 (F0 · 518:88353)', () {
|
|
test('이클립스와 아이템 사용은 이동하지 않는다', () {
|
|
expect(NotificationKind.eclipse.navigable, isFalse);
|
|
expect(NotificationKind.itemUsed.navigable, isFalse);
|
|
});
|
|
|
|
test('나머지 넷은 갈 곳이 있다', () {
|
|
for (final kind in const [
|
|
NotificationKind.like,
|
|
NotificationKind.comment,
|
|
NotificationKind.campaign,
|
|
NotificationKind.quest,
|
|
]) {
|
|
expect(kind.navigable, isTrue, reason: kind.label);
|
|
}
|
|
});
|
|
});
|
|
|
|
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() 기준이라 자정 전후로 「오늘/어제」가 갈린다.
|
|
// 시각을 못박아 넣는다.
|
|
final now = DateTime.now();
|
|
await pumpWide(
|
|
tester,
|
|
NotificationScreen(
|
|
repository: SampleNotificationRepository(
|
|
items: [
|
|
AppNotification(
|
|
id: 'q',
|
|
kind: NotificationKind.quest,
|
|
title: '만보기 퀘스트를 완료했습니다',
|
|
at: now,
|
|
),
|
|
AppNotification(
|
|
id: 'o',
|
|
kind: NotificationKind.like,
|
|
title: '오래된 알림',
|
|
at: now.subtract(const Duration(days: 9)),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
expect(find.text('오늘'), findsOneWidget);
|
|
expect(find.textContaining('만보기 퀘스트를 완료'), findsOneWidget);
|
|
expect(find.text('오래된 알림'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('이동하지 않는 알림에는 화살표를 그리지 않는다', (tester) async {
|
|
await pumpWide(
|
|
tester,
|
|
NotificationScreen(
|
|
repository: SampleNotificationRepository(
|
|
items: [
|
|
AppNotification(
|
|
id: 'e',
|
|
kind: NotificationKind.eclipse,
|
|
title: '이클립스',
|
|
at: DateTime.now(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
expect(
|
|
find.byWidgetPredicate(
|
|
(w) => w is ImageIcon && w.image == AppIcons.chevronRight,
|
|
),
|
|
findsNothing,
|
|
);
|
|
});
|
|
|
|
testWidgets('누르면 읽음으로 바뀌고 뒤로가기가 읽은 목록을 돌려준다', (tester) async {
|
|
final items = [
|
|
AppNotification(
|
|
id: 'n1',
|
|
kind: NotificationKind.quest,
|
|
title: '만보기 퀘스트를 완료했습니다',
|
|
at: DateTime.now(),
|
|
),
|
|
];
|
|
Set<String>? returned;
|
|
await pumpWide(
|
|
tester,
|
|
Builder(
|
|
builder: (context) => TextButton(
|
|
onPressed: () async {
|
|
returned = await Navigator.of(context).push<Set<String>>(
|
|
MaterialPageRoute(
|
|
builder: (_) => NotificationScreen(
|
|
repository: SampleNotificationRepository(items: items),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
child: const Text('열기'),
|
|
),
|
|
),
|
|
);
|
|
await tester.tap(find.text('열기'));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.textContaining('만보기 퀘스트를 완료'));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byIcon(Icons.arrow_back));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(returned, contains('n1'));
|
|
});
|
|
|
|
testWidgets('누르면 스낵바가 아니라 대상 화면으로 간다 — 이클립스는 경로가 있어도 이동 X', (tester) async {
|
|
final opened = <String>[];
|
|
final now = DateTime.now();
|
|
await pumpWide(
|
|
tester,
|
|
NotificationTargets(
|
|
screenFor: (path) {
|
|
opened.add(path);
|
|
return Scaffold(body: Text('열린 화면 $path'));
|
|
},
|
|
child: NotificationScreen(
|
|
repository: SampleNotificationRepository(
|
|
items: [
|
|
AppNotification(
|
|
id: 'q',
|
|
kind: NotificationKind.quest,
|
|
title: '[만보기] 퀘스트를 완료했어요!',
|
|
at: now,
|
|
targetPath: 'quests/steps-1',
|
|
),
|
|
AppNotification(
|
|
id: 'e',
|
|
kind: NotificationKind.eclipse,
|
|
title: '내 행성의 이클립스가 발생했어요.',
|
|
at: now,
|
|
targetPath: 'fandoms/bts',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.tap(find.text('내 행성의 이클립스가 발생했어요.'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(SnackBar), findsNothing);
|
|
expect(find.textContaining('열린 화면'), findsNothing);
|
|
|
|
await tester.tap(find.text('[만보기] 퀘스트를 완료했어요!'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('열린 화면 quests/steps-1'), findsOneWidget);
|
|
expect(find.byType(SnackBar), findsNothing);
|
|
});
|
|
|
|
testWidgets('비어 있으면 그렇게 알린다', (tester) async {
|
|
await pumpWide(
|
|
tester,
|
|
NotificationScreen(
|
|
repository: const SampleNotificationRepository(items: []),
|
|
),
|
|
);
|
|
expect(find.text('알림이 없습니다.'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('문구에 내부 경로가 새지 않는다', (tester) async {
|
|
await pumpWide(
|
|
tester,
|
|
NotificationScreen(repository: const SampleNotificationRepository()),
|
|
);
|
|
await tester.pump();
|
|
for (final text in tester.widgetList<Text>(find.byType(Text))) {
|
|
final data = text.data ?? '';
|
|
expect(data, isNot(contains('lib/')));
|
|
expect(data, isNot(contains('.dart')));
|
|
}
|
|
});
|
|
});
|
|
}
|