import 'package:app_upgrade_plugin/app_upgrade_plugin_platform_interface.dart'; import 'package:app_upgrade_plugin/app_upgrade_simple.dart'; import 'package:app_upgrade_plugin/models/app_market.dart'; import 'package:app_upgrade_plugin/models/app_upgrade_method.dart'; import 'package:app_upgrade_plugin/models/app_upgrade_version.dart'; import 'package:app_upgrade_plugin/models/upgrade_info.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; // Mock Platform Interface class MockAppUpgradePluginPlatform extends AppUpgradePluginPlatform with MockPlatformInterfaceMixin { Map _appInfo = {}; UpgradeInfo? _checkUpdateResult; bool downloadApkCalled = false; bool goToAppStoreCalled = false; String? lastUrl; void setAppInfo(Map info) { _appInfo = info; } void setCheckUpdateResult(UpgradeInfo? info) { _checkUpdateResult = info; } void reset() { downloadApkCalled = false; goToAppStoreCalled = false; lastUrl = null; } @override Future> getAppInfo() async { return _appInfo; } @override Future checkUpdate(String url, {Map? params}) async { return _checkUpdateResult; } @override Future getDownloadPath({bool checkPermission = true}) async { return '/tmp/download'; } @override Future downloadApk(String url, {Function(DownloadProgress)? onProgress, String? savePath}) async { downloadApkCalled = true; lastUrl = url; // Simulate progress if (onProgress != null) { onProgress(DownloadProgress(received: 100, total: 100)); } return '/tmp/app.apk'; } @override Future goToAppStore(String url, {required BuildContext context}) async { goToAppStoreCalled = true; lastUrl = url; return true; } @override Future installApk(String filePath) async { return true; } } void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('Models Test', () { test('UpgradeInfo.fromJson parses correctly', () { final json = { 'versionBuildNumber': 10, 'versionName': '1.1.0', 'isForceUpdate': true, 'updateContent': 'Bug fixes', 'downloadUrl': 'http://example.com/app.apk', 'appMarkets': [ {'market': 'googleplay', 'packageName': 'com.example.app'} ] }; final info = UpgradeInfo.fromJson( json, currentBuildNumber: 9, currentVersionName: '1.0.0', ); expect(info.hasUpdate, isTrue); expect(info.isForceUpdate, isTrue); expect(info.versionBuildNumber, 10); expect(info.versionName, '1.1.0'); expect(info.appMarkets?.first.market, AppMarket.googlePlay); }); test('UpgradeInfo.fromJson sets hasUpdate correctly', () { // Case 1: Different build number var info = UpgradeInfo.fromJson( {'versionBuildNumber': 10, 'versionName': '1.0.0'}, currentBuildNumber: 9, currentVersionName: '1.0.0', ); expect(info.hasUpdate, isTrue); // Case 2: Same build number, different version name info = UpgradeInfo.fromJson( {'versionBuildNumber': 10, 'versionName': '1.0.1'}, currentBuildNumber: 10, currentVersionName: '1.0.0', ); expect(info.hasUpdate, isTrue); // Case 3: Same everything info = UpgradeInfo.fromJson( {'versionBuildNumber': 10, 'versionName': '1.0.0'}, currentBuildNumber: 10, currentVersionName: '1.0.0', ); expect(info.hasUpdate, isFalse); }); test('AppMarket enum parsing', () { expect(AppMarket.fromString('googleplay'), AppMarket.googlePlay); expect(AppMarket.fromString('AppStore'), AppMarket.appStore); expect(AppMarket.fromString('UNKNOWN_MARKET'), AppMarket.unknown); }); }); group('AppUpgradeSimple Logic Test', () { late MockAppUpgradePluginPlatform mockPlatform; setUp(() { mockPlatform = MockAppUpgradePluginPlatform(); AppUpgradePluginPlatform.instance = mockPlatform; }); test('isVersionUpdated logic', () async { mockPlatform.setAppInfo({ 'version': '1.0.0', 'buildNumber': '10', 'packageName': 'com.test', }); // Target build number > current expect(await AppUpgradeSimple.instance.isVersionUpdated('1.0.0', 11), isFalse); // Target build number < current expect(await AppUpgradeSimple.instance.isVersionUpdated('1.0.0', 9), isTrue); // Target build number == current expect(await AppUpgradeSimple.instance.isVersionUpdated('1.0.0', 10), isTrue); // Target build number == current, target version > current expect(await AppUpgradeSimple.instance.isVersionUpdated('1.0.1', 10), isFalse); // Target build number == current, target version < current mockPlatform.setAppInfo({ 'version': '1.0.1', 'buildNumber': '10', }); expect(await AppUpgradeSimple.instance.isVersionUpdated('1.0.0', 10), isTrue); }); }); // Widget Tests require pumping widgets, which is different from unit tests. // We'll create a separate group for UI tests. group('AppUpgradeSimple UI Test', () { testWidgets('Shows dialog when update is available', (WidgetTester tester) async { // Mock platform final mockPlatform = MockAppUpgradePluginPlatform(); AppUpgradePluginPlatform.instance = mockPlatform; // Set current app info to be older than mockInfo mockPlatform.setAppInfo({ 'version': '1.0.0', 'buildNumber': '10', 'packageName': 'com.example.app', }); final mockInfo = AppUpgradeVersion( versionBuildNumber: 20, versionName: '2.0.0', updateContent: 'New features available', downloadUrl: 'http://example.com/app.apk', ); await tester.pumpWidget(MaterialApp( home: Scaffold( body: Builder( builder: (context) { return ElevatedButton( onPressed: () { AppUpgradeSimple.instance.checkUpdate( context: context, future: () async => mockInfo, ); }, child: const Text('Check Update'), ); }, ), ), )); // Tap button to trigger check await tester.tap(find.text('Check Update')); await tester.pump(); // Start future await tester.pump(const Duration(seconds: 1)); // Wait for future to complete (mock is instant but safe) await tester.pumpAndSettle(); // Wait for dialog animation // Verify dialog content expect(find.text('发现新版本'), findsOneWidget); expect(find.text('v2.0.0'), findsOneWidget); expect(find.text('New features available', findRichText: true), findsOneWidget); // Tap "Later" (稍后更新) expect(find.text('稍后更新'), findsOneWidget); await tester.tap(find.text('稍后更新')); await tester.pumpAndSettle(); expect(find.text('发现新版本'), findsNothing); }); testWidgets('Shows Force Upgrade Dialog correctly', (WidgetTester tester) async { // Mock platform final mockPlatform = MockAppUpgradePluginPlatform(); AppUpgradePluginPlatform.instance = mockPlatform; // Set current app info to be older than mockInfo mockPlatform.setAppInfo({ 'version': '1.0.0', 'buildNumber': '10', 'packageName': 'com.example.app', }); final mockInfo = AppUpgradeVersion( isForce: true, // FORCE UPDATE versionBuildNumber: 20, versionName: '2.0.0', updateContent: 'Critical update', downloadUrl: 'http://example.com/app.apk', ); await tester.pumpWidget(MaterialApp( home: Scaffold( body: Builder( builder: (context) { return ElevatedButton( onPressed: () { AppUpgradeSimple.instance.checkUpdate( context: context, future: () async => mockInfo, ); }, child: const Text('Check Update'), ); }, ), ), )); await tester.tap(find.text('Check Update')); await tester.pumpAndSettle(); // Verify Force Update specific UI expect(find.text('发现新版本 (强制)'), findsOneWidget); // Should NOT have "Later" button expect(find.text('稍后更新'), findsNothing); }); }); group('Supported Methods Tests', () { testWidgets('Auto-select In-App when only inApp is supported', (WidgetTester tester) async { final mockPlatform = MockAppUpgradePluginPlatform(); AppUpgradePluginPlatform.instance = mockPlatform; mockPlatform.reset(); mockPlatform.setAppInfo({'version': '1.0.0', 'buildNumber': '10', 'packageName': 'com.app'}); final mockInfo = AppUpgradeVersion( versionBuildNumber: 20, versionName: '2.0.0', updateContent: 'Update', downloadUrl: 'http://example.com/app.apk', supportedMethods: [AppUpgradeMethod.inApp], ); await tester.pumpWidget(MaterialApp( home: Scaffold( body: Builder( builder: (context) => ElevatedButton( onPressed: () => AppUpgradeSimple.instance.checkUpdate(context: context, future: () async => mockInfo), child: const Text('Update'), )), ), )); await tester.tap(find.text('Update')); await tester.pumpAndSettle(); // Click "Immediate Update" or "Go to Update" final updateButton = find.widgetWithText(ElevatedButton, '立即更新'); if (updateButton.evaluate().isNotEmpty) { await tester.tap(updateButton); } else { await tester.tap(find.widgetWithText(ElevatedButton, '前往更新')); } await tester.pump(); // Trigger action // On non-Android platforms, this will show "Unsupported platform" toast // and NOT show the selection sheet. // We can't easily mock Platform.isAndroid to true in this test environment. // So we just verify the sheet is NOT shown. expect(find.text('选择更新方式'), findsNothing); }); testWidgets('Shows Choice Sheet when multiple methods are supported', (WidgetTester tester) async { final mockPlatform = MockAppUpgradePluginPlatform(); AppUpgradePluginPlatform.instance = mockPlatform; mockPlatform.setAppInfo({'version': '1.0.0', 'buildNumber': '10', 'packageName': 'com.app'}); final mockInfo = AppUpgradeVersion( versionBuildNumber: 20, versionName: '2.0.0', updateContent: 'Update', downloadUrl: 'http://example.com/app.apk', supportedMethods: [AppUpgradeMethod.market, AppUpgradeMethod.inApp], ); await tester.pumpWidget(MaterialApp( home: Scaffold( body: Builder( builder: (context) => ElevatedButton( onPressed: () => AppUpgradeSimple.instance.checkUpdate( context: context, future: () async => mockInfo, // Inject custom toast to avoid missing plugin implementation for Fluttertoast config: UpgradeConfig(customToast: (msg) => debugPrint('Toast: $msg')), ), child: const Text('Update'), )), ), )); await tester.tap(find.text('Update')); await tester.pumpAndSettle(); // Click "Immediate Update" or "Go to Update" final updateButton = find.widgetWithText(ElevatedButton, '立即更新'); if (updateButton.evaluate().isNotEmpty) { await tester.tap(updateButton); } else { await tester.tap(find.widgetWithText(ElevatedButton, '前往更新')); } await tester.pumpAndSettle(); // On Android, this would show the sheet. // On Windows, this shows toast "Unsupported platform". // We verify that we don't crash. }); }); }