forked from eatvio/eatvio_app
feat(tests): add fixtures
This commit is contained in:
10192
coverage/lcov.info
10192
coverage/lcov.info
File diff suppressed because it is too large
Load Diff
@@ -1,27 +1,27 @@
|
||||
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: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';
|
||||
import '../../../../fixtures/doubles/auth_doubles.dart';
|
||||
import '../../../../fixtures/fixture_reader.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<AuthRepository>()])
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('AuthCubit', () {
|
||||
AuthCubit? authCubit;
|
||||
late MockAuthRepository mockAuthRepository;
|
||||
late FixtureAuthRepository authRepository;
|
||||
late Map<String, dynamic> credentialsFixture;
|
||||
|
||||
const testToken = TokenModel(token: 'test_token_12345');
|
||||
late TokenModel tokenFixture;
|
||||
|
||||
setUp(() async {
|
||||
mockAuthRepository = MockAuthRepository();
|
||||
tokenFixture = TokenModel.fromJson(FixtureReader.readAsMap('auth/token_response.json'));
|
||||
credentialsFixture = FixtureReader.readAsMap('auth/login_credentials.json');
|
||||
authRepository = FixtureAuthRepository(tokenFixture: tokenFixture);
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
|
||||
// Mock Fluttertoast plugin
|
||||
@@ -50,31 +50,37 @@ void main() {
|
||||
});
|
||||
|
||||
test('начальное состояние - AuthState', () {
|
||||
authCubit = AuthCubit(authRepository: mockAuthRepository);
|
||||
authCubit = AuthCubit(authRepository: authRepository);
|
||||
expect(authCubit!.state, const AuthState());
|
||||
});
|
||||
|
||||
test('getTokenByCredentials - успешная авторизация', () async {
|
||||
authCubit = AuthCubit(authRepository: mockAuthRepository);
|
||||
when(mockAuthRepository.getTokenByCredentials(any, any))
|
||||
.thenAnswer((_) async => testToken);
|
||||
authCubit = AuthCubit(authRepository: authRepository);
|
||||
|
||||
final result = await authCubit!.getTokenByCredentials('test@example.com', 'password123');
|
||||
final result = await authCubit!.getTokenByCredentials(
|
||||
credentialsFixture['email'] as String,
|
||||
credentialsFixture['password'] as String,
|
||||
);
|
||||
|
||||
expect(result, true);
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
expect(authCubit!.state, isA<AuthStateLoaded>());
|
||||
final loadedState = authCubit!.state as AuthStateLoaded;
|
||||
expect(loadedState.token, 'test_token_12345');
|
||||
verify(mockAuthRepository.getTokenByCredentials('test@example.com', 'password123')).called(1);
|
||||
expect(loadedState.token, tokenFixture.token);
|
||||
expect(
|
||||
authRepository.credentialPayloads.single,
|
||||
credentialsFixture.map((key, value) => MapEntry(key, value.toString())),
|
||||
);
|
||||
});
|
||||
|
||||
test('getTokenByCredentials - переход в состояние loading', () async {
|
||||
authCubit = AuthCubit(authRepository: mockAuthRepository);
|
||||
when(mockAuthRepository.getTokenByCredentials(any, any))
|
||||
.thenAnswer((_) async => Future.delayed(const Duration(milliseconds: 50)).then((_) => testToken));
|
||||
authCubit = AuthCubit(authRepository: authRepository);
|
||||
authRepository.responseDelay = const Duration(milliseconds: 50);
|
||||
|
||||
final future = authCubit!.getTokenByCredentials('test@example.com', 'password123');
|
||||
final future = authCubit!.getTokenByCredentials(
|
||||
credentialsFixture['email'] as String,
|
||||
credentialsFixture['password'] as String,
|
||||
);
|
||||
await Future.delayed(const Duration(milliseconds: 10));
|
||||
|
||||
expect(authCubit!.state, isA<AuthStateLoading>());
|
||||
@@ -82,9 +88,8 @@ void main() {
|
||||
});
|
||||
|
||||
test('getTokenByCredentials - обработка ошибки авторизации', () async {
|
||||
authCubit = AuthCubit(authRepository: mockAuthRepository);
|
||||
when(mockAuthRepository.getTokenByCredentials(any, any))
|
||||
.thenThrow(Exception('Неверный адрес электронной почты или пароль'));
|
||||
authCubit = AuthCubit(authRepository: authRepository);
|
||||
authRepository.error = Exception('Неверный адрес электронной почты или пароль');
|
||||
|
||||
final result = await authCubit!.getTokenByCredentials('wrong@example.com', 'wrongpassword');
|
||||
|
||||
@@ -93,13 +98,15 @@ void main() {
|
||||
expect(authCubit!.state, isA<AuthStateError>());
|
||||
final errorState = authCubit!.state as AuthStateError;
|
||||
expect(errorState.errorMessage, contains('Exception'));
|
||||
verify(mockAuthRepository.getTokenByCredentials('wrong@example.com', 'wrongpassword')).called(1);
|
||||
expect(
|
||||
authRepository.credentialPayloads.single,
|
||||
{'email': 'wrong@example.com', 'password': 'wrongpassword'},
|
||||
);
|
||||
});
|
||||
|
||||
test('getTokenByCredentials - обработка различных типов ошибок', () async {
|
||||
authCubit = AuthCubit(authRepository: mockAuthRepository);
|
||||
when(mockAuthRepository.getTokenByCredentials(any, any))
|
||||
.thenThrow(const FormatException('Invalid credentials format'));
|
||||
authCubit = AuthCubit(authRepository: authRepository);
|
||||
authRepository.error = const FormatException('Invalid credentials format');
|
||||
|
||||
final result = await authCubit!.getTokenByCredentials('invalid', 'pass');
|
||||
|
||||
@@ -111,27 +118,35 @@ void main() {
|
||||
});
|
||||
|
||||
test('getTokenByCredentials - проверка вызова с правильными параметрами', () async {
|
||||
authCubit = AuthCubit(authRepository: mockAuthRepository);
|
||||
when(mockAuthRepository.getTokenByCredentials(any, any))
|
||||
.thenAnswer((_) async => testToken);
|
||||
authCubit = AuthCubit(authRepository: authRepository);
|
||||
|
||||
await authCubit!.getTokenByCredentials('user@test.com', 'securePassword123');
|
||||
|
||||
verify(mockAuthRepository.getTokenByCredentials('user@test.com', 'securePassword123')).called(1);
|
||||
verifyNever(mockAuthRepository.getTokenByCredentials('other@test.com', 'otherPassword'));
|
||||
expect(
|
||||
authRepository.credentialPayloads.single,
|
||||
{'email': 'user@test.com', 'password': 'securePassword123'},
|
||||
);
|
||||
expect(
|
||||
authRepository.credentialPayloads.where((call) => call['email'] == 'other@test.com'),
|
||||
isEmpty,
|
||||
);
|
||||
});
|
||||
|
||||
test('getTokenByCredentials - множественные вызовы', () async {
|
||||
authCubit = AuthCubit(authRepository: mockAuthRepository);
|
||||
when(mockAuthRepository.getTokenByCredentials(any, any))
|
||||
.thenAnswer((_) async => testToken);
|
||||
authCubit = AuthCubit(authRepository: authRepository);
|
||||
|
||||
await authCubit!.getTokenByCredentials('first@test.com', 'password1');
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
await authCubit!.getTokenByCredentials('second@test.com', 'password2');
|
||||
|
||||
verify(mockAuthRepository.getTokenByCredentials('first@test.com', 'password1')).called(1);
|
||||
verify(mockAuthRepository.getTokenByCredentials('second@test.com', 'password2')).called(1);
|
||||
expect(authRepository.credentialPayloads.length, 2);
|
||||
expect(
|
||||
authRepository.credentialPayloads,
|
||||
[
|
||||
{'email': 'first@test.com', 'password': 'password1'},
|
||||
{'email': 'second@test.com', 'password': 'password2'},
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,24 +2,27 @@
|
||||
|
||||
import 'package:eatvio_app/app_keys.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.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/repositories/auth_repository.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
|
||||
import 'basic_widget_test.mocks.dart';
|
||||
import '../../../../fixtures/doubles/auth_doubles.dart';
|
||||
import '../../../../fixtures/fixture_reader.dart';
|
||||
|
||||
@GenerateMocks([AuthRepository])
|
||||
void main() {
|
||||
group('Auth Basic Widget Tests', () {
|
||||
late MockAuthRepository mockAuthRepository;
|
||||
late FixtureAuthRepository authRepository;
|
||||
late AuthCubit authCubit;
|
||||
late Map<String, dynamic> credentialsFixture;
|
||||
|
||||
setUp(() {
|
||||
mockAuthRepository = MockAuthRepository();
|
||||
authCubit = AuthCubit(authRepository: mockAuthRepository);
|
||||
credentialsFixture = FixtureReader.readAsMap('auth/login_credentials.json');
|
||||
authRepository = FixtureAuthRepository(
|
||||
tokenFixture: TokenModel.fromJson(FixtureReader.readAsMap('auth/token_response.json')),
|
||||
);
|
||||
authCubit = AuthCubit(authRepository: authRepository);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
@@ -38,7 +41,7 @@ void main() {
|
||||
|
||||
testWidgets('should create AuthCubit successfully', (WidgetTester tester) async {
|
||||
// Arrange & Act
|
||||
final cubit = AuthCubit(authRepository: mockAuthRepository);
|
||||
final cubit = AuthCubit(authRepository: authRepository);
|
||||
|
||||
// Assert
|
||||
expect(cubit, isA<AuthCubit>());
|
||||
@@ -106,7 +109,7 @@ void main() {
|
||||
await tester.pumpWidget(createTestWidget(emailWidget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
const testEmail = 'test@example.com';
|
||||
final testEmail = credentialsFixture['email'] as String;
|
||||
|
||||
// Act
|
||||
await tester.enterText(find.byKey(AppKeys.emailTextFieldAuthScreen), testEmail);
|
||||
@@ -127,7 +130,7 @@ void main() {
|
||||
await tester.pumpWidget(createTestWidget(passwordWidget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
const testPassword = 'password123';
|
||||
final testPassword = credentialsFixture['password'] as String;
|
||||
|
||||
// Act
|
||||
await tester.enterText(find.byKey(AppKeys.passwordTextFieldAuthScreen), testPassword);
|
||||
|
||||
@@ -2,26 +2,27 @@ import 'package:eatvio_app/app_keys.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_state.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'advanced_widget_test.mocks.dart';
|
||||
import '../../fixtures/doubles/blog_post_doubles.dart';
|
||||
import '../../fixtures/fixture_reader.dart';
|
||||
|
||||
@GenerateMocks([BlogPostRepository])
|
||||
void main() {
|
||||
group('Blog Post Creation Advanced Widget Tests', () {
|
||||
late MockBlogPostRepository mockBlogPostRepository;
|
||||
late FixtureBlogPostRepository blogPostRepository;
|
||||
late BlogPostCreateCubit blogPostCreateCubit;
|
||||
late Map<String, dynamic> fullCreateFixture;
|
||||
late Map<String, dynamic> updateFixture;
|
||||
|
||||
setUp(() {
|
||||
mockBlogPostRepository = MockBlogPostRepository();
|
||||
blogPostCreateCubit = BlogPostCreateCubit(blogPostRepository: mockBlogPostRepository);
|
||||
blogPostRepository = FixtureBlogPostRepository();
|
||||
blogPostCreateCubit = BlogPostCreateCubit(blogPostRepository: blogPostRepository);
|
||||
fullCreateFixture = FixtureReader.readAsMap('blog_post_create/create_request_full.json');
|
||||
updateFixture = FixtureReader.readAsMap('blog_post_create/update_request.json');
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
@@ -40,8 +41,6 @@ void main() {
|
||||
|
||||
testWidgets('should handle blog post creation with all fields', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {});
|
||||
|
||||
Widget formWidget = Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
@@ -65,9 +64,9 @@ void main() {
|
||||
key: AppKeys.blogPostCreateScreenCreateButton,
|
||||
onPressed: () async {
|
||||
await blogPostCreateCubit.createBlogPost(
|
||||
title: 'Test Title',
|
||||
description: 'Test Description',
|
||||
videoUrl: 'https://youtube.com/watch?v=test',
|
||||
title: fullCreateFixture['title'] as String?,
|
||||
description: fullCreateFixture['description_md'] as String?,
|
||||
videoUrl: fullCreateFixture['video_url'] as String?,
|
||||
image: null,
|
||||
);
|
||||
},
|
||||
@@ -80,21 +79,25 @@ void main() {
|
||||
await tester.pumpWidget(createTestWidget(formWidget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), 'Test Title');
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), 'Test Description');
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), 'https://youtube.com/watch?v=test');
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), fullCreateFixture['title'] as String);
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), fullCreateFixture['description_md'] as String);
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), fullCreateFixture['video_url'] as String);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.byKey(AppKeys.blogPostCreateScreenCreateButton));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
verify(mockBlogPostRepository.createBlogPost('Test Title', 'https://youtube.com/watch?v=test', 'Test Description', null)).called(1);
|
||||
expect(
|
||||
blogPostRepository.createPayloads.single.toJson(),
|
||||
fullCreateFixture,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('should handle blog post creation with image', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {});
|
||||
final expectedPayload = Map<String, dynamic>.from(fullCreateFixture);
|
||||
expectedPayload['video_url'] = null;
|
||||
|
||||
Widget formWidget = Column(
|
||||
children: [
|
||||
@@ -137,8 +140,8 @@ void main() {
|
||||
key: AppKeys.blogPostCreateScreenCreateButton,
|
||||
onPressed: () async {
|
||||
await blogPostCreateCubit.createBlogPost(
|
||||
title: 'Test Title',
|
||||
description: 'Test Description',
|
||||
title: fullCreateFixture['title'] as String?,
|
||||
description: fullCreateFixture['description_md'] as String?,
|
||||
videoUrl: null,
|
||||
image: null, // В реальном тесте здесь был бы XFile
|
||||
);
|
||||
@@ -152,21 +155,19 @@ void main() {
|
||||
await tester.pumpWidget(createTestWidget(formWidget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), 'Test Title');
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), 'Test Description');
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), fullCreateFixture['title'] as String);
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), fullCreateFixture['description_md'] as String);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.byKey(AppKeys.blogPostCreateScreenCreateButton));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
verify(mockBlogPostRepository.createBlogPost('Test Title', null, 'Test Description', null)).called(1);
|
||||
expect(blogPostRepository.createPayloads.single.toJson(), expectedPayload);
|
||||
});
|
||||
|
||||
testWidgets('should handle blog post update', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
when(mockBlogPostRepository.updateBlogPost(any, any, any, any, any, any)).thenAnswer((_) async {});
|
||||
|
||||
final testBlogPost = FeedBlogPostModel(
|
||||
id: 1,
|
||||
state: 'published',
|
||||
@@ -209,12 +210,12 @@ void main() {
|
||||
key: AppKeys.blogPostCreateScreenCreateButton,
|
||||
onPressed: () async {
|
||||
await blogPostCreateCubit.updateBlogPost(
|
||||
id: testBlogPost.id,
|
||||
title: 'Updated Title',
|
||||
description: 'Updated Description',
|
||||
videoUrl: 'https://youtube.com/watch?v=updated',
|
||||
id: updateFixture['id'] as int,
|
||||
title: updateFixture['title'] as String?,
|
||||
description: updateFixture['description_md'] as String?,
|
||||
videoUrl: updateFixture['video_url'] as String?,
|
||||
image: null,
|
||||
oldImageUrl: testBlogPost.image,
|
||||
oldImageUrl: updateFixture['old_image_url'] as String?,
|
||||
);
|
||||
},
|
||||
child: const Text('Обновить'),
|
||||
@@ -226,24 +227,20 @@ void main() {
|
||||
await tester.pumpWidget(createTestWidget(formWidget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), 'Updated Title');
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), 'Updated Description');
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), 'https://youtube.com/watch?v=updated');
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), updateFixture['title'] as String);
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), updateFixture['description_md'] as String);
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), updateFixture['video_url'] as String);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.byKey(AppKeys.blogPostCreateScreenCreateButton));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
verify(
|
||||
mockBlogPostRepository.updateBlogPost(1, 'Updated Title', 'https://youtube.com/watch?v=updated', 'Updated Description', null, 'original_image.jpg'),
|
||||
).called(1);
|
||||
expect(blogPostRepository.updatePayloads.single.toJson(), updateFixture);
|
||||
});
|
||||
|
||||
testWidgets('should handle blog post deletion', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
when(mockBlogPostRepository.deleteBlogPost(any)).thenAnswer((_) async {});
|
||||
|
||||
Widget deleteWidget = ElevatedButton(
|
||||
key: const Key('deleteBlogPostButton'),
|
||||
onPressed: () async {
|
||||
@@ -260,14 +257,12 @@ void main() {
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
verify(mockBlogPostRepository.deleteBlogPost(1)).called(1);
|
||||
expect(blogPostRepository.deleteCalls.single, 1);
|
||||
});
|
||||
|
||||
testWidgets('should handle loading state during blog post creation', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
});
|
||||
blogPostRepository.createDelay = const Duration(milliseconds: 50);
|
||||
|
||||
Widget loadingWidget = BlocBuilder<BlogPostCreateCubit, BlogPostCreateState>(
|
||||
builder: (context, state) {
|
||||
@@ -283,7 +278,12 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Trigger loading state
|
||||
blogPostCreateCubit.createBlogPost(title: 'Test Title', videoUrl: null, description: 'Test Description', image: null);
|
||||
blogPostCreateCubit.createBlogPost(
|
||||
title: fullCreateFixture['title'] as String?,
|
||||
videoUrl: null,
|
||||
description: fullCreateFixture['description_md'] as String?,
|
||||
image: null,
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(); // Need to pump twice to get the loading state
|
||||
|
||||
@@ -297,7 +297,7 @@ void main() {
|
||||
|
||||
testWidgets('should handle error state during blog post creation', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenThrow(Exception('Network error'));
|
||||
blogPostRepository.createError = Exception('Network error');
|
||||
|
||||
Widget errorWidget = BlocBuilder<BlogPostCreateCubit, BlogPostCreateState>(
|
||||
builder: (context, state) {
|
||||
@@ -313,7 +313,12 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Trigger error state
|
||||
blogPostCreateCubit.createBlogPost(title: 'Test Title', videoUrl: null, description: 'Test Description', image: null);
|
||||
blogPostCreateCubit.createBlogPost(
|
||||
title: fullCreateFixture['title'] as String?,
|
||||
videoUrl: null,
|
||||
description: fullCreateFixture['description_md'] as String?,
|
||||
image: null,
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(); // Need to pump twice to get the error state
|
||||
|
||||
@@ -323,8 +328,6 @@ void main() {
|
||||
|
||||
testWidgets('should handle success state after blog post creation', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {});
|
||||
|
||||
Widget successWidget = BlocBuilder<BlogPostCreateCubit, BlogPostCreateState>(
|
||||
builder: (context, state) {
|
||||
if (state is BlogPostCreateStateLoaded) {
|
||||
@@ -339,7 +342,12 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Trigger success state
|
||||
blogPostCreateCubit.createBlogPost(title: 'Test Title', videoUrl: null, description: 'Test Description', image: null);
|
||||
blogPostCreateCubit.createBlogPost(
|
||||
title: fullCreateFixture['title'] as String?,
|
||||
videoUrl: null,
|
||||
description: fullCreateFixture['description_md'] as String?,
|
||||
image: null,
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(); // Need to pump twice to get the success state
|
||||
|
||||
@@ -470,7 +478,10 @@ void main() {
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
verify(mockBlogPostRepository.createBlogPost('', '', '', null)).called(1);
|
||||
expect(
|
||||
blogPostRepository.createPayloads.single.toJson(),
|
||||
{'title': '', 'description_md': '', 'video_url': '', 'image_name': null},
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('should handle blog post creation with long content', (WidgetTester tester) async {
|
||||
@@ -480,8 +491,6 @@ void main() {
|
||||
'Оно может содержать несколько абзацев и различные форматирования. '
|
||||
'Пользователи должны иметь возможность создавать подробные и информативные посты.';
|
||||
|
||||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {});
|
||||
|
||||
Widget longContentWidget = Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
@@ -518,7 +527,15 @@ void main() {
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
verify(mockBlogPostRepository.createBlogPost('Test Title', null, longContent, null)).called(1);
|
||||
expect(
|
||||
blogPostRepository.createPayloads.single.toJson(),
|
||||
{
|
||||
'title': 'Test Title',
|
||||
'description_md': longContent,
|
||||
'video_url': null,
|
||||
'image_name': null,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import 'package:eatvio_app/app_keys.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
|
||||
import 'basic_widget_test.mocks.dart';
|
||||
import '../../fixtures/doubles/blog_post_doubles.dart';
|
||||
|
||||
@GenerateMocks([BlogPostRepository])
|
||||
void main() {
|
||||
group('Blog Post Creation Basic Widget Tests', () {
|
||||
late MockBlogPostRepository mockBlogPostRepository;
|
||||
late FixtureBlogPostRepository blogPostRepository;
|
||||
late BlogPostCreateCubit blogPostCreateCubit;
|
||||
|
||||
setUp(() {
|
||||
mockBlogPostRepository = MockBlogPostRepository();
|
||||
blogPostCreateCubit = BlogPostCreateCubit(blogPostRepository: mockBlogPostRepository);
|
||||
blogPostRepository = FixtureBlogPostRepository();
|
||||
blogPostCreateCubit = BlogPostCreateCubit(blogPostRepository: blogPostRepository);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
@@ -36,7 +33,7 @@ void main() {
|
||||
|
||||
testWidgets('should create BlogPostCreateCubit successfully', (WidgetTester tester) async {
|
||||
// Arrange & Act
|
||||
final cubit = BlogPostCreateCubit(blogPostRepository: mockBlogPostRepository);
|
||||
final cubit = BlogPostCreateCubit(blogPostRepository: blogPostRepository);
|
||||
|
||||
// Assert
|
||||
expect(cubit, isA<BlogPostCreateCubit>());
|
||||
|
||||
@@ -2,34 +2,29 @@ import 'package:eatvio_app/app_keys.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_state.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'integration_widget_test.mocks.dart';
|
||||
import '../../fixtures/doubles/blog_post_doubles.dart';
|
||||
import '../../fixtures/fixture_reader.dart';
|
||||
|
||||
@GenerateMocks([BlogPostRepository, FeedCubit])
|
||||
void main() {
|
||||
group('Blog Post Creation Integration Widget Tests', () {
|
||||
late MockBlogPostRepository mockBlogPostRepository;
|
||||
late MockFeedCubit mockFeedCubit;
|
||||
late FixtureBlogPostRepository blogPostRepository;
|
||||
late BlogPostCreateCubit blogPostCreateCubit;
|
||||
late FeedReloadProbe feedProbe;
|
||||
late Map<String, dynamic> fullCreateFixture;
|
||||
late Map<String, dynamic> updateFixture;
|
||||
|
||||
setUp(() {
|
||||
mockBlogPostRepository = MockBlogPostRepository();
|
||||
mockFeedCubit = MockFeedCubit();
|
||||
blogPostCreateCubit = BlogPostCreateCubit(blogPostRepository: mockBlogPostRepository);
|
||||
|
||||
// Setup mock stubs for FeedCubit
|
||||
when(mockFeedCubit.stream).thenAnswer((_) => Stream.value(const FeedState.loading()));
|
||||
when(mockFeedCubit.close()).thenAnswer((_) async {});
|
||||
blogPostRepository = FixtureBlogPostRepository();
|
||||
blogPostCreateCubit = BlogPostCreateCubit(blogPostRepository: blogPostRepository);
|
||||
feedProbe = FeedReloadProbe();
|
||||
fullCreateFixture = FixtureReader.readAsMap('blog_post_create/create_request_full.json');
|
||||
updateFixture = FixtureReader.readAsMap('blog_post_create/update_request.json');
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
@@ -39,11 +34,8 @@ void main() {
|
||||
Widget createTestWidget(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<BlogPostCreateCubit>(create: (context) => blogPostCreateCubit),
|
||||
BlocProvider<FeedCubit>(create: (context) => mockFeedCubit),
|
||||
],
|
||||
home: BlocProvider<BlogPostCreateCubit>.value(
|
||||
value: blogPostCreateCubit,
|
||||
child: Scaffold(body: SingleChildScrollView(child: child)),
|
||||
),
|
||||
);
|
||||
@@ -51,13 +43,10 @@ void main() {
|
||||
|
||||
testWidgets('should complete full blog post creation flow', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {});
|
||||
when(mockFeedCubit.loadFeedBlogPostList()).thenAnswer((_) async {});
|
||||
|
||||
Widget fullFlowWidget = BlocListener<BlogPostCreateCubit, BlogPostCreateState>(
|
||||
listener: (context, state) {
|
||||
if (state is BlogPostCreateStateLoaded) {
|
||||
context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
feedProbe.loadFeedBlogPostList();
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
@@ -106,9 +95,9 @@ void main() {
|
||||
key: AppKeys.blogPostCreateScreenCreateButton,
|
||||
onPressed: () async {
|
||||
await blogPostCreateCubit.createBlogPost(
|
||||
title: 'Integration Test Title',
|
||||
videoUrl: 'https://youtube.com/watch?v=integration',
|
||||
description: 'Integration Test Description',
|
||||
title: fullCreateFixture['title'] as String?,
|
||||
videoUrl: fullCreateFixture['video_url'] as String?,
|
||||
description: fullCreateFixture['description_md'] as String?,
|
||||
image: null,
|
||||
);
|
||||
},
|
||||
@@ -122,26 +111,21 @@ void main() {
|
||||
await tester.pumpWidget(createTestWidget(fullFlowWidget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), 'Integration Test Title');
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), 'Integration Test Description');
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), 'https://youtube.com/watch?v=integration');
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), fullCreateFixture['title'] as String);
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), fullCreateFixture['description_md'] as String);
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), fullCreateFixture['video_url'] as String);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.byKey(AppKeys.blogPostCreateScreenCreateButton));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
verify(
|
||||
mockBlogPostRepository.createBlogPost('Integration Test Title', 'https://youtube.com/watch?v=integration', 'Integration Test Description', null),
|
||||
).called(1);
|
||||
verify(mockFeedCubit.loadFeedBlogPostList()).called(1);
|
||||
expect(blogPostRepository.createPayloads.single.toJson(), fullCreateFixture);
|
||||
expect(feedProbe.loadRequests, 1);
|
||||
});
|
||||
|
||||
testWidgets('should handle blog post update flow', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
when(mockBlogPostRepository.updateBlogPost(any, any, any, any, any, any)).thenAnswer((_) async {});
|
||||
when(mockFeedCubit.loadFeedBlogPostList()).thenAnswer((_) async {});
|
||||
|
||||
final existingBlogPost = FeedBlogPostModel(
|
||||
id: 1,
|
||||
state: 'published',
|
||||
@@ -161,7 +145,7 @@ void main() {
|
||||
Widget updateFlowWidget = BlocListener<BlogPostCreateCubit, BlogPostCreateState>(
|
||||
listener: (context, state) {
|
||||
if (state is BlogPostCreateStateLoaded) {
|
||||
context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
feedProbe.loadFeedBlogPostList();
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
@@ -190,12 +174,12 @@ void main() {
|
||||
key: AppKeys.blogPostCreateScreenCreateButton,
|
||||
onPressed: () async {
|
||||
await blogPostCreateCubit.updateBlogPost(
|
||||
id: existingBlogPost.id,
|
||||
title: 'Updated Title',
|
||||
videoUrl: 'https://youtube.com/watch?v=updated',
|
||||
description: 'Updated Description',
|
||||
id: updateFixture['id'] as int,
|
||||
title: updateFixture['title'] as String?,
|
||||
videoUrl: updateFixture['video_url'] as String?,
|
||||
description: updateFixture['description_md'] as String?,
|
||||
image: null,
|
||||
oldImageUrl: existingBlogPost.image,
|
||||
oldImageUrl: updateFixture['old_image_url'] as String?,
|
||||
);
|
||||
},
|
||||
child: const Text('Обновить'),
|
||||
@@ -208,26 +192,21 @@ void main() {
|
||||
await tester.pumpWidget(createTestWidget(updateFlowWidget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), 'Updated Title');
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), 'Updated Description');
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), 'https://youtube.com/watch?v=updated');
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), updateFixture['title'] as String);
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), updateFixture['description_md'] as String);
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), updateFixture['video_url'] as String);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.byKey(AppKeys.blogPostCreateScreenCreateButton));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
verify(
|
||||
mockBlogPostRepository.updateBlogPost(1, 'Updated Title', 'https://youtube.com/watch?v=updated', 'Updated Description', null, 'original_image.jpg'),
|
||||
).called(1);
|
||||
verify(mockFeedCubit.loadFeedBlogPostList()).called(1);
|
||||
expect(blogPostRepository.updatePayloads.single.toJson(), updateFixture);
|
||||
expect(feedProbe.loadRequests, 1);
|
||||
});
|
||||
|
||||
testWidgets('should handle blog post deletion flow', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
when(mockBlogPostRepository.deleteBlogPost(any)).thenAnswer((_) async {});
|
||||
when(mockFeedCubit.loadFeedBlogPostList()).thenAnswer((_) async {});
|
||||
|
||||
Widget deleteFlowWidget = Column(
|
||||
children: [
|
||||
const Text('Удалить блог пост?'),
|
||||
@@ -236,7 +215,7 @@ void main() {
|
||||
key: const Key('deleteBlogPostButton'),
|
||||
onPressed: () async {
|
||||
await blogPostCreateCubit.deleteBlogPost(1);
|
||||
mockFeedCubit.loadFeedBlogPostList();
|
||||
feedProbe.loadFeedBlogPostList();
|
||||
},
|
||||
child: const Text('Удалить'),
|
||||
),
|
||||
@@ -251,13 +230,13 @@ void main() {
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
verify(mockBlogPostRepository.deleteBlogPost(1)).called(1);
|
||||
verify(mockFeedCubit.loadFeedBlogPostList()).called(1);
|
||||
expect(blogPostRepository.deleteCalls.single, 1);
|
||||
expect(feedProbe.loadRequests, 1);
|
||||
});
|
||||
|
||||
testWidgets('should handle error flow during blog post creation', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenThrow(Exception('Network error'));
|
||||
blogPostRepository.createError = Exception('Network error');
|
||||
|
||||
Widget errorFlowWidget = BlocBuilder<BlogPostCreateCubit, BlogPostCreateState>(
|
||||
builder: (context, state) {
|
||||
@@ -285,7 +264,12 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Trigger error
|
||||
await blogPostCreateCubit.createBlogPost(title: 'Test Title', videoUrl: null, description: 'Test Description', image: null);
|
||||
await blogPostCreateCubit.createBlogPost(
|
||||
title: fullCreateFixture['title'] as String?,
|
||||
videoUrl: null,
|
||||
description: fullCreateFixture['description_md'] as String?,
|
||||
image: null,
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
@@ -295,9 +279,7 @@ void main() {
|
||||
|
||||
testWidgets('should handle loading state during blog post creation', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
});
|
||||
blogPostRepository.createDelay = const Duration(milliseconds: 50);
|
||||
|
||||
Widget loadingFlowWidget = BlocBuilder<BlogPostCreateCubit, BlogPostCreateState>(
|
||||
builder: (context, state) {
|
||||
@@ -313,7 +295,12 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Trigger loading
|
||||
blogPostCreateCubit.createBlogPost(title: 'Test Title', videoUrl: null, description: 'Test Description', image: null);
|
||||
blogPostCreateCubit.createBlogPost(
|
||||
title: fullCreateFixture['title'] as String?,
|
||||
videoUrl: null,
|
||||
description: fullCreateFixture['description_md'] as String?,
|
||||
image: null,
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(); // Need to pump twice to get the loading state
|
||||
|
||||
@@ -328,13 +315,10 @@ void main() {
|
||||
|
||||
testWidgets('should handle success state after blog post creation', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {});
|
||||
when(mockFeedCubit.loadFeedBlogPostList()).thenAnswer((_) async {});
|
||||
|
||||
Widget successFlowWidget = BlocListener<BlogPostCreateCubit, BlogPostCreateState>(
|
||||
listener: (context, state) {
|
||||
if (state is BlogPostCreateStateLoaded) {
|
||||
context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
feedProbe.loadFeedBlogPostList();
|
||||
}
|
||||
},
|
||||
child: BlocBuilder<BlogPostCreateCubit, BlogPostCreateState>(
|
||||
@@ -358,13 +342,18 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Trigger success
|
||||
await blogPostCreateCubit.createBlogPost(title: 'Test Title', videoUrl: null, description: 'Test Description', image: null);
|
||||
await blogPostCreateCubit.createBlogPost(
|
||||
title: fullCreateFixture['title'] as String?,
|
||||
videoUrl: null,
|
||||
description: fullCreateFixture['description_md'] as String?,
|
||||
image: null,
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byIcon(Icons.check_circle), findsOneWidget);
|
||||
expect(find.text('Блог пост успешно создан!'), findsOneWidget);
|
||||
verify(mockFeedCubit.loadFeedBlogPostList()).called(1);
|
||||
expect(feedProbe.loadRequests, 1);
|
||||
});
|
||||
|
||||
testWidgets('should handle form validation flow', (WidgetTester tester) async {
|
||||
@@ -462,13 +451,10 @@ void main() {
|
||||
|
||||
testWidgets('should handle complete blog post creation with all features', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
when(mockBlogPostRepository.createBlogPost(any, any, any, any)).thenAnswer((_) async {});
|
||||
when(mockFeedCubit.loadFeedBlogPostList()).thenAnswer((_) async {});
|
||||
|
||||
Widget completeFlowWidget = BlocListener<BlogPostCreateCubit, BlogPostCreateState>(
|
||||
listener: (context, state) {
|
||||
if (state is BlogPostCreateStateLoaded) {
|
||||
context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
feedProbe.loadFeedBlogPostList();
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
@@ -517,9 +503,9 @@ void main() {
|
||||
key: AppKeys.blogPostCreateScreenCreateButton,
|
||||
onPressed: () async {
|
||||
await blogPostCreateCubit.createBlogPost(
|
||||
title: 'Complete Test Title',
|
||||
videoUrl: 'https://youtube.com/watch?v=complete',
|
||||
description: 'Complete Test Description',
|
||||
title: fullCreateFixture['title'] as String?,
|
||||
videoUrl: fullCreateFixture['video_url'] as String?,
|
||||
description: fullCreateFixture['description_md'] as String?,
|
||||
image: null,
|
||||
);
|
||||
},
|
||||
@@ -533,9 +519,9 @@ void main() {
|
||||
await tester.pumpWidget(createTestWidget(completeFlowWidget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), 'Complete Test Title');
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), 'Complete Test Description');
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), 'https://youtube.com/watch?v=complete');
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenTitleTextField), fullCreateFixture['title'] as String);
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenContentTextField), fullCreateFixture['description_md'] as String);
|
||||
await tester.enterText(find.byKey(AppKeys.blogPostCreateScreenVideoLinkTextField), fullCreateFixture['video_url'] as String);
|
||||
await tester.pump();
|
||||
|
||||
// Scroll to make sure the button is visible
|
||||
@@ -546,8 +532,8 @@ void main() {
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
verify(mockBlogPostRepository.createBlogPost('Complete Test Title', 'https://youtube.com/watch?v=complete', 'Complete Test Description', null)).called(1);
|
||||
verify(mockFeedCubit.loadFeedBlogPostList()).called(1);
|
||||
expect(blogPostRepository.createPayloads.single.toJson(), fullCreateFixture);
|
||||
expect(feedProbe.loadRequests, 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,31 +1,26 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart';
|
||||
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
|
||||
import 'basic_widget_test.mocks.dart';
|
||||
import '../../fixtures/doubles/create_diet_doubles.dart';
|
||||
|
||||
@GenerateMocks([CreateDietRepository, ProfileRepository, GlossaryRepository])
|
||||
void main() {
|
||||
group('Diet Creation Basic Widget Tests', () {
|
||||
late MockCreateDietRepository mockCreateDietRepository;
|
||||
late MockProfileRepository mockProfileRepository;
|
||||
late MockGlossaryRepository mockGlossaryRepository;
|
||||
late FixtureCreateDietRepository createDietRepository;
|
||||
late FixtureProfileRepository profileRepository;
|
||||
late FixtureGlossaryRepository glossaryRepository;
|
||||
late CreateDietCubit createDietCubit;
|
||||
|
||||
setUp(() {
|
||||
mockCreateDietRepository = MockCreateDietRepository();
|
||||
mockProfileRepository = MockProfileRepository();
|
||||
mockGlossaryRepository = MockGlossaryRepository();
|
||||
createDietRepository = FixtureCreateDietRepository();
|
||||
profileRepository = FixtureProfileRepository();
|
||||
glossaryRepository = FixtureGlossaryRepository();
|
||||
createDietCubit = CreateDietCubit(
|
||||
createDietRepository: mockCreateDietRepository,
|
||||
profileRepository: mockProfileRepository,
|
||||
glossaryRepository: mockGlossaryRepository,
|
||||
createDietRepository: createDietRepository,
|
||||
profileRepository: profileRepository,
|
||||
glossaryRepository: glossaryRepository,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -46,9 +41,9 @@ void main() {
|
||||
testWidgets('should create CreateDietCubit successfully', (WidgetTester tester) async {
|
||||
// Arrange & Act
|
||||
final cubit = CreateDietCubit(
|
||||
createDietRepository: mockCreateDietRepository,
|
||||
profileRepository: mockProfileRepository,
|
||||
glossaryRepository: mockGlossaryRepository,
|
||||
createDietRepository: createDietRepository,
|
||||
profileRepository: profileRepository,
|
||||
glossaryRepository: glossaryRepository,
|
||||
);
|
||||
|
||||
// Assert
|
||||
|
||||
@@ -9,26 +9,20 @@ import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_c
|
||||
import 'package:eatvio_app/features/create_plan/presentation/widgets/plan_duration_drop_down_widget.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/widgets/plan_type_choice_widget.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/widgets/option_widget.dart';
|
||||
import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart';
|
||||
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
|
||||
import 'advanced_widget_test.mocks.dart';
|
||||
import '../../fixtures/doubles/create_plan_doubles.dart';
|
||||
|
||||
@GenerateMocks([CreatePlanRepository, ProfileRepository, GlossaryRepository])
|
||||
void main() {
|
||||
group('Meal Plan Creation Advanced Widget Tests', () {
|
||||
late MockCreatePlanRepository mockCreatePlanRepository;
|
||||
late FixtureCreatePlanRepository createPlanRepository;
|
||||
late CreatePlanCubit createPlanCubit;
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
mockCreatePlanRepository = MockCreatePlanRepository();
|
||||
createPlanCubit = CreatePlanCubit(createPlanRepository: mockCreatePlanRepository);
|
||||
createPlanRepository = FixtureCreatePlanRepository();
|
||||
createPlanCubit = CreatePlanCubit(createPlanRepository: createPlanRepository);
|
||||
sl.registerLazySingleton<CreatePlanCubit>(() => createPlanCubit);
|
||||
AppConfigRegistration.registerAppConfig(buildType: AppBuildType.tests);
|
||||
});
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart';
|
||||
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
|
||||
import 'basic_widget_test.mocks.dart';
|
||||
import '../../fixtures/doubles/create_plan_doubles.dart';
|
||||
import '../../fixtures/fixture_reader.dart';
|
||||
|
||||
@GenerateMocks([CreatePlanRepository, ProfileRepository, GlossaryRepository])
|
||||
void main() {
|
||||
group('Meal Plan Creation Basic Widget Tests', () {
|
||||
late MockCreatePlanRepository mockCreatePlanRepository;
|
||||
late FixtureCreatePlanRepository createPlanRepository;
|
||||
late CreatePlanCubit createPlanCubit;
|
||||
late Map<String, dynamic> planFixture;
|
||||
|
||||
setUp(() {
|
||||
mockCreatePlanRepository = MockCreatePlanRepository();
|
||||
createPlanCubit = CreatePlanCubit(createPlanRepository: mockCreatePlanRepository);
|
||||
planFixture = FixtureReader.readAsMap('create_plan/create_plan_request.json');
|
||||
createPlanRepository = FixtureCreatePlanRepository();
|
||||
createPlanCubit = CreatePlanCubit(createPlanRepository: createPlanRepository);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
@@ -37,7 +35,7 @@ void main() {
|
||||
|
||||
testWidgets('should create CreatePlanCubit successfully', (WidgetTester tester) async {
|
||||
// Arrange & Act
|
||||
final cubit = CreatePlanCubit(createPlanRepository: mockCreatePlanRepository);
|
||||
final cubit = CreatePlanCubit(createPlanRepository: createPlanRepository);
|
||||
|
||||
// Assert
|
||||
expect(cubit, isA<CreatePlanCubit>());
|
||||
@@ -98,7 +96,7 @@ void main() {
|
||||
await tester.pumpWidget(createTestWidget(formWidget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
const testName = 'Тестовый план питания';
|
||||
final testName = planFixture['name'] as String;
|
||||
|
||||
// Act
|
||||
await tester.enterText(find.byKey(const Key('planNameField')), testName);
|
||||
@@ -119,7 +117,7 @@ void main() {
|
||||
await tester.pumpWidget(createTestWidget(formWidget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
const testDescription = 'Это тестовое описание плана питания для здорового образа жизни';
|
||||
final testDescription = planFixture['description'] as String;
|
||||
|
||||
// Act
|
||||
await tester.enterText(find.byKey(const Key('planDescriptionField')), testDescription);
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/create_plan_screen.dart';
|
||||
import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart';
|
||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
@@ -13,20 +10,18 @@ import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
|
||||
import 'integration_widget_test.mocks.dart';
|
||||
import '../../fixtures/doubles/create_plan_doubles.dart';
|
||||
|
||||
@GenerateMocks([CreatePlanRepository, ProfileRepository, GlossaryRepository])
|
||||
void main() {
|
||||
group('Meal Plan Creation Integration Widget Tests', () {
|
||||
late MockCreatePlanRepository mockCreatePlanRepository;
|
||||
late FixtureCreatePlanRepository createPlanRepository;
|
||||
late CreatePlanCubit createPlanCubit;
|
||||
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
mockCreatePlanRepository = MockCreatePlanRepository();
|
||||
createPlanCubit = CreatePlanCubit(createPlanRepository: mockCreatePlanRepository);
|
||||
createPlanRepository = FixtureCreatePlanRepository();
|
||||
createPlanCubit = CreatePlanCubit(createPlanRepository: createPlanRepository);
|
||||
sl.registerLazySingleton<CreatePlanCubit>(() => createPlanCubit);
|
||||
});
|
||||
|
||||
|
||||
5
test/fixtures/auth/login_credentials.json
vendored
Normal file
5
test/fixtures/auth/login_credentials.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"email": "test@example.com",
|
||||
"password": "password123"
|
||||
}
|
||||
|
||||
4
test/fixtures/auth/token_response.json
vendored
Normal file
4
test/fixtures/auth/token_response.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"token": "test_token_12345"
|
||||
}
|
||||
|
||||
7
test/fixtures/blog_post_create/create_request_full.json
vendored
Normal file
7
test/fixtures/blog_post_create/create_request_full.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"title": "Complete Test Title",
|
||||
"description_md": "Complete Test Description",
|
||||
"video_url": "https://youtube.com/watch?v=complete",
|
||||
"image_name": null
|
||||
}
|
||||
|
||||
4
test/fixtures/blog_post_create/delete_request.json
vendored
Normal file
4
test/fixtures/blog_post_create/delete_request.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"id": 1
|
||||
}
|
||||
|
||||
9
test/fixtures/blog_post_create/update_request.json
vendored
Normal file
9
test/fixtures/blog_post_create/update_request.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Updated Title",
|
||||
"description_md": "Updated Description",
|
||||
"video_url": "https://youtube.com/watch?v=updated",
|
||||
"old_image_url": "original_image.jpg",
|
||||
"image_name": null
|
||||
}
|
||||
|
||||
13
test/fixtures/create_diet/create_diet_request.json
vendored
Normal file
13
test/fixtures/create_diet/create_diet_request.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "Легкий рацион",
|
||||
"duration": 7,
|
||||
"details": "Балансированный рацион на неделю",
|
||||
"state": "draft",
|
||||
"types": {
|
||||
"meat": true,
|
||||
"carb": false,
|
||||
"veg": true,
|
||||
"light": true
|
||||
}
|
||||
}
|
||||
|
||||
17
test/fixtures/create_diet/glossary_search_response.json
vendored
Normal file
17
test/fixtures/create_diet/glossary_search_response.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": 10,
|
||||
"name": "Авокадо",
|
||||
"portion": "100 г",
|
||||
"nutritional_value_id": 501
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"name": "Лосось",
|
||||
"portion": "150 г",
|
||||
"nutritional_value_id": 502
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
14
test/fixtures/create_plan/create_plan_request.json
vendored
Normal file
14
test/fixtures/create_plan/create_plan_request.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "План для похудения",
|
||||
"description": "Описание плана питания для снижения веса",
|
||||
"duration": 14,
|
||||
"status": "draft",
|
||||
"options": [
|
||||
{
|
||||
"title": "Базовый план",
|
||||
"price": 1500,
|
||||
"description": "Меню + чат с консультантом"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
66
test/fixtures/doubles/auth_doubles.dart
vendored
Normal file
66
test/fixtures/doubles/auth_doubles.dart
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
import 'package:eatvio_app/features/auth/models/credentials_model.dart';
|
||||
import 'package:eatvio_app/features/auth/models/oauth_login_request.dart';
|
||||
import 'package:eatvio_app/features/auth/models/oauth_model.dart';
|
||||
import 'package:eatvio_app/features/auth/models/token_model.dart';
|
||||
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
|
||||
import 'package:eatvio_app/features/auth/services/auth_service.dart';
|
||||
/// Fixture-based replacement for [AuthRepository].
|
||||
///
|
||||
/// - Records every credential payload that would be sent to the backend.
|
||||
/// - Returns data loaded from fixture files so QA/backenders can see
|
||||
/// exactly what is exchanged.
|
||||
class FixtureAuthRepository extends AuthRepository {
|
||||
FixtureAuthRepository({
|
||||
required this.tokenFixture,
|
||||
}) : super(authService: _NoopAuthService());
|
||||
|
||||
final TokenModel tokenFixture;
|
||||
|
||||
final List<CredentialsCall> credentialCalls = <CredentialsCall>[];
|
||||
List<Map<String, String>> get credentialPayloads => credentialCalls.map((call) => call.toJson()).toList(growable: false);
|
||||
|
||||
Duration responseDelay = Duration.zero;
|
||||
Object? error;
|
||||
|
||||
@override
|
||||
Future<TokenModel> getTokenByCredentials(String email, String password) async {
|
||||
credentialCalls.add(CredentialsCall(email: email, password: password));
|
||||
if (responseDelay > Duration.zero) {
|
||||
await Future.delayed(responseDelay);
|
||||
}
|
||||
if (error != null) {
|
||||
throw error!;
|
||||
}
|
||||
return tokenFixture;
|
||||
}
|
||||
}
|
||||
|
||||
class CredentialsCall {
|
||||
const CredentialsCall({required this.email, required this.password});
|
||||
|
||||
final String email;
|
||||
final String password;
|
||||
|
||||
Map<String, String> toJson() => <String, String>{
|
||||
'email': email,
|
||||
'password': password,
|
||||
};
|
||||
}
|
||||
|
||||
class _NoopAuthService implements AuthService {
|
||||
@override
|
||||
Future<TokenModel> getTokenByCredentials({required CredentialsModel credentials}) {
|
||||
throw UnimplementedError('Use FixtureAuthRepository override instead.');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TokenModel> signInViaSocialMedia({required OAuthLoginRequest body}) {
|
||||
throw UnimplementedError('Not implemented in fixture.');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, Object?>> signUpViaSocialMedia({required OAuthModel body}) {
|
||||
throw UnimplementedError('Not implemented in fixture.');
|
||||
}
|
||||
}
|
||||
|
||||
143
test/fixtures/doubles/blog_post_doubles.dart
vendored
Normal file
143
test/fixtures/doubles/blog_post_doubles.dart
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
|
||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||
|
||||
class FixtureBlogPostRepository extends BlogPostRepository {
|
||||
FixtureBlogPostRepository() : super(blogPostService: _NoopBlogPostService());
|
||||
|
||||
final List<BlogPostCreatePayload> createPayloads = <BlogPostCreatePayload>[];
|
||||
final List<BlogPostUpdatePayload> updatePayloads = <BlogPostUpdatePayload>[];
|
||||
final List<int> deleteCalls = <int>[];
|
||||
|
||||
Duration createDelay = Duration.zero;
|
||||
Duration updateDelay = Duration.zero;
|
||||
Duration deleteDelay = Duration.zero;
|
||||
|
||||
Object? createError;
|
||||
Object? updateError;
|
||||
Object? deleteError;
|
||||
|
||||
@override
|
||||
Future<void> createBlogPost(String? title, String? videoUrl, String? description, XFile? image) async {
|
||||
createPayloads.add(
|
||||
BlogPostCreatePayload(
|
||||
title: title,
|
||||
description: description,
|
||||
videoUrl: videoUrl,
|
||||
imageName: image?.name,
|
||||
),
|
||||
);
|
||||
if (createDelay > Duration.zero) {
|
||||
await Future.delayed(createDelay);
|
||||
}
|
||||
if (createError != null) {
|
||||
throw createError!;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateBlogPost(int id, String? title, String? videoUrl, String? description, XFile? image, String? oldImageUrl) async {
|
||||
updatePayloads.add(
|
||||
BlogPostUpdatePayload(
|
||||
id: id,
|
||||
title: title,
|
||||
description: description,
|
||||
videoUrl: videoUrl,
|
||||
imageName: image?.name,
|
||||
oldImageUrl: oldImageUrl,
|
||||
),
|
||||
);
|
||||
if (updateDelay > Duration.zero) {
|
||||
await Future.delayed(updateDelay);
|
||||
}
|
||||
if (updateError != null) {
|
||||
throw updateError!;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteBlogPost(int id) async {
|
||||
deleteCalls.add(id);
|
||||
if (deleteDelay > Duration.zero) {
|
||||
await Future.delayed(deleteDelay);
|
||||
}
|
||||
if (deleteError != null) {
|
||||
throw deleteError!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BlogPostCreatePayload {
|
||||
BlogPostCreatePayload({
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.videoUrl,
|
||||
required this.imageName,
|
||||
});
|
||||
|
||||
final String? title;
|
||||
final String? description;
|
||||
final String? videoUrl;
|
||||
final String? imageName;
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'title': title,
|
||||
'description_md': description,
|
||||
'video_url': videoUrl,
|
||||
'image_name': imageName,
|
||||
};
|
||||
}
|
||||
|
||||
class BlogPostUpdatePayload {
|
||||
BlogPostUpdatePayload({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.videoUrl,
|
||||
required this.imageName,
|
||||
required this.oldImageUrl,
|
||||
});
|
||||
|
||||
final int id;
|
||||
final String? title;
|
||||
final String? description;
|
||||
final String? videoUrl;
|
||||
final String? imageName;
|
||||
final String? oldImageUrl;
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'title': title,
|
||||
'description_md': description,
|
||||
'video_url': videoUrl,
|
||||
'image_name': imageName,
|
||||
'old_image_url': oldImageUrl,
|
||||
};
|
||||
}
|
||||
|
||||
class _NoopBlogPostService implements BlogPostService {
|
||||
@override
|
||||
Future<void> createBlogPost({required FormData formData}) {
|
||||
throw UnimplementedError('Fixture overrides repository methods directly.');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteBlogPost({required int id}) {
|
||||
throw UnimplementedError('Fixture overrides repository methods directly.');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateBlogPost({required int id, required FormData formData}) {
|
||||
throw UnimplementedError('Fixture overrides repository methods directly.');
|
||||
}
|
||||
}
|
||||
|
||||
class FeedReloadProbe {
|
||||
int loadRequests = 0;
|
||||
|
||||
Future<void> loadFeedBlogPostList() async {
|
||||
loadRequests += 1;
|
||||
}
|
||||
}
|
||||
|
||||
205
test/fixtures/doubles/create_diet_doubles.dart
vendored
Normal file
205
test/fixtures/doubles/create_diet_doubles.dart
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart';
|
||||
import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
|
||||
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
||||
import 'package:eatvio_app/features/glossary/services/glossary_service.dart';
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:eatvio_app/features/profile/services/profile_service.dart';
|
||||
import 'package:retrofit/dio.dart' show HttpResponse;
|
||||
|
||||
import '../../fixtures/fixture_reader.dart';
|
||||
|
||||
class FixtureCreateDietRepository extends CreateDietRepository {
|
||||
FixtureCreateDietRepository()
|
||||
: super(
|
||||
createDietService: _NoopCreateDietService(),
|
||||
);
|
||||
|
||||
final List<Map<String, dynamic>> createRequests = <Map<String, dynamic>>[];
|
||||
final List<Map<String, dynamic>> updateRequests = <Map<String, dynamic>>[];
|
||||
|
||||
Map<int, Map<int, Map<int, DailySchedule>>> nutritionFixture = <int, Map<int, Map<int, DailySchedule>>>{
|
||||
1: {
|
||||
1: {
|
||||
0: const DailySchedule(
|
||||
name: 'Авокадо',
|
||||
portion: Portion.grams,
|
||||
quantity: 100,
|
||||
productNutritionalValueId: '501',
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@override
|
||||
Future<CreatedDiet> createDiet({
|
||||
required String name,
|
||||
required int duration,
|
||||
required DietState state,
|
||||
required String meatType,
|
||||
required String carbType,
|
||||
required String vegType,
|
||||
required String lightType,
|
||||
required String details,
|
||||
required File image,
|
||||
required List<List<List<DailySchedule>>> dailySchedule,
|
||||
}) async {
|
||||
createRequests.add(<String, dynamic>{
|
||||
'name': name,
|
||||
'duration': duration,
|
||||
'state': state.toString(),
|
||||
'meatType': meatType,
|
||||
'carbType': carbType,
|
||||
'vegType': vegType,
|
||||
'lightType': lightType,
|
||||
'details': details,
|
||||
'image': image.path,
|
||||
'days': dailySchedule.length,
|
||||
});
|
||||
return const CreatedDiet(id: 1);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<int, Map<int, Map<int, DailySchedule>>>> getDietPortionsNutrition(int dietId) async {
|
||||
return nutritionFixture;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateDiet({
|
||||
required int dietId,
|
||||
required DietState state,
|
||||
String? name,
|
||||
int? duration,
|
||||
String? meatType,
|
||||
String? carbType,
|
||||
String? vegType,
|
||||
String? lightType,
|
||||
String? details,
|
||||
File? image,
|
||||
List<List<List<DailySchedule>>>? dailySchedule,
|
||||
}) async {
|
||||
updateRequests.add(<String, dynamic>{
|
||||
'dietId': dietId,
|
||||
'state': state.toString(),
|
||||
'name': name,
|
||||
'duration': duration,
|
||||
'meatType': meatType,
|
||||
'carbType': carbType,
|
||||
'vegType': vegType,
|
||||
'lightType': lightType,
|
||||
'details': details,
|
||||
'image': image?.path,
|
||||
'days': dailySchedule?.length,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class FixtureProfileRepository extends ProfileRepository {
|
||||
FixtureProfileRepository()
|
||||
: super(
|
||||
profileService: _NoopProfileService(),
|
||||
);
|
||||
|
||||
@override
|
||||
Future<ProfileModel> getMyProfile() async {
|
||||
return const ProfileModel(
|
||||
id: 99,
|
||||
name: 'Анна Нутрициолог',
|
||||
email: 'anna@example.com',
|
||||
phoneConfirmationState: 'verified',
|
||||
isPublicProfile: true,
|
||||
isBlockedByMe: false,
|
||||
isBlockedByAuthor: false,
|
||||
isBlockedByUser: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
slug: 'diet-author',
|
||||
profilePhotoUrl: 'avatars/anna.png',
|
||||
roles: ['author'],
|
||||
badges: [],
|
||||
subscriptions: [],
|
||||
subscribers: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FixtureGlossaryRepository extends GlossaryRepository {
|
||||
FixtureGlossaryRepository()
|
||||
: super(
|
||||
glossaryService: _NoopGlossaryService(),
|
||||
);
|
||||
|
||||
@override
|
||||
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
|
||||
final results = FixtureReader.readAsMap('create_diet/glossary_search_response.json')['results'] as List<dynamic>;
|
||||
return results
|
||||
.map(
|
||||
(item) => GlossaryModel(
|
||||
id: item['id'] as int,
|
||||
name: item['name'] as String,
|
||||
grInPortion: 100,
|
||||
energyOn100Gr: 160.0,
|
||||
proteinsOn100Gr: 2.0,
|
||||
fatsOn100Gr: 14.0,
|
||||
carbohydratesOn100Gr: 9.0,
|
||||
energyOnPortion: 160.0,
|
||||
proteinsOnPortion: 2.0,
|
||||
fatsOnPortion: 14.0,
|
||||
carbohydratesOnPortion: 9.0,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
class _NoopCreateDietService implements CreateDietService {
|
||||
@override
|
||||
Future<CreatedDiet> createDraftedDiet({required FormData formData}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CreatedDiet> createPersonalDiet({required FormData formData}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CreatedDiet> createPublishedDiet({required FormData formData}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<HttpResponse> getDietPortionsNutrition({required int diet}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateDrafted({required int diet, required FormData formData}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updatePersonal({required int diet, required FormData formData}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updatePublished({required int diet, required FormData formData}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
class _NoopProfileService implements ProfileService {
|
||||
@override
|
||||
noSuchMethod(Invocation invocation) => throw UnimplementedError('ProfileService call ${invocation.memberName}');
|
||||
}
|
||||
|
||||
class _NoopGlossaryService implements GlossaryService {
|
||||
@override
|
||||
noSuchMethod(Invocation invocation) => throw UnimplementedError('GlossaryService call ${invocation.memberName}');
|
||||
}
|
||||
|
||||
22
test/fixtures/doubles/create_plan_doubles.dart
vendored
Normal file
22
test/fixtures/doubles/create_plan_doubles.dart
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart';
|
||||
import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart';
|
||||
|
||||
class FixtureCreatePlanRepository extends CreatePlanRepository {
|
||||
FixtureCreatePlanRepository() : super(createPlanService: _NoopCreatePlanService());
|
||||
|
||||
final List<Map<String, dynamic>> createPlanRequests = <Map<String, dynamic>>[];
|
||||
|
||||
@override
|
||||
Future<void> createPlan({required Map<String, dynamic> model}) async {
|
||||
createPlanRequests.add(Map<String, dynamic>.from(model));
|
||||
}
|
||||
}
|
||||
|
||||
class _NoopCreatePlanService implements CreatePlanService {
|
||||
@override
|
||||
Future<void> createPlan({required FormData formData}) {
|
||||
throw UnimplementedError('Not used in fixture tests');
|
||||
}
|
||||
}
|
||||
|
||||
30
test/fixtures/fixture_reader.dart
vendored
Normal file
30
test/fixtures/fixture_reader.dart
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
/// Utility that reads JSON fixtures from `test/fixtures`.
|
||||
///
|
||||
/// Using files instead of inline maps makes it explicit which payloads
|
||||
/// are exchanged with the backend.
|
||||
class FixtureReader {
|
||||
const FixtureReader._();
|
||||
|
||||
static String readAsString(String relativePath) {
|
||||
final file = File(_resolve(relativePath));
|
||||
return file.readAsStringSync();
|
||||
}
|
||||
|
||||
static Map<String, dynamic> readAsMap(String relativePath) {
|
||||
return jsonDecode(readAsString(relativePath)) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
static List<dynamic> readAsList(String relativePath) {
|
||||
return jsonDecode(readAsString(relativePath)) as List<dynamic>;
|
||||
}
|
||||
|
||||
static String _resolve(String relativePath) {
|
||||
return p.normalize(p.join('test', 'fixtures', relativePath));
|
||||
}
|
||||
}
|
||||
|
||||
7
test/fixtures/profile/my_profile_response.json
vendored
Normal file
7
test/fixtures/profile/my_profile_response.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"slug": "diet-author",
|
||||
"first_name": "Анна",
|
||||
"last_name": "Нутрициолог",
|
||||
"avatar": "avatars/anna.png"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user