Merge pull request 'feat(tests)' (#876) from misha/eatvio_app:main into main

Reviewed-on: eatvio/eatvio_app#876
This commit is contained in:
2026-01-28 09:44:50 +00:00
78 changed files with 4501 additions and 133 deletions

View File

@@ -0,0 +1,232 @@
import 'package:eatvio_app/features/auth/models/oauth_verification_confirm_request.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('OAuthVerificationConfirmRequest', () {
test('creates instance with required fields', () {
const request = OAuthVerificationConfirmRequest(
accessToken: 'access_token_123',
refreshToken: 'refresh_token_123',
clientId: 'client_id_123',
provider: 'vk',
id: 'user_id_123',
code: 'code_123',
verificationType: 'email',
firstName: 'John',
lastName: 'Doe',
dateOfBirth: '1990-01-01',
gender: 'male',
weight: 70,
height: 180,
);
expect(request.accessToken, 'access_token_123');
expect(request.refreshToken, 'refresh_token_123');
expect(request.clientId, 'client_id_123');
expect(request.provider, 'vk');
expect(request.id, 'user_id_123');
expect(request.code, 'code_123');
expect(request.verificationType, 'email');
expect(request.firstName, 'John');
expect(request.lastName, 'Doe');
expect(request.dateOfBirth, '1990-01-01');
expect(request.gender, 'male');
expect(request.weight, 70);
expect(request.height, 180);
});
test('creates instance with optional email', () {
const request = OAuthVerificationConfirmRequest(
accessToken: 'access_token_123',
refreshToken: 'refresh_token_123',
clientId: 'client_id_123',
provider: 'vk',
id: 'user_id_123',
email: 'test@example.com',
code: 'code_123',
verificationType: 'email',
firstName: 'John',
lastName: 'Doe',
dateOfBirth: '1990-01-01',
gender: 'male',
weight: 70,
height: 180,
);
expect(request.email, 'test@example.com');
});
test('creates instance with optional phone', () {
const request = OAuthVerificationConfirmRequest(
accessToken: 'access_token_123',
refreshToken: 'refresh_token_123',
clientId: 'client_id_123',
provider: 'vk',
id: 'user_id_123',
phone: '+79991234567',
code: 'code_123',
verificationType: 'phone',
firstName: 'John',
lastName: 'Doe',
dateOfBirth: '1990-01-01',
gender: 'male',
weight: 70,
height: 180,
);
expect(request.phone, '+79991234567');
});
test('fromJson creates instance from JSON', () {
final json = {
'access_token': 'access_token_123',
'refresh_token': 'refresh_token_123',
'client_id': 'client_id_123',
'provider': 'vk',
'id': 'user_id_123',
'code': 'code_123',
'verification_type': 'email',
'first_name': 'John',
'last_name': 'Doe',
'date_of_birth': '1990-01-01',
'gender': 'male',
'weight': 70,
'height': 180,
};
final request = OAuthVerificationConfirmRequest.fromJson(json);
expect(request.accessToken, 'access_token_123');
expect(request.refreshToken, 'refresh_token_123');
expect(request.clientId, 'client_id_123');
expect(request.provider, 'vk');
expect(request.id, 'user_id_123');
expect(request.code, 'code_123');
expect(request.verificationType, 'email');
expect(request.firstName, 'John');
expect(request.lastName, 'Doe');
expect(request.dateOfBirth, '1990-01-01');
expect(request.gender, 'male');
expect(request.weight, 70);
expect(request.height, 180);
});
test('toJson converts instance to JSON', () {
const request = OAuthVerificationConfirmRequest(
accessToken: 'access_token_123',
refreshToken: 'refresh_token_123',
clientId: 'client_id_123',
provider: 'vk',
id: 'user_id_123',
email: 'test@example.com',
code: 'code_123',
verificationType: 'email',
firstName: 'John',
lastName: 'Doe',
dateOfBirth: '1990-01-01',
gender: 'male',
weight: 70,
height: 180,
);
final json = request.toJson();
expect(json['access_token'], 'access_token_123');
expect(json['refresh_token'], 'refresh_token_123');
expect(json['client_id'], 'client_id_123');
expect(json['provider'], 'vk');
expect(json['id'], 'user_id_123');
expect(json['email'], 'test@example.com');
expect(json['code'], 'code_123');
expect(json['verification_type'], 'email');
expect(json['first_name'], 'John');
expect(json['last_name'], 'Doe');
expect(json['date_of_birth'], '1990-01-01');
expect(json['gender'], 'male');
expect(json['weight'], 70);
expect(json['height'], 180);
});
test('copyWith creates new instance with updated values', () {
const original = OAuthVerificationConfirmRequest(
accessToken: 'access_token_123',
refreshToken: 'refresh_token_123',
clientId: 'client_id_123',
provider: 'vk',
id: 'user_id_123',
code: 'code_123',
verificationType: 'email',
firstName: 'John',
lastName: 'Doe',
dateOfBirth: '1990-01-01',
gender: 'male',
weight: 70,
height: 180,
);
final updated = original.copyWith(firstName: 'Jane', weight: 65);
expect(updated.firstName, 'Jane');
expect(updated.weight, 65);
expect(original.firstName, 'John');
expect(original.weight, 70);
});
test('copyWith keeps original values when not updated', () {
const original = OAuthVerificationConfirmRequest(
accessToken: 'access_token_123',
refreshToken: 'refresh_token_123',
clientId: 'client_id_123',
provider: 'vk',
id: 'user_id_123',
code: 'code_123',
verificationType: 'email',
firstName: 'John',
lastName: 'Doe',
dateOfBirth: '1990-01-01',
gender: 'male',
weight: 70,
height: 180,
);
final updated = original.copyWith();
expect(updated.accessToken, original.accessToken);
expect(updated.firstName, original.firstName);
});
test('handles different providers', () {
const vkRequest = OAuthVerificationConfirmRequest(
accessToken: 'token',
refreshToken: 'refresh',
clientId: 'client',
provider: 'vk',
id: 'id',
code: 'code',
verificationType: 'email',
firstName: 'John',
lastName: 'Doe',
dateOfBirth: '1990-01-01',
gender: 'male',
weight: 70,
height: 180,
);
const googleRequest = OAuthVerificationConfirmRequest(
accessToken: 'token',
refreshToken: 'refresh',
clientId: 'client',
provider: 'google',
id: 'id',
code: 'code',
verificationType: 'email',
firstName: 'John',
lastName: 'Doe',
dateOfBirth: '1990-01-01',
gender: 'male',
weight: 70,
height: 180,
);
expect(vkRequest.provider, 'vk');
expect(googleRequest.provider, 'google');
});
});
}

View File

@@ -0,0 +1,152 @@
import 'package:eatvio_app/features/auth/models/oauth_verification_confirm_response.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('OAuthVerificationConfirmResponse', () {
test('creates instance with required fields', () {
const response = OAuthVerificationConfirmResponse(
success: true,
message: 'Success',
token: 'token_123',
user: {'id': 1, 'name': 'John'},
isNewUser: false,
);
expect(response.success, true);
expect(response.message, 'Success');
expect(response.token, 'token_123');
expect(response.user, {'id': 1, 'name': 'John'});
expect(response.isNewUser, false);
});
test('creates instance with isNewUser true', () {
const response = OAuthVerificationConfirmResponse(
success: true,
message: 'New user created',
token: 'token_123',
user: {'id': 1, 'name': 'John'},
isNewUser: true,
);
expect(response.isNewUser, true);
});
test('fromJson creates instance from JSON', () {
final json = {
'success': true,
'message': 'Success',
'token': 'token_123',
'user': {'id': 1, 'name': 'John'},
'is_new_user': false,
};
final response = OAuthVerificationConfirmResponse.fromJson(json);
expect(response.success, true);
expect(response.message, 'Success');
expect(response.token, 'token_123');
expect(response.user, {'id': 1, 'name': 'John'});
expect(response.isNewUser, false);
});
test('toJson converts instance to JSON', () {
const response = OAuthVerificationConfirmResponse(
success: true,
message: 'Success',
token: 'token_123',
user: {'id': 1, 'name': 'John'},
isNewUser: false,
);
final json = response.toJson();
expect(json['success'], true);
expect(json['message'], 'Success');
expect(json['token'], 'token_123');
expect(json['user'], {'id': 1, 'name': 'John'});
expect(json['is_new_user'], false);
});
test('equality works correctly', () {
const response1 = OAuthVerificationConfirmResponse(
success: true,
message: 'Success',
token: 'token_123',
user: {'id': 1},
isNewUser: false,
);
const response2 = OAuthVerificationConfirmResponse(
success: true,
message: 'Success',
token: 'token_123',
user: {'id': 1},
isNewUser: false,
);
const response3 = OAuthVerificationConfirmResponse(
success: false,
message: 'Error',
token: 'token_123',
user: {'id': 1},
isNewUser: false,
);
expect(response1, equals(response2));
expect(response1, isNot(equals(response3)));
});
test('copyWith creates new instance with updated values', () {
const original = OAuthVerificationConfirmResponse(
success: true,
message: 'Success',
token: 'token_123',
user: {'id': 1, 'name': 'John'},
isNewUser: false,
);
final updated = original.copyWith(success: false, message: 'Error', isNewUser: true);
expect(updated.success, false);
expect(updated.message, 'Error');
expect(updated.isNewUser, true);
expect(original.success, true);
expect(original.isNewUser, false);
});
test('copyWith keeps original values when not updated', () {
const original = OAuthVerificationConfirmResponse(
success: true,
message: 'Success',
token: 'token_123',
user: {'id': 1, 'name': 'John'},
isNewUser: false,
);
final updated = original.copyWith();
expect(updated.success, original.success);
expect(updated.message, original.message);
expect(updated.token, original.token);
expect(updated.user, original.user);
expect(updated.isNewUser, original.isNewUser);
});
test('handles different user data', () {
const response1 = OAuthVerificationConfirmResponse(
success: true,
message: 'Success',
token: 'token_123',
user: {'id': 1, 'name': 'John'},
isNewUser: false,
);
const response2 = OAuthVerificationConfirmResponse(
success: true,
message: 'Success',
token: 'token_123',
user: {'id': 2, 'name': 'Jane', 'email': 'jane@example.com'},
isNewUser: true,
);
expect(response1.user['id'], 1);
expect(response2.user['id'], 2);
expect(response2.user['email'], 'jane@example.com');
});
});
}

