Files
social-app/apps/test/features/calendar/reminders/reminder_notification_bridge_test.dart
T

147 lines
4.5 KiB
Dart

import 'dart:convert';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:social_app/core/notifications/local_notification_service.dart';
import 'package:social_app/core/notifications/reminder_notification_callbacks.dart';
import 'package:social_app/features/calendar/reminders/models/reminder_action.dart';
import 'package:social_app/features/calendar/reminders/models/reminder_payload.dart';
class MockFlutterLocalNotificationsPlugin extends Mock
implements FlutterLocalNotificationsPlugin {}
void main() {
setUpAll(() {
registerFallbackValue(
const InitializationSettings(
android: AndroidInitializationSettings('@mipmap/ic_launcher'),
iOS: DarwinInitializationSettings(),
),
);
});
late MockFlutterLocalNotificationsPlugin plugin;
late LocalNotificationService service;
late List<ReminderAction> handledActions;
late List<ReminderPayload> presentedPayloads;
DidReceiveNotificationResponseCallback? callback;
setUp(() async {
SharedPreferences.setMockInitialValues({});
plugin = MockFlutterLocalNotificationsPlugin();
service = LocalNotificationService(plugin: plugin);
handledActions = <ReminderAction>[];
presentedPayloads = <ReminderPayload>[];
when(
() => plugin.initialize(
any(),
onDidReceiveNotificationResponse: any(
named: 'onDidReceiveNotificationResponse',
),
onDidReceiveBackgroundNotificationResponse: any(
named: 'onDidReceiveBackgroundNotificationResponse',
),
),
).thenAnswer((invocation) async {
callback =
invocation.namedArguments[#onDidReceiveNotificationResponse]
as DidReceiveNotificationResponseCallback?;
return true;
});
when(
() => plugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin
>(),
).thenReturn(null);
when(
() => plugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin
>(),
).thenReturn(null);
service.bindActionHandler(({required action, required payload}) async {
handledActions.add(action);
});
service.bindInAppReminderHandler((payload) async {
presentedPayloads.add(payload);
});
await ReminderNotificationCallbacks.bindResponseHandler(
service.handleNotificationResponse,
);
await service.initialize();
});
test('cancel action from system notification maps to archive', () async {
callback!(
NotificationResponse(
notificationResponseType:
NotificationResponseType.selectedNotificationAction,
id: 101,
actionId: 'cancel',
payload: jsonEncode(
ReminderPayload(
eventId: 'evt_1',
title: 'sync',
startAt: DateTime.parse('2026-03-19T10:00:00+08:00'),
timezone: 'Asia/Shanghai',
).toJson(),
),
),
);
await Future<void>.delayed(Duration.zero);
expect(handledActions, [ReminderAction.archive]);
});
test('duplicate notification response is handled only once', () async {
final response = NotificationResponse(
notificationResponseType:
NotificationResponseType.selectedNotificationAction,
id: 201,
actionId: 'cancel',
payload: jsonEncode(
ReminderPayload(
eventId: 'evt_2',
title: 'retro',
startAt: DateTime.parse('2026-03-19T11:00:00+08:00'),
timezone: 'Asia/Shanghai',
).toJson(),
),
);
callback!(response);
callback!(response);
await Future<void>.delayed(Duration.zero);
expect(handledActions, [ReminderAction.archive]);
});
test('notification body tap forwards payload to in-app presenter', () async {
callback!(
NotificationResponse(
notificationResponseType: NotificationResponseType.selectedNotification,
id: 301,
payload: jsonEncode(
ReminderPayload(
eventId: 'evt_3',
title: 'daily sync',
startAt: DateTime.parse('2026-03-19T12:00:00+08:00'),
timezone: 'Asia/Shanghai',
).toJson(),
),
),
);
await Future<void>.delayed(Duration.zero);
expect(presentedPayloads.map((item) => item.eventId), ['evt_3']);
expect(handledActions, isEmpty);
});
}