test: 测试覆盖率 54→75 用例

新增 7 个测试组:
- ShellEnvironment 配置验证(字段/默认URL/https地址)
- Bridge JS 协议格式(版本号/通道守卫/8 Action/成功响应/失败响应)
- AndroidCompatibilityPlan describe 格式(fallback/现代设备/F136A/空字段)
- 相机边界场景(无 controller/不弹窗/返回 null)
- 多文件序列化(多 XFile/空列表/URI 转换)
- 看门狗常量验证(旧相机脚本/版本解析)
This commit is contained in:
Max 2026-03-19 20:05:43 +08:00
parent 115713d8f4
commit a3b970d2b8
1 changed files with 298 additions and 0 deletions

View File

@ -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 <String>[
'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: <String, Object?>{'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(<int>[1, 2]);
final file2 = File('${tempDirectory.path}/b.jpg')
..writeAsBytesSync(<int>[3, 4]);
addTearDown(() async {
if (tempDirectory.existsSync()) {
await tempDirectory.delete(recursive: true);
}
});
final result = await shellCoreTestHooks.serializeXFiles(
<XFile>[
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(<XFile>[]);
expect(uris, isEmpty);
});
test('XFile 路径正确转换为 file:// URI', () {
final uris = shellCoreTestHooks.xFilesToUriStrings(<XFile>[
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 {