View File

@@ -0,0 +1,76 @@
import 'package:eatvio_app/features/auth/models/oauth_verification_request_response.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('OAuthVerificationRequestResponse', () {
test('creates instance with required fields', () {
const response = OAuthVerificationRequestResponse(
email: 'test@example.com',
status: 'pending',
);
expect(response.email, 'test@example.com');
expect(response.status, 'pending');
});
test('creates instance with different status values', () {
const pending = OAuthVerificationRequestResponse(
email: 'test@example.com',
status: 'pending',
);
const completed = OAuthVerificationRequestResponse(
email: 'test@example.com',
status: 'completed',
);
const failed = OAuthVerificationRequestResponse(
email: 'test@example.com',
status: 'failed',
);
expect(pending.status, 'pending');
expect(completed.status, 'completed');
expect(failed.status, 'failed');
});
test('equality works correctly', () {
const response1 = OAuthVerificationRequestResponse(
email: 'test@example.com',
status: 'pending',
);
const response2 = OAuthVerificationRequestResponse(
email: 'test@example.com',
status: 'pending',
);
const response3 = OAuthVerificationRequestResponse(
email: 'other@example.com',
status: 'pending',
);
expect(response1, equals(response2));
expect(response1, isNot(equals(response3)));
});
test('copyWith creates new instance with updated values', () {
const original = OAuthVerificationRequestResponse(
email: 'test@example.com',
status: 'pending',
);
final updated = original.copyWith(status: 'completed');
expect(updated.email, 'test@example.com');
expect(updated.status, 'completed');
expect(original.status, 'pending');
});
test('copyWith keeps original values when not updated', () {
const original = OAuthVerificationRequestResponse(
email: 'test@example.com',
status: 'pending',
);
final updated = original.copyWith();
expect(updated.email, original.email);
expect(updated.status, original.status);
});
});
}

View File

@@ -0,0 +1,204 @@
import 'package:eatvio_app/core/firebase_api.dart';
import 'package:eatvio_app/core/prefs.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/features/auth/models/token_model.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_state.dart';
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:flutter/services.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 'auth_cubit_test.mocks.dart';
@GenerateNiceMocks([
MockSpec<AuthRepository>(),
MockSpec<ProfileRepository>(),
MockSpec<FirebaseApi>(),
MockSpec<Prefs>(),
])
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('AuthCubit', () {
AuthCubit? cubit;
late MockAuthRepository mockAuthRepository;
late MockProfileRepository mockProfileRepository;
late MockFirebaseApi mockFirebaseApi;
late MockPrefs mockPrefs;
setUpAll(() {
// Mock Fluttertoast plugin
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
const MethodChannel('PonnamKarthik/fluttertoast'),
(MethodCall methodCall) async {
return null;
},
);
// Mock SharedPreferences for TokenStorage
SharedPreferences.setMockInitialValues({});
});
setUp(() async {
mockAuthRepository = MockAuthRepository();
mockProfileRepository = MockProfileRepository();
mockFirebaseApi = MockFirebaseApi();
mockPrefs = MockPrefs();
// Clear TokenStorage cache
TokenStorage.clearCache();
final prefs = await SharedPreferences.getInstance();
await prefs.clear();
});
tearDownAll(() {
// Clear mock handlers
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
const MethodChannel('PonnamKarthik/fluttertoast'),
null,
);
});
tearDown(() async {
if (cubit != null && !cubit!.isClosed) {
await cubit!.close();
cubit = null;
}
});
test('начальное состояние - AuthState', () {
cubit = AuthCubit(
authRepository: mockAuthRepository,
profileRepository: mockProfileRepository,
firebaseApi: mockFirebaseApi,
prefs: mockPrefs,
);
expect(cubit!.state, isA<AuthState>());
});
test('getTokenByCredentials - успешная аутентификация', () async {
cubit = AuthCubit(
authRepository: mockAuthRepository,
profileRepository: mockProfileRepository,
firebaseApi: mockFirebaseApi,
prefs: mockPrefs,
);
const email = 'test@example.com';
const password = 'password123';
const token = 'test_token_123';
when(mockAuthRepository.getTokenByCredentials(email, password))
.thenAnswer((_) async => const TokenModel(token: token));
// Убеждаемся, что SharedPreferences готов к использованию
final prefs = await SharedPreferences.getInstance();
// Очищаем токен перед тестом
await prefs.remove('token');
final result = await cubit!.getTokenByCredentials(email, password);
// Даем время для завершения всех асинхронных операций, включая сохранение токена
await Future.delayed(const Duration(milliseconds: 200));
expect(result, true);
expect(cubit!.state, isA<AuthStateLoaded>());
expect((cubit!.state as AuthStateLoaded).token, token);
});
test('getTokenByCredentials - обработка ошибки', () async {
cubit = AuthCubit(
authRepository: mockAuthRepository,
profileRepository: mockProfileRepository,
firebaseApi: mockFirebaseApi,
prefs: mockPrefs,
);
const email = 'test@example.com';
const password = 'wrong_password';
when(mockAuthRepository.getTokenByCredentials(email, password))
.thenThrow(Exception('Invalid credentials'));
final result = await cubit!.getTokenByCredentials(email, password);
// Даем время для завершения всех асинхронных операций, включая showErrorToast
await Future.delayed(const Duration(milliseconds: 200));
expect(result, false);
expect(cubit!.state, isA<AuthStateError>());
expect((cubit!.state as AuthStateError).errorMessage, contains('Invalid credentials'));
});
test('registerAnonymousPushToken - вызывает firebaseApi когда токен еще не отправлен', () async {
cubit = AuthCubit(
authRepository: mockAuthRepository,
profileRepository: mockProfileRepository,
firebaseApi: mockFirebaseApi,
prefs: mockPrefs,
);
when(mockPrefs.getBool(any)).thenReturn(false);
when(mockFirebaseApi.registerAnonymousPushToken())
.thenAnswer((_) async => Future.value());
when(mockFirebaseApi.getToken()).thenAnswer((_) async => 'test_token');
await cubit!.registerAnonymousPushToken();
verify(mockFirebaseApi.registerAnonymousPushToken()).called(1);
verify(mockPrefs.setBool(any, true)).called(1);
});
test('registerAnonymousPushToken - не вызывает firebaseApi когда токен уже отправлен', () async {
cubit = AuthCubit(
authRepository: mockAuthRepository,
profileRepository: mockProfileRepository,
firebaseApi: mockFirebaseApi,
prefs: mockPrefs,
);
when(mockPrefs.getBool(any)).thenReturn(true);
await cubit!.registerAnonymousPushToken();
verifyNever(mockFirebaseApi.registerAnonymousPushToken());
});
test('registerPushToken - вызывает firebaseApi когда есть токен в хранилище', () async {
cubit = AuthCubit(
authRepository: mockAuthRepository,
profileRepository: mockProfileRepository,
firebaseApi: mockFirebaseApi,
prefs: mockPrefs,
);
when(mockFirebaseApi.registerPushToken())
.thenAnswer((_) async => Future.value());
await cubit!.registerPushToken();
verify(mockFirebaseApi.registerPushToken()).called(1);
});
test('registerPushToken - не вызывает firebaseApi когда нет токена в хранилище', () async {
cubit = AuthCubit(
authRepository: mockAuthRepository,
profileRepository: mockProfileRepository,
firebaseApi: mockFirebaseApi,
prefs: mockPrefs,
);
// TokenStorage.haveTokenInStorage() вернет false
// registerPushToken проверяет это внутри и не вызывает firebaseApi
await cubit!.registerPushToken();
// В тестах TokenStorage может быть не инициализирован, поэтому метод может не вызваться
// Проверяем, что метод завершился без ошибок
expect(cubit!.state, isA<AuthState>());
});
});
}

