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" application_id: "com.wanmake.quanxue"
app_key: "quanxue_prod" app_key: "quanxue_prod"
theme: theme:

View File

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

View File

@ -154,4 +154,24 @@ class ShellCoreTestHooks {
int? parseWebViewMajorVersion(String? versionName) { int? parseWebViewMajorVersion(String? versionName) {
return _parseWebViewMajorVersion(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); // LaunchOverlay +
expect(find.byType(LinearProgressIndicator), findsNWidgets(2)); 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), find.byType(LinearProgressIndicator),
); );
expect(indicators.first.value, 0.8); expect(indicator.value, 0.5);
expect(indicators.last.value, 0.5);
}); });
testWidgets('LaunchOverlay 未量测进度时显示不确定进度', (tester) async { testWidgets('LaunchOverlay 始终显示不定态 CircularProgressIndicator',
(tester) async {
await tester.pumpWidget( await tester.pumpWidget(
const MaterialApp( const MaterialApp(
home: LaunchOverlay(progress: 0, hasMeasuredProgress: false), home: LaunchOverlay(progress: 0, hasMeasuredProgress: false),
), ),
); );
final indicator = tester.widget<LinearProgressIndicator>( // CircularProgressIndicator value
find.byType(LinearProgressIndicator), final indicator = tester.widget<CircularProgressIndicator>(
find.byType(CircularProgressIndicator),
); );
expect(indicator.value, isNull); expect(indicator.value, isNull);
}); });
@ -1099,7 +1103,7 @@ void main() {
expect(script, contains('window.AppShell')); expect(script, contains('window.AppShell'));
}); });
test('脚本暴露全部 8 个 Action', () { test('脚本暴露全部 12 个 Action', () {
final script = shellCoreTestHooks.buildAppShellBridgeScript(); final script = shellCoreTestHooks.buildAppShellBridgeScript();
for (final action in <String>[ for (final action in <String>[
@ -1111,6 +1115,10 @@ void main() {
'reloadPage', 'reloadPage',
'goBack', 'goBack',
'closeApp', 'closeApp',
'getDeviceInfo',
'getNetworkStatus',
'showToast',
'setStatusBar',
]) { ]) {
expect(script, contains(action), reason: 'Missing action: $action'); 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 { class _FakeUrlLauncherPlatform extends UrlLauncherPlatform {