Files
FlClash/test/plugins/app_test.dart
T
chen08209 bc72bba981 feat(android): drop Firebase analytics and crash reporting
Changelog: No more Firebase analytics or crash reporting
2026-10-17 17:21:43 +08:00

201 lines
6.2 KiB
Dart

import 'dart:async';
import 'dart:math';
import 'package:fl_clash/common/boot_record.dart';
import 'package:fl_clash/common/constant.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
const channel = MethodChannel('$packageName/app');
setUp(() {
App().clearPackageIconCache();
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, null);
App().clearPackageIconCache();
});
test('requests every package icon from Android only once', () async {
var iconCallCount = 0;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (call) async {
iconCallCount++;
return '/icons/${call.arguments['packageName']}.png';
});
final app = App();
final results = await Future.wait([
app.getPackageIcon('com.a'),
app.getPackageIcon('com.a'),
]);
final cached = await app.getPackageIcon('com.a');
expect(iconCallCount, 1);
expect(results.first, isNotNull);
expect(cached, same(results.first));
expect(app.getCachedPackageIcon('com.a'), same(results.first));
await app.getPackageIcon('com.b');
expect(iconCallCount, 2);
});
test(
'falls back to the default activity icon for unknown packages',
() async {
final requested = <String?>[];
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (call) async {
final packageName = call.arguments['packageName'] as String?;
requested.add(packageName);
return packageName == '' ? '/icons/default_icon.webp' : null;
});
final app = App();
final fallback = await app.getPackageIcon('com.a');
final defaultIcon = await app.getPackageIcon('');
expect(fallback, isNotNull);
expect(fallback, same(defaultIcon));
expect(requested, ['com.a', '']);
expect(await app.getPackageIcon('com.b'), same(defaultIcon));
expect(requested, ['com.a', '', 'com.b']);
},
);
test(
'asks Android for a bounded number of package icons at a time',
() async {
var inFlight = 0;
var peak = 0;
final release = Completer<void>();
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (call) async {
inFlight++;
peak = max(peak, inFlight);
await release.future;
inFlight--;
return '/icons/${call.arguments['packageName']}.png';
});
final app = App();
final icons = Future.wait([
for (var i = 0; i < maxConcurrentIconLoads * 2; i++)
app.getPackageIcon('com.$i'),
]);
await pumpEventQueue();
expect(inFlight, maxConcurrentIconLoads);
release.complete();
expect(await icons, everyElement(isNotNull));
expect(peak, maxConcurrentIconLoads);
},
);
test('caches a failed package icon lookup', () async {
var iconCallCount = 0;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (_) async {
iconCallCount++;
throw PlatformException(code: 'unavailable');
});
final app = App();
expect(await app.getPackageIcon('com.a'), isNull);
expect(await app.getPackageIcon('com.a'), isNull);
expect(await app.getPackageIcon(''), isNull);
expect(iconCallCount, 2);
});
test('reads the last process exit info from Android', () async {
MethodCall? receivedCall;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (call) async {
receivedCall = call;
return <String, Object?>{
'reason': 10,
'timestamp': 1234,
'description': 'user requested',
};
});
final info = await App().getLastExitInfo();
expect(receivedCall?.method, 'getLastExitInfo');
expect(info?.reason, AppExitReason.userRequested);
expect(info?.timestamp, 1234);
expect(info?.description, 'user requested');
});
test('uses no exit info when Android cannot report one', () async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (_) async => null);
expect(await App().getLastExitInfo(), isNull);
});
test('uses no exit info when the platform call fails', () async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (_) async {
throw PlatformException(code: 'unavailable');
});
expect(await App().getLastExitInfo(), isNull);
});
test('forwards package change notices from Android', () async {
var changes = 0;
final app = App();
app.onPackagesChanged = () => changes++;
addTearDown(() => app.onPackagesChanged = null);
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.handlePlatformMessage(
channel.name,
channel.codec.encodeMethodCall(const MethodCall('packagesChanged')),
(_) {},
);
expect(changes, 1);
});
test('reports the installed apps permission Android answers with', () async {
final methods = <String>[];
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (call) async {
methods.add(call.method);
return false;
});
expect(await App().isInstalledAppsPermissionGranted(), isFalse);
expect(await App().requestInstalledAppsPermission(), isFalse);
expect(methods, [
'isInstalledAppsPermissionGranted',
'requestInstalledAppsPermission',
]);
});
test(
'treats a missing installed apps permission answer as granted',
() async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (_) async => null);
expect(await App().isInstalledAppsPermissionGranted(), isTrue);
expect(await App().requestInstalledAppsPermission(), isFalse);
},
);
}