Files
gopeed/ui/flutter/test/updater_test.dart
2026-09-05 19:53:41 +08:00

203 lines
7.6 KiB
Dart

import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gopeed/api/model/downloader_config.dart';
import 'package:gopeed/features/settings/presentation/widgets/app_update_dialog.dart';
import 'package:gopeed/shared/theme/app_theme.dart';
import 'package:gopeed/util/arch/arch.dart';
import 'package:gopeed/util/github_mirror.dart';
import 'package:gopeed/util/package_info.dart' as app_package;
import 'package:gopeed/util/updater.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart' as shad;
void main() {
TestWidgetsFlutterBinding.ensureInitialized().platformDispatcher.localeTestValue = const Locale('en');
setUpAll(() {
app_package.packageInfo = PackageInfo(
appName: 'Gopeed',
packageName: 'com.gopeed.Gopeed',
version: '1.7.0',
buildNumber: '1',
);
});
group('version comparison', () {
test('uses semantic version precedence', () {
expect(isNewerVersion('1.10.0', '1.9.9'), isTrue);
expect(isNewerVersion('2.0.0-beta.2', '2.0.0-beta.1'), isTrue);
expect(isNewerVersion('2.0.0-rc.1', '2.0.0-beta.2'), isTrue);
expect(isNewerVersion('2.0.0', '2.0.0-rc.1'), isTrue);
expect(isNewerVersion('1.2.3', '1.2.3'), isFalse);
expect(isNewerVersion('1.2.2', '1.2.3'), isFalse);
expect(isNewerVersion('invalid', '1.2.3'), isFalse);
});
});
group('release selection', () {
Map<String, dynamic> release(String version, {bool prerelease = false, bool draft = false}) => {
'tag_name': 'v$version',
'prerelease': prerelease,
'draft': draft,
'body': '$version notes',
'html_url': 'https://example.com/$version',
};
test('stable builds ignore preview releases', () {
final selected = selectUpdateRelease([release('2.1.0-beta.1', prerelease: true), release('2.0.1')], '2.0.0');
expect(selected?.version, '2.0.1');
});
test('preview builds advance within the same preview line', () {
final selected = selectUpdateRelease([
release('2.0.0-beta.2', prerelease: true),
release('1.9.9'),
release('2.1.0-beta.1', prerelease: true),
], '2.0.0-beta.1');
expect(selected?.version, '2.0.0-beta.2');
});
test('four-part platform beta selects the next GitHub prerelease and matching asset', () {
final currentVersion = app_package.normalizeAppVersion('2.0.0.1');
final selected = selectUpdateRelease([release('2.0.0-beta.2', prerelease: true)], currentVersion);
expect(selected?.version, '2.0.0-beta.2');
expect(
updateAssetName(selected!.version, channel: UpdateChannel.macosDmg, architecture: Architecture.arm64),
'Gopeed-v2.0.0-beta.2-macos-arm64.dmg',
);
});
test('preview builds graduate to the final stable release', () {
final selected = selectUpdateRelease([
release('2.0.0-beta.2', prerelease: true),
release('2.0.0'),
], '2.0.0-beta.1');
expect(selected?.version, '2.0.0');
});
test('selects the greatest eligible version and ignores invalid releases', () {
final selected = selectUpdateRelease([
release('2.0.0-beta.3', prerelease: true),
release('2.0.0-beta.4', prerelease: true, draft: true),
{'tag_name': 'not-a-version'},
release('2.0.0-beta.2', prerelease: true),
], '2.0.0-beta.1');
expect(selected?.version, '2.0.0-beta.3');
});
});
group('release assets', () {
test('uses the release naming convention for desktop packages', () {
expect(
updateAssetName('1.8.0', channel: UpdateChannel.windowsPortable, architecture: Architecture.x64),
'Gopeed-v1.8.0-windows-amd64-portable.zip',
);
expect(
updateAssetName('1.8.0', channel: UpdateChannel.macosDmg, architecture: Architecture.arm64),
'Gopeed-v1.8.0-macos-arm64.dmg',
);
expect(
updateAssetName('1.8.0', channel: UpdateChannel.linuxDeb, architecture: Architecture.ia32),
'Gopeed-v1.8.0-linux-386.deb',
);
});
test('maps Android ABI names', () {
expect(
updateAssetName('1.8.0', channel: UpdateChannel.androidApk, architecture: Architecture.arm),
'Gopeed-v1.8.0-android-armeabi-v7a.apk',
);
expect(
updateAssetName('1.8.0', channel: UpdateChannel.androidApk, architecture: Architecture.arm64),
'Gopeed-v1.8.0-android-arm64-v8a.apk',
);
});
});
test('selects the matching bilingual release notes', () {
const notes = '# Release notes\n\n- English change\n\n# 更新日志\n\n- 中文更新';
expect(localizedReleaseNotes(notes, 'en'), '# Release notes\n\n- English change');
expect(localizedReleaseNotes(notes, 'zh-CN'), '# 更新日志\n\n- 中文更新');
});
test('builds enabled GitHub proxy URLs and ignores deleted mirrors', () {
final config = ExtraConfigGithubMirror(
mirrors: [
GithubMirror(type: GithubMirrorType.ghProxy, url: 'https://mirror.example'),
GithubMirror(type: GithubMirrorType.ghProxy, url: 'https://deleted.example', isDeleted: true),
],
);
expect(
githubMirrorUrls(
'https://github.com/GopeedLab/gopeed/releases/download/v1.8.0/app.zip',
MirrorType.githubRelease,
config: config,
),
['https://mirror.example/https://github.com/GopeedLab/gopeed/releases/download/v1.8.0/app.zip'],
);
});
testWidgets('update dialog shows beta versions without tag prefixes', (tester) async {
tester.view.physicalSize = const Size(390, 760);
tester.view.devicePixelRatio = 1;
addTearDown(() {
tester.view.resetPhysicalSize();
tester.view.resetDevicePixelRatio();
});
app_package.packageInfo = PackageInfo(
appName: 'Gopeed',
packageName: 'com.gopeed.Gopeed',
version: '2.0.0.1',
buildNumber: '1',
);
addTearDown(() {
app_package.packageInfo = PackageInfo(
appName: 'Gopeed',
packageName: 'com.gopeed.Gopeed',
version: '1.7.0',
buildNumber: '1',
);
});
const version = VersionInfo(
version: '2.0.0-beta.2',
changeLog: '# Release notes\n\n- Faster downloads\n\n# 更新日志\n\n- 下载更快',
releaseUrl: 'https://example.com/release',
);
await tester.pumpWidget(
shad.ShadcnApp(
theme: AppTheme.light(),
materialTheme: AppTheme.materialLight(),
home: Builder(
builder: (context) => shad.SecondaryButton(
onPressed: () => showAppUpdateDialog(context, versionInfo: version, onUpdate: (_, _) async {}),
child: const Text('Open update'),
),
),
),
);
await tester.tap(find.text('Open update'));
await tester.pumpAndSettle();
expect(find.byKey(const ValueKey('app-update-dialog')), findsOneWidget);
expect(find.text('A new version is available'), findsOneWidget);
expect(find.text('2.0.0-beta.1 → 2.0.0-beta.2'), findsOneWidget);
expect(find.textContaining('v2.0.0'), findsNothing);
expect(find.text('Faster downloads'), findsOneWidget);
expect(find.text('下载更快'), findsNothing);
expect(find.text('Later'), findsOneWidget);
expect(find.text('Update Now'), findsOneWidget);
final dialog = tester.getRect(find.byKey(const ValueKey('app-update-dialog')));
expect(dialog.width, lessThan(320));
expect(tester.getCenter(find.byKey(const ValueKey('app-update-heading'))).dx, closeTo(dialog.center.dx, 0.01));
expect(tester.getCenter(find.byKey(const ValueKey('app-update-actions'))).dx, closeTo(dialog.center.dx, 0.01));
expect(tester.takeException(), isNull);
});
}