Files
Code/python/persian-tutor/tests/test_vocab.py
local 2e8c2c11d0 Add persian-tutor: Gradio-based GCSE Persian language learning app
Vocabulary study with FSRS spaced repetition, AI tutoring (Ollama/Claude),
essay marking, idioms browser, Anki export, and dashboard. 918 vocabulary
entries across 39 categories. 41 tests passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 01:57:44 +00:00

205 lines
6.0 KiB
Python

"""Tests for modules/vocab.py — vocabulary search and flashcard logic."""
import json
import sys
from pathlib import Path
from unittest.mock import patch
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent))
SAMPLE_VOCAB = [
{
"id": "verb_go",
"section": "High-frequency language",
"category": "Common verbs",
"english": "to go",
"persian": "رفتن",
"finglish": "raftan",
},
{
"id": "verb_eat",
"section": "High-frequency language",
"category": "Common verbs",
"english": "to eat",
"persian": "خوردن",
"finglish": "khordan",
},
{
"id": "adj_big",
"section": "High-frequency language",
"category": "Common adjectives",
"english": "big",
"persian": "بزرگ",
"finglish": "bozorg",
},
{
"id": "colour_red",
"section": "High-frequency language",
"category": "Colours",
"english": "red",
"persian": "قرمز",
"finglish": "ghermez",
},
]
@pytest.fixture(autouse=True)
def mock_vocab_and_db(tmp_path):
"""Mock vocabulary loading and use temp DB."""
import db as db_mod
import modules.vocab as vocab_mod
# Temp DB
db_mod._conn = None
db_mod.DB_PATH = tmp_path / "test.db"
db_mod.init_db()
# Mock vocab
vocab_mod._vocab_data = SAMPLE_VOCAB
yield vocab_mod
db_mod.close()
vocab_mod._vocab_data = None
def test_load_vocab(mock_vocab_and_db):
"""load_vocab should return the vocabulary data."""
data = mock_vocab_and_db.load_vocab()
assert len(data) == 4
def test_get_categories(mock_vocab_and_db):
"""get_categories should return unique sorted categories."""
cats = mock_vocab_and_db.get_categories()
assert "Colours" in cats
assert "Common verbs" in cats
assert "Common adjectives" in cats
def test_search_english(mock_vocab_and_db):
"""Search should find entries by English text."""
results = mock_vocab_and_db.search("go")
assert len(results) == 1
assert results[0]["id"] == "verb_go"
def test_search_persian(mock_vocab_and_db):
"""Search should find entries by Persian text."""
results = mock_vocab_and_db.search("رفتن")
assert len(results) == 1
assert results[0]["id"] == "verb_go"
def test_search_finglish(mock_vocab_and_db):
"""Search should find entries by Finglish text."""
results = mock_vocab_and_db.search("raftan")
assert len(results) == 1
assert results[0]["id"] == "verb_go"
def test_search_empty(mock_vocab_and_db):
"""Empty search should return empty list."""
assert mock_vocab_and_db.search("") == []
assert mock_vocab_and_db.search(None) == []
def test_search_no_match(mock_vocab_and_db):
"""Search with no match should return empty list."""
assert mock_vocab_and_db.search("zzzzz") == []
def test_get_random_word(mock_vocab_and_db):
"""get_random_word should return a valid entry."""
word = mock_vocab_and_db.get_random_word()
assert word is not None
assert "id" in word
assert "english" in word
assert "persian" in word
def test_get_random_word_with_category(mock_vocab_and_db):
"""get_random_word with category filter should only return matching entries."""
word = mock_vocab_and_db.get_random_word(category="Colours")
assert word is not None
assert word["category"] == "Colours"
def test_get_random_word_nonexistent_category(mock_vocab_and_db):
"""get_random_word with bad category should return None."""
word = mock_vocab_and_db.get_random_word(category="Nonexistent")
assert word is None
def test_check_answer_correct_en_to_fa(mock_vocab_and_db):
"""Correct Persian answer should be marked correct."""
correct, answer, entry = mock_vocab_and_db.check_answer(
"verb_go", "رفتن", direction="en_to_fa"
)
assert correct is True
def test_check_answer_incorrect_en_to_fa(mock_vocab_and_db):
"""Incorrect Persian answer should be marked incorrect with correct answer."""
correct, answer, entry = mock_vocab_and_db.check_answer(
"verb_go", "خوردن", direction="en_to_fa"
)
assert correct is False
assert answer == "رفتن"
def test_check_answer_fa_to_en(mock_vocab_and_db):
"""Correct English answer (case-insensitive) should be marked correct."""
correct, answer, entry = mock_vocab_and_db.check_answer(
"verb_go", "To Go", direction="fa_to_en"
)
assert correct is True
def test_check_answer_nonexistent_word(mock_vocab_and_db):
"""Checking answer for nonexistent word should return False."""
correct, answer, entry = mock_vocab_and_db.check_answer(
"nonexistent", "test", direction="en_to_fa"
)
assert correct is False
assert entry is None
def test_format_word_card(mock_vocab_and_db):
"""format_word_card should produce RTL HTML with correct content."""
entry = SAMPLE_VOCAB[0]
html = mock_vocab_and_db.format_word_card(entry, show_transliteration="Finglish")
assert "رفتن" in html
assert "to go" in html
assert "raftan" in html
def test_format_word_card_no_transliteration(mock_vocab_and_db):
"""format_word_card with transliteration off should not show finglish."""
entry = SAMPLE_VOCAB[0]
html = mock_vocab_and_db.format_word_card(entry, show_transliteration="off")
assert "raftan" not in html
def test_get_flashcard_batch(mock_vocab_and_db):
"""get_flashcard_batch should return a batch of entries."""
batch = mock_vocab_and_db.get_flashcard_batch(count=2)
assert len(batch) == 2
assert all("id" in e for e in batch)
def test_get_word_status_new(mock_vocab_and_db):
"""Unreviewed word should have status 'new'."""
assert mock_vocab_and_db.get_word_status("verb_go") == "new"
def test_get_word_status_learning(mock_vocab_and_db):
"""Recently reviewed word should have status 'learning'."""
import db
import fsrs
db.update_word_progress("verb_go", fsrs.Rating.Good)
assert mock_vocab_and_db.get_word_status("verb_go") == "learning"