Skip to content

Commit

Permalink
fix: board not loading when completed (#595)
Browse files Browse the repository at this point in the history
* feat: add method to get any random section

* feat: select a completed word when an empty is not found

* fix: select completed word when none empty are available

---------

Co-authored-by: Mark Fairless <marwfair@gmail.com>
  • Loading branch information
jsgalarraga and marwfair authored Jun 27, 2024
1 parent d90ebc8 commit 9381133
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 70 deletions.
3 changes: 2 additions & 1 deletion lib/crossword/view/crossword_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@ class _CrosswordViewState extends State<CrosswordView>
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!,
Expand All @@ -116,6 +116,7 @@ class _CrosswordViewState extends State<CrosswordView>
context
.read<CrosswordBloc>()
.add(CrosswordSectionsLoaded(initialWord));
case RandomWordSelectionStatus.notFound:
case RandomWordSelectionStatus.success:
context.read<WordSelectionBloc>().add(
WordSelected(
Expand Down
17 changes: 16 additions & 1 deletion lib/random_word_selection/bloc/random_word_selection_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ enum RandomWordSelectionStatus {
initialSuccess,
success,
notFound,
initialNotFound,
failure,
}

Expand Down
17 changes: 16 additions & 1 deletion packages/crossword_repository/lib/src/crossword_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<Map<String, dynamic>> sectionCollection;

final Random _rng;
Expand Down Expand Up @@ -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<BoardSection> 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<BoardSection> watchSection(String id) {
final ref = sectionCollection.doc(id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,45 @@ void main() {
});
});

group('getRandomSection', () {
const bottomRight = Point(2, 2);

Future<void> 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<BoardSection>()
.having((s) => s.position, 'position', equals(Point(0, 2))),
);
});
});

group('watchSection', () {
setUp(() async {
await firebaseFirestore
Expand Down
200 changes: 136 additions & 64 deletions test/crossword/view/crossword_page_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading

0 comments on commit 9381133

Please sign in to comment.