Merge remote-tracking branch 'upstream/main' into issue-914

This commit is contained in:
misha
2026-03-04 09:21:45 +03:00
31 changed files with 793 additions and 200 deletions

View File

@@ -433,9 +433,49 @@ void main() {
final tempFile = File('${Directory.systemTemp.path}/test_image.jpg');
await tempFile.writeAsBytes([1, 2, 3]);
cubit.replaceImage(tempFile);
cubit.addIngredient(const i.Ingredient(name: 'Test Ingredient', unit: 'gr', portion: '100', productId: 1));
cubit.replaceDescriptionInStep('Step 1 description', 0);
expect(cubit.validateAll(), true);
});
// validateForDraft tests
test('validateForDraft - возвращает false когда title не задан', () {
expect(cubit.validateForDraft(), false);
expect(cubit.state.titleValidationError, 'Поле не заполнено');
});
test('validateForDraft - возвращает false когда title слишком короткий', () {
cubit.replaceTitle('AB');
expect(cubit.validateForDraft(), false);
expect(cubit.state.titleValidationError, 'Минимальная длина 3 символа ');
});
test('validateForDraft - возвращает true когда задан только title', () {
cubit.replaceTitle('Мой черновик');
expect(cubit.validateForDraft(), true);
expect(cubit.state.titleValidationError, isNull);
});
test('validateForDraft - сбрасывает ошибки остальных полей', () {
// Сначала вызываем validateAll, чтобы появились ошибки
cubit.validateAll();
expect(cubit.state.descriptionValidationError, isNotNull);
expect(cubit.state.imageValidationError, isNotNull);
// Теперь задаём title и вызываем validateForDraft
cubit.replaceTitle('Мой черновик');
final result = cubit.validateForDraft();
expect(result, true);
expect(cubit.state.descriptionValidationError, isNull);
expect(cubit.state.cookingTimeValidationError, isNull);
expect(cubit.state.activeTimeValidationError, isNull);
expect(cubit.state.servingsValidationError, isNull);
expect(cubit.state.energyPerServingValidationError, isNull);
expect(cubit.state.imageValidationError, isNull);
expect(cubit.state.stepsValidationError, isNull);
expect(cubit.state.ingredientsValidationError, isNull);
});
});
}

View File