View File

@@ -0,0 +1,83 @@
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_state.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('AuthState', () {
test('creates initial AuthState', () {
const state = AuthState();
expect(state, isA<AuthState>());
});
test('creates loading AuthState', () {
const state = AuthState.loading();
expect(state, isA<AuthState>());
expect(state, isA<AuthStateLoading>());
});
test('creates loaded AuthState with token', () {
const state = AuthState.loaded(token: 'test_token_123');
expect(state, isA<AuthState>());
expect(state, isA<AuthStateLoaded>());
expect((state as AuthStateLoaded).token, 'test_token_123');
});
test('creates error AuthState with error message', () {
const state = AuthState.error(errorMessage: 'Test error');
expect(state, isA<AuthState>());
expect(state, isA<AuthStateError>());
expect((state as AuthStateError).errorMessage, 'Test error');
});
test('different state types are not equal', () {
const loading = AuthState.loading();
const loaded = AuthState.loaded(token: 'token');
const error = AuthState.error(errorMessage: 'Error');
expect(loading == loaded, isFalse);
expect(loading == error, isFalse);
expect(loaded == error, isFalse);
});
test('same state types with same data are equal', () {
const state1 = AuthState.loaded(token: 'token');
const state2 = AuthState.loaded(token: 'token');
expect(state1 == state2, isTrue);
});
test('same state types with different data are not equal', () {
const state1 = AuthState.loaded(token: 'token1');
const state2 = AuthState.loaded(token: 'token2');
expect(state1 == state2, isFalse);
});
test('error states with different messages are not equal', () {
const state1 = AuthState.error(errorMessage: 'Error 1');
const state2 = AuthState.error(errorMessage: 'Error 2');
expect(state1 == state2, isFalse);
});
test('error states with same messages are equal', () {
const state1 = AuthState.error(errorMessage: 'Error');
const state2 = AuthState.error(errorMessage: 'Error');
expect(state1 == state2, isTrue);
});
test('initial AuthState instances are equal', () {
const state1 = AuthState();
const state2 = AuthState();
expect(state1 == state2, isTrue);
});
test('loading AuthState instances are equal', () {
const state1 = AuthState.loading();
const state2 = AuthState.loading();
expect(state1 == state2, isTrue);
});
});
}

View File

@@ -0,0 +1,43 @@
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/auth/presentation/pages/scan_qr_code_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('ScanQRCodeScreen smoke', () {
setUpAll(() async {
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
});
setUp(() async {
await sl.reset();
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
});
Widget wrap(Widget child) {
return MaterialApp(
theme: ThemeHelper().themeData(),
home: child,
);
}
testWidgets('renders scan QR code screen', (tester) async {
await tester.pumpWidget(wrap(const ScanQRCodeScreen()));
await tester.pumpAndSettle();
expect(find.byType(ScanQRCodeScreen), findsOneWidget);
expect(find.text('Сканирование QR-кода'), findsOneWidget);
});
testWidgets('displays QR scanner UI', (tester) async {
await tester.pumpWidget(wrap(const ScanQRCodeScreen()));
await tester.pumpAndSettle();
expect(find.byType(ScanQRCodeScreen), findsOneWidget);
});
});
}

View File

@@ -17,6 +17,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:network_image_mock/network_image_mock.dart';
import 'blog_post_screen_smoke_test.mocks.dart';
@@ -105,5 +106,58 @@ void main() {
expect(find.byType(BlogPostScreen), findsOneWidget);
});
testWidgets('renders error state', (tester) async {
await mockNetworkImagesFor(() async {
final blogPostCubit = MockBlogPostCubit();
final feedCubit = MockFeedCubit();
final commentsCubit = MockCommentsCubit();
final state = BlogPostState(isLoading: false, blogPostModel: null, error: 'Test error');
when(blogPostCubit.state).thenReturn(state);
when(blogPostCubit.stream).thenAnswer((_) => Stream.value(state));
when(blogPostCubit.close()).thenAnswer((_) async => Future<void>.value());
when(feedCubit.viewPost(any)).thenAnswer((_) async => Future<void>.value());
when(feedCubit.stream).thenAnswer((_) => const Stream.empty());
when(feedCubit.close()).thenAnswer((_) async => Future<void>.value());
when(commentsCubit.state).thenReturn(const CommentsState());
when(commentsCubit.stream).thenAnswer((_) => const Stream<CommentsState>.empty());
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
when(commentsCubit.getComments(any, any, any)).thenAnswer((_) async => Future<void>.value());
registerMocks(blogPostCubit, commentsCubit);
await tester.pumpWidget(wrap(blogPostCubit, feedCubit, commentsCubit, const BlogPostScreen(id: 1)));
await tester.pump();
expect(find.byType(BlogPostScreen), findsOneWidget);
});
});
testWidgets('renders loaded state', (tester) async {
await mockNetworkImagesFor(() async {
final blogPostCubit = MockBlogPostCubit();
final feedCubit = MockFeedCubit();
final commentsCubit = MockCommentsCubit();
final state = BlogPostState(isLoading: false, blogPostModel: null, error: null);
when(blogPostCubit.state).thenReturn(state);
when(blogPostCubit.stream).thenAnswer((_) => Stream.value(state));
when(blogPostCubit.close()).thenAnswer((_) async => Future<void>.value());
when(feedCubit.viewPost(any)).thenAnswer((_) async => Future<void>.value());
when(feedCubit.stream).thenAnswer((_) => const Stream.empty());
when(feedCubit.close()).thenAnswer((_) async => Future<void>.value());
when(commentsCubit.state).thenReturn(const CommentsState());
when(commentsCubit.stream).thenAnswer((_) => const Stream<CommentsState>.empty());
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
when(commentsCubit.getComments(any, any, any)).thenAnswer((_) async => Future<void>.value());
registerMocks(blogPostCubit, commentsCubit);
await tester.pumpWidget(wrap(blogPostCubit, feedCubit, commentsCubit, const BlogPostScreen(id: 1)));
await tester.pump();
expect(find.byType(BlogPostScreen), findsOneWidget);
});
});
});
}

View File

