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:
Yuriy Panov
2026-02-04 14:40:00 +06:00
parent 3004f712f3
commit 310463e89a
177 changed files with 9718 additions and 0 deletions

View 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);
});
});
});
}