Full UI redesign pass complete: persistent tab shell with an always-visible background map, Modern Professional Dark theme, monochrome dark map tiles, offline skeleton map, a shared GlassPanel/FloatingPill component kit, customizable HUD telemetry widgets, and the Map HUD / Plan & Route Planning / Rides History screen rebuilds. 374 tests passing, up from 316. The APK is a fresh release build (debug-signed, no release signing config exists yet). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Xki7YAcc2TiN2PRZJ2tXr
117 lines
3.6 KiB
Dart
117 lines
3.6 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:rippr/src/tiles/map_connectivity.dart';
|
|
|
|
/// UI-02: exercises the failure-count/threshold/probe logic with a fake probe -- no
|
|
/// real network -- mirroring V3-11's `tile_downloader_test.dart` pattern of injecting
|
|
/// fake fetch outcomes rather than hitting a real tile host from a test.
|
|
void main() {
|
|
late bool probeResult;
|
|
late int probeCalls;
|
|
late MapConnectivityState state;
|
|
|
|
setUp(() {
|
|
probeResult = false;
|
|
probeCalls = 0;
|
|
state = MapConnectivityState(
|
|
probe: () async {
|
|
probeCalls++;
|
|
return probeResult;
|
|
},
|
|
probeInterval: const Duration(milliseconds: 20),
|
|
);
|
|
});
|
|
|
|
tearDown(() {
|
|
state.dispose();
|
|
});
|
|
|
|
test('starts in live mode', () {
|
|
expect(state.skeletonMode, isFalse);
|
|
});
|
|
|
|
test('fewer than the threshold of failures stays live', () {
|
|
state.reportFailure();
|
|
state.reportFailure();
|
|
expect(state.skeletonMode, isFalse);
|
|
});
|
|
|
|
test('reaching the threshold of consecutive failures switches to skeleton mode', () {
|
|
for (var i = 0; i < skeletonFailureThreshold; i++) {
|
|
state.reportFailure();
|
|
}
|
|
expect(state.skeletonMode, isTrue);
|
|
});
|
|
|
|
test('a success before the threshold resets the failure count', () {
|
|
state.reportFailure();
|
|
state.reportFailure();
|
|
state.reportSuccess();
|
|
state.reportFailure();
|
|
state.reportFailure();
|
|
expect(state.skeletonMode, isFalse,
|
|
reason: 'the success should have reset the run -- two more failures is not '
|
|
'the same as four in a row');
|
|
});
|
|
|
|
test('notifies listeners exactly when skeletonMode actually changes', () {
|
|
var notifications = 0;
|
|
state.addListener(() => notifications++);
|
|
|
|
state.reportFailure();
|
|
state.reportFailure();
|
|
expect(notifications, 0, reason: 'still below threshold -- no state change yet');
|
|
|
|
state.reportFailure();
|
|
expect(notifications, 1);
|
|
expect(state.skeletonMode, isTrue);
|
|
});
|
|
|
|
test('a single success while already in skeleton mode recovers immediately', () {
|
|
for (var i = 0; i < skeletonFailureThreshold; i++) {
|
|
state.reportFailure();
|
|
}
|
|
expect(state.skeletonMode, isTrue);
|
|
|
|
state.reportSuccess();
|
|
expect(state.skeletonMode, isFalse);
|
|
});
|
|
|
|
test('further failures are ignored once already in skeleton mode', () {
|
|
for (var i = 0; i < skeletonFailureThreshold; i++) {
|
|
state.reportFailure();
|
|
}
|
|
var notifications = 0;
|
|
state.addListener(() => notifications++);
|
|
|
|
// Recovery is the probe loop's job from here -- more failure reports (e.g. from a
|
|
// TileLayer that hadn't yet unmounted) must not do anything further.
|
|
state.reportFailure();
|
|
state.reportFailure();
|
|
|
|
expect(notifications, 0);
|
|
expect(state.skeletonMode, isTrue);
|
|
});
|
|
|
|
test('the probe loop recovers automatically once it starts succeeding', () async {
|
|
for (var i = 0; i < skeletonFailureThreshold; i++) {
|
|
state.reportFailure();
|
|
}
|
|
expect(state.skeletonMode, isTrue);
|
|
|
|
probeResult = true;
|
|
// Give the periodic probe timer a couple of intervals to fire.
|
|
await Future<void>.delayed(const Duration(milliseconds: 60));
|
|
|
|
expect(state.skeletonMode, isFalse);
|
|
expect(probeCalls, greaterThan(0));
|
|
});
|
|
|
|
test('the probe loop does not run while already live', () async {
|
|
await Future<void>.delayed(const Duration(milliseconds: 60));
|
|
expect(probeCalls, 0,
|
|
reason: 'a TileLayer that is live and working generates its own fetch '
|
|
'reports -- a periodic probe on top of that would be exactly the '
|
|
'excess-request behaviour this ticket exists to avoid');
|
|
});
|
|
}
|