@@ -0,0 +1,169 @@
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
import 'package:eatvio_app/features/profile/models/profile_model.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('ProfileAvatarState', () {
test('creates initial ProfileAvatarState', () {
const state = ProfileAvatarState();
expect(state, isA<ProfileAvatarState>());
});
test('creates loading ProfileAvatarState', () {
const state = ProfileAvatarState.loading();
expect(state, isA<ProfileAvatarState>());
expect(state, isA<ProfileAvatarStateLoading>());
});
test('creates loaded ProfileAvatarState with profile', () {
const profile = ProfileModel(
id: 1,
slug: 'test-user',
name: 'Test User',
email: 'test@example.com',
phoneConfirmationState: 'confirmed',
isPublicProfile: true,
isBlockedByMe: false,
isBlockedByAuthor: false,
isBlockedByUser: false,
isEntitySubscribedByMe: false,
profilePhotoUrl: 'https://example.com/icon.jpg',
roles: [],
badges: [],
subscriptions: [],
subscribers: [],
);
const state = ProfileAvatarState.loaded(profile: profile);
expect(state, isA<ProfileAvatarState>());
expect(state, isA<ProfileAvatarStateLoaded>());
expect((state as ProfileAvatarStateLoaded).profile, profile);
});
test('creates error ProfileAvatarState with error message', () {
const state = ProfileAvatarState.error(errorMessage: 'Test error');
expect(state, isA<ProfileAvatarState>());
expect(state, isA<ProfileAvatarStateError>());
expect((state as ProfileAvatarStateError).errorMessage, 'Test error');
});
test('different state types are not equal', () {
const profile = ProfileModel(
id: 1,
slug: 'test-user',
name: 'Test User',
email: 'test@example.com',
phoneConfirmationState: 'confirmed',
isPublicProfile: true,
isBlockedByMe: false,
isBlockedByAuthor: false,
isBlockedByUser: false,
isEntitySubscribedByMe: false,
profilePhotoUrl: 'https://example.com/icon.jpg',
roles: [],
badges: [],
subscriptions: [],
subscribers: [],
);
const loading = ProfileAvatarState.loading();
const loaded = ProfileAvatarState.loaded(profile: profile);
const error = ProfileAvatarState.error(errorMessage: 'Error');
expect(loading == loaded, isFalse);
expect(loading == error, isFalse);
expect(loaded == error, isFalse);
});
test('same state types with same data are equal', () {
const profile = ProfileModel(
id: 1,
slug: 'test-user',
name: 'Test User',
email: 'test@example.com',
phoneConfirmationState: 'confirmed',
isPublicProfile: true,
isBlockedByMe: false,
isBlockedByAuthor: false,
isBlockedByUser: false,
isEntitySubscribedByMe: false,
profilePhotoUrl: 'https://example.com/icon.jpg',
roles: [],
badges: [],
subscriptions: [],
subscribers: [],
);
const state1 = ProfileAvatarState.loaded(profile: profile);
const state2 = ProfileAvatarState.loaded(profile: profile);
expect(state1 == state2, isTrue);
});
test('same state types with different data are not equal', () {
const profile1 = ProfileModel(
id: 1,
slug: 'test-user',
name: 'Test User',
email: 'test@example.com',
phoneConfirmationState: 'confirmed',
isPublicProfile: true,
isBlockedByMe: false,
isBlockedByAuthor: false,
isBlockedByUser: false,
isEntitySubscribedByMe: false,
profilePhotoUrl: 'https://example.com/icon.jpg',
roles: [],
badges: [],
subscriptions: [],
subscribers: [],
);
const profile2 = ProfileModel(
id: 2,
slug: 'other-user',
name: 'Other User',
email: 'other@example.com',
phoneConfirmationState: 'confirmed',
isPublicProfile: true,
isBlockedByMe: false,
isBlockedByAuthor: false,
isBlockedByUser: false,
isEntitySubscribedByMe: false,
profilePhotoUrl: 'https://example.com/icon2.jpg',
roles: [],
badges: [],
subscriptions: [],
subscribers: [],
);
const state1 = ProfileAvatarState.loaded(profile: profile1);
const state2 = ProfileAvatarState.loaded(profile: profile2);
expect(state1 == state2, isFalse);
});
test('error states with different messages are not equal', () {
const state1 = ProfileAvatarState.error(errorMessage: 'Error 1');
const state2 = ProfileAvatarState.error(errorMessage: 'Error 2');
expect(state1 == state2, isFalse);
});
test('error states with same messages are equal', () {
const state1 = ProfileAvatarState.error(errorMessage: 'Error');
const state2 = ProfileAvatarState.error(errorMessage: 'Error');
expect(state1 == state2, isTrue);
});
test('initial ProfileAvatarState instances are equal', () {
const state1 = ProfileAvatarState();
const state2 = ProfileAvatarState();
expect(state1 == state2, isTrue);
});
test('loading ProfileAvatarState instances are equal', () {
const state1 = ProfileAvatarState.loading();
const state2 = ProfileAvatarState.loading();
expect(state1 == state2, isTrue);
});
});
}

View File

@@ -0,0 +1,102 @@
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
import 'package:eatvio_app/features/chat/models/chat_room_model.dart';
import 'package:eatvio_app/features/chat/models/chat_room_settings_model.dart';
import 'package:eatvio_app/features/chat/models/chat_user_model.dart';
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('ChatRoomState', () {
test('creates initial ChatRoomState', () {
const state = ChatRoomState();
expect(state, isA<ChatRoomState>());
});
test('creates loading ChatRoomState', () {
const state = ChatRoomState.loading();
expect(state, isA<ChatRoomState>());
expect(state, isA<ChatRoomStateLoading>());
});
test('creates loaded ChatRoomState with default values', () {
const chatRoomSettings = ChatRoomSettingsModel(name: 'Test Room', state: 'active');
const sender = ChatUserModel(id: 1, name: 'Sender', slug: 'sender');
const receiver = ChatUserModel(id: 2, name: 'Receiver', slug: 'receiver');
const users = Users(sender: sender, receiver: receiver);
const chatRoom = ChatRoomModel(id: 'room-1', chatRoomSettings: chatRoomSettings, users: users, chosenPlanId: null);
const state = ChatRoomState.loaded(chatRoom: chatRoom, chatMessages: [], hasCard: false, isTyping: false, isLoading: false);
expect(state, isA<ChatRoomState>());
expect(state, isA<ChatRoomStateLoaded>());
expect((state as ChatRoomStateLoaded).chatRoom, chatRoom);
expect(state.chatMessages, isEmpty);
expect(state.hasCard, isFalse);
expect(state.isTyping, isFalse);
expect(state.isLoading, isFalse);
});
test('creates loaded ChatRoomState with messages', () {
const chatRoomSettings = ChatRoomSettingsModel(name: 'Test Room', state: 'active');
const sender = ChatUserModel(id: 1, name: 'Sender', slug: 'sender');
const receiver = ChatUserModel(id: 2, name: 'Receiver', slug: 'receiver');
const users = Users(sender: sender, receiver: receiver);
const chatRoom = ChatRoomModel(id: 'room-1', chatRoomSettings: chatRoomSettings, users: users, chosenPlanId: null);
final message = ChatMessageModel(
id: 1,
data: 'Test message',
mediaData: null,
authorId: 1,
updatedAt: DateTime.now(),
isRead: false,
isCurrentUserAuthor: true,
roomId: 'room-1',
);
final state = ChatRoomState.loaded(chatRoom: chatRoom, chatMessages: [message], hasCard: true, isTyping: true, isLoading: true);
expect(state, isA<ChatRoomStateLoaded>());
expect((state as ChatRoomStateLoaded).chatMessages.length, 1);
expect(state.hasCard, isTrue);
expect(state.isTyping, isTrue);
expect(state.isLoading, isTrue);
});
test('creates error ChatRoomState', () {
const state = ChatRoomState.error(errorMessage: 'Test error');
expect(state, isA<ChatRoomState>());
expect(state, isA<ChatRoomStateError>());
expect((state as ChatRoomStateError).errorMessage, 'Test error');
});
test('different state types are not equal', () {
const chatRoomSettings = ChatRoomSettingsModel(name: 'Test Room', state: 'active');
const sender = ChatUserModel(id: 1, name: 'Sender', slug: 'sender');
const receiver = ChatUserModel(id: 2, name: 'Receiver', slug: 'receiver');
const users = Users(sender: sender, receiver: receiver);
const chatRoom = ChatRoomModel(id: 'room-1', chatRoomSettings: chatRoomSettings, users: users, chosenPlanId: null);
const loading = ChatRoomState.loading();
const loaded = ChatRoomState.loaded(chatRoom: chatRoom, chatMessages: [], hasCard: false, isTyping: false, isLoading: false);
const error = ChatRoomState.error(errorMessage: 'Error');
expect(loading == loaded, isFalse);
expect(loading == error, isFalse);
expect(loaded == error, isFalse);
});
});
}

View File

