chore(version): update app version to 1.3.5+85 and improve test coverage reporting in CI

This commit is contained in:
mso7nueh
2025-12-08 11:19:08 +03:00
parent abe4985f0e
commit c2a506cdbf
148 changed files with 12395 additions and 1889 deletions

View File

@@ -1,29 +1,37 @@
import 'package:eatvio_app/core/views/cubit/views_cubit.dart';
import 'package:eatvio_app/core/views/cubit/views_state.dart';
import 'package:eatvio_app/core/views/repository/views_repository.dart';
import 'package:eatvio_app/features/feed/models/view_model.dart';
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:mocktail/mocktail.dart';
import 'views_cubit_test.mocks.dart';
class MockViewsRepository extends Mock implements ViewsRepository {}
class FakeViewModel extends Fake implements ViewModel {}
@GenerateNiceMocks([MockSpec<ViewsRepository>()])
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
registerFallbackValue(FakeViewModel());
});
group('ViewsCubit', () {
late ViewsCubit cubit;
late MockViewsRepository mockViewsRepository;
late MockViewsRepository mockRepository;
setUp(() {
mockViewsRepository = MockViewsRepository();
cubit = ViewsCubit(viewsRepository: mockViewsRepository);
mockRepository = MockViewsRepository();
cubit = ViewsCubit(viewsRepository: mockRepository);
});
tearDown(() async {
if (!cubit.isClosed) {
await cubit.close();
try {
await cubit.close();
} catch (e) {
// Игнорируем ошибки при закрытии (AppLifecycleListener может быть уже disposed)
}
}
});
@@ -31,124 +39,58 @@ void main() {
expect(cubit.state, isA<ViewsCubitState>());
});
test('handleVisibility - добавляет ID при visibleFraction >= 0.5', () async {
when(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).thenAnswer((_) async => Future.value());
test('handleVisibility - добавляет id когда visibleFraction >= 0.5', () async {
when(() => mockRepository.viewContent(viewModel: any(named: 'viewModel')))
.thenAnswer((_) async => {});
cubit.handleVisibility(visibleFraction: 0.6, id: 1, type: ViewableContentType.posts);
// Ждем выполнения таймера (1 секунда)
await Future.delayed(const Duration(milliseconds: 1100));
expect(cubit.postsIds, contains(1));
});
test('handleVisibility - не добавляет ID при visibleFraction < 0.5', () async {
cubit.handleVisibility(visibleFraction: 0.3, id: 1, type: ViewableContentType.posts);
await Future.delayed(const Duration(milliseconds: 100));
expect(cubit.postsIds, isEmpty);
});
test('handleVisibility - отменяет таймер при уменьшении visibleFraction', () async {
when(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).thenAnswer((_) async => Future.value());
// Сначала видимость >= 0.5
cubit.handleVisibility(visibleFraction: 0.6, id: 1, type: ViewableContentType.posts);
await Future.delayed(const Duration(milliseconds: 500));
// Затем видимость < 0.5
cubit.handleVisibility(visibleFraction: 0.3, id: 1, type: ViewableContentType.posts);
await Future.delayed(const Duration(milliseconds: 700));
// ID не должен быть добавлен, так как таймер был отменен
expect(cubit.postsIds, isEmpty);
});
test('handleVisibility - не добавляет уже отправленный ID', () async {
when(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).thenAnswer((_) async => Future.value());
// Добавляем и отправляем ID
cubit.handleVisibility(visibleFraction: 0.6, id: 1, type: ViewableContentType.posts);
await Future.delayed(const Duration(milliseconds: 1100));
await Future.delayed(const Duration(milliseconds: 4100)); // Ждем debounce и отправку
// Пытаемся добавить тот же ID снова
cubit.handleVisibility(visibleFraction: 0.6, id: 1, type: ViewableContentType.posts);
await Future.delayed(const Duration(milliseconds: 100));
// Таймер не должен быть создан, так как ID уже отправлен
expect(cubit.postsIds, isEmpty);
});
test('handleVisibility - работает для разных типов контента', () async {
when(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).thenAnswer((_) async => Future.value());
cubit.handleVisibility(visibleFraction: 0.6, id: 1, type: ViewableContentType.posts);
cubit.handleVisibility(visibleFraction: 0.6, id: 2, type: ViewableContentType.plans);
cubit.handleVisibility(visibleFraction: 0.6, id: 3, type: ViewableContentType.diets);
cubit.handleVisibility(visibleFraction: 0.6, id: 4, type: ViewableContentType.recipes);
cubit.handleVisibility(
visibleFraction: 0.6,
id: 1,
type: ViewableContentType.posts,
);
await Future.delayed(const Duration(milliseconds: 1100));
expect(cubit.postsIds, contains(1));
expect(cubit.plansIds, contains(2));
expect(cubit.dietsIds, contains(3));
expect(cubit.recipesIds, contains(4));
expect(cubit.postsIds.contains(1), true);
});
test('_sendViews - отправляет просмотры через repository', () async {
when(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).thenAnswer((_) async => Future.value());
test('handleVisibility - не добавляет id если visibleFraction < 0.5', () {
cubit.handleVisibility(
visibleFraction: 0.3,
id: 1,
type: ViewableContentType.posts,
);
// Добавляем ID для разных типов
cubit.handleVisibility(visibleFraction: 0.6, id: 1, type: ViewableContentType.posts);
cubit.handleVisibility(visibleFraction: 0.6, id: 2, type: ViewableContentType.plans);
await Future.delayed(const Duration(milliseconds: 1100));
await Future.delayed(const Duration(milliseconds: 4100)); // Ждем debounce и отправку
// Проверяем, что repository был вызван минимум 2 раза (для posts и plans)
verify(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).called(greaterThanOrEqualTo(2));
expect(cubit.postsIds.contains(1), false);
});
test('close - отправляет оставшиеся просмотры и освобождает ресурсы', () async {
when(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).thenAnswer((_) async => Future.value());
test('handleVisibility - работает с разными типами контента', () async {
when(() => mockRepository.viewContent(viewModel: any(named: 'viewModel')))
.thenAnswer((_) async => {});
cubit.handleVisibility(
visibleFraction: 0.6,
id: 1,
type: ViewableContentType.plans,
);
// Добавляем ID
cubit.handleVisibility(visibleFraction: 0.6, id: 1, type: ViewableContentType.posts);
await Future.delayed(const Duration(milliseconds: 1100));
// Очищаем предыдущие взаимодействия перед проверкой
clearInteractions(mockViewsRepository);
expect(cubit.plansIds.contains(1), true);
});
test('close отправляет оставшиеся просмотры', () async {
when(() => mockRepository.viewContent(viewModel: any(named: 'viewModel')))
.thenAnswer((_) async => {});
cubit.postsIds.add(1);
cubit.plansIds.add(2);
cubit.dietsIds.add(3);
cubit.recipesIds.add(4);
// Закрываем cubit
await cubit.close();
// Проверяем, что repository был вызван для отправки оставшихся просмотров
verify(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).called(greaterThanOrEqualTo(1));
expect(cubit.isClosed, isTrue);
});
test('_sendViews - не отправляет уже отправленные ID', () async {
when(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).thenAnswer((_) async => Future.value());
// Добавляем и отправляем ID
cubit.handleVisibility(visibleFraction: 0.6, id: 1, type: ViewableContentType.posts);
await Future.delayed(const Duration(milliseconds: 1100));
await Future.delayed(const Duration(milliseconds: 4100));
// Очищаем моки
clearInteractions(mockViewsRepository);
// Закрываем cubit
await cubit.close();
// Repository не должен быть вызван, так как все ID уже отправлены
verifyNever(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel')));
verify(() => mockRepository.viewContent(viewModel: any(named: 'viewModel'))).called(greaterThan(0));
});
});
}

View File

@@ -0,0 +1,21 @@
import 'package:eatvio_app/core/views/cubit/views_state.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('ViewsCubitState', () {
test('creates initial state', () {
final state = ViewsCubitState();
expect(state, isA<ViewsCubitState>());
});
test('can create multiple instances', () {
final state1 = ViewsCubitState();
final state2 = ViewsCubitState();
expect(state1, isA<ViewsCubitState>());
expect(state2, isA<ViewsCubitState>());
});
});
}