forked from eatvio/eatvio_app
feat(test): update oauth handling and improve preferences management
This commit is contained in:
74
test/core/app_config_test.dart
Normal file
74
test/core/app_config_test.dart
Normal file
@@ -0,0 +1,74 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('AppConfig', () {
|
||||
setUp(() async {
|
||||
// Инициализируем dotenv для тестов
|
||||
dotenv.loadFromString(envString: '''
|
||||
BASE_URL=https://api.example.com
|
||||
DEV_BASE_URL=https://dev-api.example.com
|
||||
''');
|
||||
});
|
||||
|
||||
test('создание с buildType.prod - использует baseUrl', () {
|
||||
final config = AppConfig(buildType: AppBuildType.prod);
|
||||
|
||||
expect(config.buildType, AppBuildType.prod);
|
||||
expect(config.baseUrl, isNotEmpty);
|
||||
});
|
||||
|
||||
test('создание с buildType.dev - использует devBaseUrl', () {
|
||||
final config = AppConfig(buildType: AppBuildType.dev);
|
||||
|
||||
expect(config.buildType, AppBuildType.dev);
|
||||
expect(config.baseUrl, isNotEmpty);
|
||||
});
|
||||
|
||||
test('создание с buildType.tests - использует devBaseUrl', () {
|
||||
final config = AppConfig(buildType: AppBuildType.tests);
|
||||
|
||||
expect(config.buildType, AppBuildType.tests);
|
||||
expect(config.baseUrl, isNotEmpty);
|
||||
});
|
||||
|
||||
test('setBaseUrl - изменяет baseUrl', () {
|
||||
final config = AppConfig(buildType: AppBuildType.tests);
|
||||
const newUrl = 'http://test.example.com';
|
||||
|
||||
config.setBaseUrl(newUrl);
|
||||
|
||||
expect(config.baseUrl, newUrl);
|
||||
});
|
||||
|
||||
test('globalBlocProviders - возвращает список провайдеров', () {
|
||||
final config = AppConfig(buildType: AppBuildType.tests);
|
||||
|
||||
final providers = config.globalBlocProviders;
|
||||
|
||||
expect(providers, isNotEmpty);
|
||||
expect(providers.length, greaterThan(0));
|
||||
});
|
||||
|
||||
test('localizationDelegates - возвращает список делегатов', () {
|
||||
final config = AppConfig(buildType: AppBuildType.tests);
|
||||
|
||||
final delegates = config.localizationDelegates;
|
||||
|
||||
expect(delegates, isNotEmpty);
|
||||
expect(delegates.length, greaterThan(0));
|
||||
});
|
||||
|
||||
test('supportedLocales - возвращает список локалей', () {
|
||||
final config = AppConfig(buildType: AppBuildType.tests);
|
||||
|
||||
final locales = config.supportedLocales;
|
||||
|
||||
expect(locales, isNotEmpty);
|
||||
expect(locales.length, greaterThan(0));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
36
test/core/comments/models/comment_update_model_test.dart
Normal file
36
test/core/comments/models/comment_update_model_test.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:eatvio_app/core/comments/models/comment_update_model.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('CommentUpdateModel', () {
|
||||
test('создание модели через конструктор', () {
|
||||
final model = CommentUpdateModel(body: 'Обновлённый текст комментария');
|
||||
|
||||
expect(model.body, 'Обновлённый текст комментария');
|
||||
});
|
||||
|
||||
test('создание модели из JSON', () {
|
||||
final json = {'body': 'Текст комментария'};
|
||||
final model = CommentUpdateModel.fromJson(json);
|
||||
|
||||
expect(model.body, 'Текст комментария');
|
||||
});
|
||||
|
||||
test('преобразование модели в JSON', () {
|
||||
final model = CommentUpdateModel(body: 'Текст комментария');
|
||||
final json = model.toJson();
|
||||
|
||||
expect(json['body'], 'Текст комментария');
|
||||
});
|
||||
|
||||
test('равенство моделей', () {
|
||||
final model1 = CommentUpdateModel(body: 'Текст комментария');
|
||||
final model2 = CommentUpdateModel(body: 'Текст комментария');
|
||||
final model3 = CommentUpdateModel(body: 'Другой текст');
|
||||
|
||||
expect(model1, equals(model2));
|
||||
expect(model1, isNot(equals(model3)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
73
test/core/cubit/base_cubit_test.dart
Normal file
73
test/core/cubit/base_cubit_test.dart
Normal file
@@ -0,0 +1,73 @@
|
||||
import 'package:eatvio_app/core/cubit/base_cubit.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
// Простой тестовый cubit для проверки BaseCubit
|
||||
class TestState {
|
||||
final String? value;
|
||||
TestState({this.value});
|
||||
}
|
||||
|
||||
class TestCubit extends BaseCubit<TestState> {
|
||||
TestCubit() : super(TestState());
|
||||
|
||||
void testHandleError(Object error, [StackTrace? stackTrace]) {
|
||||
handleError(error, stackTrace);
|
||||
}
|
||||
|
||||
Future<String?> testSafeExecute() async {
|
||||
return await safeExecute(() async => 'success');
|
||||
}
|
||||
|
||||
Future<String?> testSafeExecuteWithError() async {
|
||||
return await safeExecute(() async => throw Exception('Test error'));
|
||||
}
|
||||
|
||||
Future<String?> testSafeExecuteWithState() async {
|
||||
return await safeExecuteWithState<String>(
|
||||
() async => 'result',
|
||||
loadingState: TestState(value: 'loading'),
|
||||
successState: (result) => TestState(value: result),
|
||||
errorState: (error) => TestState(value: 'error'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('BaseCubit', () {
|
||||
late TestCubit testCubit;
|
||||
|
||||
setUp(() {
|
||||
testCubit = TestCubit();
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
if (!testCubit.isClosed) {
|
||||
await testCubit.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('handleError - обрабатывает ошибку без исключений', () {
|
||||
expect(() => testCubit.testHandleError(Exception('Test error')), returnsNormally);
|
||||
});
|
||||
|
||||
test('safeExecute - успешно выполняет операцию', () async {
|
||||
final result = await testCubit.testSafeExecute();
|
||||
|
||||
expect(result, 'success');
|
||||
});
|
||||
|
||||
test('safeExecute - возвращает null при ошибке', () async {
|
||||
final result = await testCubit.testSafeExecuteWithError();
|
||||
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test('safeExecuteWithState - устанавливает состояния правильно', () async {
|
||||
final result = await testCubit.testSafeExecuteWithState();
|
||||
|
||||
expect(result, 'result');
|
||||
expect(testCubit.state.value, 'result');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
98
test/core/cubit/marketing/marketing_cubit_test.dart
Normal file
98
test/core/cubit/marketing/marketing_cubit_test.dart
Normal file
@@ -0,0 +1,98 @@
|
||||
import 'package:eatvio_app/core/cubit/marketing/marketing_cubit.dart';
|
||||
import 'package:eatvio_app/core/cubit/marketing/marketing_cubit_state.dart';
|
||||
import 'package:eatvio_app/core/prefs.dart';
|
||||
import 'package:eatvio_app/core/prefs_keys.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'marketing_cubit_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<Prefs>()])
|
||||
void main() {
|
||||
group('MarketingCubit', () {
|
||||
late MarketingCubit marketingCubit;
|
||||
late MockPrefs mockPrefs;
|
||||
|
||||
setUp(() async {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
mockPrefs = MockPrefs();
|
||||
marketingCubit = MarketingCubit(mockPrefs);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
if (!marketingCubit.isClosed) {
|
||||
await marketingCubit.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('начальное состояние - MarketingCubitState', () {
|
||||
expect(marketingCubit.state, const MarketingCubitState());
|
||||
});
|
||||
|
||||
test('checkRegistrationDate - возвращает false если registrationDate отсутствует', () async {
|
||||
when(mockPrefs.getInt(PrefsKeys.registrationDate)).thenReturn(null);
|
||||
|
||||
final result = await marketingCubit.checkRegistrationDate();
|
||||
|
||||
expect(result, isFalse);
|
||||
verify(mockPrefs.getInt(PrefsKeys.registrationDate)).called(1);
|
||||
});
|
||||
|
||||
test('checkRegistrationDate - возвращает false если маркетинговый bottom sheet уже был показан', () async {
|
||||
when(mockPrefs.getInt(PrefsKeys.registrationDate)).thenReturn(DateTime.now().millisecondsSinceEpoch);
|
||||
when(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).thenReturn(true);
|
||||
|
||||
final result = await marketingCubit.checkRegistrationDate();
|
||||
|
||||
expect(result, isFalse);
|
||||
verify(mockPrefs.getInt(PrefsKeys.registrationDate)).called(1);
|
||||
verify(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).called(1);
|
||||
});
|
||||
|
||||
test('checkRegistrationDate - возвращает false если прошло менее 14 дней', () async {
|
||||
final registrationDate = DateTime.now().subtract(const Duration(days: 10));
|
||||
when(mockPrefs.getInt(PrefsKeys.registrationDate)).thenReturn(registrationDate.millisecondsSinceEpoch);
|
||||
when(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).thenReturn(false);
|
||||
|
||||
final result = await marketingCubit.checkRegistrationDate();
|
||||
|
||||
expect(result, isFalse);
|
||||
verify(mockPrefs.getInt(PrefsKeys.registrationDate)).called(1);
|
||||
verify(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).called(1);
|
||||
verifyNever(mockPrefs.setBool(any, any));
|
||||
});
|
||||
|
||||
test('checkRegistrationDate - возвращает true если прошло 14 дней или больше', () async {
|
||||
final registrationDate = DateTime.now().subtract(const Duration(days: 14));
|
||||
when(mockPrefs.getInt(PrefsKeys.registrationDate)).thenReturn(registrationDate.millisecondsSinceEpoch);
|
||||
when(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).thenReturn(false);
|
||||
when(mockPrefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true)).thenAnswer((_) async => Future.value());
|
||||
|
||||
final result = await marketingCubit.checkRegistrationDate();
|
||||
|
||||
expect(result, isTrue);
|
||||
verify(mockPrefs.getInt(PrefsKeys.registrationDate)).called(1);
|
||||
verify(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).called(1);
|
||||
verify(mockPrefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true)).called(1);
|
||||
});
|
||||
|
||||
test('checkRegistrationDate - устанавливает флаг после показа bottom sheet', () async {
|
||||
final registrationDate = DateTime.now().subtract(const Duration(days: 15));
|
||||
when(mockPrefs.getInt(PrefsKeys.registrationDate)).thenReturn(registrationDate.millisecondsSinceEpoch);
|
||||
when(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).thenReturn(false);
|
||||
when(mockPrefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true)).thenAnswer((_) async => Future.value());
|
||||
|
||||
await marketingCubit.checkRegistrationDate();
|
||||
|
||||
verify(mockPrefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true)).called(1);
|
||||
});
|
||||
|
||||
test('checkRegistrationDate - пробрасывает исключение', () async {
|
||||
when(mockPrefs.getInt(PrefsKeys.registrationDate)).thenThrow(Exception('Ошибка чтения'));
|
||||
|
||||
expect(() => marketingCubit.checkRegistrationDate(), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
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:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('InternetConnectionCubit', () {
|
||||
late InternetConnectionCubit cubit;
|
||||
const MethodChannel connectivityChannel = MethodChannel('dev.fluttercommunity.plus/connectivity');
|
||||
|
||||
setUp(() {
|
||||
// Мокируем MethodChannel для connectivity
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
connectivityChannel,
|
||||
(MethodCall methodCall) async {
|
||||
if (methodCall.method == 'check') {
|
||||
return ['wifi']; // ConnectivityResult.wifi как строка
|
||||
}
|
||||
if (methodCall.method == 'onConnectivityChanged') {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
// Очищаем моки
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
connectivityChannel,
|
||||
null,
|
||||
);
|
||||
if (!cubit.isClosed) {
|
||||
await cubit.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('начальное состояние - isInternetAvailable = true', () {
|
||||
cubit = InternetConnectionCubit();
|
||||
expect(cubit.state.isInternetAvailable, isTrue);
|
||||
});
|
||||
|
||||
test('checkConnection - успешная проверка с интернетом', () async {
|
||||
cubit = InternetConnectionCubit();
|
||||
|
||||
// Ждем немного, чтобы cubit инициализировался
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
// Проверяем, что метод checkConnection не выбрасывает исключений
|
||||
await cubit.checkConnection();
|
||||
|
||||
// Состояние должно быть определено (true или false в зависимости от реального состояния)
|
||||
expect(cubit.state, isA<InternetConnectionState>());
|
||||
});
|
||||
|
||||
test('checkConnection - обработка PlatformException', () async {
|
||||
// Этот тест сложно выполнить без реального мока Connectivity,
|
||||
// так как Connectivity создается внутри cubit
|
||||
// Оставим базовую проверку, что метод не падает
|
||||
cubit = InternetConnectionCubit();
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
// Просто проверяем, что метод выполняется без ошибок
|
||||
await cubit.checkConnection();
|
||||
expect(cubit.state, isA<InternetConnectionState>());
|
||||
});
|
||||
|
||||
test('close - корректно закрывает cubit и освобождает ресурсы', () async {
|
||||
cubit = InternetConnectionCubit();
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
await cubit.close();
|
||||
|
||||
expect(cubit.isClosed, isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
112
test/core/token_storage_test.dart
Normal file
112
test/core/token_storage_test.dart
Normal file
@@ -0,0 +1,112 @@
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
void main() {
|
||||
group('TokenStorage', () {
|
||||
setUp(() async {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
});
|
||||
|
||||
test('haveTokenInStorage - возвращает false если токен отсутствует', () async {
|
||||
final result = await TokenStorage.haveTokenInStorage();
|
||||
|
||||
expect(result, isFalse);
|
||||
});
|
||||
|
||||
test('haveTokenInStorage - возвращает true если токен присутствует', () async {
|
||||
SharedPreferences.setMockInitialValues({'token': 'test_token_123'});
|
||||
|
||||
final result = await TokenStorage.haveTokenInStorage();
|
||||
|
||||
expect(result, isTrue);
|
||||
});
|
||||
|
||||
test('getTokenFromStorage - возвращает null если токен отсутствует', () async {
|
||||
final result = await TokenStorage.getTokenFromStorage();
|
||||
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test('getTokenFromStorage - возвращает токен если он присутствует', () async {
|
||||
const testToken = 'test_token_123';
|
||||
SharedPreferences.setMockInitialValues({'token': testToken});
|
||||
|
||||
final result = await TokenStorage.getTokenFromStorage();
|
||||
|
||||
expect(result, testToken);
|
||||
});
|
||||
|
||||
test('saveTokenInStorage - сохраняет токен', () async {
|
||||
const testToken = 'new_token_456';
|
||||
|
||||
await TokenStorage.saveTokenInStorage(testToken);
|
||||
|
||||
final savedToken = await TokenStorage.getTokenFromStorage();
|
||||
expect(savedToken, testToken);
|
||||
});
|
||||
|
||||
test('removeTokenInStorage - удаляет токен', () async {
|
||||
SharedPreferences.setMockInitialValues({'token': 'test_token_123'});
|
||||
|
||||
await TokenStorage.removeTokenInStorage();
|
||||
|
||||
final result = await TokenStorage.getTokenFromStorage();
|
||||
expect(result, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('SocketIdStorage', () {
|
||||
setUp(() async {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
});
|
||||
|
||||
test('haveSocketIdInStorage - возвращает false если socketId отсутствует', () async {
|
||||
final result = await SocketIdStorage.haveSocketIdInStorage();
|
||||
|
||||
expect(result, isFalse);
|
||||
});
|
||||
|
||||
test('haveSocketIdInStorage - возвращает true если socketId присутствует', () async {
|
||||
SharedPreferences.setMockInitialValues({'socketId': 'socket_id_123'});
|
||||
|
||||
final result = await SocketIdStorage.haveSocketIdInStorage();
|
||||
|
||||
expect(result, isTrue);
|
||||
});
|
||||
|
||||
test('getSocketIdFromStorage - возвращает null если socketId отсутствует', () async {
|
||||
final result = await SocketIdStorage.getSocketIdFromStorage();
|
||||
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test('getSocketIdFromStorage - возвращает socketId если он присутствует', () async {
|
||||
const testSocketId = 'socket_id_123';
|
||||
SharedPreferences.setMockInitialValues({'socketId': testSocketId});
|
||||
|
||||
final result = await SocketIdStorage.getSocketIdFromStorage();
|
||||
|
||||
expect(result, testSocketId);
|
||||
});
|
||||
|
||||
test('saveSocketIdInStorage - сохраняет socketId', () async {
|
||||
const testSocketId = 'new_socket_id_456';
|
||||
|
||||
await SocketIdStorage.saveSocketIdInStorage(testSocketId);
|
||||
|
||||
final savedSocketId = await SocketIdStorage.getSocketIdFromStorage();
|
||||
expect(savedSocketId, testSocketId);
|
||||
});
|
||||
|
||||
test('removeSocketIdInStorage - удаляет socketId', () async {
|
||||
SharedPreferences.setMockInitialValues({'socketId': 'socket_id_123'});
|
||||
|
||||
await SocketIdStorage.removeSocketIdInStorage();
|
||||
|
||||
final result = await SocketIdStorage.getSocketIdFromStorage();
|
||||
expect(result, isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
59
test/core/utils/toasts_test.dart
Normal file
59
test/core/utils/toasts_test.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Toasts', () {
|
||||
setUp(() {
|
||||
// Mock Fluttertoast plugin
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
const MethodChannel('PonnamKarthik/fluttertoast'),
|
||||
(MethodCall methodCall) async {
|
||||
return null;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
// Clear mock handlers
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
const MethodChannel('PonnamKarthik/fluttertoast'),
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
test('showInfoToast - вызывает Fluttertoast без ошибок', () {
|
||||
expect(() => showInfoToast('Информационное сообщение'), returnsNormally);
|
||||
});
|
||||
|
||||
test('showErrorToast - вызывает Fluttertoast без ошибок', () {
|
||||
expect(() => showErrorToast('Сообщение об ошибке'), returnsNormally);
|
||||
});
|
||||
|
||||
test('showSuccessToast - вызывает Fluttertoast без ошибок', () {
|
||||
expect(() => showSuccessToast('Успешное сообщение'), returnsNormally);
|
||||
});
|
||||
|
||||
test('showInternetConnectionToast - вызывает Fluttertoast без ошибок', () {
|
||||
expect(() => showInternetConnectionToast(), returnsNormally);
|
||||
});
|
||||
|
||||
test('showInfoToast - работает с пустой строкой', () {
|
||||
expect(() => showInfoToast(''), returnsNormally);
|
||||
});
|
||||
|
||||
test('showErrorToast - работает с длинным сообщением', () {
|
||||
final longMessage = 'Очень длинное сообщение об ошибке ' * 10;
|
||||
expect(() => showErrorToast(longMessage), returnsNormally);
|
||||
});
|
||||
|
||||
test('showSuccessToast - работает с различными сообщениями', () {
|
||||
expect(() => showSuccessToast('Операция выполнена успешно'), returnsNormally);
|
||||
expect(() => showSuccessToast('Данные сохранены'), returnsNormally);
|
||||
expect(() => showSuccessToast('Загрузка завершена'), returnsNormally);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
154
test/core/views/cubit/views_cubit_test.dart
Normal file
154
test/core/views/cubit/views_cubit_test.dart
Normal file
@@ -0,0 +1,154 @@
|
||||
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/presentation/pages/feed_screen.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'views_cubit_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<ViewsRepository>()])
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('ViewsCubit', () {
|
||||
late ViewsCubit cubit;
|
||||
late MockViewsRepository mockViewsRepository;
|
||||
|
||||
setUp(() {
|
||||
mockViewsRepository = MockViewsRepository();
|
||||
cubit = ViewsCubit(viewsRepository: mockViewsRepository);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
if (!cubit.isClosed) {
|
||||
await cubit.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('начальное состояние - ViewsCubitState', () {
|
||||
expect(cubit.state, isA<ViewsCubitState>());
|
||||
});
|
||||
|
||||
test('handleVisibility - добавляет ID при visibleFraction >= 0.5', () async {
|
||||
when(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).thenAnswer((_) async => Future.value());
|
||||
|
||||
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);
|
||||
|
||||
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));
|
||||
});
|
||||
|
||||
test('_sendViews - отправляет просмотры через repository', () async {
|
||||
when(mockViewsRepository.viewContent(viewModel: anyNamed('viewModel'))).thenAnswer((_) async => Future.value());
|
||||
|
||||
// Добавляем 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));
|
||||
});
|
||||
|
||||
test('close - отправляет оставшиеся просмотры и освобождает ресурсы', () 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));
|
||||
|
||||
// Очищаем предыдущие взаимодействия перед проверкой
|
||||
clearInteractions(mockViewsRepository);
|
||||
|
||||
// Закрываем 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')));
|
||||
});
|
||||
});
|
||||
}
|
||||
71
test/core/views/repository/views_repository_test.dart
Normal file
71
test/core/views/repository/views_repository_test.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
import 'package:eatvio_app/core/views/repository/views_repository.dart';
|
||||
import 'package:eatvio_app/core/views/service/views_service.dart';
|
||||
import 'package:eatvio_app/features/feed/models/view_model.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'views_repository_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<ViewsService>()])
|
||||
void main() {
|
||||
group('ViewsRepository', () {
|
||||
late ViewsRepository viewsRepository;
|
||||
late MockViewsService mockViewsService;
|
||||
|
||||
setUp(() {
|
||||
mockViewsService = MockViewsService();
|
||||
viewsRepository = ViewsRepository(viewsService: mockViewsService);
|
||||
});
|
||||
|
||||
test('viewContent - успешная отправка просмотров', () async {
|
||||
final viewModel = ViewModel(
|
||||
type: 'blog_posts',
|
||||
ids: [1, 2, 3],
|
||||
);
|
||||
|
||||
when(mockViewsService.viewContent(viewModel: viewModel))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
await viewsRepository.viewContent(viewModel: viewModel);
|
||||
|
||||
verify(mockViewsService.viewContent(viewModel: viewModel)).called(1);
|
||||
});
|
||||
|
||||
test('viewContent - пробрасывает исключение', () async {
|
||||
final viewModel = ViewModel(
|
||||
type: 'blog_posts',
|
||||
ids: [1, 2, 3],
|
||||
);
|
||||
|
||||
when(mockViewsService.viewContent(viewModel: viewModel))
|
||||
.thenThrow(Exception('Ошибка отправки'));
|
||||
|
||||
expect(
|
||||
() => viewsRepository.viewContent(viewModel: viewModel),
|
||||
throwsException,
|
||||
);
|
||||
});
|
||||
|
||||
test('viewContent - работает с разными типами контента', () async {
|
||||
final postsModel = ViewModel(type: 'blog_posts', ids: [1]);
|
||||
final plansModel = ViewModel(type: 'plans', ids: [2]);
|
||||
final dietsModel = ViewModel(type: 'diets', ids: [3]);
|
||||
final recipesModel = ViewModel(type: 'recipes', ids: [4]);
|
||||
|
||||
when(mockViewsService.viewContent(viewModel: anyNamed('viewModel')))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
await viewsRepository.viewContent(viewModel: postsModel);
|
||||
await viewsRepository.viewContent(viewModel: plansModel);
|
||||
await viewsRepository.viewContent(viewModel: dietsModel);
|
||||
await viewsRepository.viewContent(viewModel: recipesModel);
|
||||
|
||||
verify(mockViewsService.viewContent(viewModel: postsModel)).called(1);
|
||||
verify(mockViewsService.viewContent(viewModel: plansModel)).called(1);
|
||||
verify(mockViewsService.viewContent(viewModel: dietsModel)).called(1);
|
||||
verify(mockViewsService.viewContent(viewModel: recipesModel)).called(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_state.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('NewMessagesCubit', () {
|
||||
late NewMessagesCubit newMessagesCubit;
|
||||
|
||||
setUp(() {
|
||||
newMessagesCubit = NewMessagesCubit();
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
if (!newMessagesCubit.isClosed) {
|
||||
await newMessagesCubit.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('начальное состояние - NewMessagesState.empty', () {
|
||||
expect(newMessagesCubit.state, const NewMessagesState.empty());
|
||||
});
|
||||
|
||||
test('incrementCounter - увеличивает счётчик сообщений', () {
|
||||
newMessagesCubit.incrementCounter();
|
||||
|
||||
expect(newMessagesCubit.state, isA<NewMessagesStateLoaded>());
|
||||
final loadedState = newMessagesCubit.state as NewMessagesStateLoaded;
|
||||
expect(loadedState.messagesCount, 1);
|
||||
});
|
||||
|
||||
test('incrementCounter - увеличивает счётчик несколько раз', () {
|
||||
newMessagesCubit.incrementCounter();
|
||||
newMessagesCubit.incrementCounter();
|
||||
newMessagesCubit.incrementCounter();
|
||||
|
||||
expect(newMessagesCubit.state, isA<NewMessagesStateLoaded>());
|
||||
final loadedState = newMessagesCubit.state as NewMessagesStateLoaded;
|
||||
expect(loadedState.messagesCount, 3);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import 'package:eatvio_app/features/feed/models/feed_consultant_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/consultants_cubit/consultants_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/consultants_cubit/consultants_state.dart';
|
||||
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'consultants_cubit_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<FeedRepository>()])
|
||||
void main() {
|
||||
group('ConsultantsCubit', () {
|
||||
late ConsultantsCubit consultantsCubit;
|
||||
late MockFeedRepository mockFeedRepository;
|
||||
|
||||
setUp(() {
|
||||
mockFeedRepository = MockFeedRepository();
|
||||
consultantsCubit = ConsultantsCubit(feedRepository: mockFeedRepository);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
if (!consultantsCubit.isClosed) {
|
||||
await consultantsCubit.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('начальное состояние - ConsultantsState', () {
|
||||
expect(consultantsCubit.state, const ConsultantsState());
|
||||
});
|
||||
|
||||
test('getConsultants - успешная загрузка консультантов', () async {
|
||||
final testConsultants = [
|
||||
FeedConsultantModel(
|
||||
slug: 'consultant-1',
|
||||
name: 'Консультант 1',
|
||||
description: 'Описание консультанта 1',
|
||||
totalScores: 100,
|
||||
photo: 'https://example.com/icon1.jpg',
|
||||
badges: [],
|
||||
ratingPlace: 1,
|
||||
),
|
||||
FeedConsultantModel(
|
||||
slug: 'consultant-2',
|
||||
name: 'Консультант 2',
|
||||
description: 'Описание консультанта 2',
|
||||
totalScores: 200,
|
||||
photo: 'https://example.com/icon2.jpg',
|
||||
badges: [],
|
||||
ratingPlace: 2,
|
||||
),
|
||||
];
|
||||
|
||||
when(mockFeedRepository.getConsultants()).thenAnswer((_) async => testConsultants);
|
||||
|
||||
await consultantsCubit.getConsultants();
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
expect(consultantsCubit.state, isA<ConsultantsStateLoaded>());
|
||||
final loadedState = consultantsCubit.state as ConsultantsStateLoaded;
|
||||
expect(loadedState.consultantsList.length, 2);
|
||||
expect(loadedState.isLoading, false);
|
||||
verify(mockFeedRepository.getConsultants()).called(1);
|
||||
});
|
||||
|
||||
test('getConsultants - переход в состояние loading', () async {
|
||||
when(mockFeedRepository.getConsultants()).thenAnswer((_) async {
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
return [];
|
||||
});
|
||||
|
||||
final future = consultantsCubit.getConsultants();
|
||||
await Future.delayed(const Duration(milliseconds: 10));
|
||||
|
||||
expect(consultantsCubit.state, const ConsultantsState.loading());
|
||||
await future;
|
||||
});
|
||||
|
||||
test('getConsultants - обработка ошибки', () async {
|
||||
when(mockFeedRepository.getConsultants()).thenThrow(Exception('Ошибка загрузки'));
|
||||
|
||||
await consultantsCubit.getConsultants();
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
expect(consultantsCubit.state, isA<ConsultantsStateError>());
|
||||
final errorState = consultantsCubit.state as ConsultantsStateError;
|
||||
expect(errorState.errorMessage, contains('Exception'));
|
||||
});
|
||||
|
||||
test('getConsultants - загрузка с пустым списком', () async {
|
||||
when(mockFeedRepository.getConsultants()).thenAnswer((_) async => []);
|
||||
|
||||
await consultantsCubit.getConsultants();
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
expect(consultantsCubit.state, isA<ConsultantsStateLoaded>());
|
||||
final loadedState = consultantsCubit.state as ConsultantsStateLoaded;
|
||||
expect(loadedState.consultantsList, isEmpty);
|
||||
expect(loadedState.isLoading, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user