import 'package:timezone/data/latest.dart' as tzdata; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:hepan_everyday/services/local_notification_service.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); test( 'querying future reminders does not depend on active notification access', () async { debugDefaultTargetPlatformOverride = TargetPlatform.android; AndroidFlutterLocalNotificationsPlugin.registerWith(); const channel = MethodChannel( 'dexterous.com/flutter/local_notifications', ); TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(channel, (call) async { if (call.method == 'initialize') return true; if (call.method == 'getNotificationAppLaunchDetails') { return {'notificationLaunchedApp': false}; } if (call.method == 'pendingNotificationRequests') { return [ { 'id': 123, 'title': '课程', 'body': '教室', 'payload': 'hepan:course/2026-10-14', }, ]; } if (call.method == 'getActiveNotifications') { throw PlatformException(code: 'active_query_unavailable'); } return null; }); addTearDown(() { debugDefaultTargetPlatformOverride = null; TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(channel, null); }); expect(await PluginReminderGateway().pendingIds(), {123}); }, ); test('active lookup failure preserves owned future alarms', () async { debugDefaultTargetPlatformOverride = TargetPlatform.android; AndroidFlutterLocalNotificationsPlugin.registerWith(); const channel = MethodChannel('dexterous.com/flutter/local_notifications'); final messenger = TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger; messenger.setMockMethodCallHandler(channel, (call) async { if (call.method == 'initialize') return true; if (call.method == 'getNotificationAppLaunchDetails') { return {'notificationLaunchedApp': false}; } if (call.method == 'pendingNotificationRequests') { return [ {'id': 123, 'title': '', 'body': '', 'payload': 'hepan:course'}, { 'id': -1002, 'title': '', 'body': '', 'payload': 'hepan-test:scheduled', }, ]; } if (call.method == 'getActiveNotifications') { throw PlatformException(code: 'unavailable'); } return null; }); addTearDown(() { debugDefaultTargetPlatformOverride = null; messenger.setMockMethodCallHandler(channel, null); }); expect(await PluginReminderGateway().ownedIds(), {123}); }); test( 'background test uses an exact alarm isolated from course ownership', () async { tzdata.initializeTimeZones(); debugDefaultTargetPlatformOverride = TargetPlatform.android; AndroidFlutterLocalNotificationsPlugin.registerWith(); const channel = MethodChannel( 'dexterous.com/flutter/local_notifications', ); final messenger = TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger; Map? scheduled; messenger.setMockMethodCallHandler(channel, (call) async { if (call.method == 'initialize') return true; if (call.method == 'getNotificationAppLaunchDetails') { return {'notificationLaunchedApp': false}; } if (call.method == 'areNotificationsEnabled' || call.method == 'canScheduleExactNotifications') { return true; } if (call.method == 'zonedSchedule') { scheduled = call.arguments as Map; return null; } if (call.method == 'pendingNotificationRequests') { return [ if (scheduled != null) { 'id': -1002, 'title': '', 'body': '', 'payload': 'hepan-test:scheduled', }, ]; } return null; }); addTearDown(() { debugDefaultTargetPlatformOverride = null; messenger.setMockMethodCallHandler(channel, null); }); final gateway = PluginReminderGateway(); expect(await gateway.sendTestReminder(scheduled: true), contains('10 秒')); expect(scheduled?['id'], -1002); expect(scheduled?['payload'], 'hepan-test:scheduled'); expect( (scheduled?['platformSpecifics'] as Map?)?['scheduleMode'], 'exactAllowWhileIdle', ); expect(await gateway.pendingIds(), isEmpty); }, ); test('transient native initialization failure can be retried', () async { debugDefaultTargetPlatformOverride = TargetPlatform.android; AndroidFlutterLocalNotificationsPlugin.registerWith(); const channel = MethodChannel('dexterous.com/flutter/local_notifications'); var attempts = 0; TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(channel, (call) async { if (call.method == 'initialize') { attempts++; if (attempts == 1) { throw PlatformException(code: 'temporarily-unavailable'); } return true; } if (call.method == 'getNotificationAppLaunchDetails') { return {'notificationLaunchedApp': false}; } return null; }); addTearDown(() { debugDefaultTargetPlatformOverride = null; TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(channel, null); }); final gateway = PluginReminderGateway(); await expectLater(gateway.initialize(), throwsA(isA())); await gateway.initialize(); expect(attempts, 2); }); test( 'disabled course notification channel is reported even with app permission', () async { debugDefaultTargetPlatformOverride = TargetPlatform.android; AndroidFlutterLocalNotificationsPlugin.registerWith(); const channel = MethodChannel( 'dexterous.com/flutter/local_notifications', ); var importance = 0; TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(channel, (call) async { switch (call.method) { case 'initialize': return true; case 'getNotificationAppLaunchDetails': return {'notificationLaunchedApp': false}; case 'areNotificationsEnabled': return true; case 'canScheduleExactNotifications': return true; case 'getNotificationChannels': return [ { 'id': 'hepan_lessons', 'name': '课前提醒', 'description': '课程开始前的提醒', 'importance': importance, 'showBadge': true, 'playSound': true, 'enableVibration': true, 'enableLights': false, 'bypassDnd': false, 'ledColor': 0, 'audioAttributesUsage': 5, }, ]; } return null; }); addTearDown(() { debugDefaultTargetPlatformOverride = null; TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(channel, null); }); final gateway = PluginReminderGateway(); expect( (await gateway.permissions(request: false)).notifications, isFalse, ); importance = 4; expect((await gateway.permissions(request: false)).notifications, isTrue); }, ); }