diff --git a/lib/core/config/app_config.dart b/lib/core/config/app_config.dart index f42a67e..0727c39 100644 --- a/lib/core/config/app_config.dart +++ b/lib/core/config/app_config.dart @@ -1,173 +1,51 @@ // Author: fruit.com.cn -import 'dart:io'; +import 'dev_config.dart'; +import 'prod_config.dart'; -import 'package:flutter/foundation.dart'; - -/// 客户端与后端约定的运行时配置。 +/// 客户端运行时配置。 /// -/// 接口地址优先级:运行时设置 > `--dart-define=API_BASE_URL=...` > 平台默认值。 -/// 开发机的局域网地址会随网络变化,换网络后改 [devHostBaseUrl] 即可。 -class AppConfig { - const AppConfig._(); - - /// 开发调试默认服务地址,当前指向本机局域网 IP。 +/// 两套环境各一个文件: +/// - `dev_config.dart`:[DevConfig](本机联调) +/// - `prod_config.dart`:[ProdConfig](生产) +/// +/// 切换环境只改下面这一个布尔值,不用传任何构建参数、也不做网络探测。 +abstract final class AppConfig { + /// **环境开关**:true 用开发环境,false 用生产环境。 /// - /// 换网络时只改这一行,地址不要带结尾斜杠。 - static const String devHostBaseUrl = 'http://192.168.31.243:8081'; + /// 打生产包前必须改成 false;release 构建若仍为 true 会在启动时直接报错。 + static const bool useDevConfig = true; - /// 本机后端地址:通过 `adb reverse tcp:8081 tcp:8081` 映射到开发机, - /// 探测可达时 App 直连本机服务;不可达时回落到公网地址。 - static const String lanBaseUrl = 'http://127.0.0.1:8081'; - - /// 公网地址(远程/移动网络访问时使用)。 - static const String publicBaseUrl = 'http://39.162.50.99:8081'; - - static const String _overrideBaseUrl = String.fromEnvironment('API_BASE_URL'); + /// 当前选中的环境地址。 + static String get baseUrl => + useDevConfig ? DevConfig.baseUrl : ProdConfig.baseUrl; + /// 调试面板手改的地址(见 `ServerEndpoint`);为空表示用上面的环境地址。 static String _runtimeBaseUrl = ''; + /// 启动期异步准备(iOS 无线数据权限弹窗),目录请求前等待它。 static Future? _ready; - /// 启动地址探测完成信号;默认已完成,避免影响既有测试与调用方。 + /// 启动准备完成信号;默认已完成,避免影响既有测试与调用方。 static Future get ready => _ready ?? Future.value(); static set ready(Future value) => _ready = value; - static const Duration _probeTimeout = Duration(seconds: 2); - static const List _probeCandidates = [ - lanBaseUrl, - devHostBaseUrl, - if (devHostBaseUrl != publicBaseUrl) publicBaseUrl, - ]; + static const Duration requestTimeout = Duration(seconds: 8); - /// Build-time server URL supplied through `--dart-define=API_BASE_URL`. - static String get compiledBaseUrl => _overrideBaseUrl; + /// 实际使用的服务地址:运行时手改优先,否则用环境开关选中的地址。 + static String get apiBaseUrl => + _runtimeBaseUrl.isNotEmpty ? _runtimeBaseUrl : baseUrl; - /// Existing platform-aware fallback used when no runtime or build override exists. - static String get defaultBaseUrl => _defaultBaseUrl; - - /// Effective server URL, including the debug-only runtime override. - static String get apiBaseUrl => _runtimeBaseUrl.isNotEmpty - ? _runtimeBaseUrl - : (compiledBaseUrl.isNotEmpty ? compiledBaseUrl : defaultBaseUrl); - - /// 生产包固定走公网地址;仅 debug 才按平台默认到环回/开发机。 - static final String _defaultBaseUrl = kReleaseMode - ? publicBaseUrl - : (_runsOnDevMachine - ? lanBaseUrl - : devHostBaseUrl); - - /// 模拟器、桌面端与开发机同机,环回地址可用;iOS 真机不满足。 - /// Android 没有等价的探测方式,统一按真机处理(模拟器用 - /// `--dart-define=API_BASE_URL=http://10.0.2.2:8081` 覆盖)。 - static final bool _runsOnDevMachine = - kIsWeb || - defaultTargetPlatform == TargetPlatform.macOS || - defaultTargetPlatform == TargetPlatform.windows || - defaultTargetPlatform == TargetPlatform.linux || - (defaultTargetPlatform == TargetPlatform.iOS && - Platform.environment.containsKey('SIMULATOR_DEVICE_NAME')); - - /// Applies an already normalized runtime URL. Passing null clears it. + /// 应用一个已经规范化的运行时地址;传 null 表示清除。 static void applyRuntimeBaseUrl(String? value) { _runtimeBaseUrl = value ?? ''; } - /// Clears the in-memory runtime override, mainly for tests and restarts. + /// 清除运行时地址,主要给测试与重启流程用。 static void clearRuntimeBaseUrl() { _runtimeBaseUrl = ''; } - static const Duration requestTimeout = Duration(seconds: 8); - - /// 按环回、开发机、公网顺序探测,写入首个返回 200 的地址。 - /// - /// 全部不可达时使用平台默认地址,真机保留开发机地址,避免退回公网。 - static Future probeAndApplyBaseUrl({ - Future Function(String baseUrl)? probe, - String? fallbackBaseUrl, - - /// 编译期地址测试注入;未传时读取 `API_BASE_URL`。 - String? compiledOverride, - }) async { - final compiled = compiledOverride ?? compiledBaseUrl; - if (compiled.isNotEmpty) { - if (_runtimeBaseUrl.isEmpty) applyRuntimeBaseUrl(compiled); - return; - } - // 生产包固定公网地址,不再探测环回/开发机局域网地址,避免请求到开发机。 - if (kReleaseMode) { - if (_runtimeBaseUrl.isEmpty) applyRuntimeBaseUrl(publicBaseUrl); - return; - } - final candidates = [ - if (_runtimeBaseUrl.isNotEmpty) _runtimeBaseUrl, - for (final candidate in _probeCandidates) - if (candidate != _runtimeBaseUrl) candidate, - ]; - final results = await Future.wait([ - for (final candidate in candidates) - probe == null - ? _probeBaseUrl(candidate) - : _runInjectedProbe(candidate, probe), - ]); - for (var index = 0; index < candidates.length; index++) { - final candidate = candidates[index]; - final result = results[index]; - if (kDebugMode) { - final status = result.ok - ? 'ok' - : 'fail(${result.reason ?? 'request failed'})'; - debugPrint('[probe] $candidate -> $status'); - } - if (result.ok) { - applyRuntimeBaseUrl(candidate); - return; - } - } - applyRuntimeBaseUrl(fallbackBaseUrl ?? defaultBaseUrl); - } - - static Future<_ProbeResult> _runInjectedProbe( - String baseUrl, - Future Function(String baseUrl) probe, - ) async { - try { - return _ProbeResult(ok: await probe(baseUrl)); - } catch (error) { - return _ProbeResult(ok: false, reason: error.toString()); - } - } - - static Future<_ProbeResult> _probeBaseUrl(String baseUrl) async { - final client = HttpClient()..connectionTimeout = _probeTimeout; - try { - return await _requestCategories( - client, - Uri.parse('$baseUrl/app/categories'), - ).timeout(_probeTimeout); - } catch (error) { - return _ProbeResult(ok: false, reason: error.toString()); - } finally { - client.close(force: true); - } - } - - static Future<_ProbeResult> _requestCategories( - HttpClient client, - Uri uri, - ) async { - final request = await client.getUrl(uri); - final response = await request.close(); - await response.drain(); - final ok = response.statusCode == 200; - return _ProbeResult( - ok: ok, - reason: ok ? null : 'HTTP ${response.statusCode}', - ); - } - /// 后端返回的是相对路径(/files/...),这里补上服务地址。 static String mediaUrl(String path) { if (path.isEmpty) return ''; @@ -175,10 +53,3 @@ class AppConfig { return '$apiBaseUrl$path'; } } - -class _ProbeResult { - const _ProbeResult({required this.ok, this.reason}); - - final bool ok; - final String? reason; -} diff --git a/lib/core/config/dev_config.dart b/lib/core/config/dev_config.dart new file mode 100644 index 0000000..1cbd306 --- /dev/null +++ b/lib/core/config/dev_config.dart @@ -0,0 +1,9 @@ +// Author: fruit.com.cn + +/// 开发环境配置。 +/// +/// 本机联调用:换 Wi-Fi、换网段后只改这里的 [baseUrl],地址不要带结尾斜杠。 +abstract final class DevConfig { + /// 开发机后端地址。 + static const String baseUrl = 'http://192.168.31.243:8081'; +} diff --git a/lib/core/config/prod_config.dart b/lib/core/config/prod_config.dart new file mode 100644 index 0000000..4eebfc8 --- /dev/null +++ b/lib/core/config/prod_config.dart @@ -0,0 +1,9 @@ +// Author: fruit.com.cn + +/// 生产环境配置。 +/// +/// 发版前确认这里的 [baseUrl],地址不要带结尾斜杠。 +abstract final class ProdConfig { + /// 生产后端地址。 + static const String baseUrl = 'http://39.162.50.99:8081'; +} diff --git a/lib/core/config/server_endpoint.dart b/lib/core/config/server_endpoint.dart index b5c6b5f..3ec6161 100644 --- a/lib/core/config/server_endpoint.dart +++ b/lib/core/config/server_endpoint.dart @@ -22,16 +22,9 @@ abstract final class ServerEndpoint { return value.replaceFirst(RegExp(r'/+$'), ''); } - /// Resolves runtime > dart-define > default using normalized valid values. - static String resolve({ - required String? stored, - required String compiled, - required String fallback, - }) { - return normalize(stored) ?? - normalize(compiled) ?? - normalize(fallback) ?? - fallback; + /// 运行时手改优先,其次构建期默认地址;两者都必须先通过规范校验。 + static String resolve({required String? stored, required String fallback}) { + return normalize(stored) ?? normalize(fallback) ?? fallback; } /// Loads the persisted override before the app creates its ApiClient. @@ -46,11 +39,7 @@ abstract final class ServerEndpoint { final prefs = preferences ?? SharedPreferencesAsync(); final stored = await prefs.getString(preferenceKey); AppConfig.applyRuntimeBaseUrl( - resolve( - stored: stored, - compiled: AppConfig.compiledBaseUrl, - fallback: AppConfig.defaultBaseUrl, - ), + resolve(stored: stored, fallback: AppConfig.baseUrl), ); } catch (_) { AppConfig.clearRuntimeBaseUrl(); diff --git a/lib/core/data/services/catalog_retry.dart b/lib/core/data/services/catalog_retry.dart deleted file mode 100644 index df7b809..0000000 --- a/lib/core/data/services/catalog_retry.dart +++ /dev/null @@ -1,12 +0,0 @@ -// Author: fruit.com.cn - -import '../../config/app_config.dart'; - -/// 重新探测服务地址后再加载指定目录数据。 -Future retryCatalogLoad({ - required Future Function() reload, - Future Function()? probe, -}) async { - await (probe ?? AppConfig.probeAndApplyBaseUrl)(); - await reload(); -} diff --git a/lib/core/widgets/catalog_endpoint_editor.dart b/lib/core/widgets/catalog_endpoint_editor.dart index cafd56c..2a377ac 100644 --- a/lib/core/widgets/catalog_endpoint_editor.dart +++ b/lib/core/widgets/catalog_endpoint_editor.dart @@ -15,12 +15,11 @@ const String _invalid = '请输入合法地址,例如 http://192.168.1.10:8081 const String _saveFailed = '服务地址保存失败'; const String _localNetworkHint = '手机需与电脑同一 Wi-Fi,并在 iOS 设置-隐私与安全性-本地网络 中允许本应用'; -/// Debug 下编辑服务地址,保存后重新探测并加载目录。 +/// Debug 下编辑服务地址,保存后立即按新地址重新加载目录。 Future showCatalogEndpointEditor( BuildContext context, { required Future Function() reload, Future Function(String value)? save, - Future Function()? probe, }) async { if (!kDebugMode) return; final value = await showDialog( @@ -37,7 +36,6 @@ Future showCatalogEndpointEditor( } return; } - await (probe ?? () => AppConfig.probeAndApplyBaseUrl())(); await reload(); } diff --git a/lib/main.dart b/lib/main.dart index 933dcc9..f2156de 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,10 +13,14 @@ import 'core/platform/wireless_data_prompt.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); + // 生产包不能连开发机:忘了改开关时宁可启动即失败。 + if (kReleaseMode && AppConfig.useDevConfig) { + throw StateError('release 构建请把 AppConfig.useDevConfig 改成 false'); + } await ServerEndpoint.load(); AppConfig.ready = WirelessDataPrompt.awaitDecision( enabled: !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS, - ).then((_) => AppConfig.probeAndApplyBaseUrl()); + ); runApp(const LumaDramaApp()); unawaited( SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]), diff --git a/lib/pages/discover/discover_view.dart b/lib/pages/discover/discover_view.dart index 176be60..c32bf0b 100644 --- a/lib/pages/discover/discover_view.dart +++ b/lib/pages/discover/discover_view.dart @@ -10,7 +10,6 @@ import 'package:provider/provider.dart'; import '../../core/data/models/drama.dart'; import '../../core/data/repositories/catalog_repository.dart'; import '../../core/data/repositories/search_repository.dart'; -import '../../core/data/services/catalog_retry.dart'; import '../../core/data/services/category_controller.dart'; import '../../core/data/services/theater_feed_controller.dart'; import '../../core/formatting/error_text.dart'; @@ -408,15 +407,11 @@ class _DiscoverPageState extends State { ); Future _retryCategories(CategoryController categories) { - return retryCatalogLoad(reload: categories.load); + return categories.load(); } - Future _retryTheater() async { - await retryCatalogLoad( - reload: () async { - await theater.refreshCurrent(); - }, - ); + Future _retryTheater() { + return theater.refreshCurrent(); } Future _editTheaterEndpoint() { diff --git a/lib/pages/home/home_view.dart b/lib/pages/home/home_view.dart index b6dc824..6cdb3ff 100644 --- a/lib/pages/home/home_view.dart +++ b/lib/pages/home/home_view.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; -import '../../core/config/app_config.dart'; import '../../core/data/remote/catalog_api.dart'; import '../../core/data/services/home_feed_controller.dart'; import '../../core/data/services/catalog_controller.dart'; @@ -67,7 +66,7 @@ class _HomePageState extends State with WidgetsBindingObserver { ) async { _resumeRetryInFlight = true; try { - await AppConfig.probeAndApplyBaseUrl(); + // 地址是构建期常量,回前台只需要重试失败的数据请求。 await Future.wait([ if (catalog.error != null) catalog.load(), if (categories.error != null) categories.load(), @@ -80,10 +79,7 @@ class _HomePageState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { final catalog = context.watch(); - return Theme( - data: AppTheme.dark(), - child: _buildHome(context, catalog), - ); + return Theme(data: AppTheme.dark(), child: _buildHome(context, catalog)); } Widget _buildHome(BuildContext context, CatalogController catalog) { diff --git a/test/config/server_endpoint_test.dart b/test/config/server_endpoint_test.dart index 379fd13..5349d2a 100644 --- a/test/config/server_endpoint_test.dart +++ b/test/config/server_endpoint_test.dart @@ -1,4 +1,4 @@ -import 'dart:async'; +// Author: fruit.com.cn import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -6,6 +6,8 @@ import 'package:shared_preferences_platform_interface/in_memory_shared_preferenc import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; import 'package:fruitshorts/core/config/app_config.dart'; +import 'package:fruitshorts/core/config/dev_config.dart'; +import 'package:fruitshorts/core/config/prod_config.dart'; import 'package:fruitshorts/core/config/server_endpoint.dart'; void main() { @@ -35,11 +37,10 @@ void main() { expect(ServerEndpoint.normalize('http://'), isNull); }); - test('should_prioritize_runtime_over_dart_define_over_default', () { + test('should_prefer_valid_stored_url_over_build_default', () { expect( ServerEndpoint.resolve( stored: ' http://runtime.test:8081/ ', - compiled: 'http://define.test:8081', fallback: 'http://default.test:8081', ), 'http://runtime.test:8081', @@ -47,173 +48,57 @@ void main() { expect( ServerEndpoint.resolve( stored: 'ftp://invalid', - compiled: 'http://define.test:8081/', - fallback: 'http://default.test:8081', + fallback: 'http://default.test:8081/', ), - 'http://define.test:8081', + 'http://default.test:8081', ); expect( ServerEndpoint.resolve( stored: null, - compiled: '', - fallback: 'http://default.test:8081/', + fallback: 'http://default.test:8081', ), 'http://default.test:8081', ); }); - test('should_use_the_configured_local_address_for_device_default', () { - final uri = Uri.parse(AppConfig.devHostBaseUrl); - expect(uri.scheme, 'http'); - expect(uri.host, isNotEmpty); - expect(uri.port, 8081); - expect(uri.path, isEmpty); + test('should_pick_address_by_environment_switch', () { + // 仓库里的开关默认指向开发环境。 + expect(AppConfig.useDevConfig, isTrue); + expect(AppConfig.baseUrl, DevConfig.baseUrl); + expect(AppConfig.apiBaseUrl, DevConfig.baseUrl); + expect(Uri.parse(AppConfig.baseUrl).port, 8081); }); - test('should_skip_probe_when_compiled_override_is_set', () async { - const compiled = 'http://compiled.test:8081'; - var probeCalls = 0; + test('should_keep_dev_and_prod_addresses_in_separate_files', () { + final dev = Uri.parse(DevConfig.baseUrl); + final prod = Uri.parse(ProdConfig.baseUrl); - await AppConfig.probeAndApplyBaseUrl( - compiledOverride: compiled, - probe: (_) async { - probeCalls++; - return true; - }, - ); - - expect(probeCalls, 0); - expect(AppConfig.apiBaseUrl, compiled); + expect(dev.scheme, 'http'); + expect(dev.port, 8081); + expect(dev.path, isEmpty); + expect(prod.scheme, 'http'); + expect(prod.port, 8081); + expect(prod.path, isEmpty); + expect(DevConfig.baseUrl, isNot(ProdConfig.baseUrl)); }); - test('should_keep_runtime_endpoint_when_compiled_override_is_set', () async { - const runtime = 'http://runtime.test:8081'; - const compiled = 'http://compiled.test:8081'; - var probeCalls = 0; - AppConfig.applyRuntimeBaseUrl(runtime); + test('should_prefer_runtime_override_over_environment_switch', () { + AppConfig.applyRuntimeBaseUrl('http://runtime.test:8081'); - await AppConfig.probeAndApplyBaseUrl( - compiledOverride: compiled, - probe: (_) async { - probeCalls++; - return true; - }, - ); - - expect(probeCalls, 0); - expect(AppConfig.apiBaseUrl, runtime); + expect(AppConfig.apiBaseUrl, 'http://runtime.test:8081'); + expect(AppConfig.baseUrl, DevConfig.baseUrl); }); - test('should_keep_probing_when_compiled_override_is_empty', () async { - final attempts = []; - - await AppConfig.probeAndApplyBaseUrl( - compiledOverride: '', - probe: (baseUrl) async { - attempts.add(baseUrl); - return baseUrl == AppConfig.publicBaseUrl; - }, + test('should_append_host_to_relative_media_paths', () { + expect(AppConfig.mediaUrl(''), ''); + expect( + AppConfig.mediaUrl('https://cdn.test/a.mp4'), + 'https://cdn.test/a.mp4', ); - - expect(attempts, [ - AppConfig.lanBaseUrl, - AppConfig.devHostBaseUrl, - AppConfig.publicBaseUrl, - ]); - expect(AppConfig.apiBaseUrl, AppConfig.publicBaseUrl); - }); - - test( - 'should_probe_all_candidates_in_parallel_before_applying_priority', - () async { - final attempts = []; - var active = 0; - var maxActive = 0; - final allStarted = Completer(); - - final probing = AppConfig.probeAndApplyBaseUrl( - fallbackBaseUrl: AppConfig.devHostBaseUrl, - probe: (baseUrl) async { - attempts.add(baseUrl); - active++; - if (active > maxActive) maxActive = active; - if (active == 3) allStarted.complete(); - await allStarted.future; - active--; - return baseUrl == AppConfig.lanBaseUrl || - baseUrl == AppConfig.devHostBaseUrl; - }, - ); - - await probing.timeout(const Duration(seconds: 1)); - - expect(attempts, [ - AppConfig.lanBaseUrl, - AppConfig.devHostBaseUrl, - AppConfig.publicBaseUrl, - ]); - expect(maxActive, 3); - expect(AppConfig.apiBaseUrl, AppConfig.lanBaseUrl); - }, - ); - - test('should_probe_loopback_then_public_in_order', () async { - final attempts = []; - - await AppConfig.probeAndApplyBaseUrl( - fallbackBaseUrl: AppConfig.devHostBaseUrl, - probe: (baseUrl) async { - attempts.add(baseUrl); - return baseUrl == AppConfig.publicBaseUrl; - }, + expect( + AppConfig.mediaUrl('/files/a.mp4'), + '${AppConfig.apiBaseUrl}/files/a.mp4', ); - - expect(attempts, [ - AppConfig.lanBaseUrl, - AppConfig.devHostBaseUrl, - AppConfig.publicBaseUrl, - ]); - expect(AppConfig.apiBaseUrl, AppConfig.publicBaseUrl); - }); - - test('should_use_platform_default_when_all_probes_fail', () async { - final attempts = []; - - await AppConfig.probeAndApplyBaseUrl( - fallbackBaseUrl: AppConfig.devHostBaseUrl, - probe: (baseUrl) async { - attempts.add(baseUrl); - return false; - }, - ); - - expect(attempts, [ - AppConfig.lanBaseUrl, - AppConfig.devHostBaseUrl, - AppConfig.publicBaseUrl, - ]); - expect(AppConfig.apiBaseUrl, AppConfig.devHostBaseUrl); - }); - - test('should_probe_explicit_runtime_endpoint_first', () async { - const custom = 'http://custom.test:8081'; - final attempts = []; - AppConfig.applyRuntimeBaseUrl(custom); - - await AppConfig.probeAndApplyBaseUrl( - probe: (baseUrl) async { - attempts.add(baseUrl); - return baseUrl == custom; - }, - ); - - expect(attempts, [ - custom, - AppConfig.lanBaseUrl, - AppConfig.devHostBaseUrl, - AppConfig.publicBaseUrl, - ]); - expect(AppConfig.apiBaseUrl, custom); }); test('should_apply_stored_url_only_when_debug_is_enabled', () async { diff --git a/test/core/api_client_test.dart b/test/core/api_client_test.dart index b8f12c1..0974a04 100644 --- a/test/core/api_client_test.dart +++ b/test/core/api_client_test.dart @@ -136,19 +136,19 @@ void main() { }); test( - 'should_use_probed_endpoint_when_client_was_created_before_probe', + 'should_follow_runtime_endpoint_applied_after_client_creation', () async { - const probedBaseUrl = 'http://probed.test:8081'; + const runtimeBaseUrl = 'http://runtime.test:8081'; final adapter = FakeHttpAdapter( (_) async => FakeHttpResponse(envelope(null)), ); final client = ApiClient(adapter: adapter); - AppConfig.applyRuntimeBaseUrl(probedBaseUrl); + AppConfig.applyRuntimeBaseUrl(runtimeBaseUrl); addTearDown(AppConfig.clearRuntimeBaseUrl); await client.get('/app/home/feed'); - expect(adapter.requests.single.uri.origin, probedBaseUrl); + expect(adapter.requests.single.uri.origin, runtimeBaseUrl); }, ); } diff --git a/test/home/catalog_diagnostics_test.dart b/test/home/catalog_diagnostics_test.dart index 80be4fa..368d586 100644 --- a/test/home/catalog_diagnostics_test.dart +++ b/test/home/catalog_diagnostics_test.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/core/config/app_config.dart'; -import 'package:fruitshorts/core/data/services/catalog_retry.dart'; import 'package:fruitshorts/core/theme/app_colors.dart'; import 'package:fruitshorts/core/widgets/catalog_endpoint_editor.dart'; import 'package:fruitshorts/l10n/app_localizations.dart'; @@ -55,20 +54,7 @@ void main() { ); }); - test('should_probe_before_reloading_catalog', () async { - final steps = []; - - await retryCatalogLoad( - probe: () async => steps.add('probe'), - reload: () async => steps.add('load'), - ); - - expect(steps, ['probe', 'load']); - }); - - testWidgets('should_save_probe_and_reload_after_valid_endpoint', ( - tester, - ) async { + testWidgets('should_save_and_reload_after_valid_endpoint', (tester) async { AppConfig.applyRuntimeBaseUrl('http://127.0.0.1:8081'); final steps = []; await _pumpEndpointEditor( @@ -77,7 +63,6 @@ void main() { steps.add('save:$value'); return true; }, - probe: () async => steps.add('probe'), reload: () async => steps.add('load'), ); @@ -101,12 +86,11 @@ void main() { await tester.tap(find.byKey(const Key('catalog-debug-endpoint-save'))); await tester.pumpAndSettle(); - expect(steps, ['save:http://10.0.0.8:8081', 'probe', 'load']); + expect(steps, ['save:http://10.0.0.8:8081', 'load']); }); testWidgets('should_reject_invalid_endpoint_without_saving', (tester) async { var saves = 0; - var probes = 0; var loads = 0; await _pumpEndpointEditor( tester, @@ -114,7 +98,6 @@ void main() { saves++; return true; }, - probe: () async => probes++, reload: () async => loads++, ); @@ -134,7 +117,6 @@ void main() { findsOneWidget, ); expect(saves, 0); - expect(probes, 0); expect(loads, 0); }); } @@ -142,7 +124,6 @@ void main() { Future _pumpEndpointEditor( WidgetTester tester, { required Future Function(String value) save, - required Future Function() probe, required Future Function() reload, }) async { await tester.pumpWidget( @@ -152,12 +133,7 @@ Future _pumpEndpointEditor( body: TextButton( key: const Key('open-endpoint-editor'), onPressed: () => unawaited( - showCatalogEndpointEditor( - context, - save: save, - probe: probe, - reload: reload, - ), + showCatalogEndpointEditor(context, save: save, reload: reload), ), child: const Text('open'), ), diff --git a/test/playback/video_player_port_test.dart b/test/playback/video_player_port_test.dart index d40a7f3..f0d3bc5 100644 --- a/test/playback/video_player_port_test.dart +++ b/test/playback/video_player_port_test.dart @@ -18,9 +18,8 @@ void main() { ); expect(uri.isAbsolute, isTrue); - // Uri.parse().toString() 会把 host 规范成小写,而 devHostBaseUrl 里的 - // Bonjour 名字带大写(jiangdeMacBook-Air.local)—— 比较时统一小写, - // 否则会依赖运行平台(flutter test 里 defaultTargetPlatform 默认是 android) + // Uri.parse().toString() 会把 host 规范成小写,而开发地址可能带大写 + // (Bonjour 名字),比较时统一小写,否则会依赖运行平台。 expect( uri.toString().toLowerCase(), '${AppConfig.apiBaseUrl}/files/episode/202610/clip.mp4'.toLowerCase(),