// Author: fruit.com.cn import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/core/data/models/drama.dart'; import 'package:fruitshorts/core/data/models/search_result.dart'; import 'package:fruitshorts/core/data/remote/api_exception.dart'; import 'package:fruitshorts/core/data/repositories/search_repository.dart'; import 'package:fruitshorts/pages/discover/discover_logic.dart'; Drama _drama(String id, {String language = 'id'}) => Drama( id: id, title: id, synopsis: 'synopsis', posterUrl: '/files/$id.png', language: language, episodes: const [], ); void main() { TestWidgetsFlutterBinding.ensureInitialized(); test( 'should_query_backend_and_render_results_when_search_text_changes', () async { final repository = _FakeSearchRepository(results: [_drama('velvet')]); final logic = DiscoverLogic(searchRepository: repository) ..searchDebounce = Duration.zero; logic.search.text = 'velvet'; logic.onSearchChanged(); await pumpEventQueue(); expect(repository.calls, [ (keyword: 'velvet', pageIndex: 1, pageSize: 20), ]); expect(logic.searchLoading, isFalse); expect(logic.searchResults.single.id, 'velvet'); logic.dispose(); }, ); test('should_keep_server_results_when_text_only_loses_whitespace', () async { final repository = _FakeSearchRepository(results: [_drama('velvet')]); final logic = DiscoverLogic(searchRepository: repository) ..searchDebounce = Duration.zero; logic.search.text = 'velvet '; logic.onSearchChanged(); await pumpEventQueue(); logic.search.text = 'velvet'; logic.onSearchChanged(); await pumpEventQueue(); expect(repository.calls, hasLength(1)); expect(logic.searchResults.single.id, 'velvet'); logic.dispose(); }); test('should_expose_error_message_when_search_fails', () async { final logic = DiscoverLogic( searchRepository: _FakeSearchRepository( error: const ApiException(400, 'Keyword is invalid'), ), )..searchDebounce = Duration.zero; logic.search.text = '%%%'; logic.onSearchChanged(); await pumpEventQueue(); expect(logic.searchError, 'Keyword is invalid'); expect(logic.searchLoading, isFalse); logic.dispose(); }); test('should_clear_server_results_when_search_text_is_empty', () async { final repository = _FakeSearchRepository(results: [_drama('velvet')]); final logic = DiscoverLogic(searchRepository: repository) ..searchDebounce = Duration.zero; logic.search.text = 'velvet'; logic.onSearchChanged(); await pumpEventQueue(); logic.search.clear(); logic.onSearchChanged(); expect(logic.hasSearchQuery, isFalse); expect(logic.searchResults, isEmpty); logic.dispose(); }); } class _FakeSearchRepository implements SearchRepository { _FakeSearchRepository({this.results = const [], this.error}); final List results; final ApiException? error; final List<({String keyword, int pageIndex, int pageSize})> calls = []; @override Future search( String keyword, { int pageIndex = 1, int pageSize = 20, }) async { calls.add((keyword: keyword, pageIndex: pageIndex, pageSize: pageSize)); if (error != null) throw error!; return SearchResult(total: results.length, list: results); } }