177 lines
9.5 KiB
Python
177 lines
9.5 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
def test_review_interface_marks_keyboard_modal_and_responsive_state_hooks() -> None:
|
|
web_root = Path(__file__).parents[2] / "src" / "game_data_capture" / "web"
|
|
template = (web_root / "templates" / "index.html").read_text(encoding="utf-8")
|
|
script = (web_root / "static" / "review.js").read_text(encoding="utf-8")
|
|
stylesheet = (web_root / "static" / "review.css").read_text(encoding="utf-8")
|
|
|
|
assert 'aria-live="polite"' in template
|
|
assert '<dialog class="confirmation-dialog"' in template
|
|
assert 'value="cancel"' in template
|
|
assert 'type="radio"' not in template
|
|
assert "input.type = type" in script
|
|
assert 'input.type = "button"' not in script
|
|
assert 'input.type = "radio"' not in script
|
|
assert 'input.type = "checkbox"' not in script
|
|
assert "input.type = type" in script
|
|
assert "els.dialog.showModal()" in script
|
|
assert 'els.dialog.returnValue = "cancel"' in script
|
|
assert 'els.dialog.dataset.action = ""' in script
|
|
assert "const focusTarget = returnFocus;" in script
|
|
assert "if (focusTarget) focusTarget.focus();" in script
|
|
assert "fallbackFocus?.focus();" in script
|
|
assert 'els.media.addEventListener("error"' in script
|
|
assert ":focus-visible" in stylesheet
|
|
assert "min-height: 44px" in stylesheet
|
|
assert "@media (max-width: 52rem)" in stylesheet
|
|
assert "@media (prefers-reduced-motion: reduce)" in stylesheet
|
|
assert "@media (forced-colors: active)" in stylesheet
|
|
|
|
|
|
def test_review_script_guards_session_taxonomy_pending_and_selection_state() -> None:
|
|
script = (
|
|
Path(__file__).parents[2] / "src" / "game_data_capture" / "web" / "static" / "review.js"
|
|
).read_text(encoding="utf-8")
|
|
|
|
assert 'headers.set("Origin"' not in script
|
|
assert "Origin:" not in script
|
|
assert 'const bootstrap = await request("/api/bootstrap"' in script
|
|
assert 'const session = await request("/api/session")' in script
|
|
assert script.index('"/api/bootstrap"') < script.index('"/api/session"')
|
|
assert "if (!validSession(session))" in script
|
|
assert "family, false, () =>" in script
|
|
assert "view, false, renderDependentFields" in script
|
|
assert "if (!classification) { renderViews(); renderDependentFields(); return; }" in script
|
|
assert "function routeForAction(action)" in script
|
|
assert '(action === "reject" || action === "delete") && activeCapture' in script
|
|
assert "const routes =" not in script
|
|
assert 'document.querySelectorAll("#label-form input")' in script
|
|
assert "input.disabled = pending || locked || !activeCapture" in script
|
|
assert 'document.querySelectorAll("#capture-list button")' in script
|
|
assert "button.disabled = pending || locked;" in script
|
|
assert "armed === true" in script
|
|
assert "armed !== true" in script
|
|
assert "function acceptMutationCsrf(payload)" in script
|
|
assert "if (!payload || !acceptMutationCsrf(payload))" in script
|
|
assert 'retained ? "RETAINED"' in script
|
|
assert "function setCheckedMembers(name, values)" in script
|
|
assert "input.checked = selectedValues.has(input.value);" in script
|
|
assert 'setCheckedMembers("record_type", classification.record_types || []);' in script
|
|
assert 'forEach((record) => setChecked("record_type", record))' not in script
|
|
assert (
|
|
"async function refreshCaptures({ announce = true, fallbackFocus = null } = {})" in script
|
|
)
|
|
assert "async function selectCapture(captureId, { announce = true } = {})" in script
|
|
assert 'if (announce) notice("success", "QUEUE REFRESHED"' in script
|
|
assert 'if (announce) notice("success", "CAPTURE READY"' in script
|
|
assert "refreshCaptures({ announce: false })" in script
|
|
assert "selectCapture(captureId, { announce: false })" in script
|
|
assert script.index("selectCapture(captureId, { announce: false })") < script.index(
|
|
'notice("success", "LABEL SAVED"'
|
|
)
|
|
assert 'if (!activeCapture) els.reviewState.textContent = "READY";' in script
|
|
assert '"Capture is ARMED. Stop Session requires explicit confirmation."' in script
|
|
assert '"Capture is DISARMED. Start Session requires explicit confirmation."' in script
|
|
assert 'action === "start" ? els.stop' in script
|
|
assert 'action === "stop" ? els.start' in script
|
|
assert 'action === "delete" ? els.refresh' in script
|
|
assert "function focusIfAvailable(control)" in script
|
|
assert "focusIfAvailable(successFocus);" in script
|
|
assert "async function confirmed(action, fallbackFocus)" in script
|
|
assert "fallbackFocus?.focus();" in script
|
|
assert "function clearActiveDetail()" in script
|
|
assert 'if (action === "delete") clearActiveDetail();' in script
|
|
assert (
|
|
"const savedCapture = captures.find((capture) => capture.capture_id === captureId);"
|
|
in script
|
|
)
|
|
assert "if (savedCapture && !savedCaptureIsHidden) await selectCapture" in script
|
|
assert "else { clearActiveDetail(); renderCaptures(); }" in script
|
|
assert "refreshCaptures({ fallbackFocus: els.refresh })" in script
|
|
assert "focusIfAvailable(els.save);" in script
|
|
|
|
|
|
def test_queue_filter_keeps_reviewed_captures_local_and_focus_safe() -> None:
|
|
web_root = Path(__file__).parents[2] / "src" / "game_data_capture" / "web"
|
|
template = (web_root / "templates" / "index.html").read_text(encoding="utf-8")
|
|
script = (web_root / "static" / "review.js").read_text(encoding="utf-8")
|
|
stylesheet = (web_root / "static" / "review.css").read_text(encoding="utf-8")
|
|
|
|
assert '<input id="hide-reviewed" type="checkbox" />' in template
|
|
assert "Hide reviewed captures" in template
|
|
assert (
|
|
"checked"
|
|
not in template[
|
|
template.index('id="hide-reviewed"') - 20 : template.index('id="hide-reviewed"') + 50
|
|
]
|
|
)
|
|
assert "let hideReviewed = false;" in script
|
|
assert "els.filter.checked = false; hideReviewed = false;" in script
|
|
assert "function isReviewedCapture(capture)" in script
|
|
assert "capture?.classification !== null" in script
|
|
assert "function visibleCaptures()" in script
|
|
assert "captures.filter((capture) => !isReviewedCapture(capture))" in script
|
|
assert "`${visible.length} shown / ${captures.length} loaded`" in script
|
|
assert "ALL LOADED CAPTURES HIDDEN" in script
|
|
assert 'button.setAttribute("aria-current", "true")' in script
|
|
assert "els.filter.disabled = pending || locked;" in script
|
|
assert 'els.filter.addEventListener("change", applyReviewedFilter);' in script
|
|
filter_start = script.index("function applyReviewedFilter()")
|
|
filter_end = script.index("async function refreshCaptures", filter_start)
|
|
filter_source = script[filter_start:filter_end]
|
|
assert "const activeProjection = activeCaptureProjection();" in filter_source
|
|
assert (
|
|
"const activeIsHidden = hideReviewed && isReviewedCapture(activeProjection);"
|
|
in filter_source
|
|
)
|
|
assert "isReviewedCapture(activeCapture)" not in filter_source
|
|
assert "function activeCaptureProjection()" in script
|
|
assert (
|
|
"captures.find((capture) => capture.capture_id === activeCapture.capture_id)"
|
|
" || activeCapture" in script
|
|
)
|
|
assert "if (activeIsHidden) clearActiveDetail();" in script
|
|
assert "function focusVisibleCaptureOrFilter()" in script
|
|
assert 'els.list.querySelector("button:not(:disabled)") || els.filter' in script
|
|
assert "const savedCaptureIsHidden" in script
|
|
assert "if (savedCapture && !savedCaptureIsHidden) await selectCapture" in script
|
|
assert "if (savedCaptureIsHidden) focusVisibleCaptureOrFilter();" in script
|
|
assert ".capture-scroll { min-height: 0; overflow-x: hidden; overflow-y: auto;" in stylesheet
|
|
assert ".capture-panel { height: auto; min-height: 0;" in stylesheet
|
|
assert ".capture-scroll { overflow: visible;" in stylesheet
|
|
assert "body { margin: 0; min-width:" not in stylesheet
|
|
assert "function abbreviateCaptureId(value)" in script
|
|
assert "value.slice(0, 11)" in script
|
|
assert "function formatCaptureTime(value)" in script
|
|
assert '"TIME UNAVAILABLE"' in script
|
|
assert 'typeof value !== "string" || value.length > 64' in script
|
|
assert 'const reviewState = isReviewedCapture(capture) ? "REVIEWED" : "UNREVIEWED";' in script
|
|
assert 'button.setAttribute("aria-label", `Capture ${capture.capture_id}.' in script
|
|
assert "id.textContent = abbreviateCaptureId(capture.capture_id);" in script
|
|
assert "selectCapture(capture.capture_id)" in script
|
|
assert "els.reviewTitle.textContent = activeCapture.capture_id;" in script
|
|
assert ".capture-list .capture-id { min-width: 0; overflow: hidden;" in stylesheet
|
|
assert "text-overflow: ellipsis" in stylesheet
|
|
assert ".capture-list .capture-review" in stylesheet
|
|
|
|
|
|
def test_mod_taxonomy_maps_exact_labels_without_gear_slots_and_restores_them() -> None:
|
|
script = (
|
|
Path(__file__).parents[2] / "src" / "game_data_capture" / "web" / "static" / "review.js"
|
|
).read_text(encoding="utf-8")
|
|
|
|
for family, record_type in (("weapon_mod", "weapon_mod"), ("gear_mod", "gear_mod")):
|
|
assert (
|
|
f'{family}: {{ views: ["detail"], records: {{ detail: ["{record_type}"] }} }}' in script
|
|
)
|
|
assert 'const isGear = family === "gear";' in script
|
|
assert "if (isGear) gearSlots.forEach" in script
|
|
assert 'gear_slot: family === "gear" ? selected("gear_slot") : null' in script
|
|
assert 'setChecked("family", classification.family);' in script
|
|
assert 'setChecked("view", classification.view);' in script
|
|
assert 'setCheckedMembers("record_type", classification.record_types || []);' in script
|