Files
Qlyra/test/webapp_bridge_test.dart
T
sevenhill 50816588e4
Build Android (FCM) / build-android-fcm (push) Canceled after 0s
Build Android / build-android (push) Canceled after 0s
Build iOS / build-ios (push) Canceled after 0s
Build Linux / build-linux (push) Canceled after 0s
Build macOS / build-macos (push) Canceled after 0s
Build Windows / build-windows (push) Canceled after 0s
Release (main) / android (oneme) (push) Canceled after 0s
Release (main) / android (qlyra) (push) Canceled after 0s
Release (main) / windows (push) Canceled after 0s
Release (main) / linux (push) Canceled after 0s
Release (main) / macos (push) Canceled after 0s
Release (main) / ios (push) Canceled after 0s
Release (main) / release (push) Canceled after 0s
Update Qlyra application
2026-08-30 22:53:15 +03:00

201 lines
5.1 KiB
Dart

import 'dart:convert';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:qlyra/frontend/screens/webapp/web_app_bridge.dart';
void main() {
late List<(String, Map<String, dynamic>, bool)> sent;
late int closeCalls;
WebAppBridge buildBridge({
bool privateChannel = false,
String entryPoint = WebAppEntryPoint.webApp,
}) {
return WebAppBridge(
botId: 777,
entryPoint: entryPoint,
privateChannel: privateChannel,
contextResolver: () => null,
viewportResolver: () => const Size(420, 800),
onClose: () => closeCalls++,
emitter: (method, payload, private) => sent.add((
method,
jsonDecode(payload) as Map<String, dynamic>,
private,
)),
);
}
setUp(() {
sent = [];
closeCalls = 0;
});
test('reports the launch context it was created with', () async {
final bridge = buildBridge(entryPoint: WebAppEntryPoint.inlineButton);
await bridge.handleEvent(
'WebAppGetLaunchContext',
'{"requestId":"r1"}',
false,
);
expect(sent, hasLength(1));
expect(sent.first.$1, 'WebAppGetLaunchContext');
expect(sent.first.$2, {'requestId': 'r1', 'entryPoint': 'inline_button'});
});
test('answers viewport requests with the current webview size', () async {
final bridge = buildBridge();
await bridge.handleEvent(
'WebAppGetViewportSize',
'{"requestId":"r2"}',
false,
);
expect(sent.first.$2['width'], 420);
expect(sent.first.$2['height'], 800);
expect(sent.first.$2['isStateStable'], isTrue);
});
test('rejects an unknown method with the client error code', () async {
final bridge = buildBridge();
await bridge.handleEvent(
'WebAppSomethingElse',
'{"requestId":"r3"}',
false,
);
expect(sent.first.$2['error'], {
'code': 'client.unsupported_method.unsupported_method',
});
});
test('stays silent for methods that never get an answer', () async {
final bridge = buildBridge();
await bridge.handleEvent('WebAppReady', '{}', false);
await bridge.handleEvent('WebAppStat', '{}', false);
expect(sent, isEmpty);
});
test('drops gesture-gated methods until the user touches the page', () async {
final bridge = buildBridge();
await bridge.handleEvent(
'WebAppShare',
'{"requestId":"r4","text":"hi"}',
false,
);
for (final method in [
'WebAppRequestPhone',
'WebAppDownloadFile',
'WebAppOpenCodeReader',
'WebAppBiometryRequestAccess',
'WebAppBiometryRequestAuth',
'WebAppBiometryUpdateToken',
]) {
await bridge.handleEvent(method, '{"requestId":"blocked"}', false);
}
expect(sent, isEmpty);
bridge.registerGesture();
await bridge.handleEvent('WebAppShare', '{"requestId":"r5"}', false);
expect(sent.single.$2['error'], {
'code': 'client.web_app_share.invalid_request',
});
});
test('ignores private-channel events when the channel is off', () async {
final bridge = buildBridge();
await bridge.handleEvent(
'WebAppVerifyMobileId',
'{"requestId":"r6","url":"https://example.test/verify"}',
true,
);
expect(sent, isEmpty);
});
test('reports malformed payloads as a decode error', () async {
final bridge = buildBridge();
await bridge.handleEvent('WebAppGetViewportSize', 'not-json', false);
expect(sent, isEmpty);
});
test(
'tracks the back button and closing behaviour the app asked for',
() async {
final bridge = buildBridge();
expect(bridge.handlesBackButton, isFalse);
expect(bridge.needsCloseConfirmation, isFalse);
await bridge.handleEvent(
'WebAppSetupBackButton',
'{"isVisible":true}',
false,
);
await bridge.handleEvent(
'WebAppSetupClosingBehavior',
'{"needConfirmation":true}',
false,
);
expect(bridge.handlesBackButton, isTrue);
expect(bridge.needsCloseConfirmation, isTrue);
bridge.notifyBackPressed();
expect(sent.single.$1, 'WebAppBackButtonPressed');
},
);
test('closes the screen when the app asks to', () async {
final bridge = buildBridge();
await bridge.handleEvent('WebAppClose', '{}', false);
expect(closeCalls, 1);
});
test('echoes the screen capture behaviour back', () async {
final bridge = buildBridge();
await bridge.handleEvent(
'WebAppSetupScreenCaptureBehavior',
'{"requestId":"r7","isScreenCaptureEnabled":true}',
false,
);
expect(sent.first.$2, {'requestId': 'r7', 'isScreenCaptureEnabled': true});
});
test('answers NFC availability without pretending to support it', () async {
final bridge = buildBridge();
await bridge.handleEvent('WebAppNfcGetInfo', '{"requestId":"r8"}', false);
await bridge.handleEvent(
'WebAppNfcEmulateNfcTag',
'{"requestId":"r9"}',
false,
);
expect(sent[0].$2, {
'requestId': 'r8',
'available': false,
'enabled': false,
});
expect(sent[1].$2['error'], {
'code': 'client.nfc_emulate_nfc_tag.not_supported',
});
});
}