@@ -0,0 +1,128 @@
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/features/complaining/models/complain_model.dart';
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_state.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('ComplainingState', () {
test('creates ComplainingState with default values', () {
final state = ComplainingState();
expect(state.isLoading, false);
expect(state.wasSentSuccessfully, null);
expect(state.category, null);
expect(state.contentType, null);
expect(state.contentId, null);
expect(state.contentSlug, null);
expect(state.complainInfo, isA<ComplainModel>());
});
test('creates ComplainingState with custom values', () {
final state = ComplainingState(
isLoading: true,
wasSentSuccessfully: true,
category: ComplainType.spam,
contentType: ComplainedContent.recipe,
contentId: 1,
contentSlug: 'test-slug',
complainInfo: const ComplainModel(
description: 'Test description',
tags: ['tag1', 'tag2'],
),
);
expect(state.isLoading, true);
expect(state.wasSentSuccessfully, true);
expect(state.category, ComplainType.spam);
expect(state.contentType, ComplainedContent.recipe);
expect(state.contentId, 1);
expect(state.contentSlug, 'test-slug');
expect(state.complainInfo.description, 'Test description');
expect(state.complainInfo.tags, ['tag1', 'tag2']);
});
test('copyWith creates new instance with updated values', () {
final original = ComplainingState();
final updated = original.copyWith(
isLoading: true,
category: ComplainType.spam,
contentType: ComplainedContent.plan,
);
expect(updated.isLoading, true);
expect(updated.category, ComplainType.spam);
expect(updated.contentType, ComplainedContent.plan);
expect(original.isLoading, false);
});
test('copyWith keeps original values when not updated', () {
final original = ComplainingState(
isLoading: true,
category: ComplainType.spam,
);
final updated = original.copyWith();
expect(updated.isLoading, original.isLoading);
expect(updated.category, original.category);
});
test('copyWith.complainInfo updates complainInfo', () {
final original = ComplainingState();
final updated = original.copyWith.complainInfo(
description: 'New description',
tags: ['new_tag'],
);
expect(updated.complainInfo.description, 'New description');
expect(updated.complainInfo.tags, ['new_tag']);
expect(original.complainInfo.description, isNull);
});
test('handles different ComplainType values', () {
final state1 = ComplainingState(category: ComplainType.spam);
final state2 = ComplainingState(category: ComplainType.lawViolation);
final state3 = ComplainingState(category: ComplainType.deception);
final state4 = ComplainingState(category: ComplainType.violenceDndHostility);
final state5 = ComplainingState(category: ComplainType.explicitImage);
expect(state1.category, ComplainType.spam);
expect(state2.category, ComplainType.lawViolation);
expect(state3.category, ComplainType.deception);
expect(state4.category, ComplainType.violenceDndHostility);
expect(state5.category, ComplainType.explicitImage);
});
test('handles different ComplainedContent values', () {
final state1 = ComplainingState(contentType: ComplainedContent.diet);
final state2 = ComplainingState(contentType: ComplainedContent.plan);
final state3 = ComplainingState(contentType: ComplainedContent.recipe);
expect(state1.contentType, ComplainedContent.diet);
expect(state2.contentType, ComplainedContent.plan);
expect(state3.contentType, ComplainedContent.recipe);
});
test('equality works correctly', () {
final state1 = ComplainingState(
isLoading: true,
category: ComplainType.spam,
contentType: ComplainedContent.recipe,
contentId: 1,
);
final state2 = ComplainingState(
isLoading: true,
category: ComplainType.spam,
contentType: ComplainedContent.recipe,
contentId: 1,
);
final state3 = ComplainingState(
isLoading: false,
category: ComplainType.spam,
contentType: ComplainedContent.recipe,
contentId: 1,
);
expect(state1 == state2, isTrue);
expect(state1 == state3, isFalse);
});
});
}

View File

@@ -0,0 +1,89 @@
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/enums/base_state.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_state.dart';
import 'package:eatvio_app/features/consultations/presentation/pages/consultations_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'consultations_screen_smoke_test.mocks.dart';
@GenerateNiceMocks([MockSpec<ConsultationsCubit>()])
void main() {
group('ConsultationsScreen smoke', () {
setUpAll(() async {
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
});
setUp(() async {
await sl.reset();
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
});
Widget wrap(Widget child, ConsultationsCubit cubit) {
return MaterialApp.router(
theme: ThemeHelper().themeData(),
routerConfig: GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => Scaffold(
body: BlocProvider<ConsultationsCubit>.value(value: cubit, child: child),
),
),
],
),
);
}
testWidgets('renders consultations screen', (tester) async {
final cubit = MockConsultationsCubit();
final state = ConsultationsState(state: BaseState.initial, consultations: []);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
await tester.pumpWidget(wrap(const ConsultationsContent(), cubit));
await tester.pump();
expect(find.byType(ConsultationsContent), findsOneWidget);
});
testWidgets('displays consultations title', (tester) async {
final cubit = MockConsultationsCubit();
final state = ConsultationsState(state: BaseState.success, consultations: []);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
await tester.pumpWidget(wrap(const ConsultationsContent(), cubit));
await tester.pump();
expect(find.text('Консультации'), findsOneWidget);
});
testWidgets('displays archive button when loaded', (tester) async {
final cubit = MockConsultationsCubit();
final state = ConsultationsState(state: BaseState.success, consultations: []);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
await tester.pumpWidget(wrap(const ConsultationsContent(), cubit));
await tester.pump();
expect(find.text('Архив консультаций'), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,43 @@
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('DevToolsScreen smoke', () {
setUpAll(() async {
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
});
setUp(() async {
await sl.reset();
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
});
Widget wrap(Widget child) {
return MaterialApp(
theme: ThemeHelper().themeData(),
home: child,
);
}
testWidgets('renders dev tools screen', (tester) async {
await tester.pumpWidget(wrap(const DevToolsScreen()));
await tester.pump();
expect(find.byType(DevToolsScreen), findsOneWidget);
expect(find.text('Для разработчиков'), findsOneWidget);
});
testWidgets('displays dev tools screen content', (tester) async {
await tester.pumpWidget(wrap(const DevToolsScreen()));
await tester.pump();
expect(find.byType(DevToolsScreenContent), findsOneWidget);
});
});
}

View File

@@ -965,5 +965,116 @@ void main() {
verify(mockDiaryRepository.deletePhoto(photo: photo, diaryId: 1, mealId: 1)).called(1);
});
test('setSelectedDay - возвращает true даже если getDiary возвращает null', () async {
diaryCubit = DiaryCubit(
diaryRepository: mockDiaryRepository,
glossaryRepository: mockGlossaryRepository,
profileRepository: mockProfileRepository,
dietRepository: mockDietRepository,
chatRepository: mockChatRepository,
settingsRepository: mockSettingsRepository,
);
when(mockDiaryRepository.createDiaryDay(day: anyNamed('day'))).thenThrow(
DioException(
requestOptions: RequestOptions(path: ''),
response: Response(requestOptions: RequestOptions(path: ''), statusCode: 500),
),
);
final result = await diaryCubit!.setSelectedDay(DateTime(2024, 1, 20));
// fetchDiaryV2 возвращает true когда getDiary() == null, так как эмитит состояние ошибки
expect(result, true);
expect(diaryCubit!.state, isA<DiaryStateError>());
});
test('fetchDiaryV2 - обрабатывает случай когда diaryEntity == null и нет диеты', () async {
diaryCubit = DiaryCubit(
diaryRepository: mockDiaryRepository,
glossaryRepository: mockGlossaryRepository,
profileRepository: mockProfileRepository,
dietRepository: mockDietRepository,
chatRepository: mockChatRepository,
settingsRepository: mockSettingsRepository,
);
// toEntity() возвращает null когда id == null
const diaryDayWithoutEntity = DiaryDayModel(
id: null,
date: '2024-01-15',
closed: 0,
comment: '',
typeOfDiary: TypeOfDiaryModel(hasChosenDiet: false, hasChosenPlan: false, diaryState: null),
meals: [],
chosenPlanId: null,
dayOfChosenDiet: null,
diet: null,
);
when(mockDiaryRepository.createDiaryDay(day: anyNamed('day'))).thenAnswer((_) async => Future.value());
when(mockDiaryRepository.getDiaryDayV2(day: anyNamed('day'))).thenAnswer((_) async => diaryDayWithoutEntity);
final result = await diaryCubit!.fetchDiaryV2(needLoading: false);
// Если diaryEntity == null (id == null) и нет диеты, fetchDiaryV2 возвращает false
// и эмитит предыдущее состояние (loading)
expect(result, false);
expect(diaryCubit!.state, const DiaryState.loading());
});
test('fetchDiaryV2 - обрабатывает случай когда diaryEntity == null но есть диета', () async {
diaryCubit = DiaryCubit(
diaryRepository: mockDiaryRepository,
glossaryRepository: mockGlossaryRepository,
profileRepository: mockProfileRepository,
dietRepository: mockDietRepository,
chatRepository: mockChatRepository,
settingsRepository: mockSettingsRepository,
);
// id должен быть null, чтобы toEntity() вернул null и код пошел по ветке setDiaryForDietOnly
const diaryDayWithDiet = DiaryDayModel(
id: null,
date: '2024-01-15',
closed: 0,
comment: '',
typeOfDiary: TypeOfDiaryModel(hasChosenDiet: true, hasChosenPlan: false, diaryState: null),
meals: [],
chosenPlanId: null,
dayOfChosenDiet: 1,
diet: DiaryDietModel(id: 1, duration: 7, name: 'Test Diet'),
);
when(mockDiaryRepository.createDiaryDay(day: anyNamed('day'))).thenAnswer((_) async => Future.value());
when(mockDiaryRepository.getDiaryDayV2(day: anyNamed('day'))).thenAnswer((_) async => diaryDayWithDiet);
when(mockDietRepository.getDiet(1)).thenAnswer(
(_) async => const Diet(
id: 1,
slug: 'test-diet',
name: 'Test Diet',
image: '',
duration: 7,
state: DietState.active,
type: DietType(),
details: '',
dailySchedule: {
1: {
1: {1: DailySchedule(name: 'Яблоко', portion: Portion.grams, quantity: 1)},
},
},
author: Author(authorSlug: 'test', authorName: 'Test', authorProfileIcon: '', authorIsMe: false),
totalLikes: 0,
totalViews: 0,
totalComments: 0,
wasLikedByMe: false,
isFavorite: false,
dailyNutrition: [],
),
);
final result = await diaryCubit!.fetchDiaryV2(needLoading: false);
await Future.delayed(const Duration(milliseconds: 100));
expect(result, true);
// setDiaryForDietOnly сначала эмитит loading, затем loadedTomorrowWithDiet
expect(diaryCubit!.state, isA<DiaryStateLoadedTomorrowWithDiet>());
});
});
}

