diff --git a/lib/crossword/view/crossword_page.dart b/lib/crossword/view/crossword_page.dart index aeddc95c7..d4780d03c 100644 --- a/lib/crossword/view/crossword_page.dart +++ b/lib/crossword/view/crossword_page.dart @@ -103,10 +103,10 @@ class _CrosswordViewState extends State listener: (context, state) { switch (state.status) { case RandomWordSelectionStatus.loading: - case RandomWordSelectionStatus.notFound: case RandomWordSelectionStatus.initial: case RandomWordSelectionStatus.failure: break; + case RandomWordSelectionStatus.initialNotFound: case RandomWordSelectionStatus.initialSuccess: final initialWord = SelectedWord( section: state.sectionPosition!, @@ -116,6 +116,7 @@ class _CrosswordViewState extends State context .read() .add(CrosswordSectionsLoaded(initialWord)); + case RandomWordSelectionStatus.notFound: case RandomWordSelectionStatus.success: context.read().add( WordSelected( diff --git a/lib/random_word_selection/bloc/random_word_selection_bloc.dart b/lib/random_word_selection/bloc/random_word_selection_bloc.dart index dfe88a0bf..9c4ed3675 100644 --- a/lib/random_word_selection/bloc/random_word_selection_bloc.dart +++ b/lib/random_word_selection/bloc/random_word_selection_bloc.dart @@ -55,7 +55,22 @@ class RandomWordSelectionBloc ), ); } else { - emit(state.copyWith(status: RandomWordSelectionStatus.notFound)); + final randomSection = await _crosswordRepository.getRandomSection(); + final randomizedWords = [...randomSection.words]..shuffle(); + final randomWord = randomizedWords.first; + + emit( + state.copyWith( + status: event.isInitial + ? RandomWordSelectionStatus.initialNotFound + : RandomWordSelectionStatus.notFound, + randomWord: randomWord, + sectionPosition: ( + randomSection.position.x, + randomSection.position.y + ), + ), + ); } } catch (error, stackTrace) { addError(error, stackTrace); diff --git a/lib/random_word_selection/bloc/random_word_selection_state.dart b/lib/random_word_selection/bloc/random_word_selection_state.dart index 01eb20d85..c4c3b4135 100644 --- a/lib/random_word_selection/bloc/random_word_selection_state.dart +++ b/lib/random_word_selection/bloc/random_word_selection_state.dart @@ -6,6 +6,7 @@ enum RandomWordSelectionStatus { initialSuccess, success, notFound, + initialNotFound, failure, } diff --git a/packages/crossword_repository/lib/src/crossword_repository.dart b/packages/crossword_repository/lib/src/crossword_repository.dart index abc09bc92..b31047486 100644 --- a/packages/crossword_repository/lib/src/crossword_repository.dart +++ b/packages/crossword_repository/lib/src/crossword_repository.dart @@ -19,7 +19,7 @@ class CrosswordRepository { /// The [FirebaseFirestore] instance. final FirebaseFirestore db; - /// The [CollectionReference] for the matches. + /// The [CollectionReference] for the board sections. late final CollectionReference> sectionCollection; final Random _rng; @@ -90,6 +90,21 @@ class CrosswordRepository { return null; } + /// Returns the position of a random section. This section can have all words + /// solved or some unsolved. + Future getRandomSection() async { + final result = await sectionCollection.get(); + + final sections = result.docs.map((sectionDoc) { + return BoardSection.fromJson({ + 'id': sectionDoc.id, + ...sectionDoc.data(), + }); + }).toList(); + + return sections[_rng.nextInt(sections.length)]; + } + /// Watches a section of the crossword board Stream watchSection(String id) { final ref = sectionCollection.doc(id); diff --git a/packages/crossword_repository/test/src/crossword_repository_test.dart b/packages/crossword_repository/test/src/crossword_repository_test.dart index 80c2406f8..c2198ebdd 100644 --- a/packages/crossword_repository/test/src/crossword_repository_test.dart +++ b/packages/crossword_repository/test/src/crossword_repository_test.dart @@ -107,6 +107,45 @@ void main() { }); }); + group('getRandomSection', () { + const bottomRight = Point(2, 2); + + Future setUpSections() async { + for (var x = 0; x <= bottomRight.x; x++) { + for (var y = 0; y <= bottomRight.y; y++) { + final section = BoardSection( + id: '$x-$y', + position: Point(x, y), + words: [ + word.copyWith( + position: Point(x, y), + solvedTimestamp: 1, + ), + ], + ); + + await firebaseFirestore + .collection(sectionsCollection) + .doc(section.id) + .set(section.toJson()); + } + } + } + + test('returns a valid section', () async { + await setUpSections(); + when(() => rng.nextInt(any())).thenReturn(2); + + final section = await crosswordRepository.getRandomSection(); + + expect( + section, + isA() + .having((s) => s.position, 'position', equals(Point(0, 2))), + ); + }); + }); + group('watchSection', () { setUp(() async { await firebaseFirestore diff --git a/test/crossword/view/crossword_page_test.dart b/test/crossword/view/crossword_page_test.dart index 8f189cd1e..e231e9c99 100644 --- a/test/crossword/view/crossword_page_test.dart +++ b/test/crossword/view/crossword_page_test.dart @@ -122,87 +122,159 @@ void main() { when(() => crosswordBloc.state).thenReturn(const CrosswordState()); }); - testWidgets('success, adds $WordSelected event', (tester) async { - final randomWordSelectionBloc = _MockRandomWordSelectionBloc(); - whenListen( - randomWordSelectionBloc, - Stream.fromIterable([ - RandomWordSelectionState( - status: RandomWordSelectionStatus.success, - randomWord: word, - sectionPosition: (1, 1), + testWidgets( + 'success, adds $WordSelected event', + (tester) async { + final randomWordSelectionBloc = _MockRandomWordSelectionBloc(); + whenListen( + randomWordSelectionBloc, + Stream.value( + RandomWordSelectionState( + status: RandomWordSelectionStatus.success, + randomWord: word, + sectionPosition: (1, 1), + ), ), - ]), - initialState: RandomWordSelectionState(), - ); - final wordSelectionBloc = _MockWordSelectionBloc(); + initialState: RandomWordSelectionState(), + ); + final wordSelectionBloc = _MockWordSelectionBloc(); - await tester.pumpSubject( - CrosswordView(), - wordSelectionBloc: wordSelectionBloc, - crosswordBloc: crosswordBloc, - randomWordSelectionBloc: randomWordSelectionBloc, - ); + await tester.pumpSubject( + CrosswordView(), + wordSelectionBloc: wordSelectionBloc, + crosswordBloc: crosswordBloc, + randomWordSelectionBloc: randomWordSelectionBloc, + ); - verify( - () => wordSelectionBloc.add( - WordSelected( - selectedWord: SelectedWord(section: (1, 1), word: word), + verify( + () => wordSelectionBloc.add( + WordSelected( + selectedWord: SelectedWord(section: (1, 1), word: word), + ), ), - ), - ).called(1); - }); + ).called(1); + }, + ); - testWidgets('initialSuccess, adds CrosswordSectionLoaded event', - (tester) async { - when(() => crosswordBloc.state).thenReturn( - const CrosswordState( - status: CrosswordStatus.success, - ), - ); + testWidgets( + 'notFound, adds $WordSelected event', + (tester) async { + final randomWordSelectionBloc = _MockRandomWordSelectionBloc(); + whenListen( + randomWordSelectionBloc, + Stream.value( + RandomWordSelectionState( + status: RandomWordSelectionStatus.notFound, + randomWord: word, + sectionPosition: (1, 0), + ), + ), + initialState: RandomWordSelectionState(), + ); + final wordSelectionBloc = _MockWordSelectionBloc(); - whenListen( - crosswordBloc, - Stream.fromIterable( - [ - CrosswordState( - status: CrosswordStatus.success, + await tester.pumpSubject( + CrosswordView(), + wordSelectionBloc: wordSelectionBloc, + crosswordBloc: crosswordBloc, + randomWordSelectionBloc: randomWordSelectionBloc, + ); + + verify( + () => wordSelectionBloc.add( + WordSelected( + selectedWord: SelectedWord(section: (1, 0), word: word), ), - ], - ), - initialState: CrosswordState(), - ); + ), + ).called(1); + }, + ); - final wordSelectionBloc = _MockWordSelectionBloc(); - final randomWordSelectionBloc = _MockRandomWordSelectionBloc(); + testWidgets( + 'initialSuccess, adds $CrosswordSectionsLoaded event', + (tester) async { + when(() => crosswordBloc.state).thenReturn( + const CrosswordState(status: CrosswordStatus.success), + ); - whenListen( - randomWordSelectionBloc, - Stream.fromIterable( - [ + whenListen( + crosswordBloc, + Stream.value(CrosswordState(status: CrosswordStatus.success)), + initialState: CrosswordState(), + ); + + final wordSelectionBloc = _MockWordSelectionBloc(); + final randomWordSelectionBloc = _MockRandomWordSelectionBloc(); + + whenListen( + randomWordSelectionBloc, + Stream.value( RandomWordSelectionState( status: RandomWordSelectionStatus.initialSuccess, randomWord: word, sectionPosition: (1, 1), ), - ], - ), - initialState: RandomWordSelectionState(), - ); + ), + initialState: RandomWordSelectionState(), + ); - await tester.pumpSubject( - CrosswordView(), - wordSelectionBloc: wordSelectionBloc, - crosswordBloc: crosswordBloc, - randomWordSelectionBloc: randomWordSelectionBloc, - ); + await tester.pumpSubject( + CrosswordView(), + wordSelectionBloc: wordSelectionBloc, + crosswordBloc: crosswordBloc, + randomWordSelectionBloc: randomWordSelectionBloc, + ); - final initialWord = - SelectedWord(section: (1, 1), word: section.words.first); + final initialWord = + SelectedWord(section: (1, 1), word: section.words.first); - verify(() => crosswordBloc.add(CrosswordSectionsLoaded(initialWord))) - .called(1); - }); + verify(() => crosswordBloc.add(CrosswordSectionsLoaded(initialWord))) + .called(1); + }, + ); + + testWidgets( + 'initialNotFound, adds $CrosswordSectionsLoaded event', + (tester) async { + when(() => crosswordBloc.state).thenReturn( + const CrosswordState(status: CrosswordStatus.success), + ); + + whenListen( + crosswordBloc, + Stream.value(CrosswordState(status: CrosswordStatus.success)), + initialState: CrosswordState(), + ); + + final wordSelectionBloc = _MockWordSelectionBloc(); + final randomWordSelectionBloc = _MockRandomWordSelectionBloc(); + + whenListen( + randomWordSelectionBloc, + Stream.value( + RandomWordSelectionState( + status: RandomWordSelectionStatus.initialNotFound, + randomWord: word, + sectionPosition: (1, 0), + ), + ), + initialState: RandomWordSelectionState(), + ); + + await tester.pumpSubject( + CrosswordView(), + wordSelectionBloc: wordSelectionBloc, + crosswordBloc: crosswordBloc, + randomWordSelectionBloc: randomWordSelectionBloc, + ); + + final initialWord = + SelectedWord(section: (1, 0), word: section.words.first); + + verify(() => crosswordBloc.add(CrosswordSectionsLoaded(initialWord))) + .called(1); + }, + ); }); testWidgets('when success, adds initial RandomWordRequested', diff --git a/test/random_word_selection/bloc/random_word_selection_bloc_test.dart b/test/random_word_selection/bloc/random_word_selection_bloc_test.dart index 5f5af25d2..f57613f54 100644 --- a/test/random_word_selection/bloc/random_word_selection_bloc_test.dart +++ b/test/random_word_selection/bloc/random_word_selection_bloc_test.dart @@ -84,8 +84,8 @@ void main() { ); blocTest( - 'emits state with status notFound when' - ' getRandomUncompletedSection returns null', + 'emits state with status notFound when event is not initial and ' + 'getRandomUncompletedSection returns null', build: () => RandomWordSelectionBloc( crosswordRepository: crosswordRepository, boardInfoRepository: boardInfoRepository, @@ -93,11 +93,41 @@ void main() { setUp: () { when(() => crosswordRepository.getRandomUncompletedSection(any())) .thenAnswer((_) => Future.value()); + when(() => crosswordRepository.getRandomSection()) + .thenAnswer((_) => Future.value(section)); }, act: (bloc) => bloc.add(RandomWordRequested()), expect: () => [ RandomWordSelectionState(status: RandomWordSelectionStatus.loading), - RandomWordSelectionState(status: RandomWordSelectionStatus.notFound), + RandomWordSelectionState( + status: RandomWordSelectionStatus.notFound, + randomWord: word, + sectionPosition: (1, 1), + ), + ], + ); + + blocTest( + 'emits state with status initialNotFound when event is initial and ' + 'getRandomUncompletedSection returns null', + build: () => RandomWordSelectionBloc( + crosswordRepository: crosswordRepository, + boardInfoRepository: boardInfoRepository, + ), + setUp: () { + when(() => crosswordRepository.getRandomUncompletedSection(any())) + .thenAnswer((_) => Future.value()); + when(() => crosswordRepository.getRandomSection()) + .thenAnswer((_) => Future.value(section)); + }, + act: (bloc) => bloc.add(RandomWordRequested(isInitial: true)), + expect: () => [ + RandomWordSelectionState(status: RandomWordSelectionStatus.loading), + RandomWordSelectionState( + status: RandomWordSelectionStatus.initialNotFound, + randomWord: word, + sectionPosition: (1, 1), + ), ], ); });