test: 测试覆盖率 54→75 用例
新增 7 个测试组: - ShellEnvironment 配置验证(字段/默认URL/https地址) - Bridge JS 协议格式(版本号/通道守卫/8 Action/成功响应/失败响应) - AndroidCompatibilityPlan describe 格式(fallback/现代设备/F136A/空字段) - 相机边界场景(无 controller/不弹窗/返回 null) - 多文件序列化(多 XFile/空列表/URI 转换) - 看门狗常量验证(旧相机脚本/版本解析)
This commit is contained in:
parent
115713d8f4
commit
a3b970d2b8
|
|
@ -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 {
|
class _FakeUrlLauncherPlatform extends UrlLauncherPlatform {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue