264 lines
10 KiB
Dart
264 lines
10 KiB
Dart
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('fresh notification initialization creates the course category before checks', () async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
|
AndroidFlutterLocalNotificationsPlugin.registerWith();
|
|
const channel = MethodChannel('dexterous.com/flutter/local_notifications');
|
|
const system = MethodChannel('com.tpschedule.app/reminders');
|
|
final messenger =
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
|
|
final categories = <Map<Object?, Object?>>[];
|
|
messenger.setMockMethodCallHandler(channel, (call) async {
|
|
switch (call.method) {
|
|
case 'initialize':
|
|
return true;
|
|
case 'createNotificationChannel':
|
|
final data = call.arguments as Map;
|
|
categories.add({
|
|
...Map<Object?, Object?>.from(data),
|
|
// Android returns a numeric color even when no LED was set.
|
|
'ledColor': data['ledColor'] ?? 0,
|
|
});
|
|
return null;
|
|
case 'getNotificationAppLaunchDetails':
|
|
return {'notificationLaunchedApp': false};
|
|
case 'areNotificationsEnabled':
|
|
case 'canScheduleExactNotifications':
|
|
return true;
|
|
case 'getNotificationChannels':
|
|
return categories;
|
|
case 'pendingNotificationRequests':
|
|
return [];
|
|
}
|
|
return null;
|
|
});
|
|
messenger.setMockMethodCallHandler(
|
|
system,
|
|
(_) async => <Object?, Object?>{},
|
|
);
|
|
addTearDown(() {
|
|
debugDefaultTargetPlatformOverride = null;
|
|
messenger.setMockMethodCallHandler(channel, null);
|
|
messenger.setMockMethodCallHandler(system, null);
|
|
});
|
|
final gateway = PluginReminderGateway();
|
|
final diagnosis = await gateway.diagnose();
|
|
expect(diagnosis['channelImportance'], Importance.high.value);
|
|
expect(diagnosis['appNotificationsEnabled'], isTrue);
|
|
expect(diagnosis['channelExists'], isTrue);
|
|
expect(categories.single['id'], 'hepan_lessons');
|
|
});
|
|
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<Object?, Object?>? 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<Object?, Object?>;
|
|
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<PlatformException>()));
|
|
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);
|
|
},
|
|
);
|
|
}
|