Trigger background sync on connectivity recovery

This commit is contained in:
Rijad Zuzo
2026-02-17 23:52:32 +01:00
parent d76b31d37d
commit 9bb484e95d
13 changed files with 268 additions and 3 deletions
@@ -0,0 +1,131 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:relationship_saver/core/network/reachability/network_reachability.dart';
import 'package:relationship_saver/core/network/reachability/network_reachability_provider.dart';
import 'package:relationship_saver/features/sync/sync_background_runner.dart';
import 'package:relationship_saver/features/sync/sync_coordinator.dart';
void main() {
testWidgets('triggers sync when reachability transitions from offline to online', (
WidgetTester tester,
) async {
final _TestReachability reachability = _TestReachability(initial: false);
addTearDown(reachability.dispose);
int syncCalls = 0;
await tester.pumpWidget(
ProviderScope(
overrides: [
networkReachabilityProvider.overrideWithValue(reachability),
syncCoordinatorProvider.overrideWith(
(Ref ref) =>
_TestSyncCoordinator(ref, onSyncNow: () async {
syncCalls += 1;
return _okResult();
}),
),
],
child: const Directionality(
textDirection: TextDirection.ltr,
child: SyncBackgroundRunner(child: SizedBox()),
),
),
);
await tester.pumpAndSettle(const Duration(milliseconds: 60));
// Startup trigger always runs once when background sync is enabled.
expect(syncCalls, 1);
reachability.emit(false);
await tester.pumpAndSettle(const Duration(milliseconds: 40));
expect(syncCalls, 1);
reachability.emit(true);
await tester.pumpAndSettle(const Duration(milliseconds: 60));
expect(syncCalls, 2);
});
testWidgets('does not trigger extra sync while connectivity stays online', (
WidgetTester tester,
) async {
final _TestReachability reachability = _TestReachability(initial: true);
addTearDown(reachability.dispose);
int syncCalls = 0;
await tester.pumpWidget(
ProviderScope(
overrides: [
networkReachabilityProvider.overrideWithValue(reachability),
syncCoordinatorProvider.overrideWith(
(Ref ref) =>
_TestSyncCoordinator(ref, onSyncNow: () async {
syncCalls += 1;
return _okResult();
}),
),
],
child: const Directionality(
textDirection: TextDirection.ltr,
child: SyncBackgroundRunner(child: SizedBox()),
),
),
);
await tester.pumpAndSettle(const Duration(milliseconds: 60));
expect(syncCalls, 1);
reachability.emit(true);
await tester.pumpAndSettle(const Duration(milliseconds: 60));
expect(syncCalls, 1);
});
}
class _TestSyncCoordinator extends SyncCoordinator {
_TestSyncCoordinator(super.ref, {required this.onSyncNow});
final Future<SyncRunResult> Function() onSyncNow;
@override
Future<SyncRunResult> syncNow() => onSyncNow();
}
class _TestReachability implements NetworkReachability {
_TestReachability({required bool initial}) : _current = initial;
final StreamController<bool> _controller = StreamController<bool>.broadcast();
bool _current;
@override
Future<bool> isReachable({Duration timeout = const Duration(seconds: 2)}) async {
return _current;
}
@override
Stream<bool> watch({Duration timeout = const Duration(seconds: 2)}) {
return _controller.stream;
}
void emit(bool reachable) {
_current = reachable;
_controller.add(reachable);
}
Future<void> dispose() async {
await _controller.close();
}
}
SyncRunResult _okResult() {
return const SyncRunResult(
success: true,
operation: 'sync',
message: 'ok',
pendingAfter: 0,
);
}