Files
ux/test/notifications_test.dart
agra 27cfc87def notifications + window: add Android native plugins
`ux/notifications/events` and `ux/window/events` only had macOS stream
handlers, so on Android/iOS the unconditional Dart subscription threw
MissingPluginException at startup (EventChannel reports activation
failures straight to FlutterError.onError, bypassing the `onError:`
callback).

- Gate each Dart event-channel subscription to platforms that register a
  native handler (`defaultTargetPlatform`), silencing iOS.
- `WindowPlugin`: report app foreground/background as host focus via
  `ProcessLifecycleOwner` ON_START/ON_STOP, so a backgrounded-but-alive
  process reports `focused = false`.
- `NotificationsPlugin`: local notifications (show/cancel by thread/all),
  POST_NOTIFICATIONS request on 13+, and tap routing back over the event
  channel — a tap that cold-starts the process is buffered until Dart
  subscribes.
- Regression tests for the subscription gate plus contract tests for the
  method/event payloads.
2026-05-30 13:39:49 +03:00

124 lines
3.9 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:ux/ux.dart';
void main() {
final messenger =
TestWidgetsFlutterBinding.ensureInitialized().defaultBinaryMessenger;
const methods = MethodChannel('ux/notifications');
const events = MethodChannel('ux/notifications/events');
final calls = <MethodCall>[];
setUp(() {
calls.clear();
messenger.setMockMethodCallHandler(methods, (call) async {
calls.add(call);
return call.method == 'requestPermission' ? true : null;
});
messenger.setMockMethodCallHandler(events, (_) async => null);
});
tearDown(() {
messenger.setMockMethodCallHandler(methods, null);
messenger.setMockMethodCallHandler(events, null);
debugDefaultTargetPlatformOverride = null;
});
Future<void> sendEvent(Object? event) => messenger.handlePlatformMessage(
'ux/notifications/events',
const StandardMethodCodec().encodeSuccessEnvelope(event),
(_) {},
);
// Regression: iOS has no native handler, so activating the broadcast
// stream throws MissingPluginException straight to FlutterError.onError.
test('iOS does not activate the events channel', () async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
final eventCalls = <String>[];
messenger.setMockMethodCallHandler(events, (call) async {
eventCalls.add(call.method);
return null;
});
MethodChannelXNotifications();
await pumpEventQueue();
expect(eventCalls, isEmpty);
});
test('Android activates the events channel', () async {
debugDefaultTargetPlatformOverride = TargetPlatform.android;
final eventCalls = <String>[];
messenger.setMockMethodCallHandler(events, (call) async {
eventCalls.add(call.method);
return null;
});
MethodChannelXNotifications();
await pumpEventQueue();
expect(eventCalls, contains('listen'));
});
test('show forwards the exact argument map', () async {
debugDefaultTargetPlatformOverride = TargetPlatform.android;
final n = MethodChannelXNotifications();
await n.show(XNotification(
id: 'd1:7',
title: 'Alice',
body: 'hi',
threadId: 'd1',
data: const {'dialogId': 'd1', 'messageId': '7'},
));
final show = calls.firstWhere((c) => c.method == 'show');
expect(show.arguments, {
'id': 'd1:7',
'title': 'Alice',
'body': 'hi',
'threadId': 'd1',
'data': {'dialogId': 'd1', 'messageId': '7'},
});
});
test('requestPermission invokes the method and updates authorized', () async {
debugDefaultTargetPlatformOverride = TargetPlatform.android;
final n = MethodChannelXNotifications();
expect(await n.requestPermission(), isTrue);
expect(calls.any((c) => c.method == 'requestPermission'), isTrue);
expect(n.authorized.value, isTrue);
});
test('cancelByThread and cancelAll invoke their methods', () async {
debugDefaultTargetPlatformOverride = TargetPlatform.android;
final n = MethodChannelXNotifications();
await n.cancelByThread('d1');
await n.cancelAll();
expect(
calls.any((c) =>
c.method == 'cancelByThread' &&
(c.arguments as Map)['threadId'] == 'd1'),
isTrue,
);
expect(calls.any((c) => c.method == 'cancelAll'), isTrue);
});
test('authorization and tap events reach the Dart side', () async {
debugDefaultTargetPlatformOverride = TargetPlatform.android;
final n = MethodChannelXNotifications();
await pumpEventQueue();
final taps = <Map<String, String>>[];
n.onTap.listen(taps.add);
await sendEvent({'type': 'authorization', 'granted': true});
await pumpEventQueue();
expect(n.authorized.value, isTrue);
await sendEvent({
'type': 'tap',
'data': {'dialogId': 'd1', 'messageId': '7'},
});
await pumpEventQueue();
expect(taps, [
{'dialogId': 'd1', 'messageId': '7'},
]);
});
}