웹 smook 테스트 분리, helper 경로, 수동 증빙 정리를 통해 마일스톤 완료 근거를 milestone 기준으로 반영해 로드맵 상태를 검토중으로 맞췄다.
704 lines
22 KiB
Dart
704 lines
22 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,
|
|
this.permissionParam = 'granted',
|
|
}) : _isSupported = isSupportedParam,
|
|
_permission = permissionParam;
|
|
|
|
final bool _isSupported;
|
|
final String _permission;
|
|
final bool isSupportedParam;
|
|
final String permissionParam;
|
|
|
|
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);
|
|
return true;
|
|
}
|
|
|
|
@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'}) {
|
|
final ac = MethodChannel(NexoMessagingPlugin.actionChannelName);
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
|
.setMockMethodCallHandler(ac, (call) async => null);
|
|
fake = FakeBrowserNotificationInterop(
|
|
isSupportedParam: isSupported,
|
|
permissionParam: perm,
|
|
);
|
|
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('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');
|
|
});
|
|
});
|
|
}
|