Review record RC-041 (Go, autonomous mode, not independent). Task: MIL-008#1 Task: MIL-008#2 Task: MIL-008#3 Task: MIL-008#4 Task: MIL-008#5 Task: MIL-008#6 Task: MIL-008#7 Task: MIL-008#8
245 lines
7.2 KiB
Python
245 lines
7.2 KiB
Python
"""Tests of the StudySession use case."""
|
|
|
|
import itertools
|
|
from collections.abc import Callable, Sequence
|
|
|
|
from flash_card.application.dto.card_dto import CardDto
|
|
from flash_card.application.use_cases.study_session import StudySession
|
|
from flash_card.domain.entities.card import Card
|
|
from flash_card.domain.entities.deck import Deck
|
|
from flash_card.domain.value_objects.language_pair import LanguagePair
|
|
|
|
PAIR = LanguagePair("ms", "da")
|
|
A = Card("aku", "mig")
|
|
B = Card("kau", "du")
|
|
|
|
|
|
def _first(cards: Sequence[Card]) -> Card:
|
|
return cards[0]
|
|
|
|
|
|
class _RecordingProgress:
|
|
def __init__(self) -> None:
|
|
self.saved: list[tuple[Card, ...]] = []
|
|
|
|
def load(self) -> Deck | None:
|
|
return None
|
|
|
|
def save(self, deck: Deck) -> None:
|
|
self.saved.append(deck.cards)
|
|
|
|
|
|
class _RecordingSpeech:
|
|
def __init__(self) -> None:
|
|
self.spoken: list[tuple[str, str]] = []
|
|
|
|
def speak(self, text: str, language_code: str) -> None:
|
|
self.spoken.append((text, language_code))
|
|
|
|
|
|
def _session(
|
|
*cards: Card,
|
|
progress: _RecordingProgress | None = None,
|
|
speech: _RecordingSpeech | None = None,
|
|
) -> StudySession:
|
|
store = _RecordingProgress() if progress is None else progress
|
|
voice = _RecordingSpeech() if speech is None else speech
|
|
return StudySession(Deck(PAIR, cards), _first, store, voice)
|
|
|
|
|
|
def test_current_card_is_none_before_first_draw() -> None:
|
|
assert _session(A).current_card is None
|
|
|
|
|
|
def test_next_card_returns_chosen_card_as_dto() -> None:
|
|
assert _session(A, B).next_card() == CardDto("aku", "mig", "ms", "da")
|
|
|
|
|
|
def test_next_card_returns_different_card_when_two_or_more() -> None:
|
|
session = _session(A, B)
|
|
first = session.next_card()
|
|
second = session.next_card()
|
|
assert first != second
|
|
assert second == CardDto("kau", "du", "ms", "da")
|
|
|
|
|
|
def test_next_card_returns_same_card_when_one() -> None:
|
|
session = _session(A)
|
|
assert session.next_card() == session.next_card()
|
|
|
|
|
|
def test_next_card_returns_none_when_deck_is_empty() -> None:
|
|
session = _session()
|
|
assert session.next_card() is None
|
|
assert session.current_card is None
|
|
|
|
|
|
def test_current_card_follows_last_draw() -> None:
|
|
session = _session(A, B)
|
|
shown = session.next_card()
|
|
assert session.current_card == shown
|
|
|
|
|
|
def test_remaining_counts_cards_in_deck() -> None:
|
|
session = _session(A, B)
|
|
session.next_card()
|
|
assert session.remaining == 2
|
|
|
|
|
|
def test_mark_known_removes_current_card_and_saves_remaining_once() -> None:
|
|
progress = _RecordingProgress()
|
|
session = _session(A, B, progress=progress)
|
|
session.next_card()
|
|
session.mark_known()
|
|
assert session.remaining == 1
|
|
assert session.current_card is None
|
|
assert progress.saved == [(B,)]
|
|
|
|
|
|
def test_mark_known_saves_nothing_when_no_current_card() -> None:
|
|
progress = _RecordingProgress()
|
|
session = _session(A, progress=progress)
|
|
session.mark_known()
|
|
assert session.remaining == 1
|
|
assert progress.saved == []
|
|
|
|
|
|
def test_mark_known_on_last_card_leaves_session_finished() -> None:
|
|
progress = _RecordingProgress()
|
|
session = _session(A, progress=progress)
|
|
session.next_card()
|
|
session.mark_known()
|
|
assert progress.saved == [()]
|
|
assert session.next_card() is None
|
|
|
|
|
|
def test_next_card_never_saves() -> None:
|
|
progress = _RecordingProgress()
|
|
session = _session(A, B, progress=progress)
|
|
session.next_card()
|
|
session.next_card()
|
|
assert progress.saved == []
|
|
|
|
|
|
def test_next_card_speaks_front_text_once_in_front_language() -> None:
|
|
speech = _RecordingSpeech()
|
|
session = _session(A, B, speech=speech)
|
|
session.next_card()
|
|
assert speech.spoken == [("aku", "ms")]
|
|
|
|
|
|
def test_next_card_does_not_speak_when_deck_is_empty() -> None:
|
|
speech = _RecordingSpeech()
|
|
session = _session(speech=speech)
|
|
session.next_card()
|
|
assert speech.spoken == []
|
|
|
|
|
|
def test_next_card_speaks_again_on_every_draw() -> None:
|
|
speech = _RecordingSpeech()
|
|
session = _session(A, B, speech=speech)
|
|
session.next_card()
|
|
session.next_card()
|
|
assert speech.spoken == [("aku", "ms"), ("kau", "ms")]
|
|
|
|
|
|
def test_mark_known_does_not_speak_but_following_draw_does() -> None:
|
|
speech = _RecordingSpeech()
|
|
session = _session(A, B, speech=speech)
|
|
session.next_card()
|
|
session.mark_known()
|
|
assert speech.spoken == [("aku", "ms")]
|
|
session.next_card()
|
|
assert speech.spoken == [("aku", "ms"), ("kau", "ms")]
|
|
|
|
|
|
C = Card("ini", "denne")
|
|
D = Card("itu", "den")
|
|
|
|
|
|
def _level_session(
|
|
deck_cards: Sequence[Card],
|
|
level_cards: Sequence[Card] | None,
|
|
choose: Callable[[Sequence[Card]], Card] = _first,
|
|
progress: _RecordingProgress | None = None,
|
|
speech: _RecordingSpeech | None = None,
|
|
) -> StudySession:
|
|
store = _RecordingProgress() if progress is None else progress
|
|
voice = _RecordingSpeech() if speech is None else speech
|
|
return StudySession(Deck(PAIR, deck_cards), choose, store, voice, level_cards)
|
|
|
|
|
|
def _last(cards: Sequence[Card]) -> Card:
|
|
return cards[-1]
|
|
|
|
|
|
def test_next_card_draws_only_cards_of_the_level_over_many_draws() -> None:
|
|
chooser = itertools.cycle([_first, _last])
|
|
session = _level_session(
|
|
[A, B, C, D], [A, B], choose=lambda cards: next(chooser)(cards)
|
|
)
|
|
drawn = {session.next_card() for _ in range(40)}
|
|
assert drawn == {
|
|
CardDto("aku", "mig", "ms", "da"),
|
|
CardDto("kau", "du", "ms", "da"),
|
|
}
|
|
|
|
|
|
def test_next_card_never_draws_a_card_outside_the_level() -> None:
|
|
session = _level_session([A, B, C], [B], choose=_last)
|
|
for _ in range(5):
|
|
assert session.next_card() == CardDto("kau", "du", "ms", "da")
|
|
|
|
|
|
def test_remaining_counts_whole_deck_and_remaining_in_level_counts_level() -> None:
|
|
session = _level_session([A, B, C], [A, B])
|
|
assert session.remaining == 3
|
|
assert session.remaining_in_level == 2
|
|
|
|
|
|
def test_remaining_in_level_counts_whole_deck_without_level() -> None:
|
|
assert _level_session([A, B, C], None).remaining_in_level == 3
|
|
|
|
|
|
def test_remaining_in_level_ignores_level_cards_missing_from_deck() -> None:
|
|
assert _level_session([B], [A, B]).remaining_in_level == 1
|
|
|
|
|
|
def test_mark_known_keeps_cards_outside_the_level_and_saves_whole_deck() -> None:
|
|
progress = _RecordingProgress()
|
|
session = _level_session([A, B, C], [A], progress=progress)
|
|
session.next_card()
|
|
session.mark_known()
|
|
assert progress.saved == [(B, C)]
|
|
assert session.remaining == 2
|
|
assert session.remaining_in_level == 0
|
|
|
|
|
|
def test_next_card_is_none_when_nothing_of_the_level_is_left() -> None:
|
|
session = _level_session([B, C], [A])
|
|
assert session.next_card() is None
|
|
assert session.current_card is None
|
|
|
|
|
|
def test_session_finishes_after_last_card_of_level_is_known() -> None:
|
|
session = _level_session([A, B], [A])
|
|
session.next_card()
|
|
session.mark_known()
|
|
assert session.next_card() is None
|
|
assert session.remaining == 1
|
|
|
|
|
|
def test_next_card_speaks_only_cards_of_the_level() -> None:
|
|
speech = _RecordingSpeech()
|
|
session = _level_session([A, B, C], [B], choose=_last, speech=speech)
|
|
session.next_card()
|
|
session.next_card()
|
|
assert speech.spoken == [("kau", "ms"), ("kau", "ms")]
|
|
|
|
|
|
def test_next_card_speaks_nothing_when_level_is_finished() -> None:
|
|
speech = _RecordingSpeech()
|
|
session = _level_session([B], [A], speech=speech)
|
|
session.next_card()
|
|
assert speech.spoken == []
|