feat(diary): tests

This commit is contained in:
mso7nueh
2026-05-04 17:36:48 +03:00
parent ea107ff07e
commit 91c8300da7
13 changed files with 2067 additions and 536 deletions

View File

@@ -0,0 +1,206 @@
import 'dart:async';
import 'package:eatvio_app/core/internet_connection_cubit/internet_connection_cubit.dart';
import 'package:eatvio_app/core/internet_connection_cubit/internet_connection_state.dart';
import 'package:eatvio_app/core/local_db/services/glossary_sync_runner.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/glossary_sync_cubit.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/glossary_sync_state.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
@GenerateNiceMocks([
MockSpec<GlossarySyncService>(),
MockSpec<InternetConnectionCubit>(),
])
import 'glossary_sync_cubit_test.mocks.dart';
void main() {
late MockGlossarySyncService mockSyncService;
late MockInternetConnectionCubit mockConnectionCubit;
setUp(() {
mockSyncService = MockGlossarySyncService();
mockConnectionCubit = MockInternetConnectionCubit();
when(mockConnectionCubit.stream).thenAnswer((_) => const Stream.empty());
when(mockConnectionCubit.state).thenReturn(InternetConnectionState(isInternetAvailable: false));
});
GlossarySyncCubit buildCubit() => GlossarySyncCubit(
syncService: mockSyncService,
connectionCubit: mockConnectionCubit,
);
group('GlossarySyncCubit', () {
test('initial state is idle', () {
final cubit = buildCubit();
expect(cubit.state, const GlossarySyncState.idle());
cubit.close();
});
test('syncNow emits completed with count when sync returns > 0', () async {
when(mockSyncService.sync(
onStageChanged: anyNamed('onStageChanged'),
onDownloadProgress: anyNamed('onDownloadProgress'),
onProcessingProgress: anyNamed('onProcessingProgress'),
)).thenAnswer((_) async => 100);
final cubit = buildCubit();
final states = <GlossarySyncState>[];
final sub = cubit.stream.listen(states.add);
await cubit.syncNow();
await Future.delayed(const Duration(milliseconds: 50));
expect(states.last, isA<GlossarySyncState>());
await sub.cancel();
await cubit.close();
});
test('syncNow emits upToDate when sync returns 0', () async {
when(mockSyncService.sync(
onStageChanged: anyNamed('onStageChanged'),
onDownloadProgress: anyNamed('onDownloadProgress'),
onProcessingProgress: anyNamed('onProcessingProgress'),
)).thenAnswer((_) async => 0);
final cubit = buildCubit();
final states = <GlossarySyncState>[];
final sub = cubit.stream.listen(states.add);
await cubit.syncNow();
await Future.delayed(const Duration(milliseconds: 50));
expect(states.last, const GlossarySyncState.upToDate());
await sub.cancel();
await cubit.close();
});
test('syncNow emits error on exception', () async {
when(mockSyncService.sync(
onStageChanged: anyNamed('onStageChanged'),
onDownloadProgress: anyNamed('onDownloadProgress'),
onProcessingProgress: anyNamed('onProcessingProgress'),
)).thenThrow(Exception('Test error'));
final cubit = buildCubit();
final states = <GlossarySyncState>[];
final sub = cubit.stream.listen(states.add);
await cubit.syncNow();
await Future.delayed(const Duration(milliseconds: 50));
expect(states.isNotEmpty, true);
await sub.cancel();
await cubit.close();
});
test('syncNow emits error with socket message', () async {
when(mockSyncService.sync(
onStageChanged: anyNamed('onStageChanged'),
onDownloadProgress: anyNamed('onDownloadProgress'),
onProcessingProgress: anyNamed('onProcessingProgress'),
)).thenThrow(Exception('SocketException: connection refused'));
final cubit = buildCubit();
final states = <GlossarySyncState>[];
final sub = cubit.stream.listen(states.add);
await cubit.syncNow();
await Future.delayed(const Duration(milliseconds: 50));
expect(states.isNotEmpty, true);
await sub.cancel();
await cubit.close();
});
test('syncNow emits error with timeout message', () async {
when(mockSyncService.sync(
onStageChanged: anyNamed('onStageChanged'),
onDownloadProgress: anyNamed('onDownloadProgress'),
onProcessingProgress: anyNamed('onProcessingProgress'),
)).thenThrow(TimeoutException('timeout'));
final cubit = buildCubit();
final states = <GlossarySyncState>[];
final sub = cubit.stream.listen(states.add);
await cubit.syncNow();
await Future.delayed(const Duration(milliseconds: 50));
expect(states.isNotEmpty, true);
await sub.cancel();
await cubit.close();
});
test('start() listens to connectionCubit', () {
final cubit = buildCubit();
cubit.start();
verify(mockConnectionCubit.stream).called(1);
cubit.close();
});
test('duplicate syncNow calls are ignored', () async {
final completer = Completer<int>();
when(mockSyncService.sync(
onStageChanged: anyNamed('onStageChanged'),
onDownloadProgress: anyNamed('onDownloadProgress'),
onProcessingProgress: anyNamed('onProcessingProgress'),
)).thenAnswer((_) => completer.future);
final cubit = buildCubit();
cubit.syncNow();
cubit.syncNow(); // Should be ignored
completer.complete(5);
await Future.delayed(const Duration(milliseconds: 100));
verify(mockSyncService.sync(
onStageChanged: anyNamed('onStageChanged'),
onDownloadProgress: anyNamed('onDownloadProgress'),
onProcessingProgress: anyNamed('onProcessingProgress'),
)).called(1);
await cubit.close();
});
test('stage callbacks emit correct states', () async {
when(mockSyncService.sync(
onStageChanged: anyNamed('onStageChanged'),
onDownloadProgress: anyNamed('onDownloadProgress'),
onProcessingProgress: anyNamed('onProcessingProgress'),
)).thenAnswer((invocation) async {
final stageCallback = invocation.namedArguments[#onStageChanged] as Function(GlossarySyncStage);
final downloadCallback = invocation.namedArguments[#onDownloadProgress] as Function(double);
final processingCallback = invocation.namedArguments[#onProcessingProgress] as Function(int, int);
stageCallback(GlossarySyncStage.checking);
stageCallback(GlossarySyncStage.downloading);
downloadCallback(0.5);
stageCallback(GlossarySyncStage.processing);
processingCallback(50, 100);
stageCallback(GlossarySyncStage.saving);
return 100;
});
final cubit = buildCubit();
final states = <GlossarySyncState>[];
final sub = cubit.stream.listen(states.add);
await cubit.syncNow();
await Future.delayed(const Duration(milliseconds: 100));
expect(states.length, greaterThanOrEqualTo(4));
await sub.cancel();
await cubit.close();
});
test('close cancels subscriptions', () async {
final cubit = buildCubit();
cubit.start();
await cubit.close();
// Should not crash
});
});
}