focus-flow/packages/scheduler_notifications_desktop/test/desktop_notification_adapter_test.dart

155 lines
4.7 KiB
Dart

/// Tests Desktop Notification Adapter behavior for the Scheduler Notifications Desktop package.
library;
import 'dart:io';
import 'package:scheduler_notifications/notifications.dart';
import 'package:test/test.dart';
import '../../scheduler_notifications/test/notification_adapter_contract.dart';
// ignore: avoid_relative_lib_imports
import '../lib/desktop_notifications.dart';
void main() {
runNotificationAdapterContractTests('desktop', () {
final backend = _RecordingBackend();
return NotificationAdapterContractHarness(
adapter: DesktopNotificationAdapter(backend: backend),
scheduledRequests: () => backend.delivered,
cancelledIds: () => backend.cancelled,
);
});
test('desktop adapter delegates schedule and cancel to backend', () async {
final backend = _RecordingBackend();
final adapter = DesktopNotificationAdapter(backend: backend);
final request = _request('reminder-1');
await adapter.schedule(request);
await adapter.cancel(' reminder-1 ');
expect(backend.delivered, [request]);
expect(backend.cancelled, ['reminder-1']);
expect(adapter, isA<NotificationAdapter>());
});
test('linux backend invokes notify-send', () async {
final calls = <_ProcessCall>[];
final logs = <String>[];
final backend = createDefaultDesktopNotificationBackend(
platform: DesktopNotificationPlatform.linux,
processRunner: (executable, arguments) async {
calls.add(_ProcessCall(executable, arguments));
return ProcessResult(1, 0, '', '');
},
logSink: logs.add,
);
await backend.deliver(_request('reminder-1'));
expect(backend.isSupported, isTrue);
expect(calls.single.executable, 'notify-send');
expect(calls.single.arguments, contains('Start task'));
expect(logs, isEmpty);
});
test('macOS backend invokes osascript', () async {
final calls = <_ProcessCall>[];
final backend = createDefaultDesktopNotificationBackend(
platform: DesktopNotificationPlatform.macos,
processRunner: (executable, arguments) async {
calls.add(_ProcessCall(executable, arguments));
return ProcessResult(1, 0, '', '');
},
);
await backend.deliver(_request('reminder-1'));
expect(backend.isSupported, isTrue);
expect(calls.single.executable, 'osascript');
expect(calls.single.arguments, contains('-e'));
});
test('unsupported and Windows platforms fall back to log output', () async {
final logs = <String>[];
final windows = createDefaultDesktopNotificationBackend(
platform: DesktopNotificationPlatform.windows,
logSink: logs.add,
);
final unsupported = createDefaultDesktopNotificationBackend(
platform: DesktopNotificationPlatform.unsupported,
logSink: logs.add,
);
await windows.deliver(_request('windows-reminder'));
await unsupported.cancel(' unsupported-reminder ');
expect(windows.isSupported, isFalse);
expect(unsupported.isSupported, isFalse);
expect(logs.first, contains('windows-reminder'));
expect(logs.last, contains('unsupported-reminder'));
});
test('fake adapter can stand in for scheduling workflows', () async {
final adapter = FakeNotificationAdapter();
addTearDown(adapter.close);
await _scheduleAndCancelReminder(adapter, _request('reminder-1'));
expect(adapter.scheduledRequests, isEmpty);
expect(adapter.cancelledIds, ['reminder-1']);
});
test('default desktop adapter can be created on supported test platforms',
() {
if (!Platform.isLinux && !Platform.isMacOS && !Platform.isWindows) {
markTestSkipped(
'Desktop notifications are unsupported on this platform.');
}
expect(DesktopNotificationAdapter.defaultInstance(),
isA<NotificationAdapter>());
});
}
Future<void> _scheduleAndCancelReminder(
NotificationAdapter adapter,
NotificationRequest request,
) async {
await adapter.schedule(request);
await adapter.cancel(request.id);
}
NotificationRequest _request(String id) {
return NotificationRequest(
id: id,
title: 'Start task',
body: 'Time to begin.',
scheduledDateTimeUtc: DateTime.utc(2026, 1, 1, 9),
);
}
final class _RecordingBackend implements DesktopNotificationBackend {
final List<NotificationRequest> delivered = <NotificationRequest>[];
final List<String> cancelled = <String>[];
@override
bool get isSupported => true;
@override
Future<void> deliver(NotificationRequest request) async {
delivered.add(request);
}
@override
Future<void> cancel(String id) async {
cancelled.add(id);
}
}
final class _ProcessCall {
const _ProcessCall(this.executable, this.arguments);
final String executable;
final List<String> arguments;
}