diff --git a/packages/web_shell_core/test/web_shell_core_test.dart b/packages/web_shell_core/test/web_shell_core_test.dart index 3520e66..d5ff5f9 100644 --- a/packages/web_shell_core/test/web_shell_core_test.dart +++ b/packages/web_shell_core/test/web_shell_core_test.dart @@ -1022,6 +1022,304 @@ void main() { ); }); }); + + group('ShellEnvironment 配置验证', () { + test('字段正确传递到全局环境', () { + expect(_testEnvironment.appName, '测试应用'); + expect(_testEnvironment.appKey, 'test_app'); + expect(_testEnvironment.accentColor, const Color(0xFF3ED37B)); + expect(_testEnvironment.backgroundColor, const Color(0xFFFFFFFF)); + expect(_testEnvironment.textColor, const Color(0xFF1F2937)); + expect(_testEnvironment.mutedTextColor, const Color(0xFF6B7280)); + expect(_testEnvironment.initialUrl, 'example.com/login'); + }); + + test('initialUrl 可选字段缺省时使用默认地址', () { + const noUrlEnv = ShellEnvironment( + appName: '无URL', + appKey: 'no_url', + accentColor: Color(0xFF000000), + backgroundColor: Color(0xFFFFFFFF), + textColor: Color(0xFF000000), + mutedTextColor: Color(0xFF888888), + ); + + expect(noUrlEnv.initialUrl, isNull); + shellCoreTestHooks.initializeEnvironment(noUrlEnv); + expect( + shellCoreTestHooks.initialUrl, + 'http://xszy.lzzneng.com/login.html', + ); + + // 恢复测试环境 + shellCoreTestHooks.initializeEnvironment(_testEnvironment); + }); + + test('https 地址直接使用不做修改', () { + shellCoreTestHooks.initializeEnvironment( + const ShellEnvironment( + appName: 'HTTPS', + appKey: 'https', + accentColor: Color(0xFF000000), + backgroundColor: Color(0xFFFFFFFF), + textColor: Color(0xFF000000), + mutedTextColor: Color(0xFF888888), + initialUrl: 'https://app.example.com/start', + ), + ); + expect( + shellCoreTestHooks.initialUrl, + 'https://app.example.com/start', + ); + expect( + shellCoreTestHooks.initialUri, + Uri.parse('https://app.example.com/start'), + ); + + // 恢复测试环境 + shellCoreTestHooks.initializeEnvironment(_testEnvironment); + }); + }); + + group('Bridge JS 协议格式', () { + test('脚本包含版本号和 app-shell-ready 事件', () { + final script = shellCoreTestHooks.buildAppShellBridgeScript(); + + expect(script, contains('__nativeShellVersion')); + expect(script, contains("'1.0.0'")); + expect(script, contains('app-shell-ready')); + expect(script, contains('isNativeShell: true')); + }); + + test('脚本包含通道守卫和去重逻辑', () { + final script = shellCoreTestHooks.buildAppShellBridgeScript(); + + expect(script, contains('AppShellBridge')); + expect(script, contains("typeof channel.postMessage !== 'function'")); + expect(script, contains('window.AppShell')); + }); + + test('脚本暴露全部 8 个 Action', () { + final script = shellCoreTestHooks.buildAppShellBridgeScript(); + + for (final action in [ + 'pickImage', + 'captureImage', + 'pickFile', + 'openExternal', + 'requestPermissions', + 'reloadPage', + 'goBack', + 'closeApp', + ]) { + expect(script, contains(action), reason: 'Missing action: $action'); + } + }); + + test('bridge 响应包含 requestId/success/data 字段', () async { + final platformController = _FakePlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + final controller = WebViewController.fromPlatform(platformController); + + await shellCoreTestHooks.sendBridgeResponse( + controller, + requestId: 'test-123', + success: true, + data: {'count': 5}, + ); + + expect(platformController.javaScriptCalls, hasLength(1)); + final js = platformController.javaScriptCalls.first; + expect(js, contains('__appShellReceiveResponse')); + expect(js, contains('"requestId":"test-123"')); + expect(js, contains('"success":true')); + expect(js, contains('"count":5')); + }); + + test('bridge 失败响应包含 error 字段', () async { + final platformController = _FakePlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + final controller = WebViewController.fromPlatform(platformController); + + await shellCoreTestHooks.sendBridgeResponse( + controller, + requestId: 'err-456', + success: false, + error: 'Something went wrong', + ); + + final js = platformController.javaScriptCalls.first; + expect(js, contains('"success":false')); + expect(js, contains('"error":"Something went wrong"')); + }); + }); + + group('AndroidCompatibilityPlan describe 格式', () { + test('fallback 策略描述包含必要字段', () { + final fallbackPlan = AndroidCompatibilityPlan.fallback(); + final description = fallbackPlan.describe(); + + expect(description, contains('modes=')); + expect(description, contains('wideViewport=true')); + expect(description, contains('aggressiveRecovery=true')); + }); + + test('现代设备策略不建议激进恢复', () { + final info = AndroidWebViewInfo( + sdkInt: 34, + manufacturer: 'Google', + brand: 'google', + model: 'Pixel Tablet', + webViewVersionName: '120.0.0', + ); + final plan = AndroidCompatibilityPlan.fromInfo(info); + final description = plan.describe(); + + expect(description, contains('texture-layer -> hybrid-composition')); + expect(description, contains('aggressiveRecovery=false')); + }); + + test('F136A 设备策略建议更新 WebView', () { + final info = AndroidWebViewInfo( + sdkInt: 30, + manufacturer: 'Yuanxuan', + brand: 'YX', + model: 'F136A', + webViewVersionName: '109.0.1', + ); + final plan = AndroidCompatibilityPlan.fromInfo(info); + + expect(plan.suggestWebViewUpdate, isTrue); + expect(plan.prefersAggressiveRecovery, isTrue); + expect(plan.renderModes.first, AndroidRenderMode.hybrid); + }); + + test('summary 对空制造商不重复空格', () { + final info = AndroidWebViewInfo( + sdkInt: 30, + manufacturer: '', + brand: '', + model: 'TestDevice', + ); + + expect(info.summary, contains('device=TestDevice')); + expect(info.summary, isNot(contains('device= '))); + }); + + test('summary 对空型号不显示 device 字段', () { + final info = AndroidWebViewInfo( + sdkInt: 30, + manufacturer: '', + brand: '', + model: '', + ); + + expect(info.summary, isNot(contains('device='))); + expect(info.summary, startsWith('sdk=30')); + }); + }); + + group('相机功能边界场景', () { + test('权限拒绝且不带 controller 时静默返回 null', () async { + cameraPermissionGranted = false; + + final result = await shellCoreTestHooks.pickCameraImage(ImagePicker()); + + expect(result, isNull); + }); + + test('权限拒绝且 showPermissionAlert=false 时不弹窗', () async { + cameraPermissionGranted = false; + final platformController = _FakePlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + final controller = WebViewController.fromPlatform(platformController); + + final result = await shellCoreTestHooks.pickCameraImage( + ImagePicker(), + controller: controller, + ); + + expect(result, isNull); + expect(platformController.javaScriptCalls, isEmpty); + }); + + test('相机返回 null 时结果也为 null', () async { + fakeImagePickerPlatform.nextImage = null; + + final result = await shellCoreTestHooks.pickCameraImage(ImagePicker()); + + expect(result, isNull); + }); + }); + + group('多文件序列化', () { + test('多个 XFile 正确序列化为数组', () async { + final tempDirectory = await Directory.systemTemp.createTemp('multi'); + final file1 = File('${tempDirectory.path}/a.png') + ..writeAsBytesSync([1, 2]); + final file2 = File('${tempDirectory.path}/b.jpg') + ..writeAsBytesSync([3, 4]); + addTearDown(() async { + if (tempDirectory.existsSync()) { + await tempDirectory.delete(recursive: true); + } + }); + + final result = await shellCoreTestHooks.serializeXFiles( + [ + XFile(file1.path, name: 'a.png'), + XFile(file2.path, name: 'b.jpg'), + ], + responseType: 'dataUrl', + ); + + expect(result, hasLength(2)); + expect(result[0]['name'], 'a.png'); + expect(result[0]['mimeType'], 'image/png'); + expect(result[1]['name'], 'b.jpg'); + expect(result[1]['mimeType'], 'image/jpeg'); + }); + + test('空文件路径列表转换为空 URI 列表', () { + final uris = shellCoreTestHooks.xFilesToUriStrings([]); + expect(uris, isEmpty); + }); + + test('XFile 路径正确转换为 file:// URI', () { + final uris = shellCoreTestHooks.xFilesToUriStrings([ + XFile('/tmp/a.txt'), + XFile('/tmp/b.png'), + ]); + + expect(uris, hasLength(2)); + expect(uris[0], 'file:///tmp/a.txt'); + expect(uris[1], 'file:///tmp/b.png'); + }); + }); + + group('看门狗常量验证', () { + test('旧相机兼容脚本包含 legacy 标记和重试逻辑', () { + final script = shellCoreTestHooks.legacyCameraCompatScript; + + expect(script, contains('__appShellLegacyCameraCompatInstalled')); + expect(script, contains('installLegacyCameraCompat')); + expect(script, contains('wrappedOpenCamera')); + expect(script, contains('capturePhoto')); + expect(script, contains('setInterval')); + }); + + test('WebView 主版本解析能正确处理各种版本格式', () { + expect(shellCoreTestHooks.parseWebViewMajorVersion('83'), 83); + expect(shellCoreTestHooks.parseWebViewMajorVersion('122.0.6261'), 122); + expect( + shellCoreTestHooks.parseWebViewMajorVersion('not-a-version'), + isNull, + ); + }); + }); } class _FakeUrlLauncherPlatform extends UrlLauncherPlatform {