/
fraq11
/
ElijahFlutter
Обзор
Документация
Войти
/
fraq11
/
ElijahFlutter
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
4
CI/CD
Аналитика
Безопасность
master
test/viewmodels_test.dart
325 строк
9 KB
octomors
lab 10 flutter done
24 май 2026, 19:59
24 май 2026, 19:59
2c672b5
Код
Авторство
О чём код?
import 'package:elijahflutter/models/entities/ingredient.dart'; import 'package:elijahflutter/models/entities/recipe.dart'; import 'package:elijahflutter/models/entities/recipe_ingredient.dart'; import 'package:elijahflutter/models/entities/user.dart'; import 'package:elijahflutter/models/interfaces/auth_service.dart'; import 'package:elijahflutter/models/interfaces/ingredient_service.dart'; import 'package:elijahflutter/models/interfaces/recipe_service.dart'; import 'package:elijahflutter/models/interfaces/user_service.dart'; import 'package:elijahflutter/view/data/ui_models.dart'; import 'package:elijahflutter/view/data/ingredient_ui.dart'; import 'package:elijahflutter/viewmodel/pages/chef_mode_view_model.dart'; import 'package:elijahflutter/viewmodel/pages/onboarding_view_model.dart'; import 'package:elijahflutter/viewmodel/pages/profile_view_model.dart'; import 'package:elijahflutter/viewmodel/pages/recipe_detail_view_model.dart'; import 'package:elijahflutter/viewmodel/pages/search_view_model.dart'; import 'package:elijahflutter/viewmodel/shared/settings_view_model.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('SettingsViewModel', () { test('toggles dark theme state', () { final viewModel = SettingsViewModel(); expect(viewModel.isDarkTheme, isFalse); viewModel.toggleTheme(); expect(viewModel.isDarkTheme, isTrue); }); }); group('OnboardingViewModel', () { test('advances until the last step', () { final viewModel = OnboardingViewModel(); expect(viewModel.currentStep, 0); expect(viewModel.isLastStep, isFalse); viewModel.nextStep(); expect(viewModel.currentStep, 1); viewModel.nextStep(); expect(viewModel.currentStep, 2); expect(viewModel.isLastStep, isTrue); viewModel.nextStep(); expect(viewModel.currentStep, 2); }); }); group('SearchViewModel', () { test('filters by query, difficulty and ingredients', () async { final viewModel = SearchViewModel( recipeService: FakeRecipeService(), ingredientService: FakeIngredientService(), ); await viewModel.loadRecipes(); expect(viewModel.filteredRecipes, hasLength(2)); viewModel.setSearchQuery('Суп'); await viewModel.loadRecipes(); expect( viewModel.filteredRecipes.map((recipe) => recipe.title), contains('Крем-суп из тыквы'), ); viewModel.toggleDifficulty(RecipeDifficulty.easy); expect( viewModel.filteredRecipes.every((recipe) => recipe.difficulty == RecipeDifficulty.easy), isTrue, ); viewModel.toggleIngredient(const IngredientUi(id: 1, name: 'Яйца')); await viewModel.loadRecipes(); expect( viewModel.filteredRecipes.every((recipe) => recipe.title.contains('Суп')), isTrue, ); }); }); group('RecipeDetailViewModel', () { test('loads recipe and keeps tab when steps missing', () async { final viewModel = RecipeDetailViewModel( recipeId: '1', recipeService: FakeRecipeService(), ); await viewModel.loadRecipe(); expect(viewModel.recipeDetail?.recipe.title, 'Французские тосты с ягодами'); expect(viewModel.selectedTab, RecipeDetailTab.ingredients); viewModel.setSelectedTab(RecipeDetailTab.steps); expect(viewModel.selectedTab, RecipeDetailTab.ingredients); }); }); group('ChefModeViewModel', () { test('keeps position when steps missing', () async { final viewModel = ChefModeViewModel( recipeId: '1', recipeService: FakeRecipeService(), ); await viewModel.loadRecipe(); expect(viewModel.stepIndex, 0); expect(viewModel.isLastStep, isTrue); viewModel.nextStep(); expect(viewModel.stepIndex, 0); }); }); group('ProfileViewModel', () { test('delegates theme toggles to shared settings view model', () { final settingsViewModel = SettingsViewModel(); final viewModel = ProfileViewModel( settingsViewModel, FakeUserService(), FakeAuthService(), ); expect(viewModel.isDarkTheme, isFalse); viewModel.toggleTheme(); expect(settingsViewModel.isDarkTheme, isTrue); expect(viewModel.isDarkTheme, isTrue); expect(viewModel.notificationsEnabled, isTrue); viewModel.setNotificationsEnabled(false); expect(viewModel.notificationsEnabled, isFalse); }); }); } class FakeRecipeService implements RecipeService { final List<Recipe> _recipes = <Recipe>[ Recipe( id: 1, title: 'Французские тосты с ягодами', difficulty: 1, cookingTime: 20, ingredients: const <RecipeIngredient>[ RecipeIngredient( id: 1, name: 'Яйца', quantity: 2, measurement: IngredientMeasurement.pieces, ), ], ), Recipe( id: 2, title: 'Крем-суп из тыквы', difficulty: 2, cookingTime: 45, ingredients: const <RecipeIngredient>[ RecipeIngredient( id: 2, name: 'Тыква', quantity: 300, measurement: IngredientMeasurement.grams, ), ], ), ]; @override Future<List<Recipe>> fetchRecipes({int skip = 0, int limit = 100}) async { return _recipes; } @override Future<Recipe> fetchRecipe(int id) async { return _recipes.firstWhere((recipe) => recipe.id == id); } @override Future<List<Recipe>> fetchRecipesPaginated({ String? nameLike, List<int>? ingredientIds, String? sort, int page = 1, int size = 20, }) async { return _recipes.where((recipe) { final matchesName = nameLike == null || recipe.title.contains(nameLike); final matchesIngredient = ingredientIds == null || ingredientIds.isEmpty ? true : recipe.ingredients.any((ingredient) => ingredientIds.contains(ingredient.id)); return matchesName && matchesIngredient; }).toList(); } @override Future<Recipe> createRecipe({ required String title, String? description, int? cookingTime, int? difficulty, int? cuisineId, List<int>? allergenIds, List<Map<String, dynamic>>? ingredients, }) async { throw UnimplementedError(); } @override Future<Recipe> updateRecipe( int id, { String? title, String? description, int? cookingTime, int? difficulty, int? cuisineId, List<int>? allergenIds, List<Map<String, dynamic>>? ingredients, }) async { throw UnimplementedError(); } @override Future<void> deleteRecipe(int id) async { throw UnimplementedError(); } } class FakeIngredientService implements IngredientService { @override Future<List<Ingredient>> fetchIngredients({int skip = 0, int limit = 200}) async { return const <Ingredient>[ Ingredient(id: 1, name: 'Яйца'), Ingredient(id: 2, name: 'Тыква'), ]; } @override Future<Ingredient> fetchIngredient(int id) async { return Ingredient(id: id, name: 'Ingredient $id'); } @override Future<Ingredient> createIngredient(String name) async { throw UnimplementedError(); } @override Future<Ingredient> updateIngredient(int id, String name) async { throw UnimplementedError(); } @override Future<void> deleteIngredient(int id) async { throw UnimplementedError(); } @override Future<List<Recipe>> fetchRecipesByIngredient( int ingredientId, { List<String>? include, List<String>? select, }) async { throw UnimplementedError(); } } class FakeUserService implements UserService { @override Future<User> fetchMe() async { return const User(id: 1, email: 'user@example.com', firstName: 'Ivan', lastName: 'Petrov'); } @override Future<User> updateMe({ String? email, String? password, String? firstName, String? lastName, bool? isActive, bool? isSuperuser, bool? isVerified, }) async { return const User(id: 1, email: 'user@example.com', firstName: 'Ivan', lastName: 'Petrov'); } } class FakeAuthService implements AuthService { @override Future<String> login({required String email, required String password}) async { return 'token'; } @override Future<void> logout() async {} @override Future<void> register({ required String email, required String password, required String firstName, required String lastName, bool isActive = true, bool isSuperuser = false, bool isVerified = false, }) async {} @override Future<void> requestVerifyToken(String email) async {} @override Future<void> verify(String token) async {} @override Future<void> forgotPassword(String email) async {} @override Future<void> resetPassword({required String token, required String password}) async {} }