From 2bb52178a6fb7dd162695321a997e00e379416f9 Mon Sep 17 00:00:00 2001 From: senzyo Date: Sun, 8 Nov 2026 01:12:24 +0800 Subject: [PATCH 1/3] fix(ui): refresh latency settings summary on return --- lib/pages/advanced/xray/controller.dart | 15 ++ lib/pages/advanced/xray/page.dart | 9 +- test/pages/advanced/xray/dialing_test.dart | 2 +- test/pages/advanced/xray/lan_proxy_test.dart | 2 +- test/pages/advanced/xray/log_test.dart | 2 +- .../pages/advanced/xray/settings_ui_test.dart | 192 ++++++++++++------ 6 files changed, 160 insertions(+), 62 deletions(-) diff --git a/lib/pages/advanced/xray/controller.dart b/lib/pages/advanced/xray/controller.dart index e32b7f881..3677a7d9a 100644 --- a/lib/pages/advanced/xray/controller.dart +++ b/lib/pages/advanced/xray/controller.dart @@ -186,6 +186,21 @@ class XrayRuntimeController extends PageCubit { } } + Future refreshPingSettings() async { + try { + final preferences = PingState(); + await preferences.readFromPreferences(); + emit( + state.copyWith( + pingTimeout: preferences.timeout, + pingUrl: preferences.url, + ), + ); + } catch (error) { + emit(state.copyWith(failed: true, failure: error)); + } + } + void setLog(String key, Object value) { if (busy || state.systemExtension) return; emit(state.copyWith(log: {...log, key: value})); diff --git a/lib/pages/advanced/xray/page.dart b/lib/pages/advanced/xray/page.dart index e4a812a55..c7034f902 100644 --- a/lib/pages/advanced/xray/page.dart +++ b/lib/pages/advanced/xray/page.dart @@ -35,7 +35,7 @@ class XrayRuntimePage extends StatelessWidget { }); final void Function(BuildContext) onGeodata; final void Function(BuildContext) onUpdates; - final void Function(BuildContext) onSpeedTest; + final Future Function(BuildContext) onSpeedTest; final void Function(BuildContext, ConfigFileViewerParams) onConfig; final void Function(BuildContext)? onDiagnostics; final Future Function(BuildContext)? onLogSettings; @@ -180,7 +180,12 @@ class XrayRuntimePage extends StatelessWidget { : _chevron(context), ), enabled: true, - onTap: () => onSpeedTest(context), + onTap: () async { + await onSpeedTest(context); + if (controller.isPageActive) { + await controller.refreshPingSettings(); + } + }, ), if (onServerDialing != null) _pushRow( diff --git a/test/pages/advanced/xray/dialing_test.dart b/test/pages/advanced/xray/dialing_test.dart index 58e31ed26..c9c98d2a5 100644 --- a/test/pages/advanced/xray/dialing_test.dart +++ b/test/pages/advanced/xray/dialing_test.dart @@ -276,7 +276,7 @@ void main() { createController: () => runtime, onGeodata: (_) {}, onUpdates: (_) {}, - onSpeedTest: (_) {}, + onSpeedTest: (_) async {}, onConfig: (_, _) {}, onServerDialing: (context) { opened = true; diff --git a/test/pages/advanced/xray/lan_proxy_test.dart b/test/pages/advanced/xray/lan_proxy_test.dart index 6355c4512..b5f4318cf 100644 --- a/test/pages/advanced/xray/lan_proxy_test.dart +++ b/test/pages/advanced/xray/lan_proxy_test.dart @@ -201,7 +201,7 @@ void main() { createController: () => controller, onGeodata: (_) {}, onUpdates: (_) {}, - onSpeedTest: (_) {}, + onSpeedTest: (_) async {}, onConfig: (_, _) {}, onLanProxy: (_) async {}, ), diff --git a/test/pages/advanced/xray/log_test.dart b/test/pages/advanced/xray/log_test.dart index 11015365a..7a9ea0db1 100644 --- a/test/pages/advanced/xray/log_test.dart +++ b/test/pages/advanced/xray/log_test.dart @@ -61,7 +61,7 @@ void main() { onDiagnostics: (_) {}, onGeodata: (_) {}, onUpdates: (_) {}, - onSpeedTest: (_) {}, + onSpeedTest: (_) async {}, onConfig: (_, _) {}, onLogSettings: (_) async => null, ), diff --git a/test/pages/advanced/xray/settings_ui_test.dart b/test/pages/advanced/xray/settings_ui_test.dart index 839c8e732..a31ae647d 100644 --- a/test/pages/advanced/xray/settings_ui_test.dart +++ b/test/pages/advanced/xray/settings_ui_test.dart @@ -9,8 +9,10 @@ import 'package:onexray/core/constants/preferences.dart'; import 'package:onexray/core/db/database/database.dart'; import 'package:onexray/l10n/localizations/app_localizations.dart'; import 'package:onexray/service/settings/language/locale.dart'; +import 'package:onexray/pages/advanced/xray/controller.dart'; import 'package:onexray/pages/advanced/xray/log/controller.dart'; import 'package:onexray/pages/advanced/xray/log/params.dart'; +import 'package:onexray/pages/advanced/xray/page.dart'; import 'package:onexray/pages/advanced/xray/ping/page.dart'; import 'package:onexray/pages/advanced/xray/ping/controller.dart'; import 'package:onexray/pages/shared/widgets/page_action_bar.dart'; @@ -164,68 +166,144 @@ void main() { }); for (final save in [false, true]) { - testWidgets('ping URL ${save ? 'saves' : 'cancels'} its draft', ( - tester, - ) async { - await (PingState() - ..url = PingUrl.custom - ..customUrl = 'https://example.com/old' - ..timeout = 7) - .saveToPreferences(); - final router = GoRouter( - routes: [ - GoRoute( - path: '/', - builder: (context, _) => Scaffold( - body: TextButton( - onPressed: () => context.push('/ping'), - child: const Text('Open ping settings'), + testWidgets( + 'ping settings ${save ? 'save and refresh' : 'cancel and retain'} the Xray summary', + (tester) async { + await tester.binding.setSurfaceSize(const Size(1160, 900)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + await (PingState() + ..url = PingUrl.google + ..customUrl = 'https://example.com/old' + ..timeout = 5) + .saveToPreferences(); + final db = AppDatabase.forTesting(NativeDatabase.memory()); + final coordinator = ConnectionCoordinator( + database: db, + readRuntime: () async => null, + ); + late XrayRuntimeController root; + await tester.runAsync(() async { + root = XrayRuntimeController(coordinator: coordinator); + await root.stream.firstWhere((state) => !state.loading); + }); + addTearDown( + () => tester.runAsync(() async { + await root.close(); + coordinator.dispose(); + await db.close(); + }), + ); + expect(root.failed, isFalse); + final router = GoRouter( + routes: [ + GoRoute( + path: '/', + builder: (context, _) => XrayRuntimePage( + createController: () => root, + onGeodata: (_) {}, + onUpdates: (_) {}, + onSpeedTest: (context) => context.push('/ping'), + onConfig: (_, _) {}, ), ), + GoRoute(path: '/ping', builder: (_, _) => const PingPage()), + ], + ); + addTearDown(router.dispose); + await tester.pumpWidget( + MaterialApp.router( + theme: AppTheme.light, + locale: const Locale('en'), + localizationsDelegates: AppLocalePolicy.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + routerConfig: router, + builder: (_, child) => ShadTheme( + data: AppTheme.shad(Brightness.light), + child: ShadToaster(child: child!), + ), ), - GoRoute(path: '/ping', builder: (_, _) => const PingPage()), - ], - ); - addTearDown(router.dispose); - await tester.pumpWidget( - MaterialApp.router( - theme: AppTheme.light, - locale: const Locale('en'), - localizationsDelegates: AppLocalePolicy.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - routerConfig: router, - builder: (_, child) => ShadTheme( - data: AppTheme.shad(Brightness.light), - child: ShadToaster(child: child!), - ), - ), - ); - await tester.tap(find.text('Open ping settings')); - await tester.pumpAndSettle(); - await tester.ensureVisible(find.byType(TextField)); - await tester.enterText(find.byType(TextField), 'https://example.com/new'); - await tester.pumpAndSettle(); - final storedDraft = PingState(); - await storedDraft.readFromPreferences(); - expect(storedDraft.customUrl, 'https://example.com/old'); + ); + await tester.pumpAndSettle(); + final l = AppLocalizations.of( + tester.element(find.byType(XrayRuntimePage)), + )!; + final originalSummary = '${l.prototypeSeconds(5)} · Google'; + final savedSummary = + '${l.prototypeSeconds(3)} · ${l.prototypeCustomUrl}'; + expect(find.text(originalSummary), findsOneWidget); + await tester.ensureVisible(find.text(l.prototypeSpeedTest)); + await tester.tap(find.text(l.prototypeSpeedTest)); + await tester.pumpAndSettle(); + await tester.tap(find.byKey(const ValueKey('ping-url-preset'))); + await tester.pumpAndSettle(); + await tester.tap(find.text(l.prototypeCustom).last); + await tester.pumpAndSettle(); + await tester.tap(find.byKey(const ValueKey('ping-timeout'))); + await tester.pumpAndSettle(); + await tester.tap(find.text(l.prototypeSeconds(3)).last); + await tester.pumpAndSettle(); + await tester.ensureVisible(find.byType(TextField)); + await tester.enterText( + find.byType(TextField), + 'https://example.com/new', + ); + await tester.pumpAndSettle(); + final storedDraft = PingState(); + await storedDraft.readFromPreferences(); + expect(storedDraft.customUrl, 'https://example.com/old'); + expect(storedDraft.url, PingUrl.google); + expect(storedDraft.timeout, 5); - await tester.tap(find.text(save ? 'Save' : 'Cancel')); - await tester.pumpAndSettle(); - expect(find.byType(PingPage), findsNothing); - final restored = PingState(); - await restored.readFromPreferences(); - expect(restored.customUrl, 'https://example.com/${save ? 'new' : 'old'}'); - expect(restored.url, PingUrl.custom); - expect(restored.timeout, 7); + await tester.tap(find.text(save ? l.prototypeSave : l.prototypeCancel)); + await tester.pumpAndSettle(); + expect(find.byType(PingPage), findsNothing); + final restored = PingState(); + await restored.readFromPreferences(); + expect( + restored.customUrl, + 'https://example.com/${save ? 'new' : 'old'}', + ); + expect(restored.url, save ? PingUrl.custom : PingUrl.google); + expect(restored.timeout, save ? 3 : 5); + expect( + find.text(save ? savedSummary : originalSummary), + findsOneWidget, + ); - await tester.tap(find.text('Open ping settings')); - await tester.pumpAndSettle(); - expect( - tester.widget(find.byType(TextField)).controller!.text, - restored.customUrl, - ); - expect(tester.takeException(), isNull); - }); + await tester.tap(find.text(l.prototypeSpeedTest)); + await tester.pumpAndSettle(); + expect( + tester + .widget>( + find.byKey(const ValueKey('ping-url-preset')), + ) + .value, + restored.url.name, + ); + expect( + tester + .widget>( + find.byKey(const ValueKey('ping-timeout')), + ) + .value, + restored.timeout, + ); + if (!save) { + await tester.tap(find.byKey(const ValueKey('ping-url-preset'))); + await tester.pumpAndSettle(); + await tester.tap(find.text(l.prototypeCustom).last); + await tester.pumpAndSettle(); + } + expect( + tester.widget(find.byType(TextField)).controller!.text, + restored.customUrl, + ); + expect(tester.takeException(), isNull); + await tester.runAsync(root.close); + await tester.pumpWidget(const SizedBox()); + await tester.pumpAndSettle(); + }, + ); } for (final locale in AppLocalizations.supportedLocales) { From 0b65c3f2a0b970255d7d88b7a78cf19072d9f953 Mon Sep 17 00:00:00 2001 From: yiguo Date: Sun, 8 Nov 2026 11:08:02 +0800 Subject: [PATCH 2/3] test(ui): mock macOS capabilities in latency settings tests --- test/pages/advanced/xray/settings_ui_test.dart | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/pages/advanced/xray/settings_ui_test.dart b/test/pages/advanced/xray/settings_ui_test.dart index a31ae647d..355ed9073 100644 --- a/test/pages/advanced/xray/settings_ui_test.dart +++ b/test/pages/advanced/xray/settings_ui_test.dart @@ -1,12 +1,14 @@ import 'dart:io'; import 'package:drift/native.dart'; +import 'package:flutter/services.dart'; import 'package:material_ui/material_ui.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:go_router/go_router.dart'; import 'package:onexray/core/constants/preferences.dart'; import 'package:onexray/core/db/database/database.dart'; +import 'package:onexray/core/pigeon/messages.g.dart'; import 'package:onexray/l10n/localizations/app_localizations.dart'; import 'package:onexray/service/settings/language/locale.dart'; import 'package:onexray/pages/advanced/xray/controller.dart'; @@ -176,6 +178,21 @@ void main() { ..customUrl = 'https://example.com/old' ..timeout = 5) .saveToPreferences(); + const systemExtensionChannel = BasicMessageChannel( + 'dev.flutter.pigeon.onexray.BridgeHostApi.useSystemExtension', + BridgeHostApi.pigeonChannelCodec, + ); + final messenger = tester.binding.defaultBinaryMessenger; + messenger.setMockDecodedMessageHandler( + systemExtensionChannel, + (_) async => [false], + ); + addTearDown( + () => messenger.setMockDecodedMessageHandler( + systemExtensionChannel, + null, + ), + ); final db = AppDatabase.forTesting(NativeDatabase.memory()); final coordinator = ConnectionCoordinator( database: db, From 7669ea85532ee7c0d8b144239a27a705f1a67927 Mon Sep 17 00:00:00 2001 From: yiguo Date: Sun, 8 Nov 2026 11:08:02 +0800 Subject: [PATCH 3/3] test(dns): align expectations with TCP resolver defaults --- test/pages/connect/routing/custom_ui_test.dart | 2 +- test/pages/connect/routing/options_form_test.dart | 6 +++--- test/service/advanced/local_api/configuration_test.dart | 2 +- test/service/connect/full_config_runtime_test.dart | 6 +++--- test/service/connect/routing/custom/service_test.dart | 3 ++- test/service/connect/routing/custom/state_test.dart | 6 +++--- test/service/shared/share/configuration_transfer_test.dart | 4 ++-- test/service/shared/xray/remnawave_test.dart | 1 + 8 files changed, 16 insertions(+), 14 deletions(-) diff --git a/test/pages/connect/routing/custom_ui_test.dart b/test/pages/connect/routing/custom_ui_test.dart index 0089674e3..8b55a22f2 100644 --- a/test/pages/connect/routing/custom_ui_test.dart +++ b/test/pages/connect/routing/custom_ui_test.dart @@ -396,7 +396,7 @@ void main() { expect(controller.profileState.directDnsAddress, '1.1.1.1'); final storedDns = jsonDecode(controller.previewState!.encode())['dns']; expect(storedDns['servers'], [ - {'tag': 'app-dns-proxy', 'address': '8.8.8.8'}, + {'tag': 'app-dns-proxy', 'address': 'tcp://8.8.8.8'}, {'tag': 'app-dns-direct', 'address': '1.1.1.1'}, {'tag': 'app-dns-fake', 'address': 'fakedns'}, ]); diff --git a/test/pages/connect/routing/options_form_test.dart b/test/pages/connect/routing/options_form_test.dart index 8e6ed3c6e..2f1f26030 100644 --- a/test/pages/connect/routing/options_form_test.dart +++ b/test/pages/connect/routing/options_form_test.dart @@ -172,7 +172,7 @@ void main() { await tester.pumpAndSettle(); expect( value.effectiveProxyResolvers.map((resolver) => resolver.address), - ['8.8.8.8', 'https://dns.example/dns-query', '9.9.9.9'], + ['tcp://8.8.8.8', 'https://dns.example/dns-query', '9.9.9.9'], ); final delete = find.descendant( of: extraFallback, @@ -196,9 +196,9 @@ void main() { final restored = RoutingDnsSettings.fromJson(saved!); expect( restored.effectiveProxyResolvers.map((resolver) => resolver.address), - ['8.8.8.8', 'https://dns.example/dns-query'], + ['tcp://8.8.8.8', 'https://dns.example/dns-query'], ); - expect(restored.directAddress, '8.8.8.8'); + expect(restored.directAddress, 'tcp://8.8.8.8'); expect(restored.sameExitOnly, true); expect(restored.disableCache, true); expect(find.byType(TextField), findsNWidgets(3)); diff --git a/test/service/advanced/local_api/configuration_test.dart b/test/service/advanced/local_api/configuration_test.dart index 7248d523a..c17bd2909 100644 --- a/test/service/advanced/local_api/configuration_test.dart +++ b/test/service/advanced/local_api/configuration_test.dart @@ -523,7 +523,7 @@ void main() { expect(compiled['env']['xray.location.asset'], VpnConstants.datDir); expect(compiled.containsKey('geodata'), false); if (kind == 'advanced-routing') { - expect(compiled['dns']['servers'].single['address'], '8.8.8.8'); + expect(compiled['dns']['servers'].single['address'], 'tcp://8.8.8.8'); } } }, diff --git a/test/service/connect/full_config_runtime_test.dart b/test/service/connect/full_config_runtime_test.dart index 96049cc1d..a0e235440 100644 --- a/test/service/connect/full_config_runtime_test.dart +++ b/test/service/connect/full_config_runtime_test.dart @@ -152,7 +152,7 @@ void main() { final runtime = compiled.config; _fixture('${provider ? 'provider' : 'raw'}-$dnsCase', compiled); expect(runtime['dns']['servers'], [ - for (final address in [_ipv4, _ipv6]) + for (final address in ['tcp://$_ipv4', 'tcp://[$_ipv6]']) { 'address': address, 'tag': VpnConfigAdapter.defaultDnsTag, @@ -357,8 +357,8 @@ void main() { final config = compiled.config; final servers = config['dns']['servers'] as List; expect(servers.map((value) => value['address']).toList(), [ - _ipv4, - if (ipv6) _ipv6, + 'tcp://$_ipv4', + if (ipv6) 'tcp://[$_ipv6]', ]); expect( servers.every( diff --git a/test/service/connect/routing/custom/service_test.dart b/test/service/connect/routing/custom/service_test.dart index e36317293..46cf1f579 100644 --- a/test/service/connect/routing/custom/service_test.dart +++ b/test/service/connect/routing/custom/service_test.dart @@ -129,6 +129,7 @@ void main() { 'tag': 'dnsOut', 'protocol': 'dns', 'settings': { + 'rewriteNetwork': 'tcp', 'rules': [ {'action': 'hijack', 'qType': '1,28'}, {'action': 'direct'}, @@ -224,7 +225,7 @@ void main() { 'disableCache': false, 'disableFallbackIfMatch': false, 'servers': [ - {'tag': 'app-dns-proxy', 'address': '8.8.8.8'}, + {'tag': 'app-dns-proxy', 'address': 'tcp://8.8.8.8'}, {'tag': 'app-dns-direct', 'address': '9.9.9.9'}, ], }); diff --git a/test/service/connect/routing/custom/state_test.dart b/test/service/connect/routing/custom/state_test.dart index d54c7d299..d6aba53ff 100644 --- a/test/service/connect/routing/custom/state_test.dart +++ b/test/service/connect/routing/custom/state_test.dart @@ -403,7 +403,7 @@ void main() { RoutingProfileDocument.parse(jsonEncode(_document())) .state .directDnsAddress, - '8.8.8.8', + 'tcp://8.8.8.8', ); for (final (tag, addresses) in [ ('app-dns-proxy', ['1.1.1.1']), @@ -778,13 +778,13 @@ void _rejectAssets(Object? assets) => _reject({ }); Map _storedDns({ - String direct = '8.8.8.8', + String direct = 'tcp://8.8.8.8', bool fakeDns = false, }) => { 'disableCache': false, 'disableFallbackIfMatch': false, 'servers': [ - {'tag': 'app-dns-proxy', 'address': '8.8.8.8'}, + {'tag': 'app-dns-proxy', 'address': 'tcp://8.8.8.8'}, {'tag': 'app-dns-direct', 'address': direct}, if (fakeDns) {'tag': 'app-dns-fake', 'address': 'fakedns'}, ], diff --git a/test/service/shared/share/configuration_transfer_test.dart b/test/service/shared/share/configuration_transfer_test.dart index 0530b49fc..3b7049811 100644 --- a/test/service/shared/share/configuration_transfer_test.dart +++ b/test/service/shared/share/configuration_transfer_test.dart @@ -178,7 +178,7 @@ void main() { (json['dns']['servers'] as List).singleWhere( (server) => server['tag'] == 'app-dns-proxy', )['address'], - '8.8.8.8', + 'tcp://8.8.8.8', ); expect( (json['routing']['rules'] as List).single['ruleTag'], @@ -312,7 +312,7 @@ void main() { servers.singleWhere( (server) => server['tag'] == 'app-dns-proxy', )['address'], - '8.8.8.8', + 'tcp://8.8.8.8', ); }, ); diff --git a/test/service/shared/xray/remnawave_test.dart b/test/service/shared/xray/remnawave_test.dart index df2330b0e..e1bd7ead8 100644 --- a/test/service/shared/xray/remnawave_test.dart +++ b/test/service/shared/xray/remnawave_test.dart @@ -99,6 +99,7 @@ void main() { 'sockopt': {'dialerProxy': 'app-vpn-dns-direct'}, }, 'settings': { + 'rewriteNetwork': 'tcp', 'rules': [ {'action': 'hijack', 'qType': '1,28'}, {'action': 'direct'},