- PHASE.md에서 flutter-web-notification-smoke 마일스톤 상태를 완료로 변경하고 archive로 이동 - browser_notification_interop_web.dart에서 라우팅 데이터 캡처 방식을 인스턴스 변수에서 인자 전달로 변경하여 각 알림별 데이터 격리 - nexo_messaging_plugin.dart에서 permissionGranted를 고정 값에서 실제 권한 상태 기반으로 변경 - 코드 스타일 개선: if문 블록 포맷팅, 단일 줄 최적화 - 테스트 추가: show 실패 시 최신 권한 상태를 반환하는 검증
733 lines
23 KiB
Dart
733 lines
23 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:nexo_messaging/nexo_messaging.dart';
|
|
import 'package:nexo_messaging/src/web/browser_notification_interop.dart';
|
|
import 'package:nexo_messaging/src/web/foreground_message_mapper.dart';
|
|
|
|
/// ignore: one_member_abstracts, avoid_implementing_value_types, public_member_api_docs
|
|
class FakeBrowserNotificationInterop implements BrowserNotificationInterop {
|
|
FakeBrowserNotificationInterop({
|
|
this.isSupportedParam = true,
|
|
String permissionParam = 'granted',
|
|
this.showReturnValue = true,
|
|
String? permissionAfterShow,
|
|
}) : _isSupported = isSupportedParam,
|
|
_permission = permissionParam,
|
|
_permissionAfterShow = permissionAfterShow;
|
|
|
|
final bool _isSupported;
|
|
String _permission;
|
|
final String? _permissionAfterShow;
|
|
final bool isSupportedParam;
|
|
final bool showReturnValue;
|
|
|
|
bool showCalled = false;
|
|
Map<String, Object?>? lastShowOptions;
|
|
|
|
@override
|
|
bool get isSupported => _isSupported;
|
|
|
|
@override
|
|
String get permission => _permission;
|
|
|
|
@override
|
|
bool show(Map<String, Object?> options) {
|
|
if (!isSupported) return false;
|
|
showCalled = true;
|
|
lastShowOptions = Map<String, Object?>.from(options);
|
|
if (_permissionAfterShow != null) {
|
|
_permission = _permissionAfterShow;
|
|
}
|
|
return showReturnValue;
|
|
}
|
|
|
|
@override
|
|
Future<String> requestPermission() async => permission;
|
|
|
|
void Function(Map<String, Object?>)? _onClick;
|
|
|
|
@override
|
|
void setClickHandler(void Function(Map<String, Object?>) onClick) {
|
|
_onClick = onClick;
|
|
}
|
|
|
|
void triggerClick(Map<String, Object?> data) {
|
|
_onClick?.call(data);
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
group('NotificationOpenedEvent.fromMap', () {
|
|
test('parses server_url, channel_id, root_id, is_crt_enabled', () {
|
|
final event = NotificationOpenedEvent.fromMap({
|
|
'server_url': 'https://nexo.example.com',
|
|
'channel_id': 'channel-123',
|
|
'root_id': 'root-456',
|
|
'is_crt_enabled': 'true',
|
|
});
|
|
|
|
expect(event.serverUrl, 'https://nexo.example.com');
|
|
expect(event.channelId, 'channel-123');
|
|
expect(event.rootId, 'root-456');
|
|
expect(event.isCRTEnabled, isTrue);
|
|
});
|
|
|
|
test('defaults isCRTEnabled to false when flag is absent or non-true', () {
|
|
final missing = NotificationOpenedEvent.fromMap({
|
|
'server_url': 'https://nexo.example.com',
|
|
'channel_id': 'channel-123',
|
|
});
|
|
expect(missing.isCRTEnabled, isFalse);
|
|
|
|
final falsy = NotificationOpenedEvent.fromMap({
|
|
'is_crt_enabled': 'false',
|
|
});
|
|
expect(falsy.isCRTEnabled, isFalse);
|
|
});
|
|
|
|
test('accepts boolean and numeric-string CRT truthy flags', () {
|
|
final boolean = NotificationOpenedEvent.fromMap({'is_crt_enabled': true});
|
|
final numericString = NotificationOpenedEvent.fromMap({
|
|
'is_crt_enabled': '1',
|
|
});
|
|
|
|
expect(boolean.isCRTEnabled, isTrue);
|
|
expect(numericString.isCRTEnabled, isTrue);
|
|
});
|
|
});
|
|
|
|
group('NexoMessagingPlugin.handleNativeEvent', () {
|
|
late NexoMessagingPlugin plugin;
|
|
final actionChannel = MethodChannel(NexoMessagingPlugin.actionChannelName);
|
|
|
|
setUp(() {
|
|
plugin = NexoMessagingPlugin.instance;
|
|
plugin.onNavigateToChannel = null;
|
|
plugin.onNavigateToThread = null;
|
|
plugin.onDeviceTokenReady = null;
|
|
});
|
|
|
|
tearDown(() {
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
|
.setMockMethodCallHandler(actionChannel, null);
|
|
});
|
|
|
|
test('opened event emits NotificationOpenedEvent on stream', () async {
|
|
final future = plugin.onNotificationOpened.first;
|
|
|
|
plugin.handleNativeEvent({
|
|
'type': PushNotificationType.opened,
|
|
'server_url': 'https://nexo.example.com',
|
|
'channel_id': 'channel-123',
|
|
});
|
|
|
|
final event = await future;
|
|
expect(event.serverUrl, 'https://nexo.example.com');
|
|
expect(event.channelId, 'channel-123');
|
|
});
|
|
|
|
test(
|
|
'message event with userInteraction routes through opened handler',
|
|
() async {
|
|
String? navServer;
|
|
String? navChannel;
|
|
plugin.onNavigateToChannel = (s, c) {
|
|
navServer = s;
|
|
navChannel = c;
|
|
};
|
|
|
|
plugin.handleNativeEvent({
|
|
'type': PushNotificationType.message,
|
|
'userInteraction': true,
|
|
'server_url': 'https://nexo.example.com',
|
|
'channel_id': 'channel-xyz',
|
|
});
|
|
|
|
expect(navServer, 'https://nexo.example.com');
|
|
expect(navChannel, 'channel-xyz');
|
|
},
|
|
);
|
|
|
|
test(
|
|
'message event without userInteraction does not trigger navigation',
|
|
() async {
|
|
var channelCalled = false;
|
|
var threadCalled = false;
|
|
plugin.onNavigateToChannel = (_, _) => channelCalled = true;
|
|
plugin.onNavigateToThread = (_, _) => threadCalled = true;
|
|
|
|
final openedEvents = <NotificationOpenedEvent>[];
|
|
final sub = plugin.onNotificationOpened.listen(openedEvents.add);
|
|
|
|
plugin.handleNativeEvent({
|
|
'type': PushNotificationType.message,
|
|
'server_url': 'https://nexo.example.com',
|
|
'channel_id': 'channel-xyz',
|
|
});
|
|
|
|
// Allow any pending microtasks to settle.
|
|
await Future<void>.delayed(Duration.zero);
|
|
await sub.cancel();
|
|
|
|
expect(channelCalled, isFalse);
|
|
expect(threadCalled, isFalse);
|
|
expect(openedEvents, isEmpty);
|
|
},
|
|
);
|
|
|
|
test('opened event with CRT and rootId routes to thread callback', () {
|
|
String? navServer;
|
|
String? navRoot;
|
|
plugin.onNavigateToThread = (s, r) {
|
|
navServer = s;
|
|
navRoot = r;
|
|
};
|
|
|
|
plugin.handleNativeEvent({
|
|
'type': PushNotificationType.opened,
|
|
'server_url': 'https://nexo.example.com',
|
|
'channel_id': 'channel-123',
|
|
'root_id': 'root-789',
|
|
'is_crt_enabled': 'true',
|
|
});
|
|
|
|
expect(navServer, 'https://nexo.example.com');
|
|
expect(navRoot, 'root-789');
|
|
});
|
|
|
|
test(
|
|
'opened event with boolean CRT and rootId routes to thread callback',
|
|
() {
|
|
String? navServer;
|
|
String? navRoot;
|
|
plugin.onNavigateToThread = (s, r) {
|
|
navServer = s;
|
|
navRoot = r;
|
|
};
|
|
|
|
plugin.handleNativeEvent({
|
|
'type': PushNotificationType.opened,
|
|
'server_url': 'https://nexo.example.com',
|
|
'channel_id': 'channel-123',
|
|
'root_id': 'root-789',
|
|
'is_crt_enabled': true,
|
|
});
|
|
|
|
expect(navServer, 'https://nexo.example.com');
|
|
expect(navRoot, 'root-789');
|
|
},
|
|
);
|
|
|
|
test(
|
|
'opened event with CRT but empty rootId falls back to channel callback',
|
|
() {
|
|
String? navServer;
|
|
String? navChannel;
|
|
var threadCalled = false;
|
|
plugin.onNavigateToChannel = (s, c) {
|
|
navServer = s;
|
|
navChannel = c;
|
|
};
|
|
plugin.onNavigateToThread = (_, _) => threadCalled = true;
|
|
|
|
plugin.handleNativeEvent({
|
|
'type': PushNotificationType.opened,
|
|
'server_url': 'https://nexo.example.com',
|
|
'channel_id': 'channel-123',
|
|
'root_id': '',
|
|
'is_crt_enabled': true,
|
|
});
|
|
|
|
expect(threadCalled, isFalse);
|
|
expect(navServer, 'https://nexo.example.com');
|
|
expect(navChannel, 'channel-123');
|
|
},
|
|
);
|
|
|
|
test('opened event without server_url skips navigation callbacks', () {
|
|
var channelCalled = false;
|
|
var threadCalled = false;
|
|
plugin.onNavigateToChannel = (_, _) => channelCalled = true;
|
|
plugin.onNavigateToThread = (_, _) => threadCalled = true;
|
|
|
|
plugin.handleNativeEvent({
|
|
'type': PushNotificationType.opened,
|
|
'channel_id': 'channel-123',
|
|
});
|
|
|
|
expect(channelCalled, isFalse);
|
|
expect(threadCalled, isFalse);
|
|
});
|
|
|
|
test('every event is forwarded to onNotification stream', () async {
|
|
final future = plugin.onNotification.first;
|
|
|
|
plugin.handleNativeEvent({
|
|
'type': PushNotificationType.clear,
|
|
'channel_id': 'channel-123',
|
|
});
|
|
|
|
final raw = await future;
|
|
expect(raw['type'], PushNotificationType.clear);
|
|
expect(raw['channel_id'], 'channel-123');
|
|
});
|
|
|
|
test(
|
|
'token_refresh event saves formatted device token and invokes callback',
|
|
() async {
|
|
String? channelToken;
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
|
.setMockMethodCallHandler(actionChannel, (call) async {
|
|
if (call.method == 'saveDeviceToken') {
|
|
final args = call.arguments as Map;
|
|
channelToken = args['token'] as String?;
|
|
}
|
|
return null;
|
|
});
|
|
|
|
final completer = Completer<String>();
|
|
plugin.onDeviceTokenReady = completer.complete;
|
|
|
|
plugin.handleNativeEvent({
|
|
'type': PushNotificationType.tokenRefresh,
|
|
'token': 'raw-token',
|
|
});
|
|
|
|
final callbackToken = await completer.future;
|
|
expect(channelToken, 'android_rn-v2:raw-token');
|
|
expect(callbackToken, 'android_rn-v2:raw-token');
|
|
},
|
|
);
|
|
|
|
test(
|
|
'debugSendNativeEventForTesting invokes native debug method',
|
|
() async {
|
|
MethodCall? capturedCall;
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
|
.setMockMethodCallHandler(actionChannel, (call) async {
|
|
capturedCall = call;
|
|
return null;
|
|
});
|
|
|
|
await plugin.debugSendNativeEventForTesting({
|
|
'type': PushNotificationType.opened,
|
|
'server_url': 'https://nexo.example.com',
|
|
'channel_id': 'channel-debug',
|
|
});
|
|
|
|
expect(capturedCall?.method, 'debugSendNativeEvent');
|
|
expect(capturedCall?.arguments, {
|
|
'type': PushNotificationType.opened,
|
|
'server_url': 'https://nexo.example.com',
|
|
'channel_id': 'channel-debug',
|
|
});
|
|
},
|
|
);
|
|
});
|
|
|
|
group('Channel constants', () {
|
|
test('notification and action channel names are preserved', () {
|
|
expect(
|
|
NexoMessagingPlugin.notificationChannelName,
|
|
'com.tokilabs.nexo.messaging/notifications',
|
|
);
|
|
expect(
|
|
NexoMessagingPlugin.actionChannelName,
|
|
'com.tokilabs.nexo.messaging/notification_actions',
|
|
);
|
|
});
|
|
});
|
|
|
|
group('NexoMessagingInitializeOptions', () {
|
|
test('has all default values set to true', () {
|
|
const options = NexoMessagingInitializeOptions();
|
|
expect(options.listenForTokenRefresh, isTrue);
|
|
expect(options.requestNotificationPermission, isTrue);
|
|
expect(options.registerDeviceToken, isTrue);
|
|
});
|
|
|
|
test('can override all fields to false', () {
|
|
const options = NexoMessagingInitializeOptions(
|
|
listenForTokenRefresh: false,
|
|
requestNotificationPermission: false,
|
|
registerDeviceToken: false,
|
|
);
|
|
expect(options.listenForTokenRefresh, isFalse);
|
|
expect(options.requestNotificationPermission, isFalse);
|
|
expect(options.registerDeviceToken, isFalse);
|
|
});
|
|
|
|
test('can override individual fields', () {
|
|
const options1 = NexoMessagingInitializeOptions(
|
|
requestNotificationPermission: false,
|
|
);
|
|
expect(options1.listenForTokenRefresh, isTrue);
|
|
expect(options1.requestNotificationPermission, isFalse);
|
|
expect(options1.registerDeviceToken, isTrue);
|
|
|
|
const options2 = NexoMessagingInitializeOptions(
|
|
listenForTokenRefresh: false,
|
|
registerDeviceToken: false,
|
|
);
|
|
expect(options2.listenForTokenRefresh, isFalse);
|
|
expect(options2.requestNotificationPermission, isTrue);
|
|
expect(options2.registerDeviceToken, isFalse);
|
|
});
|
|
});
|
|
|
|
group('NexoMessagingWebNotificationStatus', () {
|
|
test('normalizes browser permission values', () {
|
|
final granted = NexoMessagingWebNotificationStatus.fromBrowserPermission(
|
|
'granted',
|
|
);
|
|
final denied = NexoMessagingWebNotificationStatus.fromBrowserPermission(
|
|
'denied',
|
|
);
|
|
final defaultPermission =
|
|
NexoMessagingWebNotificationStatus.fromBrowserPermission('default');
|
|
|
|
expect(granted.isSupported, isTrue);
|
|
expect(
|
|
granted.permission,
|
|
NexoMessagingWebNotificationPermission.granted,
|
|
);
|
|
expect(granted.rawPermission, 'granted');
|
|
expect(granted.isGranted, isTrue);
|
|
expect(denied.permission, NexoMessagingWebNotificationPermission.denied);
|
|
expect(denied.isGranted, isFalse);
|
|
expect(
|
|
defaultPermission.permission,
|
|
NexoMessagingWebNotificationPermission.defaultPermission,
|
|
);
|
|
});
|
|
|
|
test('trims and preserves unknown browser permission values', () {
|
|
final status = NexoMessagingWebNotificationStatus.fromBrowserPermission(
|
|
' prompt ',
|
|
);
|
|
|
|
expect(status.isSupported, isTrue);
|
|
expect(status.permission, NexoMessagingWebNotificationPermission.unknown);
|
|
expect(status.rawPermission, ' prompt ');
|
|
expect(status.isGranted, isFalse);
|
|
});
|
|
|
|
test('unsupported status is not granted', () {
|
|
const status = NexoMessagingWebNotificationStatus.unsupported();
|
|
|
|
expect(status.isSupported, isFalse);
|
|
expect(
|
|
status.permission,
|
|
NexoMessagingWebNotificationPermission.unsupported,
|
|
);
|
|
expect(status.rawPermission, isNull);
|
|
expect(status.isGranted, isFalse);
|
|
});
|
|
});
|
|
|
|
group('NexoMessagingWebNotificationDisplayResult', () {
|
|
test('unsupported display result is a no-op failure', () {
|
|
const result = NexoMessagingWebNotificationDisplayResult.unsupported();
|
|
|
|
expect(result.success, isFalse);
|
|
expect(result.isSupported, isFalse);
|
|
expect(result.permissionGranted, isFalse);
|
|
});
|
|
});
|
|
|
|
group('NexoMessagingTokenPrefix', () {
|
|
test('androidV2 has the expected value', () {
|
|
expect(NexoMessagingTokenPrefix.androidV2, 'android_rn-v2');
|
|
});
|
|
|
|
test('appleV2 has the expected value', () {
|
|
expect(NexoMessagingTokenPrefix.appleV2, 'apple_rn-v2');
|
|
});
|
|
|
|
test('webV2 has the expected value', () {
|
|
expect(NexoMessagingTokenPrefix.webV2, 'web_rn-v2');
|
|
});
|
|
});
|
|
|
|
group('normalizeWebForegroundMessage', () {
|
|
test('missing type defaults to PushNotificationType.message', () {
|
|
final result = normalizeWebForegroundMessage({
|
|
'server_url': 'https://nexo.example.com',
|
|
'channel_id': 'ch-1',
|
|
});
|
|
|
|
expect(result['type'], PushNotificationType.message);
|
|
});
|
|
|
|
test('existing type is preserved', () {
|
|
final result = normalizeWebForegroundMessage({
|
|
'type': PushNotificationType.clear,
|
|
});
|
|
|
|
expect(result['type'], PushNotificationType.clear);
|
|
});
|
|
|
|
test('userInteraction defaults to false when absent from payload', () {
|
|
final result = normalizeWebForegroundMessage({});
|
|
|
|
expect(result['userInteraction'], isFalse);
|
|
});
|
|
|
|
test(
|
|
'userInteraction parameter is applied when payload does not set it',
|
|
() {
|
|
final result = normalizeWebForegroundMessage({}, userInteraction: true);
|
|
|
|
expect(result['userInteraction'], isTrue);
|
|
},
|
|
);
|
|
|
|
test('userInteraction in payload overrides parameter', () {
|
|
final result = normalizeWebForegroundMessage({
|
|
'userInteraction': true,
|
|
}, userInteraction: false);
|
|
|
|
expect(result['userInteraction'], isTrue);
|
|
});
|
|
|
|
test(
|
|
'routing fields are preserved for NotificationOpenedEvent.fromMap',
|
|
() {
|
|
final result = normalizeWebForegroundMessage({
|
|
'server_url': 'https://nexo.example.com',
|
|
'channel_id': 'ch-1',
|
|
'root_id': 'root-99',
|
|
'is_crt_enabled': 'true',
|
|
});
|
|
|
|
final event = NotificationOpenedEvent.fromMap(result);
|
|
expect(event.serverUrl, 'https://nexo.example.com');
|
|
expect(event.channelId, 'ch-1');
|
|
expect(event.rootId, 'root-99');
|
|
expect(event.isCRTEnabled, isTrue);
|
|
},
|
|
);
|
|
|
|
test('null routing field values are not included in result', () {
|
|
final result = normalizeWebForegroundMessage({
|
|
'server_url': null,
|
|
'channel_id': 'ch-1',
|
|
});
|
|
|
|
expect(result.containsKey('server_url'), isFalse);
|
|
expect(result['channel_id'], 'ch-1');
|
|
});
|
|
|
|
test('absent routing fields are not included in result', () {
|
|
final result = normalizeWebForegroundMessage({
|
|
'type': PushNotificationType.message,
|
|
});
|
|
|
|
expect(result.containsKey('server_url'), isFalse);
|
|
expect(result.containsKey('channel_id'), isFalse);
|
|
expect(result.containsKey('root_id'), isFalse);
|
|
expect(result.containsKey('is_crt_enabled'), isFalse);
|
|
});
|
|
|
|
test('message stable fields are preserved in normalized result', () {
|
|
final result = normalizeWebForegroundMessage({
|
|
'server_url': 'https://nexo.example.com',
|
|
'channel_id': 'ch-1',
|
|
'server_id': 'srv-99',
|
|
'ack_id': 'ack-42',
|
|
'post_id': 'post-77',
|
|
'signature': 'sig-abc',
|
|
'id_loaded': 'true',
|
|
'data': {'key': 'value'},
|
|
});
|
|
|
|
expect(result['server_url'], 'https://nexo.example.com');
|
|
expect(result['channel_id'], 'ch-1');
|
|
expect(result['server_id'], 'srv-99');
|
|
expect(result['ack_id'], 'ack-42');
|
|
expect(result['post_id'], 'post-77');
|
|
expect(result['signature'], 'sig-abc');
|
|
expect(result['id_loaded'], 'true');
|
|
expect(result['data'], {'key': 'value'});
|
|
});
|
|
|
|
test('unknown extra fields from payload are preserved', () {
|
|
final result = normalizeWebForegroundMessage({
|
|
'custom_field': 'custom_value',
|
|
'another': 123,
|
|
});
|
|
|
|
expect(result['custom_field'], 'custom_value');
|
|
expect(result['another'], 123);
|
|
});
|
|
});
|
|
|
|
group('showWebForegroundNotification fake interop', () {
|
|
late NexoMessagingPlugin plugin;
|
|
late FakeBrowserNotificationInterop fake;
|
|
|
|
void setUpWith({
|
|
bool isSupported = true,
|
|
String perm = 'granted',
|
|
bool showReturnValue = true,
|
|
String? permissionAfterShow,
|
|
}) {
|
|
final ac = MethodChannel(NexoMessagingPlugin.actionChannelName);
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
|
.setMockMethodCallHandler(ac, (call) async => null);
|
|
fake = FakeBrowserNotificationInterop(
|
|
isSupportedParam: isSupported,
|
|
permissionParam: perm,
|
|
showReturnValue: showReturnValue,
|
|
permissionAfterShow: permissionAfterShow,
|
|
);
|
|
plugin = NexoMessagingPlugin.instance;
|
|
plugin.resetForTesting();
|
|
plugin.interopForTesting = fake;
|
|
plugin.onNavigateToChannel = null;
|
|
plugin.onNavigateToThread = null;
|
|
plugin.onDeviceTokenReady = null;
|
|
}
|
|
|
|
setUp(() => setUpWith());
|
|
tearDown(() {
|
|
final ac = MethodChannel(NexoMessagingPlugin.actionChannelName);
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
|
.setMockMethodCallHandler(ac, null);
|
|
});
|
|
|
|
test('unsupported platform returns unsupported result', () {
|
|
setUpWith(isSupported: false);
|
|
final r = plugin.showWebForegroundNotification({'title': 'H'});
|
|
expect(r.isSupported, isFalse);
|
|
expect(r.success, isFalse);
|
|
expect(r.permissionGranted, isFalse);
|
|
});
|
|
|
|
test('denied permission returns no-op result', () {
|
|
setUpWith(isSupported: true, perm: 'denied');
|
|
final r = plugin.showWebForegroundNotification({'title': 'H'});
|
|
expect(r.isSupported, isTrue);
|
|
expect(r.success, isFalse);
|
|
expect(r.permissionGranted, isFalse);
|
|
});
|
|
|
|
test('granted triggers interop.show()', () {
|
|
setUpWith(isSupported: true, perm: 'granted');
|
|
final r = plugin.showWebForegroundNotification({
|
|
'title': 'T',
|
|
'body': 'B',
|
|
'tag': 'X',
|
|
});
|
|
expect(r.success, isTrue);
|
|
expect(fake.showCalled, isTrue);
|
|
expect(fake.lastShowOptions!['title'], 'T');
|
|
expect(fake.lastShowOptions!['body'], 'B');
|
|
});
|
|
|
|
test('show failure reads latest permission before returning', () {
|
|
setUpWith(
|
|
isSupported: true,
|
|
perm: 'granted',
|
|
showReturnValue: false,
|
|
permissionAfterShow: 'denied',
|
|
);
|
|
|
|
final r = plugin.showWebForegroundNotification({'title': 'H'});
|
|
|
|
expect(r.success, isFalse);
|
|
expect(r.isSupported, isTrue);
|
|
expect(r.permissionGranted, isFalse);
|
|
});
|
|
|
|
test('interop.show() receives routing fields', () {
|
|
setUpWith();
|
|
plugin.showWebForegroundNotification({
|
|
'title': 'T',
|
|
'server_url': 'https://x.com',
|
|
'channel_id': 'c1',
|
|
'root_id': 'r1',
|
|
'is_crt_enabled': 'true',
|
|
});
|
|
final opts = fake.lastShowOptions!;
|
|
expect(opts['server_url'], 'https://x.com');
|
|
expect(opts['channel_id'], 'c1');
|
|
expect(opts['root_id'], 'r1');
|
|
expect(opts['is_crt_enabled'], 'true');
|
|
});
|
|
|
|
test('click routes onNotificationOpened + onNavigateToChannel', () {
|
|
setUpWith();
|
|
String? srv, ch;
|
|
plugin.onNavigateToChannel = (s, c) {
|
|
srv = s;
|
|
ch = c;
|
|
};
|
|
plugin.showWebForegroundNotification({
|
|
'title': 'C',
|
|
'server_url': 'https://x.com',
|
|
'channel_id': 'abc',
|
|
});
|
|
fake.triggerClick({'server_url': 'https://x.com', 'channel_id': 'abc'});
|
|
expect(srv, 'https://x.com');
|
|
expect(ch, 'abc');
|
|
});
|
|
|
|
test('click routes onNavigateToThread when CRT', () {
|
|
setUpWith();
|
|
String? srv, rt;
|
|
plugin.onNavigateToThread = (s, r) {
|
|
srv = s;
|
|
rt = r;
|
|
};
|
|
plugin.showWebForegroundNotification({
|
|
'title': 'CRT',
|
|
'server_url': 'https://x.com',
|
|
'channel_id': 'ch1',
|
|
'root_id': 'root-99',
|
|
'is_crt_enabled': 'true',
|
|
});
|
|
fake.triggerClick({
|
|
'server_url': 'https://x.com',
|
|
'channel_id': 'ch1',
|
|
'root_id': 'root-99',
|
|
'is_crt_enabled': 'true',
|
|
});
|
|
expect(srv, 'https://x.com');
|
|
expect(rt, 'root-99');
|
|
});
|
|
|
|
test('click emits opened event on stream', () async {
|
|
setUpWith();
|
|
final fut = plugin.onNotificationOpened.first;
|
|
plugin.showWebForegroundNotification({
|
|
'title': 'S',
|
|
'server_url': 'https://x.com',
|
|
'channel_id': 'cstream',
|
|
});
|
|
fake.triggerClick({
|
|
'server_url': 'https://x.com',
|
|
'channel_id': 'cstream',
|
|
});
|
|
final e = await fut;
|
|
expect(e.serverUrl, 'https://x.com');
|
|
expect(e.channelId, 'cstream');
|
|
});
|
|
|
|
test('display forwards to onNotification stream', () async {
|
|
setUpWith();
|
|
final fut = plugin.onNotification.first;
|
|
plugin.showWebForegroundNotification({
|
|
'title': 'P',
|
|
'custom_field': 'cv',
|
|
});
|
|
final raw = await fut;
|
|
expect(raw['type'], PushNotificationType.message);
|
|
expect(raw['custom_field'], 'cv');
|
|
});
|
|
});
|
|
}
|