File size: 7,042 Bytes
a7ffb22 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | """The collection screen in a real browser: what a student has mastered, and the empty start."""
import uuid
import pytest
playwright = pytest.importorskip("playwright.sync_api")
from mathly2.bank import load_bank
from mathly2.config import Settings
from mathly2.main import create_app
from mathly2.store import InMemoryStore
from mathly2.tests.serve import offline_formulas, running
KID = "kid@school.test"
KID_ID = str(uuid.uuid5(uuid.NAMESPACE_DNS, KID))
@pytest.fixture
def world():
bank = load_bank()
store = InMemoryStore()
store.add_member(store.create_class("setup", "A", lessons=[u["id"] for u in bank.lessons])["id"], KID)
return create_app(Settings(), bank=bank, store=store), store, bank
@pytest.fixture
def browser():
with playwright.sync_playwright() as p:
b = p.chromium.launch()
yield b
b.close()
def collection_page(browser, url):
page = offline_formulas(browser.new_page(viewport={"width": 390, "height": 844}))
page.goto(url + "#/collection")
page.fill("#email", KID)
page.click("#dev button")
page.wait_for_selector("#collection")
return page
def test_a_skill_mastered_through_the_server_appears_in_the_collection(browser, world):
app, store, bank = world
skill = bank.skills[0]
store.save_progress(KID_ID, skill["id"], started_at="then", mastered_at="then")
with running(app) as url:
page = collection_page(browser, url)
assert skill["name"] in page.inner_text("#done-skills") and "ืืืืื ืืืืช ืฉื ืฉืืื (1)" in page.inner_text("#collection")
assert "ืงืืืฆืืช ืืืืืื (0)" in page.inner_text("#collection")
assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth")
page.click("a[href='#/subject/math/path']")
page.wait_for_selector(".trail")
def test_the_collection_starts_empty_with_a_hint(browser, world):
app, _, _ = world
with running(app) as url:
page = collection_page(browser, url)
assert "ืืืืื ืืืืช ืฉื ืฉืืื (0)" in page.inner_text("#collection") and page.locator("#done-skills").count() == 0
# --- add-rewards 4.2 / 5.1: constellations and comets ---
PHONE = {"width": 360, "height": 800}
def master_lesson(store, bank, lesson_id):
ids = [s["id"] for s in bank.skills if s["unit"] == lesson_id]
for skill in ids:
store.save_progress(KID_ID, skill, started_at="then", mastered_at="then")
return ids
def lessons_of_size(bank, *sizes):
from collections import Counter
counts = Counter(s["unit"] for s in bank.skills)
return [next(l for l, n in counts.items() if n == size) for size in sizes]
def test_constellations_of_2_4_and_6_stars_and_the_fallback_fit_a_360px_screen(browser, world):
app, _, _ = world
with running(app) as url:
page = offline_formulas(browser.new_page(viewport=PHONE))
page.goto(url)
page.evaluate("""async () => {
const { constellationSvg } = await import('/static/sky.js');
const host = document.createElement('div'); host.id = 'probe'; host.style.cssText = 'display:grid;grid-template-columns:repeat(2,130px);gap:8px;padding:8px';
host.innerHTML = [['t', 2], ['k', 4], ['h', 6], ['zz', 4], ['zz', 7]].map(([id, n]) => constellationSvg(id, 'ืืืืงื', n)).join('');
document.body.append(host);
const style = document.createElement('style'); style.textContent = '.con{width:100%;max-width:130px;height:110px;overflow:visible}'; document.head.append(style);
}""")
svgs = page.locator("#probe svg.con")
assert svgs.count() == 5
for i, stars in enumerate([2, 4, 6, 4, 7]):
svg = svgs.nth(i)
assert svg.locator("path").count() == stars # one star per skill, fallback included
box = svg.bounding_box()
assert box["x"] >= 0 and box["x"] + box["width"] <= PHONE["width"]
assert "ืืืืงื" in svg.get_attribute("aria-label") and str(stars) in svg.get_attribute("aria-label")
assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth")
def test_mastering_a_lessons_last_skill_adds_its_constellation(browser, world):
app, store, bank = world
two, three = lessons_of_size(bank, 2, 3)
ids = [s["id"] for s in bank.skills if s["unit"] == two]
store.save_progress(KID_ID, ids[0], started_at="then", mastered_at="then")
with running(app) as url:
page = collection_page(browser, url)
assert "ืงืืืฆืืช ืืืืืื (0)" in page.inner_text("#collection")
store.save_progress(KID_ID, ids[1], started_at="then", mastered_at="then") # the lesson's last skill
page.reload(); page.wait_for_selector("#collection")
assert "ืงืืืฆืืช ืืืืืื (1)" in page.inner_text("#collection")
card = page.locator(f"#done-lessons .con-card[data-lesson='{two}']")
assert card.count() == 1 and card.locator("svg.con path").count() == 2
def test_the_collection_shows_caught_comets_and_question_marks_for_the_rest(browser, world):
app, store, _ = world
for c in ("pi", "e", "zero"):
store.add_comet(KID_ID, c)
with running(app) as url:
page = collection_page(browser, url)
tiles = page.locator("#comets .comet-tile")
assert tiles.count() == 12 and page.locator("#comets .comet-tile.have").count() == 3
assert page.locator("#comets .comet-tile:not(.have)").count() == 9
assert "ฯ" in page.inner_text("#comets .comet-tile.have >> nth=0") and "ืคืื" in page.inner_text("#comets")
assert page.locator("#comets .comet-tile:not(.have)", has_text="ืขืื ืื ื ืชืคืก").count() == 9
assert "3 ืืชืื 12" in page.inner_text("#collection")
page.click("#comets [data-comet='pi']") # a caught comet opens its fact
assert "ืืืืก ืืื ืืืืงืฃ" in page.inner_text(".comet-dialog")
page.keyboard.press("Escape")
assert page.locator(".comet-dialog").count() == 0
page.click("#comets .comet-tile:not(.have) >> nth=0") # a missing one opens a hint, no fact
assert "ืขืื ืื ื ืชืคืก" in page.inner_text(".comet-dialog") and "ืืืืก ืืื" not in page.inner_text(".comet-dialog")
page.click(".comet-dialog button")
assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth")
def test_the_collection_dims_a_faded_skill_and_says_so(browser, world):
from datetime import timedelta
from mathly2 import clock
app, store, bank = world
skill = bank.skills[0]["id"]
store.save_progress(KID_ID, skill, started_at="then", mastered_at="then")
store.add_answer(KID_ID, skill, "k", True, True, None, clock.school_day() - timedelta(days=30))
with running(app) as url:
page = collection_page(browser, url)
assert "ืืืขื" in page.inner_text("#done-skills li.faded")
|