refactor: create separate BLoCs for each screen with comprehensive tests
- Created 8 separate BLoCs (Home, Library, BookDetails, AddBook, Scanner, Categories, Wishlist, Settings) - Each BLoC has its own event, state, and bloc files - Added 70 comprehensive tests covering all BLoC functionality - All tests passing (70/70) - Fixed linting issues and updated deprecated APIs - Improved code organization and maintainability
This commit is contained in:
178
bookshelf_flutter/test/bloc/add_book/add_book_bloc_test.dart
Normal file
178
bookshelf_flutter/test/bloc/add_book/add_book_bloc_test.dart
Normal file
@@ -0,0 +1,178 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:bookshelf_flutter/bloc/add_book/add_book_bloc.dart';
|
||||
import 'package:bookshelf_flutter/bloc/add_book/add_book_event.dart';
|
||||
import 'package:bookshelf_flutter/bloc/add_book/add_book_state.dart';
|
||||
import 'package:bookshelf_flutter/models/models.dart';
|
||||
|
||||
void main() {
|
||||
group('AddBookBloc', () {
|
||||
late AddBookBloc addBookBloc;
|
||||
|
||||
setUp(() {
|
||||
addBookBloc = AddBookBloc();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
addBookBloc.close();
|
||||
});
|
||||
|
||||
test('initial state is AddBookState.initial()', () {
|
||||
expect(addBookBloc.state, isA<AddBookState>());
|
||||
expect(addBookBloc.state.isLoading, false);
|
||||
});
|
||||
|
||||
group('InitializeForm', () {
|
||||
test('initializes form with prefilled data', () async {
|
||||
final prefilledData = {
|
||||
'title': 'Test Book',
|
||||
'author': 'Test Author',
|
||||
'genre': 'Test Genre',
|
||||
'annotation': 'Test annotation',
|
||||
'coverUrl': 'https://example.com/cover.jpg',
|
||||
'pages': 200,
|
||||
'language': 'English',
|
||||
'publishedYear': 2020,
|
||||
'rating': 4.5,
|
||||
'status': BookStatus.reading,
|
||||
'progress': 50,
|
||||
'isFavorite': true,
|
||||
};
|
||||
|
||||
addBookBloc.add(InitializeForm(prefilledData));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(addBookBloc.state.title, 'Test Book');
|
||||
expect(addBookBloc.state.author, 'Test Author');
|
||||
expect(addBookBloc.state.genre, 'Test Genre');
|
||||
expect(addBookBloc.state.isFavorite, true);
|
||||
expect(addBookBloc.state.pages, 200);
|
||||
});
|
||||
|
||||
test('initializes form with empty data when prefilledData is null', () async {
|
||||
addBookBloc.add(const InitializeForm(null));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(addBookBloc.state.title, '');
|
||||
expect(addBookBloc.state.author, '');
|
||||
expect(addBookBloc.state.isFavorite, false);
|
||||
});
|
||||
});
|
||||
|
||||
group('UpdateFormField', () {
|
||||
test('updates title field', () async {
|
||||
addBookBloc.add(const UpdateFormField('title', 'New Title'));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(addBookBloc.state.title, 'New Title');
|
||||
});
|
||||
|
||||
test('updates author field', () async {
|
||||
addBookBloc.add(const UpdateFormField('author', 'New Author'));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(addBookBloc.state.author, 'New Author');
|
||||
});
|
||||
|
||||
test('updates genre field', () async {
|
||||
addBookBloc.add(const UpdateFormField('genre', 'New Genre'));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(addBookBloc.state.genre, 'New Genre');
|
||||
});
|
||||
|
||||
test('updates pages field', () async {
|
||||
addBookBloc.add(const UpdateFormField('pages', 300));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(addBookBloc.state.pages, 300);
|
||||
});
|
||||
|
||||
test('updates rating field', () async {
|
||||
addBookBloc.add(const UpdateFormField('rating', 4.8));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(addBookBloc.state.rating, 4.8);
|
||||
});
|
||||
|
||||
test('updates status field', () async {
|
||||
addBookBloc.add(const UpdateFormField('status', BookStatus.done));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(addBookBloc.state.status, BookStatus.done);
|
||||
});
|
||||
});
|
||||
|
||||
group('ToggleFavorite', () {
|
||||
test('toggles isFavorite from false to true', () async {
|
||||
expect(addBookBloc.state.isFavorite, false);
|
||||
|
||||
addBookBloc.add(const ToggleFavorite());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(addBookBloc.state.isFavorite, true);
|
||||
});
|
||||
|
||||
test('toggles isFavorite from true to false', () async {
|
||||
addBookBloc.add(const ToggleFavorite());
|
||||
await Future.delayed(Duration.zero);
|
||||
expect(addBookBloc.state.isFavorite, true);
|
||||
|
||||
addBookBloc.add(const ToggleFavorite());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(addBookBloc.state.isFavorite, false);
|
||||
});
|
||||
});
|
||||
|
||||
group('ClearForm', () {
|
||||
test('clears all form fields', () async {
|
||||
addBookBloc.add(const UpdateFormField('title', 'Test'));
|
||||
addBookBloc.add(const UpdateFormField('author', 'Test'));
|
||||
addBookBloc.add(const ToggleFavorite());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(addBookBloc.state.title, 'Test');
|
||||
|
||||
addBookBloc.add(const ClearForm());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(addBookBloc.state.title, '');
|
||||
expect(addBookBloc.state.author, '');
|
||||
expect(addBookBloc.state.isFavorite, false);
|
||||
});
|
||||
});
|
||||
|
||||
group('SubmitBook', () {
|
||||
test('creates book with form data and sets isSubmitted to true', () async {
|
||||
addBookBloc.add(const UpdateFormField('title', 'New Book'));
|
||||
addBookBloc.add(const UpdateFormField('author', 'New Author'));
|
||||
addBookBloc.add(const UpdateFormField('genre', 'New Genre'));
|
||||
addBookBloc.add(const UpdateFormField('annotation', 'New annotation'));
|
||||
addBookBloc.add(const UpdateFormField('pages', 200));
|
||||
addBookBloc.add(const UpdateFormField('language', 'English'));
|
||||
addBookBloc.add(const UpdateFormField('publishedYear', 2023));
|
||||
addBookBloc.add(const UpdateFormField('rating', 4.5));
|
||||
addBookBloc.add(const UpdateFormField('status', BookStatus.wantToRead));
|
||||
addBookBloc.add(const UpdateFormField('progress', 0));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
addBookBloc.add(const SubmitBook());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(addBookBloc.state.isSubmitted, true);
|
||||
expect(addBookBloc.state.submittedBook?.title, 'New Book');
|
||||
expect(addBookBloc.state.submittedBook?.author, 'New Author');
|
||||
});
|
||||
|
||||
test('uses default values for empty fields', () async {
|
||||
addBookBloc.add(const SubmitBook());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(addBookBloc.state.submittedBook?.title, 'Unknown');
|
||||
expect(addBookBloc.state.submittedBook?.author, 'Unknown');
|
||||
expect(addBookBloc.state.submittedBook?.genre, 'Unknown');
|
||||
expect(addBookBloc.state.submittedBook?.coverUrl?.isNotEmpty, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:bookshelf_flutter/bloc/book_details/book_details_bloc.dart';
|
||||
import 'package:bookshelf_flutter/bloc/book_details/book_details_event.dart';
|
||||
import 'package:bookshelf_flutter/bloc/book_details/book_details_state.dart';
|
||||
import 'package:bookshelf_flutter/models/models.dart';
|
||||
|
||||
void main() {
|
||||
group('BookDetailsBloc', () {
|
||||
late BookDetailsBloc bookDetailsBloc;
|
||||
late Book testBook;
|
||||
|
||||
setUp(() {
|
||||
bookDetailsBloc = BookDetailsBloc();
|
||||
testBook = createBook(
|
||||
id: '1',
|
||||
title: 'Test Book',
|
||||
author: 'Test Author',
|
||||
genre: 'Test Genre',
|
||||
annotation: 'Test annotation',
|
||||
coverUrl: 'https://example.com/cover.jpg',
|
||||
pages: 200,
|
||||
language: 'English',
|
||||
publishedYear: 2020,
|
||||
rating: 4.5,
|
||||
status: BookStatus.reading,
|
||||
progress: 50,
|
||||
isFavorite: false,
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
bookDetailsBloc.close();
|
||||
});
|
||||
|
||||
test('initial state is BookDetailsState.initial()', () {
|
||||
expect(bookDetailsBloc.state, isA<BookDetailsState>());
|
||||
expect(bookDetailsBloc.state.isLoading, true);
|
||||
});
|
||||
|
||||
group('LoadBookDetails', () {
|
||||
test('loads book details into state', () async {
|
||||
bookDetailsBloc.add(LoadBookDetails(testBook));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(bookDetailsBloc.state.book?.id, testBook.id);
|
||||
expect(bookDetailsBloc.state.book?.title, testBook.title);
|
||||
expect(bookDetailsBloc.state.isLoading, false);
|
||||
});
|
||||
});
|
||||
|
||||
group('ToggleFavorite', () {
|
||||
test('toggles isFavorite from false to true', () async {
|
||||
bookDetailsBloc.add(LoadBookDetails(testBook));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
bookDetailsBloc.add(const ToggleFavorite());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(bookDetailsBloc.state.book?.isFavorite, true);
|
||||
});
|
||||
|
||||
test('toggles isFavorite from true to false', () async {
|
||||
final favoriteBook = testBook.copyWith(isFavorite: true);
|
||||
bookDetailsBloc.add(LoadBookDetails(favoriteBook));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
bookDetailsBloc.add(const ToggleFavorite());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(bookDetailsBloc.state.book?.isFavorite, false);
|
||||
});
|
||||
|
||||
test('does nothing when no book is loaded', () async {
|
||||
final initialFavorite = bookDetailsBloc.state.book?.isFavorite;
|
||||
bookDetailsBloc.add(const ToggleFavorite());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(bookDetailsBloc.state.book?.isFavorite, initialFavorite);
|
||||
});
|
||||
});
|
||||
|
||||
group('UpdateProgress', () {
|
||||
test('updates progress and keeps reading status', () async {
|
||||
bookDetailsBloc.add(LoadBookDetails(testBook));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
bookDetailsBloc.add(const UpdateProgress(75));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(bookDetailsBloc.state.book?.progress, 75);
|
||||
expect(bookDetailsBloc.state.book?.status, BookStatus.reading);
|
||||
});
|
||||
|
||||
test('updates status to done when progress is 100', () async {
|
||||
bookDetailsBloc.add(LoadBookDetails(testBook));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
bookDetailsBloc.add(const UpdateProgress(100));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(bookDetailsBloc.state.book?.progress, 100);
|
||||
expect(bookDetailsBloc.state.book?.status, BookStatus.done);
|
||||
});
|
||||
|
||||
test('does nothing when no book is loaded', () async {
|
||||
final initialProgress = bookDetailsBloc.state.book?.progress;
|
||||
bookDetailsBloc.add(const UpdateProgress(50));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(bookDetailsBloc.state.book?.progress, initialProgress);
|
||||
});
|
||||
});
|
||||
|
||||
group('UpdateStatus', () {
|
||||
test('updates book status', () async {
|
||||
bookDetailsBloc.add(LoadBookDetails(testBook));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
bookDetailsBloc.add(const UpdateStatus(BookStatus.done));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(bookDetailsBloc.state.book?.status, BookStatus.done);
|
||||
});
|
||||
});
|
||||
|
||||
group('DeleteBook', () {
|
||||
test('sets isDeleted to true', () async {
|
||||
bookDetailsBloc.add(LoadBookDetails(testBook));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
bookDetailsBloc.add(const DeleteBook());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(bookDetailsBloc.state.isDeleted, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:bookshelf_flutter/bloc/categories/categories_bloc.dart';
|
||||
import 'package:bookshelf_flutter/bloc/categories/categories_event.dart';
|
||||
import 'package:bookshelf_flutter/bloc/categories/categories_state.dart';
|
||||
|
||||
void main() {
|
||||
group('CategoriesBloc', () {
|
||||
late CategoriesBloc categoriesBloc;
|
||||
|
||||
setUp(() {
|
||||
categoriesBloc = CategoriesBloc();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
categoriesBloc.close();
|
||||
});
|
||||
|
||||
test('initial state is CategoriesState.initial()', () {
|
||||
expect(categoriesBloc.state, isA<CategoriesState>());
|
||||
expect(categoriesBloc.state.isLoading, true);
|
||||
});
|
||||
|
||||
group('LoadCategories', () {
|
||||
test('loads categories into state', () async {
|
||||
categoriesBloc.add(const LoadCategories());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(categoriesBloc.state.categories.length, 8);
|
||||
expect(categoriesBloc.state.filteredCategories.length, 8);
|
||||
expect(categoriesBloc.state.isLoading, false);
|
||||
});
|
||||
});
|
||||
|
||||
group('SelectCategory', () {
|
||||
test('updates selectedCategory in state', () async {
|
||||
categoriesBloc.add(const LoadCategories());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
final category = categoriesBloc.state.categories.first;
|
||||
categoriesBloc.add(SelectCategory(category));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(categoriesBloc.state.selectedCategory?.id, category.id);
|
||||
});
|
||||
});
|
||||
|
||||
group('SearchCategories', () {
|
||||
test('filters categories by name', () async {
|
||||
categoriesBloc.add(const LoadCategories());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
categoriesBloc.add(const SearchCategories('Sci'));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(categoriesBloc.state.filteredCategories.length, 1);
|
||||
expect(categoriesBloc.state.filteredCategories.first.name.toLowerCase(), contains('sci'));
|
||||
});
|
||||
|
||||
test('returns empty list when no matches found', () async {
|
||||
categoriesBloc.add(const LoadCategories());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
categoriesBloc.add(const SearchCategories('NonExistent'));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(categoriesBloc.state.filteredCategories.length, 0);
|
||||
});
|
||||
|
||||
test('search is case insensitive', () async {
|
||||
categoriesBloc.add(const LoadCategories());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
categoriesBloc.add(const SearchCategories('classic'));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(categoriesBloc.state.filteredCategories.length, 1);
|
||||
expect(categoriesBloc.state.filteredCategories.first.name, 'Classic');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
85
bookshelf_flutter/test/bloc/home/home_bloc_test.dart
Normal file
85
bookshelf_flutter/test/bloc/home/home_bloc_test.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:bookshelf_flutter/bloc/home/home_bloc.dart';
|
||||
import 'package:bookshelf_flutter/bloc/home/home_event.dart';
|
||||
import 'package:bookshelf_flutter/bloc/home/home_state.dart';
|
||||
import 'package:bookshelf_flutter/models/models.dart';
|
||||
|
||||
void main() {
|
||||
group('HomeBloc', () {
|
||||
late HomeBloc homeBloc;
|
||||
|
||||
setUp(() {
|
||||
homeBloc = HomeBloc();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
homeBloc.close();
|
||||
});
|
||||
|
||||
test('initial state is HomeState.initial()', () {
|
||||
expect(homeBloc.state, isA<HomeState>());
|
||||
expect(homeBloc.state.isLoading, true);
|
||||
});
|
||||
|
||||
group('LoadHomeData', () {
|
||||
test('loads home data with default screen', () async {
|
||||
homeBloc.add(const LoadHomeData());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(homeBloc.state.currentScreen, AppScreen.library);
|
||||
expect(homeBloc.state.isLoading, false);
|
||||
});
|
||||
});
|
||||
|
||||
group('NavigateToScreen', () {
|
||||
test('navigates to library screen', () async {
|
||||
homeBloc.add(const NavigateToScreen(AppScreen.library));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(homeBloc.state.currentScreen, AppScreen.library);
|
||||
});
|
||||
|
||||
test('navigates to categories screen', () async {
|
||||
homeBloc.add(const NavigateToScreen(AppScreen.categories));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(homeBloc.state.currentScreen, AppScreen.categories);
|
||||
});
|
||||
|
||||
test('navigates to wishlist screen', () async {
|
||||
homeBloc.add(const NavigateToScreen(AppScreen.wishlist));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(homeBloc.state.currentScreen, AppScreen.wishlist);
|
||||
});
|
||||
|
||||
test('navigates to settings screen', () async {
|
||||
homeBloc.add(const NavigateToScreen(AppScreen.settings));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(homeBloc.state.currentScreen, AppScreen.settings);
|
||||
});
|
||||
|
||||
test('navigates to details screen', () async {
|
||||
homeBloc.add(const NavigateToScreen(AppScreen.details));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(homeBloc.state.currentScreen, AppScreen.details);
|
||||
});
|
||||
|
||||
test('navigates to add book screen', () async {
|
||||
homeBloc.add(const NavigateToScreen(AppScreen.addBook));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(homeBloc.state.currentScreen, AppScreen.addBook);
|
||||
});
|
||||
|
||||
test('navigates to scanner screen', () async {
|
||||
homeBloc.add(const NavigateToScreen(AppScreen.scanner));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(homeBloc.state.currentScreen, AppScreen.scanner);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
124
bookshelf_flutter/test/bloc/library/library_bloc_test.dart
Normal file
124
bookshelf_flutter/test/bloc/library/library_bloc_test.dart
Normal file
@@ -0,0 +1,124 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:bookshelf_flutter/bloc/library/library_bloc.dart';
|
||||
import 'package:bookshelf_flutter/bloc/library/library_event.dart';
|
||||
import 'package:bookshelf_flutter/bloc/library/library_state.dart';
|
||||
import 'package:bookshelf_flutter/models/models.dart';
|
||||
|
||||
void main() {
|
||||
group('LibraryBloc', () {
|
||||
late LibraryBloc libraryBloc;
|
||||
|
||||
setUp(() {
|
||||
libraryBloc = LibraryBloc();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
libraryBloc.close();
|
||||
});
|
||||
|
||||
test('initial state is LibraryState.initial()', () {
|
||||
expect(libraryBloc.state, isA<LibraryState>());
|
||||
expect(libraryBloc.state.isLoading, true);
|
||||
});
|
||||
|
||||
group('LoadBooks', () {
|
||||
test('emits [LibraryState] with books when LoadBooks is added', () async {
|
||||
final expected = [
|
||||
isA<LibraryState>()
|
||||
.having((state) => state.isLoading, 'isLoading', false)
|
||||
.having((state) => state.books.isNotEmpty, 'books.isNotEmpty', true),
|
||||
];
|
||||
|
||||
expectLater(libraryBloc.stream, emitsInOrder(expected));
|
||||
|
||||
libraryBloc.add(const LoadBooks());
|
||||
});
|
||||
});
|
||||
|
||||
group('SearchBooks', () {
|
||||
test('filters books by title', () async {
|
||||
libraryBloc.add(const LoadBooks());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
libraryBloc.add(const SearchBooks('Гэтсби'));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(libraryBloc.state.filteredBooks.length, 1);
|
||||
expect(libraryBloc.state.filteredBooks.first.title, contains('Гэтсби'));
|
||||
});
|
||||
|
||||
test('filters books by author', () async {
|
||||
libraryBloc.add(const LoadBooks());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
libraryBloc.add(const SearchBooks('Оруэлл'));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(libraryBloc.state.filteredBooks.length, 1);
|
||||
expect(libraryBloc.state.filteredBooks.first.author, contains('Оруэлл'));
|
||||
});
|
||||
|
||||
test('filters books by genre', () async {
|
||||
libraryBloc.add(const LoadBooks());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
libraryBloc.add(const SearchBooks('Fantasy'));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(libraryBloc.state.filteredBooks.isNotEmpty, true);
|
||||
});
|
||||
|
||||
test('returns empty list when no matches found', () async {
|
||||
libraryBloc.add(const LoadBooks());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
libraryBloc.add(const SearchBooks('NonExistent'));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(libraryBloc.state.filteredBooks.length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
group('BookSelected', () {
|
||||
test('updates selectedBook in state', () async {
|
||||
libraryBloc.add(const LoadBooks());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
final book = libraryBloc.state.books.first;
|
||||
libraryBloc.add(BookSelected(book));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(libraryBloc.state.selectedBook?.id, book.id);
|
||||
});
|
||||
});
|
||||
|
||||
group('FilterByStatus', () {
|
||||
test('filters books by status', () async {
|
||||
libraryBloc.add(const LoadBooks());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
libraryBloc.add(const FilterByStatus(BookStatus.reading));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(libraryBloc.state.filteredBooks.every((book) => book.status == BookStatus.reading), true);
|
||||
expect(libraryBloc.state.statusFilter, BookStatus.reading);
|
||||
});
|
||||
});
|
||||
|
||||
group('ClearFilters', () {
|
||||
test('clears search query and shows all books', () async {
|
||||
libraryBloc.add(const LoadBooks());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
libraryBloc.add(const SearchBooks('test'));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
libraryBloc.add(const ClearFilters());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(libraryBloc.state.searchQuery, '');
|
||||
expect(libraryBloc.state.filteredBooks.length, libraryBloc.state.books.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
82
bookshelf_flutter/test/bloc/scanner/scanner_bloc_test.dart
Normal file
82
bookshelf_flutter/test/bloc/scanner/scanner_bloc_test.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:bookshelf_flutter/bloc/scanner/scanner_bloc.dart';
|
||||
import 'package:bookshelf_flutter/bloc/scanner/scanner_event.dart';
|
||||
import 'package:bookshelf_flutter/bloc/scanner/scanner_state.dart';
|
||||
|
||||
void main() {
|
||||
group('ScannerBloc', () {
|
||||
late ScannerBloc scannerBloc;
|
||||
|
||||
setUp(() {
|
||||
scannerBloc = ScannerBloc();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
scannerBloc.close();
|
||||
});
|
||||
|
||||
test('initial state is ScannerState.initial()', () {
|
||||
expect(scannerBloc.state, isA<ScannerState>());
|
||||
expect(scannerBloc.state.isScanning, false);
|
||||
expect(scannerBloc.state.isProcessing, false);
|
||||
});
|
||||
|
||||
group('StartScanning', () {
|
||||
test('sets isScanning to true and clears errors', () async {
|
||||
scannerBloc.add(const StartScanning());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(scannerBloc.state.isScanning, true);
|
||||
expect(scannerBloc.state.isProcessing, false);
|
||||
expect(scannerBloc.state.errorMessage, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('StopScanning', () {
|
||||
test('sets isScanning to false', () async {
|
||||
scannerBloc.add(const StartScanning());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
scannerBloc.add(const StopScanning());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(scannerBloc.state.isScanning, false);
|
||||
});
|
||||
});
|
||||
|
||||
group('BookDetected', () {
|
||||
test('stores detected book data and sets isProcessing to true', () async {
|
||||
final bookData = {
|
||||
'title': 'Detected Book',
|
||||
'author': 'Detected Author',
|
||||
};
|
||||
|
||||
scannerBloc.add(BookDetected(bookData));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(scannerBloc.state.detectedBookData, bookData);
|
||||
expect(scannerBloc.state.isProcessing, true);
|
||||
});
|
||||
});
|
||||
|
||||
group('ClearDetectedBook', () {
|
||||
test('clears detected book data and resets processing', () async {
|
||||
final bookData = {
|
||||
'title': 'Detected Book',
|
||||
'author': 'Detected Author',
|
||||
};
|
||||
|
||||
scannerBloc.add(BookDetected(bookData));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(scannerBloc.state.detectedBookData, isNotNull);
|
||||
|
||||
scannerBloc.add(const ClearDetectedBook());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(scannerBloc.state.detectedBookData, isNull);
|
||||
expect(scannerBloc.state.isProcessing, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
105
bookshelf_flutter/test/bloc/settings/settings_bloc_test.dart
Normal file
105
bookshelf_flutter/test/bloc/settings/settings_bloc_test.dart
Normal file
@@ -0,0 +1,105 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:bookshelf_flutter/bloc/settings/settings_bloc.dart';
|
||||
import 'package:bookshelf_flutter/bloc/settings/settings_event.dart';
|
||||
import 'package:bookshelf_flutter/bloc/settings/settings_state.dart';
|
||||
|
||||
void main() {
|
||||
group('SettingsBloc', () {
|
||||
late SettingsBloc settingsBloc;
|
||||
|
||||
setUp(() {
|
||||
settingsBloc = SettingsBloc();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
settingsBloc.close();
|
||||
});
|
||||
|
||||
test('initial state is SettingsState.initial()', () {
|
||||
expect(settingsBloc.state, isA<SettingsState>());
|
||||
expect(settingsBloc.state.isLoading, true);
|
||||
});
|
||||
|
||||
group('LoadSettings', () {
|
||||
test('loads default settings into state', () async {
|
||||
settingsBloc.add(const LoadSettings());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(settingsBloc.state.isDarkMode, false);
|
||||
expect(settingsBloc.state.language, 'English');
|
||||
expect(settingsBloc.state.notificationsEnabled, true);
|
||||
expect(settingsBloc.state.isLoading, false);
|
||||
});
|
||||
});
|
||||
|
||||
group('UpdateTheme', () {
|
||||
test('updates isDarkMode to true', () async {
|
||||
settingsBloc.add(const UpdateTheme(true));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(settingsBloc.state.isDarkMode, true);
|
||||
});
|
||||
|
||||
test('updates isDarkMode to false', () async {
|
||||
settingsBloc.add(const UpdateTheme(true));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
settingsBloc.add(const UpdateTheme(false));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(settingsBloc.state.isDarkMode, false);
|
||||
});
|
||||
});
|
||||
|
||||
group('UpdateLanguage', () {
|
||||
test('updates language setting', () async {
|
||||
settingsBloc.add(const UpdateLanguage('Russian'));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(settingsBloc.state.language, 'Russian');
|
||||
});
|
||||
|
||||
test('updates language to English', () async {
|
||||
settingsBloc.add(const UpdateLanguage('English'));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(settingsBloc.state.language, 'English');
|
||||
});
|
||||
});
|
||||
|
||||
group('ToggleNotifications', () {
|
||||
test('toggles notifications from true to false', () async {
|
||||
settingsBloc.add(const LoadSettings());
|
||||
await Future.delayed(Duration.zero);
|
||||
expect(settingsBloc.state.notificationsEnabled, true);
|
||||
|
||||
settingsBloc.add(const ToggleNotifications());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(settingsBloc.state.notificationsEnabled, false);
|
||||
});
|
||||
|
||||
test('toggles notifications from false to true', () async {
|
||||
settingsBloc.add(const UpdateTheme(false));
|
||||
await Future.delayed(Duration.zero);
|
||||
settingsBloc.add(const ToggleNotifications());
|
||||
await Future.delayed(Duration.zero);
|
||||
expect(settingsBloc.state.notificationsEnabled, false);
|
||||
|
||||
settingsBloc.add(const ToggleNotifications());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(settingsBloc.state.notificationsEnabled, true);
|
||||
});
|
||||
});
|
||||
|
||||
group('ClearData', () {
|
||||
test('sets dataCleared to true', () async {
|
||||
settingsBloc.add(const ClearData());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(settingsBloc.state.dataCleared, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
104
bookshelf_flutter/test/bloc/wishlist/wishlist_bloc_test.dart
Normal file
104
bookshelf_flutter/test/bloc/wishlist/wishlist_bloc_test.dart
Normal file
@@ -0,0 +1,104 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:bookshelf_flutter/bloc/wishlist/wishlist_bloc.dart';
|
||||
import 'package:bookshelf_flutter/bloc/wishlist/wishlist_event.dart';
|
||||
import 'package:bookshelf_flutter/bloc/wishlist/wishlist_state.dart';
|
||||
import 'package:bookshelf_flutter/models/models.dart';
|
||||
|
||||
void main() {
|
||||
group('WishlistBloc', () {
|
||||
late WishlistBloc wishlistBloc;
|
||||
|
||||
setUp(() {
|
||||
wishlistBloc = WishlistBloc();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
wishlistBloc.close();
|
||||
});
|
||||
|
||||
test('initial state is WishlistState.initial()', () {
|
||||
expect(wishlistBloc.state, isA<WishlistState>());
|
||||
expect(wishlistBloc.state.isLoading, true);
|
||||
});
|
||||
|
||||
group('LoadWishlist', () {
|
||||
test('loads wishlist books into state', () async {
|
||||
wishlistBloc.add(const LoadWishlist());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(wishlistBloc.state.books.isNotEmpty, true);
|
||||
expect(wishlistBloc.state.filteredBooks.isNotEmpty, true);
|
||||
expect(wishlistBloc.state.isLoading, false);
|
||||
});
|
||||
});
|
||||
|
||||
group('RemoveFromWishlist', () {
|
||||
test('removes book from both lists', () async {
|
||||
wishlistBloc.add(const LoadWishlist());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
final bookId = wishlistBloc.state.books.first.id;
|
||||
wishlistBloc.add(RemoveFromWishlist(bookId));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(
|
||||
wishlistBloc.state.books.any((book) => book.id == bookId),
|
||||
false,
|
||||
);
|
||||
expect(
|
||||
wishlistBloc.state.filteredBooks.any((book) => book.id == bookId),
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('SearchWishlist', () {
|
||||
test('filters books by title', () async {
|
||||
wishlistBloc.add(const LoadWishlist());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
wishlistBloc.add(const SearchWishlist('1984'));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(wishlistBloc.state.filteredBooks.length, 1);
|
||||
expect(wishlistBloc.state.filteredBooks.first.title, contains('1984'));
|
||||
});
|
||||
|
||||
test('filters books by author', () async {
|
||||
wishlistBloc.add(const LoadWishlist());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
wishlistBloc.add(const SearchWishlist('Оруэлл'));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(wishlistBloc.state.filteredBooks.length, 1);
|
||||
expect(wishlistBloc.state.filteredBooks.first.author, contains('Оруэлл'));
|
||||
});
|
||||
|
||||
test('returns empty list when no matches found', () async {
|
||||
wishlistBloc.add(const LoadWishlist());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
wishlistBloc.add(const SearchWishlist('NonExistent'));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
expect(wishlistBloc.state.filteredBooks.length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
group('MoveToLibrary', () {
|
||||
test('updates book status to reading', () async {
|
||||
wishlistBloc.add(const LoadWishlist());
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
final bookId = wishlistBloc.state.books.first.id;
|
||||
wishlistBloc.add(MoveToLibrary(bookId));
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
final book = wishlistBloc.state.books.firstWhere((b) => b.id == bookId);
|
||||
expect(book.status, BookStatus.reading);
|
||||
expect(wishlistBloc.state.movedBookId, bookId);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user