MIL-008: Choose level

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
This commit is contained in:
2026-10-14 23:58:31 +08:00
parent ee9c531ea6
commit f45d6036e4
40 changed files with 1483 additions and 102 deletions
+124
View File
@@ -0,0 +1,124 @@
"""Integration tests of the levels over the real word list and a progress file."""
from collections.abc import Callable, Sequence
from pathlib import Path
from flash_card.application.use_cases.open_study_session import open_study_session
from flash_card.application.use_cases.study_session import StudySession
from flash_card.constants import (
DEFAULT_DATA_DIR,
PROGRESS_FILE_NAME,
VOCABULARY_FILE_NAME,
)
from flash_card.domain.entities.card import Card
from flash_card.domain.value_objects.level import Level
from flash_card.infrastructure.persistence.csv_progress_repository import (
CsvProgressRepository,
)
from flash_card.infrastructure.persistence.csv_vocabulary_repository import (
CsvVocabularyRepository,
)
from flash_card.infrastructure.speech.null_speech import NullSpeech
VOCABULARY = CsvVocabularyRepository(DEFAULT_DATA_DIR / VOCABULARY_FILE_NAME)
ALL_CARDS = VOCABULARY.load().cards
FIRST_HUNDRED = ALL_CARDS[:100]
Choose = Callable[[Sequence[Card]], Card]
def _first(cards: Sequence[Card]) -> Card:
return cards[0]
def _open(folder: Path, level: Level, choose: Choose = _first) -> StudySession:
return open_study_session(
VOCABULARY,
CsvProgressRepository(folder / PROGRESS_FILE_NAME),
NullSpeech(),
choose,
level,
)
def _learn_to_the_end(session: StudySession) -> list[Card]:
"""Mark every drawn card known until the session is finished."""
drawn: list[Card] = []
while (card := session.next_card()) is not None:
drawn.append(Card(card.front_text, card.back_text))
session.mark_known()
return drawn
def _saved(folder: Path) -> tuple[Card, ...]:
loaded = CsvProgressRepository(folder / PROGRESS_FILE_NAME).load()
assert loaded is not None
return loaded.cards
def test_word_list_has_one_thousand_distinct_words() -> None:
assert len(ALL_CARDS) == 1000
assert len(set(ALL_CARDS)) == 1000
def test_level_100_draws_exactly_the_first_hundred_words(tmp_path: Path) -> None:
drawn = _learn_to_the_end(_open(tmp_path, Level(100)))
assert len(drawn) == 100
assert len(set(drawn)) == 100
assert set(drawn) == set(FIRST_HUNDRED)
def test_level_100_keeps_the_other_words_in_order_in_the_file(
tmp_path: Path,
) -> None:
_learn_to_the_end(_open(tmp_path, Level(100)))
assert _saved(tmp_path) == ALL_CARDS[100:]
assert len(_saved(tmp_path)) == 900
def test_level_all_after_level_100_draws_only_the_other_words(
tmp_path: Path,
) -> None:
_learn_to_the_end(_open(tmp_path, Level(100)))
drawn = _learn_to_the_end(_open(tmp_path, Level(None)))
assert len(drawn) == 900
assert set(drawn) == set(ALL_CARDS[100:])
assert not set(drawn) & set(FIRST_HUNDRED)
def test_level_1000_equals_all_words_for_this_list(tmp_path: Path) -> None:
session_1000 = _open(tmp_path, Level(1000))
session_all = _open(tmp_path, Level(None))
assert session_1000.remaining_in_level == 1000
assert session_all.remaining_in_level == 1000
def test_words_known_at_level_100_stay_known_at_level_1000(tmp_path: Path) -> None:
first = _open(tmp_path, Level(100))
shown = first.next_card()
assert shown is not None
first.mark_known()
session = _open(tmp_path, Level(1000))
assert session.remaining == 999
assert session.remaining_in_level == 999
def test_marking_known_at_level_100_keeps_words_outside_the_level(
tmp_path: Path,
) -> None:
session = _open(tmp_path, Level(100))
session.next_card()
session.mark_known()
saved = _saved(tmp_path)
assert len(saved) == 999
assert saved[-900:] == ALL_CARDS[100:]
def test_level_whose_words_are_all_known_is_finished_at_once(
tmp_path: Path,
) -> None:
_learn_to_the_end(_open(tmp_path, Level(100)))
session = _open(tmp_path, Level(100))
assert session.remaining_in_level == 0
assert session.remaining == 900
assert session.next_card() is None