yx_app_upgrade_flutter/test/app_upgrade_comprehensive_t...

369 lines
12 KiB
Dart

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<String, String> _appInfo = {};
UpgradeInfo? _checkUpdateResult;
bool downloadApkCalled = false;
bool goToAppStoreCalled = false;
String? lastUrl;
void setAppInfo(Map<String, String> info) {
_appInfo = info;
}
void setCheckUpdateResult(UpgradeInfo? info) {
_checkUpdateResult = info;
}
void reset() {
downloadApkCalled = false;
goToAppStoreCalled = false;
lastUrl = null;
}
@override
Future<Map<String, String>> getAppInfo() async {
return _appInfo;
}
@override
Future<UpgradeInfo?> checkUpdate(String url, {Map<String, dynamic>? params}) async {
return _checkUpdateResult;
}
@override
Future<String?> getDownloadPath({bool checkPermission = true}) async {
return '/tmp/download';
}
@override
Future<String?> 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<bool> goToAppStore(String url, {required BuildContext context}) async {
goToAppStoreCalled = true;
lastUrl = url;
return true;
}
@override
Future<bool> 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.
});
});
}