forked from eatvio/eatvio_app
feat: Add user recipe management and creation functionality, including models, widgets, cubits, repositories, and tests.
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -22,4 +22,5 @@ android/key.properties
|
||||
app/upload-keystore.jks
|
||||
coverage/
|
||||
.fvm/
|
||||
.fvmrc
|
||||
.fvmrc
|
||||
/eatvio
|
||||
@@ -114,7 +114,7 @@ class CubitRegistrations {
|
||||
// User content cubits
|
||||
sl.registerFactory<MyDietsCubit>(() => MyDietsCubit(myDietsRepository: sl(), createDietRepository: sl()));
|
||||
sl.registerFactory<MyPlansCubit>(() => MyPlansCubit(myPlansRepository: sl()));
|
||||
sl.registerFactory<MyRecipesCubit>(() => MyRecipesCubit(myRecipesRepository: sl(), aboutRecipeRepository: sl()));
|
||||
sl.registerFactory<MyRecipesCubit>(() => MyRecipesCubit(myRecipesRepository: sl(), aboutRecipeRepository: sl(), recipeRepository: sl()));
|
||||
|
||||
// Other feature cubits
|
||||
sl.registerFactory<CalendarCubit>(() => CalendarCubit(calendarRepository: sl()));
|
||||
|
||||
@@ -153,7 +153,16 @@ class OtherRoutes {
|
||||
GoRoute(
|
||||
path: RecipeCreateScreen.path,
|
||||
name: RecipeCreateScreen.name,
|
||||
builder: (context, state) => RecipeCreateScreen(recipe: state.extra != null ? state.extra as Recipe : null),
|
||||
builder: (context, state) {
|
||||
if (state.extra is Map<String, dynamic>) {
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
return RecipeCreateScreen(
|
||||
recipe: data['recipe'] as Recipe?,
|
||||
autoValidate: data['autoValidate'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
return RecipeCreateScreen(recipe: state.extra != null ? state.extra as Recipe : null);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: CreateDietScreen.path,
|
||||
|
||||
@@ -19,8 +19,9 @@ import 'package:go_router/go_router.dart';
|
||||
|
||||
class RecipeCreateScreen extends StatefulWidget {
|
||||
final Recipe? recipe;
|
||||
final bool autoValidate;
|
||||
|
||||
const RecipeCreateScreen({super.key, this.recipe});
|
||||
const RecipeCreateScreen({super.key, this.recipe, this.autoValidate = false});
|
||||
|
||||
static const name = 'recipeCreateScreen';
|
||||
static const path = '/recipe-create-screen';
|
||||
@@ -40,6 +41,11 @@ class _RecipeCreateScreenState extends State<RecipeCreateScreen> {
|
||||
createRecipeCubit = sl<CreateRecipeCubit>();
|
||||
if (widget.recipe != null) {
|
||||
createRecipeCubit.recipeToEntity(widget.recipe!);
|
||||
if (widget.autoValidate) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
createRecipeCubit.validateAll();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
createRecipeCubit.disableLoading();
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ class RecipeRepository {
|
||||
'type': recipe.type,
|
||||
'title': recipe.title,
|
||||
'tags[]': recipe.tags ?? [],
|
||||
'state': 'active',
|
||||
'description_md': recipe.description,
|
||||
'video_url': recipe.videoUrl,
|
||||
'ingredients': ingredientsMap,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import 'package:eatvio_app/features/create_recipe/models/recipe_model.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/models/step.dart' as cr_step;
|
||||
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'my_recipe_model.g.dart';
|
||||
@@ -34,3 +37,49 @@ abstract class MyRecipeModel with _$MyRecipeModel {
|
||||
|
||||
factory MyRecipeModel.fromJson(Map<String, Object?> json) => _$MyRecipeModelFromJson(json);
|
||||
}
|
||||
|
||||
extension MyRecipeModelX on MyRecipeModel {
|
||||
RecipeModel toRecipeModel() {
|
||||
return RecipeModel(
|
||||
title: title,
|
||||
description: description,
|
||||
image: image,
|
||||
totalCookingTime: cookingTime,
|
||||
activeCookTime: activeCookingTime,
|
||||
energyPerServing: calories.toInt(),
|
||||
videoUrl: videoUrl,
|
||||
// The API expects 'active' to publish
|
||||
state: 'active',
|
||||
steps: steps?.map((e) => cr_step.Step(image: e.image, description: e.description)).toList().cast<cr_step.Step?>(),
|
||||
// For My Recipes we don't have ingredients directly accessible in this basic model,
|
||||
// but when it gets sent to publish, RecipeRepository merges properties correctly if available.
|
||||
ingredients: [],
|
||||
tags: [],
|
||||
);
|
||||
}
|
||||
|
||||
Recipe toRecipe() {
|
||||
return Recipe(
|
||||
id: id,
|
||||
image: image,
|
||||
state: state,
|
||||
cookingTime: cookingTime,
|
||||
activeCookingTime: activeCookingTime,
|
||||
ingredients: [],
|
||||
description: description,
|
||||
steps: steps,
|
||||
videoUrl: videoUrl,
|
||||
title: title,
|
||||
slug: slug,
|
||||
calories: calories,
|
||||
likesCount: likesCount,
|
||||
viewsCount: viewsCount,
|
||||
type: 'recipe',
|
||||
commentsCount: 0,
|
||||
wasLikedByMe: false,
|
||||
isFavorite: false,
|
||||
tags: [],
|
||||
author: const Author(authorSlug: '', authorName: '', authorProfileIcon: '', authorIsMe: true),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import 'dart:developer';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/models/recipe_model.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.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_recipes_state.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_bloc/flutter_bloc.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
class MyRecipesCubit extends Cubit<MyRecipesState> {
|
||||
final MyRecipesRepository myRecipesRepository;
|
||||
final AboutRecipeRepository aboutRecipeRepository;
|
||||
final RecipeRepository recipeRepository;
|
||||
|
||||
static const String active = 'active';
|
||||
static const String archived = 'archived';
|
||||
@@ -16,6 +22,7 @@ class MyRecipesCubit extends Cubit<MyRecipesState> {
|
||||
MyRecipesCubit({
|
||||
required this.myRecipesRepository,
|
||||
required this.aboutRecipeRepository,
|
||||
required this.recipeRepository,
|
||||
}) : super(const MyRecipesState.loading());
|
||||
|
||||
Future<void> getMyRecipes() async {
|
||||
@@ -65,4 +72,33 @@ class MyRecipesCubit extends Cubit<MyRecipesState> {
|
||||
Future<Recipe> getRecipeById(String slug) async {
|
||||
return await aboutRecipeRepository.getRecipe(slug);
|
||||
}
|
||||
|
||||
/// Attempts to publish a drafted recipe
|
||||
/// Returns `true` if successful
|
||||
/// Returns `false` if failed (e.g. validation error 422 because fields are missing)
|
||||
Future<bool> publishDraft(RecipeModel recipe, String slug) async {
|
||||
try {
|
||||
await recipeRepository.publishRecipe(recipe, true, slug);
|
||||
return true;
|
||||
} catch (e, st) {
|
||||
if (e is DioException && e.response?.statusCode == 422) {
|
||||
log('Validation failed for publishing draft: \${e.response?.data}');
|
||||
} else {
|
||||
await Sentry.captureException(e, stackTrace: st);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves an active recipe to draft
|
||||
Future<bool> draftRecipe(int id) async {
|
||||
try {
|
||||
await recipeRepository.draftExistingRecipe(id);
|
||||
return true;
|
||||
} catch (e, st) {
|
||||
log('Failed to draft recipe: \$e');
|
||||
await Sentry.captureException(e, stackTrace: st);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/m
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_create_screen.dart';
|
||||
import 'package:eatvio_app/core/widgets/markdown_html_widget.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
@@ -144,80 +143,112 @@ class _MyRecipeWidgetState extends State<MyRecipeWidget> {
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: PopupMenuButton<int>(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
offset: const Offset(0, 50),
|
||||
itemBuilder: (context) => [
|
||||
if (!isDraft)
|
||||
PopupMenuItem(
|
||||
onTap: () async {
|
||||
final box = context.findRenderObject() as RenderBox?;
|
||||
await SharePlus.instance.share(
|
||||
ShareParams(
|
||||
uri: Uri.parse('${sl<AppConfig>().baseUrl}/ru/recipe/${widget.recipe.slug}'),
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||
child: isDraft
|
||||
? CustomOutlinedButton(
|
||||
onPressed: () {
|
||||
context.push(RecipeCreateScreen.path, extra: widget.recipe.toRecipe());
|
||||
},
|
||||
height: 52,
|
||||
buttonStyle: ButtonStyle(
|
||||
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.lightGray0),
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
),
|
||||
text: 'Редактировать',
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.black),
|
||||
)
|
||||
: PopupMenuButton<int>(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
offset: const Offset(0, 50),
|
||||
itemBuilder: (context) => [
|
||||
if (!isDraft)
|
||||
PopupMenuItem(
|
||||
onTap: () async {
|
||||
final box = context.findRenderObject() as RenderBox?;
|
||||
await SharePlus.instance.share(
|
||||
ShareParams(
|
||||
uri: Uri.parse('\${sl<AppConfig>().baseUrl}/ru/recipe/\${widget.recipe.slug}'),
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.share, size: 20, color: appTheme.black),
|
||||
const SizedBox(width: 8),
|
||||
Text(S.of(context).share, style: TextStyles.textSMedium),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.share, size: 20, color: appTheme.black),
|
||||
const SizedBox(width: 8),
|
||||
Text(S.of(context).share, style: TextStyles.textSMedium),
|
||||
],
|
||||
if (!isDraft)
|
||||
PopupMenuItem(
|
||||
onTap: () async {
|
||||
final result = await context.read<MyRecipesCubit>().draftRecipe(widget.recipe.id);
|
||||
if (result) {
|
||||
showSuccessToast('Рецепт перенесен в черновики');
|
||||
if (context.mounted) {
|
||||
context.read<MyRecipesCubit>().getMyRecipes();
|
||||
}
|
||||
} else {
|
||||
showErrorToast('Не удалось перенести в черновики');
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.archive_outlined, size: 20, color: appTheme.black),
|
||||
const SizedBox(width: 8),
|
||||
const Text('В черновик', style: TextStyles.textSMedium),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
child: CustomOutlinedButton(
|
||||
onPressed: () {},
|
||||
height: 52,
|
||||
width: 52,
|
||||
buttonStyle: ButtonStyle(
|
||||
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
),
|
||||
text: s.share,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
),
|
||||
),
|
||||
if (widget.recipe.state == 'active' || isDraft)
|
||||
PopupMenuItem(
|
||||
onTap: () {
|
||||
context.push(RecipeCreateScreen.path, extra: widget.recipe);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.edit, size: 20, color: appTheme.black),
|
||||
const SizedBox(width: 8),
|
||||
Text(S.of(context).edit, style: TextStyles.textSMedium),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
child: CustomOutlinedButton(
|
||||
onPressed: () {},
|
||||
height: 52,
|
||||
width: 52,
|
||||
buttonStyle: ButtonStyle(
|
||||
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
),
|
||||
text: isDraft ? '...' : s.share,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (isDraft)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
child: CustomOutlinedButton(
|
||||
onPressed: () async {
|
||||
// Publish draft
|
||||
context.read<CreateRecipeCubit>().publishRecipe(true, widget.recipe.slug).then((success) {
|
||||
if (success && context.mounted) {
|
||||
context.read<MyRecipesCubit>().setDraftedState();
|
||||
}
|
||||
});
|
||||
final recipeModel = widget.recipe.toRecipeModel();
|
||||
final success = await context.read<MyRecipesCubit>().publishDraft(recipeModel, widget.recipe.slug);
|
||||
if (success && context.mounted) {
|
||||
showSuccessToast('Черновик опубликован');
|
||||
context.read<MyRecipesCubit>().setDraftedState();
|
||||
} else {
|
||||
showErrorToast('Заполните все необходимые поля для публикации рецепта');
|
||||
if (context.mounted) {
|
||||
context.push(RecipeCreateScreen.path, extra: {
|
||||
'recipe': widget.recipe.toRecipe(),
|
||||
'autoValidate': true,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: appTheme.blueA400,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
height: 52,
|
||||
buttonStyle: ButtonStyle(
|
||||
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
),
|
||||
child: Text('Опубликовать', style: TextStyles.textSMedium.copyWith(color: Colors.white)),
|
||||
text: 'Опубликовать',
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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_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(
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:eatvio_app/features/recipe/models/my_recipe_model.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(
|
||||
|
||||
Reference in New Issue
Block a user