Files
clide/test/builtin/menubar/about_dialog_test.dart
jpmschweitzerandClaude Opus 4.8 7c7a91545e feat(menubar): manual "Check for updates" in the About box (T-47 P1)
Help → About gains a "Check for updates" button that fetches the latest GitHub
release, semver-compares it to clideVersion, and shows the result inline:
up-to-date, available (with a tappable link to the release notes), or a clear
error. clide's first and only outbound HTTP call — a plain GET with no user
data, run ONLY on this explicit tap, never on a launch path or a timer. So it's
D-64-clean with no amendment; a background/periodic poll stays deferred (would
need the narrow opt-in amendment first).

The fetch is injectable so no test touches the network. compareSemver handles
2.3.10 > 2.3.9 and ranks pre-releases below their release. Closes T-492 (P1);
the release-channel CI for downloadable signed packages is T-491, and download/
apply (P2/P3) depend on it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 12:17:29 +02:00

63 lines
2.4 KiB
Dart

/// T-47 P1: the About box "Check for updates" button. The check runs ONLY on
/// the explicit tap (never on open — POLICY/D-64), and surfaces the result
/// inline: up-to-date, available (with a release link), or a clear error.
library;
import 'package:clide/builtin/menubar/src/about_dialog.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../helpers/kernel_fixture.dart';
import '../../helpers/widget_harness.dart';
void main() {
late KernelFixture f;
setUp(() async => f = await KernelFixture.create());
tearDown(() async => f.dispose());
Future<void> pump(WidgetTester tester, Future<String> Function(Uri) fetch) async {
tester.view.physicalSize = const Size(700, 1000);
tester.view.devicePixelRatio = 1.0;
addTearDown(() {
tester.view.resetPhysicalSize();
tester.view.resetDevicePixelRatio();
});
await tester.pumpWidget(harness(f, AboutDialog(onDismiss: () {}, updateFetch: fetch)));
await tester.pump();
}
testWidgets('does not fetch until the user taps Check for updates (no network on open)', (tester) async {
var calls = 0;
await pump(tester, (_) async {
calls++;
return '{"tag_name":"v2.9.0","html_url":"https://x/r"}';
});
expect(calls, 0, reason: 'opening the About box must not touch the network');
await tester.tap(find.text('Check for updates'));
await pumpAsync(tester);
expect(calls, 1);
});
testWidgets('shows an update-available link when a newer release exists', (tester) async {
await pump(tester, (_) async => '{"tag_name":"v99.0.0","html_url":"https://github.com/postmeridiem/clide/releases/v99.0.0"}');
await tester.tap(find.text('Check for updates'));
await pumpAsync(tester);
expect(find.textContaining('99.0.0'), findsOneWidget);
});
testWidgets('shows up-to-date when the latest release is not newer', (tester) async {
await pump(tester, (_) async => '{"tag_name":"v0.0.1","html_url":"https://x/r"}');
await tester.tap(find.text('Check for updates'));
await pumpAsync(tester);
expect(find.textContaining('latest version'), findsOneWidget);
});
testWidgets('surfaces a clear error when the check fails', (tester) async {
await pump(tester, (_) => Future.error('offline'));
await tester.tap(find.text('Check for updates'));
await pumpAsync(tester);
expect(find.textContaining("Couldn't check"), findsOneWidget);
});
}