View File

@@ -0,0 +1,45 @@
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/diary/presentation/pages/barcode_scanner_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('BarcodeScannerScreen smoke', () {
setUpAll(() async {
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
});
setUp(() async {
await sl.reset();
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
});
Widget wrap(Widget child) {
return MaterialApp(
theme: ThemeHelper().themeData(),
home: child,
);
}
testWidgets('renders barcode scanner screen', (tester) async {
await tester.pumpWidget(wrap(const BarcodeScannerScreen()));
await tester.pumpAndSettle();
expect(find.byType(BarcodeScannerScreen), findsOneWidget);
expect(find.text('Сканирование штрихкода'), findsOneWidget);
expect(find.text('Наведите камеру на штрихкод'), findsOneWidget);
});
testWidgets('displays barcode scanner UI elements', (tester) async {
await tester.pumpWidget(wrap(const BarcodeScannerScreen()));
await tester.pumpAndSettle();
expect(find.text('Найденный штрихкод'), findsOneWidget);
expect(find.text('Наведите камеру на штрихкод'), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,79 @@
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('FeedState', () {
test('creates initial FeedState', () {
const state = FeedState();
expect(state, isA<FeedState>());
});
test('creates loading FeedState', () {
const state = FeedState.loading();
expect(state, isA<FeedState>());
expect(state, isA<FeedStateLoading>());
});
test('creates loaded FeedState with data', () {
const state = FeedState.loaded(feedBlogPostList: [], isLoading: false);
expect(state, isA<FeedState>());
expect(state, isA<FeedStateLoaded>());
expect((state as FeedStateLoaded).feedBlogPostList, isEmpty);
expect(state.isLoading, isFalse);
});
test('creates loaded FeedState with blog posts', () {
const author = Author(authorSlug: 'test-author', authorName: 'Test Author', authorProfileIcon: 'https://example.com/icon.jpg', authorIsMe: false);
final blogPost = FeedBlogPostModel(
id: 1,
title: 'Test Post',
state: 'active',
image: 'https://example.com/image.jpg',
description: 'Test Description',
author: author,
videoUrl: 'https://example.com/video.mp4',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
totalLikes: 0,
totalViews: 0,
totalComments: 0,
wasLikedByMe: false,
);
final state = FeedState.loaded(feedBlogPostList: [blogPost], isLoading: true);
expect(state, isA<FeedStateLoaded>());
expect((state as FeedStateLoaded).feedBlogPostList.length, 1);
expect(state.isLoading, isTrue);
});
test('creates error FeedState', () {
const state = FeedState.error(errorMessage: 'Test error');
expect(state, isA<FeedState>());
expect(state, isA<FeedStateError>());
expect((state as FeedStateError).errorMessage, 'Test error');
});
test('different state types are not equal', () {
const loading = FeedState.loading();
const loaded = FeedState.loaded(feedBlogPostList: [], isLoading: false);
const error = FeedState.error(errorMessage: 'Error');
expect(loading == loaded, isFalse);
expect(loading == error, isFalse);
expect(loaded == error, isFalse);
});
test('same state types with same data are equal', () {
const state1 = FeedState.loaded(feedBlogPostList: [], isLoading: false);
const state2 = FeedState.loaded(feedBlogPostList: [], isLoading: false);
expect(state1 == state2, isTrue);
});
});
}

View File

@@ -0,0 +1,32 @@
import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_state.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('MenuState', () {
test('creates MenuState instance', () {
final state = MenuState();
expect(state, isNotNull);
expect(state, isA<MenuState>());
});
test('creates multiple MenuState instances', () {
final state1 = MenuState();
final state2 = MenuState();
expect(state1, isNotNull);
expect(state2, isNotNull);
expect(state1, isA<MenuState>());
expect(state2, isA<MenuState>());
});
test('MenuState instances are not equal by default', () {
final state1 = MenuState();
final state2 = MenuState();
// MenuState is a regular class without overridden == operator,
// so instances are compared by reference
expect(state1 == state2, isFalse);
expect(state1, isNot(same(state2)));
});
});
}

View File

@@ -0,0 +1,99 @@
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_state.dart';
import 'package:eatvio_app/features/menu/presentation/pages/blocked_users_screen.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'blocked_users_screen_smoke_test.mocks.dart';
@GenerateNiceMocks([MockSpec<BlockedUsersCubit>()])
void main() {
group('BlockedUsersScreen smoke', () {
setUpAll(() async {
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
});
setUp(() async {
await sl.reset();
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
});
Widget wrap(Widget child) {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru')],
home: child,
);
}
testWidgets('renders blocked users screen with loading state', (tester) async {
final cubit = MockBlockedUsersCubit();
const state = BlockedUsersStateLoading();
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
when(cubit.getBlockedUsers()).thenAnswer((_) async => Future<void>.value());
// Регистрируем cubit в sl, так как BlockedUsersScreen использует sl<BlockedUsersCubit>()
sl.registerFactory<BlockedUsersCubit>(() => cubit);
await tester.pumpWidget(wrap(const BlockedUsersScreen()));
await tester.pump();
expect(find.byType(BlockedUsersScreen), findsOneWidget);
expect(find.byType(CircularProgressIndicator), findsOneWidget);
});
testWidgets('renders blocked users screen with loaded state', (tester) async {
final cubit = MockBlockedUsersCubit();
const state = BlockedUsersStateLoaded(profilesList: []);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
when(cubit.getBlockedUsers()).thenAnswer((_) async => Future<void>.value());
// Регистрируем cubit в sl, так как BlockedUsersScreen использует sl<BlockedUsersCubit>()
sl.registerFactory<BlockedUsersCubit>(() => cubit);
await tester.pumpWidget(wrap(const BlockedUsersScreen()));
await tester.pump();
expect(find.byType(BlockedUsersScreen), findsOneWidget);
expect(find.text('Чёрный список'), findsOneWidget);
});
testWidgets('renders blocked users screen with error state', (tester) async {
final cubit = MockBlockedUsersCubit();
const state = BlockedUsersStateError(errorMessage: 'Error message');
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
when(cubit.getBlockedUsers()).thenAnswer((_) async => Future<void>.value());
sl.registerFactory<BlockedUsersCubit>(() => cubit);
await tester.pumpWidget(wrap(const BlockedUsersScreen()));
await tester.pump();
expect(find.byType(BlockedUsersScreen), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,90 @@
import 'package:eatvio_app/features/notifications/models/notification_model.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_state.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('NotificationsState', () {
test('creates initial NotificationsState', () {
const state = NotificationsState();
expect(state, isA<NotificationsState>());
});
test('creates empty NotificationsState', () {
const state = NotificationsState.empty();
expect(state, isA<NotificationsState>());
expect(state, isA<NotificationsStateEmpty>());
});
test('creates loaded NotificationsState with default values', () {
const state = NotificationsState.loaded(
notifications: [],
notificationsCount: 0,
);
expect(state, isA<NotificationsState>());
expect(state, isA<NotificationsStateLoaded>());
expect((state as NotificationsStateLoaded).notifications, isEmpty);
expect(state.notificationsCount, 0);
expect(state.isLoading, isFalse);
expect(state.hasUnreadNotifications, isFalse);
expect(state.currentPage, 1);
expect(state.hasNextPage, isTrue);
expect(state.isLoadingMore, isFalse);
});
test('creates loaded NotificationsState with custom values', () {
final notification = PlanSubscriptionStartedToAuthorNotification(
type: NotificationType.planSubscriptionStartedToAuthor,
planName: 'Test Plan',
customerName: 'Test Customer',
chatId: 'chat-123',
isRead: false,
createdAt: DateTime.now(),
);
final state = NotificationsState.loaded(
notifications: [notification],
notificationsCount: 1,
isLoading: true,
hasUnreadNotifications: true,
currentPage: 2,
hasNextPage: false,
isLoadingMore: true,
);
expect(state, isA<NotificationsStateLoaded>());
expect((state as NotificationsStateLoaded).notifications.length, 1);
expect(state.notificationsCount, 1);
expect(state.isLoading, isTrue);
expect(state.hasUnreadNotifications, isTrue);
expect(state.currentPage, 2);
expect(state.hasNextPage, isFalse);
expect(state.isLoadingMore, isTrue);
});
test('creates error NotificationsState', () {
const state = NotificationsState.error(errorMessage: 'Test error');
expect(state, isA<NotificationsState>());
expect(state, isA<NotificationsStateError>());
expect((state as NotificationsStateError).errorMessage, 'Test error');
});
test('different state types are not equal', () {
const empty = NotificationsState.empty();
const loaded = NotificationsState.loaded(notifications: [], notificationsCount: 0);
const error = NotificationsState.error(errorMessage: 'Error');
expect(empty == loaded, isFalse);
expect(empty == error, isFalse);
expect(loaded == error, isFalse);
});
test('same state types with same data are equal', () {
const state1 = NotificationsState.loaded(notifications: [], notificationsCount: 0);
const state2 = NotificationsState.loaded(notifications: [], notificationsCount: 0);
expect(state1 == state2, isTrue);
});
});
}

View File

@@ -0,0 +1,88 @@
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/personal_diet_suggestion/cubit/personal_diet_suggestion_cubit.dart';
import 'package:eatvio_app/features/personal_diet_suggestion/cubit/personal_diet_suggestion_state.dart';
import 'package:eatvio_app/features/personal_diet_suggestion/presentation/personal_diet_suggestion_screen.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'personal_diet_suggestion_screen_smoke_test.mocks.dart';
@GenerateNiceMocks([MockSpec<PersonalDietSuggestionCubit>()])
void main() {
group('PersonalDietSuggestionScreen smoke', () {
setUpAll(() async {
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
});
setUp(() async {
await sl.reset();
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
});
Widget wrap(Widget child, PersonalDietSuggestionCubit cubit) {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru')],
home: Scaffold(
body: BlocProvider<PersonalDietSuggestionCubit>.value(
value: cubit,
child: child,
),
),
);
}
testWidgets('renders personal diet suggestion screen', (tester) async {
final cubit = MockPersonalDietSuggestionCubit();
final state = PersonalDietSuggestionState(
isLoading: true,
dietName: 'Test Diet',
consultationsWithDiet: [],
consultationsWithoutDiet: [],
);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
await tester.pumpWidget(wrap(const PersonalDietSuggestionScreenContent(), cubit));
await tester.pump();
expect(find.byType(PersonalDietSuggestionScreenContent), findsOneWidget);
});
testWidgets('displays diet name in app bar', (tester) async {
final cubit = MockPersonalDietSuggestionCubit();
final state = PersonalDietSuggestionState(
isLoading: false,
dietName: 'Test Diet Name',
consultationsWithDiet: [],
consultationsWithoutDiet: [],
);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
await tester.pumpWidget(wrap(const PersonalDietSuggestionScreenContent(), cubit));
await tester.pump();
expect(find.textContaining('Test Diet Name'), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,87 @@
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart';
import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_state.dart';
import 'package:eatvio_app/features/recipe/presentation/pages/my_recipes_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'my_recipes_screen_smoke_test.mocks.dart';
@GenerateNiceMocks([MockSpec<MyRecipesCubit>()])
void main() {
group('MyRecipesScreen smoke', () {
setUpAll(() async {
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
});
setUp(() async {
await sl.reset();
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
});
Widget wrap(Widget child) {
return MaterialApp(theme: ThemeHelper().themeData(), home: child);
}
testWidgets('renders my recipes screen', (tester) async {
final cubit = MockMyRecipesCubit();
const state = MyRecipesStateLoading();
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
when(cubit.getMyRecipes()).thenAnswer((_) async => Future<void>.value());
// Регистрируем cubit в sl, так как MyRecipesScreen использует sl<MyRecipesCubit>()
sl.registerFactory<MyRecipesCubit>(() => cubit);
await tester.pumpWidget(wrap(const MyRecipesScreen()));
await tester.pump();
expect(find.byType(MyRecipesScreen), findsOneWidget);
expect(find.text('Мои рецепты'), findsOneWidget);
});
testWidgets('renders my recipes screen with loaded state', (tester) async {
final cubit = MockMyRecipesCubit();
const state = MyRecipesStateLoaded(recipes: [], selectedState: 'all');
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
when(cubit.getMyRecipes()).thenAnswer((_) async => Future<void>.value());
// Регистрируем cubit в sl, так как MyRecipesScreen использует sl<MyRecipesCubit>()
sl.registerFactory<MyRecipesCubit>(() => cubit);
await tester.pumpWidget(wrap(const MyRecipesScreen()));
await tester.pump();
expect(find.byType(MyRecipesScreen), findsOneWidget);
expect(find.text('Мои рецепты'), findsOneWidget);
});
testWidgets('renders my recipes screen with error state', (tester) async {
final cubit = MockMyRecipesCubit();
const state = MyRecipesStateError();
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
when(cubit.getMyRecipes()).thenAnswer((_) async => Future<void>.value());
sl.registerFactory<MyRecipesCubit>(() => cubit);
await tester.pumpWidget(wrap(const MyRecipesScreen()));
await tester.pump();
expect(find.byType(MyRecipesScreen), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,79 @@
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart';
import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart';
import 'package:eatvio_app/features/registration_v2/presentation/pages/registration_v2_page.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'registration_v2_page_smoke_test.mocks.dart';
@GenerateNiceMocks([MockSpec<RegistrationCubitV2>()])
void main() {
group('RegistrationV2Page smoke', () {
setUpAll(() async {
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
});
setUp(() async {
await sl.reset();
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
});
Widget wrap(Widget child, RegistrationCubitV2 cubit) {
sl.registerLazySingleton<RegistrationCubitV2>(() => cubit);
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru')],
home: Scaffold(
body: BlocProvider<RegistrationCubitV2>.value(
value: cubit,
child: child,
),
),
);
}
testWidgets('renders registration v2 page', (tester) async {
final cubit = MockRegistrationCubitV2();
const state = RegistrationStateV2(step: 0);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
await tester.pumpWidget(wrap(const RegistrationV2Page(), cubit));
await tester.pump();
expect(find.byType(RegistrationV2Page), findsOneWidget);
});
testWidgets('displays step progress', (tester) async {
final cubit = MockRegistrationCubitV2();
const state = RegistrationStateV2(step: 0);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
await tester.pumpWidget(wrap(const RegistrationV2Page(), cubit));
await tester.pump();
expect(find.byType(RegistrationV2PageContent), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,66 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart';
import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart';
import 'package:eatvio_app/features/registration_v2/presentation/widgets/phisical_activity.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'step_widget_test.mocks.dart';
void main() {
group('PhisicalActivity', () {
late MockRegistrationCubitV2 mockCubit;
setUp(() {
mockCubit = MockRegistrationCubitV2();
});
Widget createTestWidget() {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru'), Locale('en')],
locale: const Locale('ru'),
home: BlocProvider<RegistrationCubitV2>.value(
value: mockCubit,
child: const Scaffold(
body: PhisicalActivity(),
),
),
);
}
testWidgets('displays phisical activity widget', (tester) async {
when(mockCubit.state).thenReturn(
const RegistrationStateV2(activityCoefficient: 1.2),
);
when(mockCubit.stream).thenAnswer((_) => Stream.value(const RegistrationStateV2(activityCoefficient: 1.2)));
await tester.pumpWidget(createTestWidget());
await tester.pump();
expect(find.byType(PhisicalActivity), findsOneWidget);
});
testWidgets('displays phisical activity with different coefficient', (tester) async {
when(mockCubit.state).thenReturn(
const RegistrationStateV2(activityCoefficient: 1.55),
);
when(mockCubit.stream).thenAnswer((_) => Stream.value(const RegistrationStateV2(activityCoefficient: 1.55)));
await tester.pumpWidget(createTestWidget());
await tester.pump();
expect(find.byType(PhisicalActivity), findsOneWidget);
});
});
}

View File

@@ -0,0 +1,114 @@
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart';
import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart';
import 'package:eatvio_app/features/registration_v2/presentation/widgets/step_progress.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'step_progress_test.mocks.dart';
@GenerateNiceMocks([MockSpec<RegistrationCubitV2>()])
void main() {
group('StepProgress smoke', () {
setUpAll(() async {
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
});
setUp(() async {
await sl.reset();
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
});
Widget wrap(Widget child, RegistrationCubitV2 cubit) {
return MaterialApp(
theme: ThemeHelper().themeData(),
home: Scaffold(
body: BlocProvider<RegistrationCubitV2>.value(
value: cubit,
child: child,
),
),
);
}
testWidgets('renders step progress widget', (tester) async {
final cubit = MockRegistrationCubitV2();
const state = RegistrationStateV2(step: 0);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
await tester.pumpWidget(wrap(const StepProgress(), cubit));
await tester.pump();
expect(find.byType(StepProgress), findsOneWidget);
expect(find.byType(LinearProgressIndicator), findsOneWidget);
});
testWidgets('displays progress indicator for step 0', (tester) async {
final cubit = MockRegistrationCubitV2();
const state = RegistrationStateV2(step: 0);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
await tester.pumpWidget(wrap(const StepProgress(), cubit));
await tester.pump();
expect(find.byType(LinearProgressIndicator), findsOneWidget);
});
testWidgets('displays progress indicator for step 1', (tester) async {
final cubit = MockRegistrationCubitV2();
const state = RegistrationStateV2(step: 1);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
await tester.pumpWidget(wrap(const StepProgress(), cubit));
await tester.pump();
expect(find.byType(LinearProgressIndicator), findsOneWidget);
});
testWidgets('displays correct progress value for step 0', (tester) async {
final cubit = MockRegistrationCubitV2();
const state = RegistrationStateV2(step: 0);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
await tester.pumpWidget(wrap(const StepProgress(), cubit));
await tester.pump();
final progressIndicator = tester.widget<LinearProgressIndicator>(find.byType(LinearProgressIndicator));
expect(progressIndicator.value, 0.5);
});
testWidgets('displays correct progress value for step 1', (tester) async {
final cubit = MockRegistrationCubitV2();
const state = RegistrationStateV2(step: 1);
when(cubit.state).thenReturn(state);
when(cubit.stream).thenAnswer((_) => Stream.value(state));
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
await tester.pumpWidget(wrap(const StepProgress(), cubit));
await tester.pump();
final progressIndicator = tester.widget<LinearProgressIndicator>(find.byType(LinearProgressIndicator));
expect(progressIndicator.value, 1.0);
});
});
}

View File

@@ -64,6 +64,32 @@ void main() {
expect(find.byType(StepWidget), findsOneWidget);
});
testWidgets('displays step widget for step 0', (tester) async {
when(mockCubit.state).thenReturn(
const RegistrationStateV2(step: 0),
);
when(mockCubit.stream).thenAnswer((_) => Stream.value(const RegistrationStateV2(step: 0)));
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.byType(StepWidget), findsOneWidget);
expect(find.byType(Text), findsOneWidget);
});
testWidgets('displays step widget for step 1', (tester) async {
when(mockCubit.state).thenReturn(
const RegistrationStateV2(step: 1),
);
when(mockCubit.stream).thenAnswer((_) => Stream.value(const RegistrationStateV2(step: 1)));
await tester.pumpWidget(createTestWidget());
await tester.pumpAndSettle();
expect(find.byType(StepWidget), findsOneWidget);
expect(find.byType(Text), findsOneWidget);
});
});
}

View File

@@ -1,11 +1,14 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/registration_v2/models/registration_model.dart';
import 'package:eatvio_app/features/registration_v2/repositories/registration_repository_v2.dart';
import 'package:eatvio_app/features/registration_v2/services/registration_service_v2.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
class MockRegistrationServiceV2 extends Mock implements RegistrationServiceV2 {}
import 'registration_repository_v2_test.mocks.dart';
@GenerateMocks([RegistrationServiceV2])
void main() {
group('RegistrationRepositoryV2', () {
late RegistrationRepositoryV2 repository;
@@ -16,73 +19,79 @@ void main() {
repository = RegistrationRepositoryV2(registrationService: mockService);
});
test('register - успешная регистрация возвращает токен', () async {
const token = 'test-token-123';
test('register - успешная регистрация', () async {
const token = 'test_token_123';
final registrationModel = RegistrationModel(
firstName: 'Test',
lastName: 'User',
firstName: 'John',
lastName: 'Doe',
email: 'test@example.com',
phone: '+1234567890',
phone: '+79991234567',
gender: 'male',
password: 'password123',
confirmPassword: 'password123',
weight: 70,
height: 175,
activityCoefficient: 1.5,
height: 180,
activityCoefficient: 1.55,
birthday: '1990-01-01',
);
when(() => mockService.register(model: any(named: 'model')))
when(mockService.register(model: anyNamed('model')))
.thenAnswer((_) async => {'token': token});
final result = await repository.register(registrationModel: registrationModel);
expect(result, token);
verify(() => mockService.register(model: any(named: 'model'))).called(1);
verify(mockService.register(model: anyNamed('model'))).called(1);
});
test('register - возвращает null если токена нет', () async {
test('register - обработка ошибки', () async {
final registrationModel = RegistrationModel(
firstName: 'Test',
lastName: 'User',
firstName: 'John',
lastName: 'Doe',
email: 'test@example.com',
phone: '+1234567890',
phone: '+79991234567',
gender: 'male',
password: 'password123',
confirmPassword: 'password123',
weight: 70,
height: 175,
activityCoefficient: 1.5,
height: 180,
activityCoefficient: 1.55,
birthday: '1990-01-01',
);
when(() => mockService.register(model: any(named: 'model')))
when(mockService.register(model: anyNamed('model')))
.thenThrow(DioException(
requestOptions: RequestOptions(path: '/api/register'),
response: Response(
requestOptions: RequestOptions(path: '/api/register'),
statusCode: 400,
),
));
expect(() => repository.register(registrationModel: registrationModel), throwsA(isA<DioException>()));
});
test('register - возвращает null если токен отсутствует', () async {
final registrationModel = RegistrationModel(
firstName: 'John',
lastName: 'Doe',
email: 'test@example.com',
phone: '+79991234567',
gender: 'male',
password: 'password123',
confirmPassword: 'password123',
weight: 70,
height: 180,
activityCoefficient: 1.55,
birthday: '1990-01-01',
);
when(mockService.register(model: anyNamed('model')))
.thenAnswer((_) async => {});
final result = await repository.register(registrationModel: registrationModel);
expect(result, null);
});
test('register - пробрасывает ошибку', () async {
final registrationModel = RegistrationModel(
firstName: 'Test',
lastName: 'User',
email: 'test@example.com',
phone: '+1234567890',
gender: 'male',
password: 'password123',
confirmPassword: 'password123',
weight: 70,
height: 175,
activityCoefficient: 1.5,
birthday: '1990-01-01',
);
when(() => mockService.register(model: any(named: 'model')))
.thenThrow(Exception('Error'));
expect(() => repository.register(registrationModel: registrationModel), throwsException);
expect(result, isNull);
});
});
}

View File

@@ -0,0 +1,53 @@
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/update_screen/presentation/update_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('UpdateScreen smoke', () {
setUpAll(() async {
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost\nAPP_STORE_URL=https://apps.apple.com\nRUSTORE_URL=https://apps.rustore.ru');
});
setUp(() async {
await sl.reset();
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
});
Widget wrap(Widget child) {
return MaterialApp(
theme: ThemeHelper().themeData(),
home: child,
);
}
testWidgets('renders update screen', (tester) async {
await tester.pumpWidget(wrap(const UpdateScreen()));
await tester.pump();
expect(find.byType(UpdateScreen), findsOneWidget);
expect(find.text('Пора обновиться! ⚡'), findsOneWidget);
expect(find.text('Обновить'), findsOneWidget);
});
testWidgets('displays update message', (tester) async {
await tester.pumpWidget(wrap(const UpdateScreen()));
await tester.pump();
// Проверяем наличие текста об обновлении (может быть в многострочном тексте)
expect(find.textContaining('обновление'), findsWidgets);
});
testWidgets('displays update button', (tester) async {
await tester.pumpWidget(wrap(const UpdateScreen()));
await tester.pump();
expect(find.text('Обновить'), findsOneWidget);
expect(find.byType(InkWell), findsOneWidget);
});
});
}