@@ -10,8 +10,15 @@ import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:retrofit/retrofit.dart';
class MockCreateRecipeService extends Mock implements CreateRecipeService {}
final mockHttpResponse = HttpResponse(
null,
Response(requestOptions: RequestOptions(path: ''), statusCode: 200),
);
void main() {
setUpAll(() {
registerFallbackValue(FormData());
@@ -140,7 +147,7 @@ void main() {
energyPerServing: 300,
);
await repository.publishRecipe(recipe);
expect(() => repository.publishRecipe(recipe), throwsException);
// Методы не должны вызываться, так как изображение отсутствует
verifyNever(() => mockService.publishRecipe(formData: any(named: 'formData')));
@@ -162,7 +169,7 @@ void main() {
energyPerServing: 300,
);
await repository.publishRecipe(recipe);
expect(() => repository.publishRecipe(recipe), throwsException);
// Методы не должны вызываться, так как изображение пустое
verifyNever(() => mockService.publishRecipe(formData: any(named: 'formData')));
@@ -188,7 +195,7 @@ void main() {
// Мокируем ImageCompressUtility.compressFile
// Это сложно, так как это статический метод, но мы можем проверить, что вызывается updateRecipe
when(() => mockService.updateRecipe(slug: slug, formData: any(named: 'formData')))
.thenAnswer((_) async => Future.value());
.thenAnswer((_) async => Future.value(mockHttpResponse));
// Этот тест может упасть из-за ImageCompressUtility, но мы проверяем логику
try {
@@ -216,7 +223,7 @@ void main() {
);
when(() => mockService.publishRecipe(formData: any(named: 'formData')))
.thenAnswer((_) async => Future.value());
.thenAnswer((_) async => Future.value(mockHttpResponse));
try {
await repository.publishRecipe(recipe);
@@ -243,7 +250,7 @@ void main() {
);
when(() => mockService.publishRecipe(formData: any(named: 'formData')))
.thenAnswer((_) async => Future.value());
.thenAnswer((_) async => Future.value(mockHttpResponse));
try {
await repository.publishRecipe(recipe);
@@ -270,7 +277,7 @@ void main() {
);
when(() => mockService.publishRecipe(formData: any(named: 'formData')))
.thenAnswer((_) async => Future.value());
.thenAnswer((_) async => Future.value(mockHttpResponse));
try {
await repository.publishRecipe(recipe);
@@ -297,7 +304,7 @@ void main() {
);
when(() => mockService.publishRecipe(formData: any(named: 'formData')))
.thenAnswer((_) async => Future.value());
.thenAnswer((_) async => Future.value(mockHttpResponse));
try {
await repository.publishRecipe(recipe);
@@ -364,7 +371,7 @@ void main() {
);
when(() => mockService.publishRecipe(formData: any(named: 'formData')))
.thenAnswer((_) async => Future.value());
.thenAnswer((_) async => Future.value(mockHttpResponse));
try {
await repository.publishRecipe(recipe);
@@ -399,7 +406,7 @@ void main() {
);
when(() => mockService.publishRecipe(formData: any(named: 'formData')))
.thenAnswer((_) async => Future.value());
.thenAnswer((_) async => Future.value(mockHttpResponse));
try {
await repository.publishRecipe(recipe);
@@ -435,7 +442,7 @@ void main() {
);
when(() => mockService.publishRecipe(formData: any(named: 'formData')))
.thenAnswer((_) async => Future.value());
.thenAnswer((_) async => Future.value(mockHttpResponse));
try {
await repository.publishRecipe(recipe);
@@ -472,7 +479,7 @@ void main() {
);
when(() => mockService.publishRecipe(formData: any(named: 'formData')))
.thenAnswer((_) async => Future.value());
.thenAnswer((_) async => Future.value(mockHttpResponse));
try {
await repository.publishRecipe(recipe);
@@ -499,7 +506,7 @@ void main() {
);
when(() => mockService.publishRecipe(formData: any(named: 'formData')))
.thenAnswer((_) async => Future.value());
.thenAnswer((_) async => Future.value(mockHttpResponse));
try {
await repository.publishRecipe(recipe);

View File

@@ -9,6 +9,10 @@ import 'package:mockito/mockito.dart';
import 'recipe_service_test.mocks.dart';
import 'package:retrofit/retrofit.dart';
final mockHttpResponse = HttpResponse(null, Response(requestOptions: RequestOptions()));
@GenerateNiceMocks([MockSpec<CreateRecipeService>()])
void main() {
group('CreateRecipeService', () {
@@ -77,7 +81,7 @@ void main() {
final formData = FormData.fromMap({});
when(mockCreateRecipeService.publishRecipe(formData: formData))
.thenAnswer((_) async => Future.value());
.thenAnswer((_) async => Future.value(mockHttpResponse));
await mockCreateRecipeService.publishRecipe(formData: formData);

View File

@@ -1,6 +1,7 @@
import 'package:eatvio_app/features/recipe/models/my_recipe_model.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_cubit.dart';
import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_state.dart';
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
import 'package:eatvio_app/features/recipe/repositories/my_recipes_repository.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -9,16 +10,18 @@ import 'package:mockito/mockito.dart';
import 'my_recipes_cubit_test.mocks.dart';
@GenerateNiceMocks([MockSpec<MyRecipesRepository>(), MockSpec<AboutRecipeRepository>()])
@GenerateNiceMocks([MockSpec<MyRecipesRepository>(), MockSpec<AboutRecipeRepository>(), MockSpec<RecipeRepository>()])
void main() {
group('MyRecipesCubit', () {
MyRecipesCubit? cubit;
late MockMyRecipesRepository mockMyRecipesRepository;
late MockAboutRecipeRepository mockAboutRecipeRepository;
late MockRecipeRepository mockRecipeRepository;
setUp(() {
mockMyRecipesRepository = MockMyRecipesRepository();
mockAboutRecipeRepository = MockAboutRecipeRepository();
mockRecipeRepository = MockRecipeRepository();
});
tearDown(() async {
@@ -29,13 +32,13 @@ void main() {
});
test('начальное состояние - loading', () {
cubit = MyRecipesCubit(myRecipesRepository: mockMyRecipesRepository, aboutRecipeRepository: mockAboutRecipeRepository);
cubit = MyRecipesCubit(myRecipesRepository: mockMyRecipesRepository, aboutRecipeRepository: mockAboutRecipeRepository, recipeRepository: mockRecipeRepository);
expect(cubit!.state, isA<MyRecipesStateLoading>());
});
test('getMyRecipes - успешная загрузка активных рецептов', () async {
cubit = MyRecipesCubit(myRecipesRepository: mockMyRecipesRepository, aboutRecipeRepository: mockAboutRecipeRepository);
cubit = MyRecipesCubit(myRecipesRepository: mockMyRecipesRepository, aboutRecipeRepository: mockAboutRecipeRepository, recipeRepository: mockRecipeRepository);
final testRecipes = [
const MyRecipeModel(
@@ -66,7 +69,7 @@ void main() {
});
test('setActiveState - фильтрует активные рецепты', () async {
cubit = MyRecipesCubit(myRecipesRepository: mockMyRecipesRepository, aboutRecipeRepository: mockAboutRecipeRepository);
cubit = MyRecipesCubit(myRecipesRepository: mockMyRecipesRepository, aboutRecipeRepository: mockAboutRecipeRepository, recipeRepository: mockRecipeRepository);
final testRecipes = [
const MyRecipeModel(
@@ -112,7 +115,7 @@ void main() {
});
test('setArchivedState - фильтрует архивированные рецепты', () async {
cubit = MyRecipesCubit(myRecipesRepository: mockMyRecipesRepository, aboutRecipeRepository: mockAboutRecipeRepository);
cubit = MyRecipesCubit(myRecipesRepository: mockMyRecipesRepository, aboutRecipeRepository: mockAboutRecipeRepository, recipeRepository: mockRecipeRepository);
final testRecipes = [
const MyRecipeModel(

View File

@@ -2,11 +2,13 @@ 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_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: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';
@@ -26,7 +28,17 @@ void main() {
});
Widget wrap(Widget child) {
return MaterialApp(theme: ThemeHelper().themeData(), home: child);
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
home: child,
);
}
testWidgets('renders my recipes screen', (tester) async {

View File

@@ -5,7 +5,7 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_state.dart';
import 'package:eatvio_app/features/recipe/models/my_recipe_model.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_cubit.dart';
import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_state.dart';
import 'package:eatvio_app/features/recipe/presentation/widgets/my_recipe_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';

View File

@@ -7,7 +7,7 @@ import 'package:flutter_test/flutter_test.dart';
void main() {
group('MyRecipesTab smoke', () {
Widget wrap(Widget child) {
Widget wrap() {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: const [
@@ -17,12 +17,17 @@ void main() {
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru')],
home: Scaffold(body: child),
home: Material(
child: MyRecipesTab(
selectedIndex: 0,
onTabChanged: (_) {},
),
),
);
}
testWidgets('renders my recipes tab', (tester) async {
await tester.pumpWidget(wrap(const MyRecipesTab()));
await tester.pumpWidget(wrap());
await tester.pump();
expect(find.byType(MyRecipesTab), findsOneWidget);

View File

@@ -1,9 +1,10 @@
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:eatvio_app/features/recipe/models/my_recipe_model.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_cubit.dart';
import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_state.dart';
import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
import 'package:eatvio_app/features/recipe/repositories/my_recipes_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
@@ -13,12 +14,14 @@ import 'my_recipes_cubit_test.mocks.dart';
@GenerateNiceMocks([
MockSpec<MyRecipesRepository>(),
MockSpec<AboutRecipeRepository>(),
MockSpec<RecipeRepository>(),
])
void main() {
group('MyRecipesCubit', () {
MyRecipesCubit? myRecipesCubit;
late MockMyRecipesRepository mockMyRecipesRepository;
late MockAboutRecipeRepository mockAboutRecipeRepository;
late MockRecipeRepository mockRecipeRepository;
const testActiveRecipe = MyRecipeModel(
id: 1,
@@ -85,6 +88,7 @@ void main() {
setUp(() {
mockMyRecipesRepository = MockMyRecipesRepository();
mockAboutRecipeRepository = MockAboutRecipeRepository();
mockRecipeRepository = MockRecipeRepository();
});
tearDown(() async {
@@ -99,6 +103,7 @@ void main() {
myRecipesCubit = MyRecipesCubit(
myRecipesRepository: mockMyRecipesRepository,
aboutRecipeRepository: mockAboutRecipeRepository,
recipeRepository: mockRecipeRepository,
);
expect(myRecipesCubit!.state, isA<MyRecipesStateLoading>());
});
@@ -107,6 +112,7 @@ void main() {
myRecipesCubit = MyRecipesCubit(
myRecipesRepository: mockMyRecipesRepository,
aboutRecipeRepository: mockAboutRecipeRepository,
recipeRepository: mockRecipeRepository,
);
when(mockMyRecipesRepository.getMyRecipes()).thenAnswer(
@@ -127,6 +133,7 @@ void main() {
myRecipesCubit = MyRecipesCubit(
myRecipesRepository: mockMyRecipesRepository,
aboutRecipeRepository: mockAboutRecipeRepository,
recipeRepository: mockRecipeRepository,
);
when(mockMyRecipesRepository.getMyRecipes()).thenThrow(Exception('Network error'));
@@ -141,6 +148,7 @@ void main() {
myRecipesCubit = MyRecipesCubit(
myRecipesRepository: mockMyRecipesRepository,
aboutRecipeRepository: mockAboutRecipeRepository,
recipeRepository: mockRecipeRepository,
);
when(mockMyRecipesRepository.getMyRecipes()).thenAnswer(
@@ -161,6 +169,7 @@ void main() {
myRecipesCubit = MyRecipesCubit(
myRecipesRepository: mockMyRecipesRepository,
aboutRecipeRepository: mockAboutRecipeRepository,
recipeRepository: mockRecipeRepository,
);
when(mockMyRecipesRepository.getMyRecipes()).thenThrow(Exception('Network error'));
@@ -175,6 +184,7 @@ void main() {
myRecipesCubit = MyRecipesCubit(
myRecipesRepository: mockMyRecipesRepository,
aboutRecipeRepository: mockAboutRecipeRepository,
recipeRepository: mockRecipeRepository,
);
when(mockMyRecipesRepository.getMyRecipes()).thenAnswer(
@@ -195,6 +205,7 @@ void main() {
myRecipesCubit = MyRecipesCubit(
myRecipesRepository: mockMyRecipesRepository,
aboutRecipeRepository: mockAboutRecipeRepository,
recipeRepository: mockRecipeRepository,
);
when(mockMyRecipesRepository.getMyRecipes()).thenThrow(Exception('Network error'));
@@ -209,6 +220,7 @@ void main() {
myRecipesCubit = MyRecipesCubit(
myRecipesRepository: mockMyRecipesRepository,
aboutRecipeRepository: mockAboutRecipeRepository,
recipeRepository: mockRecipeRepository,
);
when(mockAboutRecipeRepository.getRecipe('test-recipe')).thenAnswer((_) async => testRecipe);
@@ -225,6 +237,7 @@ void main() {
myRecipesCubit = MyRecipesCubit(
myRecipesRepository: mockMyRecipesRepository,
aboutRecipeRepository: mockAboutRecipeRepository,
recipeRepository: mockRecipeRepository,
);
when(mockMyRecipesRepository.getMyRecipes()).thenAnswer(