Files
rely/test/features/sync/sync_background_runner_test.dart
T
Rijad Zuzo f655adfbea feat: add local-first private AI digest workflow
Migrate app code into canonical feature slices, add phone-only AI digest scheduling and review, wire local notification/background task support, and cover the flow with tests.
2026-05-17 00:17:20 +02:00

139 lines
3.8 KiB
Dart

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,
);
}