Enhance status messages and lesson assertions in the application
Browse files- Updated the status message to dynamically reflect whether the conversation or story has ended.
- Modified the lesson assertions in the scoring tests to include additional lesson IDs for consistency.
- Added new tests for workbook lessons 3, 4, 5, 6, 7, 8, 9, and 10 to ensure comprehensive coverage of lesson content and structure.
- Ensured that the story and task assertions in the tests accurately reflect the expected content and format.
- app/workbook.py +0 -0
- static/index.html +2 -2
- tests/test_scoring.py +231 -3
app/workbook.py
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
static/index.html
CHANGED
|
@@ -1674,7 +1674,7 @@ $("conjBtn").onclick=()=>variation("conjugate");
|
|
| 1674 |
function roundEnd(){ // a story stops at its end and waits for the microphone button; other topics go straight on
|
| 1675 |
const story=isStory()&&!driving;
|
| 1676 |
startCat(story);
|
| 1677 |
-
if(story) $("status").textContent=
|
| 1678 |
}
|
| 1679 |
const nextPhrase=()=>{
|
| 1680 |
if(quiz&&!quiz.over&&topic.indexOf(here)+1>=topic.length){ quizEnd(); return; } // past the quiz's last line
|
|
@@ -1938,7 +1938,7 @@ function drawContent(){
|
|
| 1938 |
const lines=c?listed.filter(p=>p.cat===c.id).length:0;
|
| 1939 |
if(n){
|
| 1940 |
const row=mark("crow","","li");
|
| 1941 |
-
row.append(mark("",c.story?"💬":c.write?"✎\uFE0E":n,"i"),mark("",c.name,"b"),mark("",c.write?`כתיבה · ${c.write.lines} משפטים`:`${c.story?"שיחה":"תרגול"} · ${lines} ${c.unit==="מילה"?"מילים":c.unit==="פועל"?"פעלים":"משפטים"}`,"small")); list.append(row); return;
|
| 1942 |
}
|
| 1943 |
const it=v||c, row=mark("crow added","","li"), text=mark("two",""), title=mark("","","b"), t=document.createElement("bdi"), acts=mark("acts","");
|
| 1944 |
const up=mark("btn move","↑","button"), down=mark("btn move","↓","button"), see=mark("btn",v?"צפייה":"תרגול","button"), del=mark("btn danger","מחיקה","button");
|
|
|
|
| 1674 |
function roundEnd(){ // a story stops at its end and waits for the microphone button; other topics go straight on
|
| 1675 |
const story=isStory()&&!driving;
|
| 1676 |
startCat(story);
|
| 1677 |
+
if(story) $("status").textContent=`הגעתם לסוף ${(CATS.find(c=>c.id===cat)||{}).voices?"השיחה":"הסיפור"}. לחצו על המיקרופון כדי להמשיך`;
|
| 1678 |
}
|
| 1679 |
const nextPhrase=()=>{
|
| 1680 |
if(quiz&&!quiz.over&&topic.indexOf(here)+1>=topic.length){ quizEnd(); return; } // past the quiz's last line
|
|
|
|
| 1938 |
const lines=c?listed.filter(p=>p.cat===c.id).length:0;
|
| 1939 |
if(n){
|
| 1940 |
const row=mark("crow","","li");
|
| 1941 |
+
row.append(mark("",c.story?"💬":c.write?"✎\uFE0E":n,"i"),mark("",c.name,"b"),mark("",c.write?`כתיבה · ${c.write.lines} משפטים`:`${c.story?c.voices?"שיחה":"סיפור":"תרגול"} · ${lines} ${c.unit==="מילה"?"מילים":c.unit==="פועל"?"פעלים":"משפטים"}`,"small")); list.append(row); return;
|
| 1942 |
}
|
| 1943 |
const it=v||c, row=mark("crow added","","li"), text=mark("two",""), title=mark("","","b"), t=document.createElement("bdi"), acts=mark("acts","");
|
| 1944 |
const up=mark("btn move","↑","button"), down=mark("btn move","↓","button"), see=mark("btn",v?"צפייה":"תרגול","button"), del=mark("btn danger","מחיקה","button");
|
tests/test_scoring.py
CHANGED
|
@@ -131,7 +131,7 @@ def test_api_end_to_end_with_stub_model():
|
|
| 131 |
ph = c.get("/api/phrases").json()
|
| 132 |
shown = {c["id"] for c in ph["categories"]} # the phrases of no lesson are in the bank, not on the page
|
| 133 |
assert len(ph["phrases"]) == sum(p["cat"] in shown for p in PHRASES) and "ipa" not in ph["phrases"][0]
|
| 134 |
-
assert TOILET["cat"] not in shown and [l["id"] for l in ph["lessons"] if not l.get("soon")] == ["1", "2"]
|
| 135 |
assert len(ph["lessons"]) == 20 and [v["name"] for v in ph["levels"]] == ["רמה 1", "רמה 2"]
|
| 136 |
pid = PHRASES.index(TOILET)
|
| 137 |
r = c.post("/api/score", data={"phrase_id": pid}, files={"audio": ("a.wav", buf.getvalue(), "audio/wav")})
|
|
@@ -355,7 +355,7 @@ def test_references_use_one_generated_voice_and_any_cached_one():
|
|
| 355 |
def test_workbook_lesson_1():
|
| 356 |
from app.phrases import CATEGORIES, LESSONS, public_phrases
|
| 357 |
|
| 358 |
-
assert [l["id"] for l in LESSONS if not l.get("soon")] == ["1", "2"]
|
| 359 |
assert [c["id"] for c in CATEGORIES if c["lesson"] == "1"] == ["l1-pron", "l1-verbs", "l1-past", "l1-say", "l1-read", "l1-hw"]
|
| 360 |
hw = CATEGORIES[5]["write"] # the workbook's homework: a task and five sentences to write, with nothing to say
|
| 361 |
assert hw["lines"] == 5 and hw["task"].startswith("כתבו חמישה משפטים") and "מִין - מי" in hw["task"]
|
|
@@ -398,7 +398,7 @@ def test_workbook_lesson_2():
|
|
| 398 |
|
| 399 |
cats = ["l2-dual", "l2-family", "l2-poss", "l2-say", "l2-say2", "l2-read", "l2-talk", "l2-meet"]
|
| 400 |
assert [c["id"] for c in CATEGORIES if c["lesson"] == "2"] == cats + ["l2-hw"] # the lesson ends with its written homework
|
| 401 |
-
hw = CATEGORIES[
|
| 402 |
assert hw["lines"] == 5 and "פתרו את כל השיעור השני" in hw["task"] and "קַעַד / יֻקְעֹד – ישב" in hw["task"]
|
| 403 |
pub = public_phrases()
|
| 404 |
assert [sum(p["cat"] == c for p in pub) for c in cats] == [17, 24, 48, 23, 25, 13, 27, 24]
|
|
@@ -426,6 +426,234 @@ def test_workbook_lesson_2():
|
|
| 426 |
assert by["l2-dual", "שבוע, שבועיים"]["he_say"] == "שָׁבוּעַ, שבועיים" # not "shvua"
|
| 427 |
|
| 428 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 429 |
def test_sign_in_guards_the_practice_endpoints():
|
| 430 |
from fastapi.testclient import TestClient
|
| 431 |
|
|
|
|
| 131 |
ph = c.get("/api/phrases").json()
|
| 132 |
shown = {c["id"] for c in ph["categories"]} # the phrases of no lesson are in the bank, not on the page
|
| 133 |
assert len(ph["phrases"]) == sum(p["cat"] in shown for p in PHRASES) and "ipa" not in ph["phrases"][0]
|
| 134 |
+
assert TOILET["cat"] not in shown and [l["id"] for l in ph["lessons"] if not l.get("soon")] == ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
|
| 135 |
assert len(ph["lessons"]) == 20 and [v["name"] for v in ph["levels"]] == ["רמה 1", "רמה 2"]
|
| 136 |
pid = PHRASES.index(TOILET)
|
| 137 |
r = c.post("/api/score", data={"phrase_id": pid}, files={"audio": ("a.wav", buf.getvalue(), "audio/wav")})
|
|
|
|
| 355 |
def test_workbook_lesson_1():
|
| 356 |
from app.phrases import CATEGORIES, LESSONS, public_phrases
|
| 357 |
|
| 358 |
+
assert [l["id"] for l in LESSONS if not l.get("soon")] == ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
|
| 359 |
assert [c["id"] for c in CATEGORIES if c["lesson"] == "1"] == ["l1-pron", "l1-verbs", "l1-past", "l1-say", "l1-read", "l1-hw"]
|
| 360 |
hw = CATEGORIES[5]["write"] # the workbook's homework: a task and five sentences to write, with nothing to say
|
| 361 |
assert hw["lines"] == 5 and hw["task"].startswith("כתבו חמישה משפטים") and "מִין - מי" in hw["task"]
|
|
|
|
| 398 |
|
| 399 |
cats = ["l2-dual", "l2-family", "l2-poss", "l2-say", "l2-say2", "l2-read", "l2-talk", "l2-meet"]
|
| 400 |
assert [c["id"] for c in CATEGORIES if c["lesson"] == "2"] == cats + ["l2-hw"] # the lesson ends with its written homework
|
| 401 |
+
hw = next(c for c in CATEGORIES if c["id"] == "l2-hw")["write"]
|
| 402 |
assert hw["lines"] == 5 and "פתרו את כל השיעור השני" in hw["task"] and "קַעַד / יֻקְעֹד – ישב" in hw["task"]
|
| 403 |
pub = public_phrases()
|
| 404 |
assert [sum(p["cat"] == c for p in pub) for c in cats] == [17, 24, 48, 23, 25, 13, 27, 24]
|
|
|
|
| 426 |
assert by["l2-dual", "שבוע, שבועיים"]["he_say"] == "שָׁבוּעַ, שבועיים" # not "shvua"
|
| 427 |
|
| 428 |
|
| 429 |
+
def test_workbook_lesson_3():
|
| 430 |
+
from app.phrases import CATEGORIES, public_phrases
|
| 431 |
+
|
| 432 |
+
cats = ["l3-verbs", "l3-past", "l3-read", "l3-say", "l3-say2", "l3-nums", "l3-numread", "l3-numsay", "l3-story"]
|
| 433 |
+
mine = [c for c in CATEGORIES if c["lesson"] == "3"]
|
| 434 |
+
assert [c["id"] for c in mine] == cats + ["l3-hw"]
|
| 435 |
+
hw = mine[-1]["write"]
|
| 436 |
+
assert hw["lines"] == 5 and "פתרו את כל השיעור השלישי" in hw["task"] and "נַקַל / יֻנְקֹל – עבר (דירה, עבודה)" in hw["task"]
|
| 437 |
+
# the story is laid out like the conversation, but told in the student's own voice: it names none
|
| 438 |
+
assert mine[-2]["story"] and "voices" not in mine[-2] and CATEGORIES[[c["id"] for c in CATEGORIES].index("l2-talk")]["voices"] == ["אמיר", "ראאף"]
|
| 439 |
+
pub = public_phrases()
|
| 440 |
+
assert [sum(p["cat"] == c for p in pub) for c in cats] == [4, 32, 26, 12, 14, 37, 11, 11, 10]
|
| 441 |
+
assert all(p["read"] == (p["cat"] in ("l3-read", "l3-numread")) and "#" not in p["ar"] for p in pub if p["cat"].startswith("l3-"))
|
| 442 |
+
assert all("seq" not in p for p in pub if p["cat"] == "l3-past")
|
| 443 |
+
by = {(p["cat"], p["he"]): p for p in PHRASES}
|
| 444 |
+
# a verb is said in the past and the future together; עבר alone would read as "past", so it names what is moved
|
| 445 |
+
assert by["l3-verbs", "בישל (עבר, עתיד)"]["tr"] == "טַבַּח', יֻטְבֹּח'" and by["l3-verbs", "בישל (עבר, עתיד)"]["he_say"] == "בִּישֵּׁל"
|
| 446 |
+
assert by["l3-verbs", "עבר דירה (עבר, עתיד)"]["variants"][:2] == [("نقل، ينقل", "n a ʔ a l | j u n ʔ u l"), ("نقل، ينقل", "n a ʔ a l | j u n q u l")]
|
| 447 |
+
assert by["l3-past", "היא שחתה"]["tr"] == "הִיֵّ סַבְּחַת" and by["l3-past", "אנחנו בישלנו"]["tr"] == "אִחְנַא טַבַּחְ'נַא"
|
| 448 |
+
# a number shows its digits; the prompt voice says the Hebrew word only
|
| 449 |
+
assert by["l3-nums", "שבע עשרה (17)"]["tr"] == "סַבְּעְתַעַש" and by["l3-nums", "שבע עשרה (17)"]["he_say"] == "שבע עשרה"
|
| 450 |
+
assert by["l3-nums", "עשרת אלפים (10,000)"]["tr"] == "עַשְרַת אַלַאף" and by["l3-nums", "שלוש מאות (300)"]["tr"] == "תַלַאת מִיֵّה"
|
| 451 |
+
p = next(p for p in PHRASES if p["cat"] == "l3-read" and p["seq"] == 9)
|
| 452 |
+
assert "הִיֵّ טַבְּחַ'ת-לוֹ מַקְלוּבֵּה טַיְّבֵּה." in p["tr"]
|
| 453 |
+
p = next(p for p in PHRASES if p["cat"] == "l3-numsay" and p["seq"] == 6)
|
| 454 |
+
assert p["tr"] == "עִנְד-נַא חְדַעְשַר עַםّ וּסַבְּעְתַעְשַר עַמֵّה." # 11-19 gain a ר before a noun, which stays singular
|
| 455 |
+
p = next(p for p in PHRASES if p["cat"] == "l3-story" and p["seq"] == 1)
|
| 456 |
+
assert p["tr"] == "צַאחִבְּת-י רוּבַּה נַקְלַת עַלַא בֵּית גְ'דִיד פִי חַארֵת-נַא."
|
| 457 |
+
|
| 458 |
+
|
| 459 |
+
def test_workbook_lesson_4():
|
| 460 |
+
from app.phrases import CATEGORIES, public_phrases
|
| 461 |
+
|
| 462 |
+
cats = ["l4-verbs", "l4-past", "l4-read", "l4-read2", "l4-say", "l4-say2", "l4-story", "l4-meet", "l4-meetread"]
|
| 463 |
+
mine = [c for c in CATEGORIES if c["lesson"] == "4"]
|
| 464 |
+
assert [c["id"] for c in mine] == cats + ["l4-hw"]
|
| 465 |
+
hw = mine[-1]["write"]
|
| 466 |
+
assert hw["lines"] == 5 and "פתרו את כל השיעור הרביעי" in hw["task"] and "אִשְתַרַא / יִשְתְרִי – קנה" in hw["task"]
|
| 467 |
+
story = mine[cats.index("l4-story")]
|
| 468 |
+
assert story["story"] and "voices" not in story
|
| 469 |
+
pub = public_phrases()
|
| 470 |
+
assert [sum(p["cat"] == c for p in pub) for c in cats] == [4, 32, 12, 14, 12, 12, 20, 18, 6]
|
| 471 |
+
reads = ("l4-read", "l4-read2", "l4-meetread")
|
| 472 |
+
assert all(p["read"] == (p["cat"] in reads) and "#" not in p["ar"] for p in pub if p["cat"].startswith("l4-"))
|
| 473 |
+
by = {(p["cat"], p["he"]): p for p in PHRASES}
|
| 474 |
+
# verbs that end in ا: the ا becomes ي before the endings of "I", "you" and "we"
|
| 475 |
+
assert by["l4-verbs", "בא (עבר, עתיד)"]["tr"] == "אַגַ'א, יִגִ'י" and by["l4-verbs", "נתן (עבר, עתיד)"]["tr"] == "אַעְטַא, יֵעַטִי"
|
| 476 |
+
assert by["l4-past", "אנחנו דיברנו"]["tr"] == "אִחְנַא חַכֵּינַא" and by["l4-past", "היא קנתה"]["tr"] == "הִיֵّ אִשְתַרַת"
|
| 477 |
+
assert by["l4-past", "הם באו"]["variants"][0] == ("همّ اجو", "h u m m e | ʔ a dʒ u")
|
| 478 |
+
p = next(p for p in PHRASES if p["cat"] == "l4-read" and p["seq"] == 4)
|
| 479 |
+
assert p["tr"].startswith("גַ'אר-י אַגַ'א וּאַעַטַא-נִי תִסְעַה וּתַמַאנִין שֵיכֵּל")
|
| 480 |
+
p = next(p for p in PHRASES if p["cat"] == "l4-story" and p["seq"] == 1)
|
| 481 |
+
assert p["tr"] == "קַבֵּל אֻסְבּוּע אַגַ'א עַלַא חַארֵת-נַא גַ'אר גְ'דִיד."
|
| 482 |
+
assert by["l4-story", "הוא אמר: \"ה' ישמור אותם!\""]["he_say"] == "הוא אמר: \"אלוהים ישמור אותם!\""
|
| 483 |
+
assert by["l4-meet", "אני רוצה קפה עם סוכר, תודה!"]["tr"] == "בִּדّ-י קַהְוֵה מַע סֻכַּّר, שֻכְּרַן!"
|
| 484 |
+
|
| 485 |
+
|
| 486 |
+
def test_workbook_lesson_5():
|
| 487 |
+
from app.phrases import CATEGORIES, public_phrases
|
| 488 |
+
|
| 489 |
+
# the workbook's order: more of lesson 4's story, the negation, then the new verbs and the market story
|
| 490 |
+
cats = ["l5-meet", "l5-meetread", "l5-neg", "l5-neg2", "l5-verbs", "l5-past", "l5-read", "l5-story", "l5-aread",
|
| 491 |
+
"l5-say", "l5-say2", "l5-say3"]
|
| 492 |
+
mine = [c for c in CATEGORIES if c["lesson"] == "5"]
|
| 493 |
+
assert [c["id"] for c in mine] == cats + ["l5-hw"]
|
| 494 |
+
hw = mine[-1]["write"]
|
| 495 |
+
assert hw["lines"] == 5 and "פתרו את כל השיעור החמישי" in hw["task"] and "זַאר / יִזוּר – ביקר" in hw["task"]
|
| 496 |
+
story = mine[cats.index("l5-story")]
|
| 497 |
+
assert story["story"] and "voices" not in story
|
| 498 |
+
pub = public_phrases()
|
| 499 |
+
assert [sum(p["cat"] == c for p in pub) for c in cats] == [13, 12, 13, 13, 4, 32, 23, 15, 12, 13, 11, 12]
|
| 500 |
+
reads = ("l5-meetread", "l5-neg", "l5-neg2", "l5-read", "l5-aread")
|
| 501 |
+
assert all(p["read"] == (p["cat"] in reads) and "#" not in p["ar"] for p in pub if p["cat"].startswith("l5-"))
|
| 502 |
+
by = {(p["cat"], p["he"]): p for p in PHRASES}
|
| 503 |
+
assert by["l5-verbs", "היה (עבר, עתיד)"]["tr"] == "כַּאן, יִכּוּן" and by["l5-past", "אנחנו היינו"]["tr"] == "אִחְנַא כֻּנַّא"
|
| 504 |
+
assert ("انا قلت", "ʔ a n a | q u l t") in by["l5-past", "אני אמרתי"]["variants"] # rural ق accepted
|
| 505 |
+
# the negation: مش before a noun or an adjective, ما before a verb, which may also end in ش
|
| 506 |
+
p = next(p for p in PHRASES if p["cat"] == "l5-neg" and p["seq"] == 9)
|
| 507 |
+
assert p["tr"].startswith("אַנַא מַא קַעַדְתֵש עִנְד גַ'אר-י") and p["he"].startswith("אני לא ישבתי")
|
| 508 |
+
p = next(p for p in PHRASES if p["cat"] == "l5-neg2" and p["seq"] == 8)
|
| 509 |
+
assert p["tr"].endswith("קֻלְתִ-לّוֹ: וַאללַّה מַא אִשְתַרֵינַא אִשִי!") # والله is one word, not "and" + الله
|
| 510 |
+
p = next(p for p in PHRASES if p["cat"] == "l5-story" and p["seq"] == 5)
|
| 511 |
+
assert p["tr"].startswith("לַמַّא וְצִלְנַא עַלַא אֵ(ל)סّוּק") and p["variants"][0][1].split(" | ")[1] == "w sˤ i l n a"
|
| 512 |
+
assert by["l5-story", "ה' ייתן לך בריאות!"]["he_say"] == "אלוהים ייתן לך בריאות!"
|
| 513 |
+
|
| 514 |
+
|
| 515 |
+
def test_workbook_lesson_6():
|
| 516 |
+
from app.phrases import CATEGORIES, public_phrases
|
| 517 |
+
|
| 518 |
+
cats = ["l6-fut", "l6-futsay", "l6-verbs", "l6-past", "l6-read", "l6-read2", "l6-story", "l6-meet", "l6-meetread"]
|
| 519 |
+
mine = [c for c in CATEGORIES if c["lesson"] == "6"]
|
| 520 |
+
assert [c["id"] for c in mine] == cats + ["l6-hw"]
|
| 521 |
+
hw = mine[-1]["write"]
|
| 522 |
+
assert hw["lines"] == 5 and "פתרו את כל השיעור השישי" in hw["task"] and "פעם אחת בעבר ופעם אחת בעתיד" in hw["task"]
|
| 523 |
+
story = mine[cats.index("l6-story")]
|
| 524 |
+
assert story["story"] and "voices" not in story
|
| 525 |
+
pub = public_phrases()
|
| 526 |
+
assert [sum(p["cat"] == c for p in pub) for c in cats] == [24, 13, 5, 40, 11, 13, 12, 12, 12]
|
| 527 |
+
reads = ("l6-read", "l6-read2", "l6-meetread")
|
| 528 |
+
assert all(p["read"] == (p["cat"] in reads) and "#" not in p["ar"] for p in pub if p["cat"].startswith("l6-"))
|
| 529 |
+
assert all("seq" not in p for p in pub if p["cat"] in ("l6-fut", "l6-past")) # conjugations are drilled in random order
|
| 530 |
+
by = {(p["cat"], p["he"]): p for p in PHRASES}
|
| 531 |
+
# the future: the Hebrew letters אית"ן on the future's base form, and אַ for "I" whatever the base form's vowel
|
| 532 |
+
assert by["l6-fut", "אני אשב"]["tr"] == "אַנַא אַקְעֹד" and by["l6-fut", "אנחנו נשב"]["tr"] == "אִחְנַא נֻקְעֹד"
|
| 533 |
+
assert by["l6-fut", "את תבקרי"]["tr"] == "אִנְתִי תְזוּרִי" and by["l6-fut", "הם ישמעו"]["he_say"] == "הֵם יִשְׁמְעוּ"
|
| 534 |
+
# فات is "entered" like lesson 3's دخل, so its card says more; اخد is the homework's fifth verb
|
| 535 |
+
assert by["l6-verbs", "נכנס ועבר (עבר, עתיד)"]["tr"] == "פַאת, יִפוּת" and by["l6-verbs", "לקח (עבר, עתיד)"]["tr"] == "אַחַ'ד, יֹאחֵ'ד"
|
| 536 |
+
assert by["l6-past", "אתם נכנסתם"]["tr"] == "אִנְתוּ פֻתّוּ" and by["l6-past", "אני ראיתי"]["tr"] == "אַנַא שֻפֵת"
|
| 537 |
+
p = next(p for p in PHRASES if p["cat"] == "l6-futsay" and p["seq"] == 3)
|
| 538 |
+
assert p["tr"] == "אִחְנַא סַבַּחְנַא פִי אֵלְבַּחַר, בֻּכְּרַא נִסְבַּח פִי אֵלְבִּרְכֵּה."
|
| 539 |
+
p = next(p for p in PHRASES if p["cat"] == "l6-story" and p["seq"] == 9)
|
| 540 |
+
assert p["tr"] == "לַוְ כַּאן עִנְד-י מַרַה זַיֵّ-כּ..." and p["variants"][0][0].endswith("زيّك...")
|
| 541 |
+
|
| 542 |
+
|
| 543 |
+
def test_workbook_lesson_7():
|
| 544 |
+
from app.phrases import CATEGORIES, public_phrases
|
| 545 |
+
|
| 546 |
+
# a review lesson: no new verbs and no story
|
| 547 |
+
cats = ["l7-fem", "l7-dual", "l7-nums", "l7-count", "l7-verbs", "l7-read", "l7-say", "l7-say2"]
|
| 548 |
+
mine = [c for c in CATEGORIES if c["lesson"] == "7"]
|
| 549 |
+
assert [c["id"] for c in mine] == cats + ["l7-hw"] and not any(c.get("story") for c in mine)
|
| 550 |
+
assert mine[0]["task"] == "אמרו בערבית מדוברת: בזכר ואז בנקבה" and mine[1]["task"].endswith("ביחיד ואז בזוגי")
|
| 551 |
+
hw = mine[-1]["write"]
|
| 552 |
+
assert hw["lines"] == 5 and "בשיעור השביעי" in hw["task"] and "פעם אחת בעבר ופעם אחת בעתיד" in hw["task"]
|
| 553 |
+
pub = public_phrases()
|
| 554 |
+
assert [sum(p["cat"] == c for p in pub) for c in cats] == [8, 13, 12, 15, 21, 23, 14, 13]
|
| 555 |
+
assert all(p["read"] == (p["cat"] == "l7-read") and "#" not in p["ar"] for p in pub if p["cat"].startswith("l7-"))
|
| 556 |
+
by = {(p["cat"], p["he"]): p for p in PHRASES}
|
| 557 |
+
assert by["l7-fem", "רווק, רווקה"]["tr"] == "עַזַّאבִּי, עַזַّאבִּיֵّה" and by["l7-dual", "מיטה, שתי מיטות"]["tr"] == "תַחְ'ת, תַחְ'תֵין"
|
| 558 |
+
assert by["l7-nums", "מאה שבעים וחמש (175)"]["tr"] == "מִיֵّה חַ'מְסֵה וּסַבְּעִין" # units before tens
|
| 559 |
+
# 3-10: the noun in the plural and the number without its ة; from 11: the noun in the singular, and 11-19 with a ר
|
| 560 |
+
assert by["l7-count", "שבעה ילדים"]["tr"] == "סַבְּע אַוְלַאד" and by["l7-count", "שתים עשרה בנות"]["tr"] == "תְנַעְשַר בִּנְת"
|
| 561 |
+
# every verb of lessons 1-6, each still said in the past and the future together
|
| 562 |
+
verbs = [p for p in PHRASES if p["cat"] == "l7-verbs"]
|
| 563 |
+
assert [p["tr"] for p in verbs][:2] == ["פַתַח, יִפְתַח", "אַכַּל, יֹאכֹּל"] and verbs[-1]["tr"] == "פַאת, יִפוּת"
|
| 564 |
+
assert all(" | " in p["variants"][0][1] and p["he"].endswith("(עבר, עתיד)") for p in verbs)
|
| 565 |
+
|
| 566 |
+
|
| 567 |
+
def test_workbook_lesson_8():
|
| 568 |
+
from app.phrases import CATEGORIES, public_phrases
|
| 569 |
+
|
| 570 |
+
cats = ["l8-howmany", "l8-verbs", "l8-past", "l8-read", "l8-read2", "l8-read3", "l8-colors", "l8-colread", "l8-story",
|
| 571 |
+
"l8-colsay", "l8-colsay2"]
|
| 572 |
+
mine = [c for c in CATEGORIES if c["lesson"] == "8"]
|
| 573 |
+
assert [c["id"] for c in mine] == cats + ["l8-hw"]
|
| 574 |
+
hw = mine[-1]["write"]
|
| 575 |
+
assert hw["lines"] == 5 and "פתרו את כל השיעור השמיני" in hw["task"] and "פעלים וצבעים" in hw["task"]
|
| 576 |
+
song = mine[cats.index("l8-story")]
|
| 577 |
+
assert song["name"] == "שיר הצבעים" and song["story"] and "voices" not in song
|
| 578 |
+
assert mine[cats.index("l8-colors")]["task"].endswith("בזכר ואז בנקבה")
|
| 579 |
+
pub = public_phrases()
|
| 580 |
+
assert [sum(p["cat"] == c for p in pub) for c in cats] == [13, 5, 40, 12, 12, 13, 17, 13, 25, 10, 12]
|
| 581 |
+
reads = ("l8-read", "l8-read2", "l8-read3", "l8-colread")
|
| 582 |
+
assert all(p["read"] == (p["cat"] in reads) and "#" not in p["ar"] for p in pub if p["cat"].startswith("l8-"))
|
| 583 |
+
by = {(p["cat"], p["he"]): p for p in PHRASES}
|
| 584 |
+
# גזרת פִעֵל: the first vowel is lost before the endings of "I", "you" and "we"
|
| 585 |
+
assert by["l8-verbs", "הבין (עבר, עתיד)"]["tr"] == "פִהֵם, יִפְהַם" and by["l8-verbs", "יצא ועלה (עבר, עתיד)"]["tr"] == "טִלֵע, יִטְלַע"
|
| 586 |
+
assert by["l8-past", "אני שתיתי"]["tr"] == "אַנַא שְרִבֵּת" and by["l8-past", "היא חזרה"]["tr"] == "הִיֵّ רִגְ'עַת"
|
| 587 |
+
# a colour is said in the masculine and the feminine together
|
| 588 |
+
assert by["l8-colors", "לבן, לבנה"]["tr"] == "אַבְּיַדֿ, בֵּידַֿא" and by["l8-colors", "לבן, לבנה"]["variants"][0][1] == "ʔ a b j a dˤ | b eː dˤ a"
|
| 589 |
+
assert by["l8-colors", "אפור, אפורה (מהמילה אפר)"]["tr"] == "רַמַאדִי, רַמַאדִיֵّה"
|
| 590 |
+
# how many: اكم and a singular noun for what is counted, قدّيش for what is not
|
| 591 |
+
p = next(p for p in PHRASES if p["cat"] == "l8-howmany" and p["seq"] == 9)
|
| 592 |
+
assert p["tr"].startswith("קַדֵّיש מַצַארִי בִּדّ-וֹ?")
|
| 593 |
+
p = next(p for p in PHRASES if p["cat"] == "l8-howmany" and p["seq"] == 2)
|
| 594 |
+
assert p["tr"].startswith("אַכַּם וַאחַד כַּאן פִי אֵ(ל)סַّיַّארַה?")
|
| 595 |
+
assert by["l8-story", "אני בשמיים נמצא"]["tr"] == "אַנַא פִי אֵ(ל)סַّמַאִי מַוְג'וּד"
|
| 596 |
+
|
| 597 |
+
|
| 598 |
+
def test_workbook_lesson_9():
|
| 599 |
+
from app.phrases import CATEGORIES, public_phrases
|
| 600 |
+
|
| 601 |
+
cats = ["l9-story", "l9-aread", "l9-asay", "l9-verbs", "l9-past", "l9-want", "l9-read", "l9-read2", "l9-say", "l9-mix",
|
| 602 |
+
"l9-mixread"]
|
| 603 |
+
mine = [c for c in CATEGORIES if c["lesson"] == "9"]
|
| 604 |
+
assert [c["id"] for c in mine] == cats + ["l9-hw"]
|
| 605 |
+
hw = mine[-1]["write"]
|
| 606 |
+
assert hw["lines"] == 5 and "השיעור התשיעי" in hw["task"] and "אחד בעבר ואחד בעתיד" in hw["task"]
|
| 607 |
+
story = mine[cats.index("l9-story")]
|
| 608 |
+
assert story["name"] == "סיפור הקפה" and story["story"] and "voices" not in story
|
| 609 |
+
pub = public_phrases()
|
| 610 |
+
assert [sum(p["cat"] == c for p in pub) for c in cats] == [15, 13, 12, 2, 16, 24, 13, 13, 12, 6, 6]
|
| 611 |
+
reads = ("l9-aread", "l9-read", "l9-read2", "l9-mixread")
|
| 612 |
+
assert all(p["read"] == (p["cat"] in reads) and "#" not in p["ar"] for p in pub if p["cat"].startswith("l9-"))
|
| 613 |
+
by = {(p["cat"], p["he"]): p for p in PHRASES}
|
| 614 |
+
assert by["l9-verbs", "הודה (עבר, עתיד)"]["tr"] == "שַכַּר, יֻשְכֹּר"
|
| 615 |
+
# "to lunch" is one verb in Arabic: the Hebrew prompt names the meal, once
|
| 616 |
+
assert by["l9-verbs", "אכל צהריים (עבר, עתיד)"]["tr"] == "תְעַ'דַّא, יִתְעַ'דַّא"
|
| 617 |
+
assert by["l9-past", "אנחנו אכלנו צהריים"]["tr"] == "אִחְנַא תְעַ'דֵّינַא"
|
| 618 |
+
# wanting: بدّ with the person's suffix, then the verb in the future (Arabic has no infinitive)
|
| 619 |
+
assert by["l9-want", "אני רוצה לצאת"]["tr"] == "בִּדّ-י אַטְלַע" and by["l9-want", "את רוצה לצאת"]["tr"] == "בִּדֵّ-כּ תִטְלַעִי"
|
| 620 |
+
assert by["l9-want", "הם רוצים לאכול צהריים"]["tr"] == "בִּדّ-הֹם יִתְעַ'דּוּ"
|
| 621 |
+
assert by["l9-want", "אתה רוצה לשמוע"]["variants"][0][1] == "b i d d a k | t i s m a ʕ"
|
| 622 |
+
p = next(p for p in PHRASES if p["cat"] == "l9-story" and p["seq"] == 8)
|
| 623 |
+
assert p["tr"].startswith("שְרִבְּנַא קַהְוֵה סַאדַא")
|
| 624 |
+
|
| 625 |
+
|
| 626 |
+
def test_workbook_lesson_10():
|
| 627 |
+
from app.phrases import CATEGORIES, public_phrases
|
| 628 |
+
|
| 629 |
+
cats = ["l10-adj", "l10-compread", "l10-compread2", "l10-verbs", "l10-past", "l10-read", "l10-read2", "l10-say", "l10-say2",
|
| 630 |
+
"l10-dem", "l10-story", "l10-phrases"]
|
| 631 |
+
mine = [c for c in CATEGORIES if c["lesson"] == "10"]
|
| 632 |
+
assert [c["id"] for c in mine] == cats + ["l10-hw"]
|
| 633 |
+
hw = mine[-1]["write"]
|
| 634 |
+
assert hw["lines"] == 5 and "השיעור העשירי" in hw["task"] and "אחד בעבר ואחד בעתיד" in hw["task"]
|
| 635 |
+
story = mine[cats.index("l10-story")]
|
| 636 |
+
assert story["name"] == "סיפור דודי סעיד" and story["story"] and "voices" not in story
|
| 637 |
+
assert mine[0]["unit"] == "תואר" and mine[0]["task"].endswith("ערך היתרון שלו")
|
| 638 |
+
pub = public_phrases()
|
| 639 |
+
assert [sum(p["cat"] == c for p in pub) for c in cats] == [33, 8, 8, 5, 40, 12, 9, 12, 9, 12, 25, 19]
|
| 640 |
+
reads = ("l10-compread", "l10-compread2", "l10-read", "l10-read2")
|
| 641 |
+
assert all(p["read"] == (p["cat"] in reads) and "#" not in p["ar"] for p in pub if p["cat"].startswith("l10-"))
|
| 642 |
+
by = {(p["cat"], p["he"]): p for p in PHRASES}
|
| 643 |
+
# an adjective is said with its comparative (אַפְעַל)
|
| 644 |
+
assert by["l10-adj", "גדול, יותר גדול"]["tr"] == "כְּבִּיר, אַכְּבַּר"
|
| 645 |
+
assert by["l10-adj", "מת��ק, יותר מתוק"]["tr"] == "חִלוּ, אַחְלַא" and by["l10-adj", "חשוב, יותר חשוב"]["tr"] == "מֻהִםّ, אַהַםّ"
|
| 646 |
+
assert by["l10-adj", "טעים, יותר טעים (לאוכל בלבד)"]["tr"] == "זַאכִּי, אַזְכַּא"
|
| 647 |
+
assert by["l10-adj", "נקי, יותר נקי"]["variants"][0][1] == "n dˤ iː f | ʔ a n dˤ a f"
|
| 648 |
+
# גזרת פִעֵל again
|
| 649 |
+
assert by["l10-verbs", "שיחק (עבר, עתיד)"]["tr"] == "לִעֵבּ, יִלְעַבּ" and by["l10-verbs", "הגיע (עבר, עתיד)"]["tr"] == "וִצֵל, יוֹצַל"
|
| 650 |
+
assert by["l10-past", "אני שיחקתי"]["tr"] == "אַנַא לְעִבֵּת" and by["l10-past", "היא צחקה"]["tr"] == "הִיֵّ דִֿחְכַּת"
|
| 651 |
+
assert by["l10-past", "אנחנו הגענו"]["tr"] == "אִחְנַא וְצִלְנַא"
|
| 652 |
+
assert by["l10-dem", "אלה"]["tr"] == "הַדוֹל" and by["l10-dem", "הפעם הזאת"]["tr"] == "הַאדִי אֵלְמַרַّה"
|
| 653 |
+
p = next(p for p in PHRASES if p["cat"] == "l10-story" and p["seq"] == 8)
|
| 654 |
+
assert p["tr"].startswith("הוּّ קַאלִّ-י: וַלַא יְהִמֵّ-כּ")
|
| 655 |
+
|
| 656 |
+
|
| 657 |
def test_sign_in_guards_the_practice_endpoints():
|
| 658 |
from fastapi.testclient import TestClient
|
| 659 |
|