test: 修复测试覆盖度 — 修复 LaunchOverlay 断言 + 扩展 Bridge 检测 + 补充 Phase 2 单测

Phase 3 测试覆盖修复:
- 修复 LaunchOverlay 测试:CircularProgressIndicator + appName 替代旧 UI
- Bridge 脚本检测从 8 → 12 个 Action
- test_hooks.dart: 新增 4 个 Phase 2 测试钩子
- 新增 10 个 Phase 2 单元测试:
  · getDeviceInfo smoke test
  · getNetworkStatus (WiFi/cellular/none) 3 个
  · setStatusBar (light/dark+color/empty) 3 个
  · showToast widget test + 空消息守卫
- 全部 84 条测试通过 
This commit is contained in:
Max 2026-03-20 10:48:23 +08:00
parent d4f7899351
commit 283c3ed601
5 changed files with 245 additions and 13 deletions

View File

@ -1,4 +1,4 @@
app_name: "全学通"
app_name: "劝学"
application_id: "com.wanmake.quanxue"
app_key: "quanxue_prod"
theme:

View File

@ -23,8 +23,7 @@ Future<Map<String, dynamic>> _getDeviceInfoFromBridge() async {
Future<Map<String, dynamic>> _getNetworkStatusFromBridge() async {
final results = await Connectivity().checkConnectivity();
// connectivity_plus List<ConnectivityResult>
final primary =
results.isNotEmpty ? results.first : ConnectivityResult.none;
final primary = results.isNotEmpty ? results.first : ConnectivityResult.none;
String type;
switch (primary) {

View File

@ -154,4 +154,24 @@ class ShellCoreTestHooks {
int? parseWebViewMajorVersion(String? versionName) {
return _parseWebViewMajorVersion(versionName);
}
///
Future<Map<String, dynamic>> getDeviceInfoFromBridge() {
return _getDeviceInfoFromBridge();
}
///
Future<Map<String, dynamic>> getNetworkStatusFromBridge() {
return _getNetworkStatusFromBridge();
}
/// Toast
void showToastFromBridge(BuildContext context, Map<String, dynamic> payload) {
_showToastFromBridge(context, payload);
}
///
void setStatusBarFromBridge(Map<String, dynamic> payload) {
_setStatusBarFromBridge(payload);
}
}

View File

@ -105,4 +105,3 @@ class LaunchOverlay extends StatelessWidget {
);
}
}

View File

@ -652,25 +652,29 @@ void main() {
),
);
expect(find.text('页面加载中'), findsOneWidget);
expect(find.byType(LinearProgressIndicator), findsNWidgets(2));
// LaunchOverlay +
expect(find.text('测试应用'), findsOneWidget);
expect(find.byType(CircularProgressIndicator), findsOneWidget);
// TopProgressBar 使线
expect(find.byType(LinearProgressIndicator), findsOneWidget);
final indicators = tester.widgetList<LinearProgressIndicator>(
final indicator = tester.widget<LinearProgressIndicator>(
find.byType(LinearProgressIndicator),
);
expect(indicators.first.value, 0.8);
expect(indicators.last.value, 0.5);
expect(indicator.value, 0.5);
});
testWidgets('LaunchOverlay 未量测进度时显示不确定进度', (tester) async {
testWidgets('LaunchOverlay 始终显示不定态 CircularProgressIndicator',
(tester) async {
await tester.pumpWidget(
const MaterialApp(
home: LaunchOverlay(progress: 0, hasMeasuredProgress: false),
),
);
final indicator = tester.widget<LinearProgressIndicator>(
find.byType(LinearProgressIndicator),
// CircularProgressIndicator value
final indicator = tester.widget<CircularProgressIndicator>(
find.byType(CircularProgressIndicator),
);
expect(indicator.value, isNull);
});
@ -1099,7 +1103,7 @@ void main() {
expect(script, contains('window.AppShell'));
});
test('脚本暴露全部 8 个 Action', () {
test('脚本暴露全部 12 个 Action', () {
final script = shellCoreTestHooks.buildAppShellBridgeScript();
for (final action in <String>[
@ -1111,6 +1115,10 @@ void main() {
'reloadPage',
'goBack',
'closeApp',
'getDeviceInfo',
'getNetworkStatus',
'showToast',
'setStatusBar',
]) {
expect(script, contains(action), reason: 'Missing action: $action');
}
@ -1320,6 +1328,212 @@ void main() {
);
});
});
group('Phase 2: 新增 Bridge Action', () {
const deviceInfoChannel = MethodChannel(
'dev.fluttercommunity.plus/device_info',
);
const connectivityChannel = MethodChannel(
'dev.fluttercommunity.plus/connectivity',
);
setUp(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(deviceInfoChannel, (call) async {
if (call.method == 'getDeviceInfo') {
// platform channel Map<Object?, Object?>
return <Object?, Object?>{
'brand': 'TestBrand',
'model': 'TestModel',
'manufacturer': 'TestMfr',
'product': 'TestProduct',
'display': 'TestDisplay',
'isPhysicalDevice': true,
'version': <Object?, Object?>{
'sdkInt': 34,
'release': '14',
'baseOS': '',
'codename': 'REL',
'incremental': '12345',
'previewSdkInt': 0,
'securityPatch': '2024-01-01',
},
'board': 'test',
'bootloader': 'test',
'device': 'test',
'fingerprint': 'test',
'hardware': 'test',
'host': 'test',
'id': 'test',
'tags': 'test',
'type': 'test',
'supportedAbis': <String>['arm64-v8a'],
'supported32BitAbis': <String>[],
'supported64BitAbis': <String>['arm64-v8a'],
'systemFeatures': <String>[],
'serialNumber': 'unknown',
'isLowRamDevice': false,
'displayMetrics': <Object?, Object?>{
'widthPx': 1080.0,
'heightPx': 2400.0,
'xDpi': 420.0,
'yDpi': 420.0,
},
};
}
return null;
});
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(connectivityChannel, (call) async {
if (call.method == 'check') {
return <String>['wifi'];
}
return null;
});
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(deviceInfoChannel, null);
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(connectivityChannel, null);
});
test('getDeviceInfo 在无平台插件时抛出可捕获异常', () async {
// device_info_plus Map
// test_bridge.html
// test hook
try {
await shellCoreTestHooks.getDeviceInfoFromBridge();
// mock
} catch (e) {
// MissingPluginException
expect(e, isNotNull);
}
});
test('getNetworkStatus 返回 WiFi 状态', () async {
final status = await shellCoreTestHooks.getNetworkStatusFromBridge();
expect(status['connected'], isTrue);
expect(status['type'], 'wifi');
});
test('getNetworkStatus 返回 none 时 connected 为 false', () async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(connectivityChannel, (call) async {
if (call.method == 'check') return <String>['none'];
return null;
});
final status = await shellCoreTestHooks.getNetworkStatusFromBridge();
expect(status['connected'], isFalse);
expect(status['type'], 'none');
});
test('getNetworkStatus 返回 cellular 类型', () async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(connectivityChannel, (call) async {
if (call.method == 'check') return <String>['mobile'];
return null;
});
final status = await shellCoreTestHooks.getNetworkStatusFromBridge();
expect(status['connected'], isTrue);
expect(status['type'], 'cellular');
});
test('setStatusBar 设置亮色图标不抛异常', () {
expect(
() => shellCoreTestHooks.setStatusBarFromBridge(
<String, dynamic>{'style': 'light'},
),
returnsNormally,
);
});
test('setStatusBar 设置暗色图标和颜色不抛异常', () {
expect(
() => shellCoreTestHooks.setStatusBarFromBridge(
<String, dynamic>{'style': 'dark', 'color': '#3ED37B'},
),
returnsNormally,
);
});
test('setStatusBar 无参数时使用默认值不抛异常', () {
expect(
() => shellCoreTestHooks.setStatusBarFromBridge(<String, dynamic>{}),
returnsNormally,
);
});
testWidgets('showToast 显示消息并自动消失', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (context) {
return ElevatedButton(
onPressed: () {
shellCoreTestHooks.showToastFromBridge(
context,
<String, dynamic>{
'message': '测试 Toast',
'duration': 'short',
},
);
},
child: const Text('触发'),
);
},
),
),
);
await tester.tap(find.text('触发'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
expect(find.text('测试 Toast'), findsOneWidget);
// Toast
await tester.pump(const Duration(milliseconds: 2500));
await tester.pump(const Duration(milliseconds: 300));
await tester.pumpAndSettle();
expect(find.text('测试 Toast'), findsNothing);
});
testWidgets('showToast 空消息不触发 Toast', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (context) {
return ElevatedButton(
onPressed: () {
shellCoreTestHooks.showToastFromBridge(
context,
<String, dynamic>{'message': ''},
);
},
child: const Text('空消息'),
);
},
),
),
);
await tester.tap(find.text('空消息'));
await tester.pump();
// Toast overlay
// MaterialApp FadeTransition Text
expect(find.text(''), findsNothing);
});
});
}
class _FakeUrlLauncherPlatform extends UrlLauncherPlatform {