"""Headless test for CONTROLS.html's bindings-loader. Builds a copy of the page with a self-test harness injected inside its IIFE, feeds it the fixture (a REAL board-2 migrated bindings.txt: the full defaults plus two player-authored rows and a wizard section), and writes the result to %TEMP%\controls_test.html. Run it under headless Chrome and read the PASS/FAIL lines out of the dumped DOM's
:
python scratchpad/test_controls_page.py
chrome --headless=new --disable-gpu --dump-dom file:///%TEMP%/controls_test.html
Asserts: player-authored rows are carried and marked custom, stock rows stay
unmarked, the PgUp/PgDn and backtick built-ins survive unbound keys, wizard
rows land in the extras list, and reset restores the stock board exactly.
Verified 11/11 on 2026-07-26.
"""
import io
import json
import os
import tempfile
SCRATCH = tempfile.gettempdir()
frag = io.open(r"C:\git\bt411\docs\CONTROLS.html", encoding="utf-8").read()
sample = io.open(r"C:\git\bt411\scratchpad\fixture_migrated_bindings.txt",
encoding="utf-8", errors="replace").read()
harness = r"""
// ---------- INJECTED SELF-TEST (test copy only) ----------
try {
var SAMPLE = __SAMPLE__;
applyFile(SAMPLE, "migrated-test");
var out = [];
function t(label, ok) { out.push((ok ? "PASS" : "FAIL") + " " + label); }
function elOf(n) { return keyEls[n]; }
function subOf(n) {
var s = elOf(n) && elOf(n).querySelector(".k-sub");
return s ? s.textContent : "";
}
t("T carries the custom button (VALVE face from 0x2B)",
subOf("T") === "VALVE" && elOf("T").classList.contains("custom"));
t("Q carries the tweaked throttle (THR +) and is custom",
subOf("Q") === "THR +" && elOf("Q").classList.contains("custom"));
t("W matches stock (VALVE, not custom)",
subOf("W") === "VALVE" && !elOf("W").classList.contains("custom"));
t("PgUp built-in volume survives an unbound key",
subOf("PAGEUP") === "VOL +"
&& !elOf("PAGEUP").classList.contains("custom"));
t("backtick view built-in survives", subOf("BACKTICK") === "VIEW");
t("extras list shows the wizard rows",
document.getElementById("load-extras").style.display === "block"
&& document.getElementById("load-extras")
.textContent.indexOf("joyaxis") >= 0);
t("status reports a load",
document.getElementById("load-status")
.textContent.indexOf("differ from stock") >= 0);
var stray = 0, strayList = [];
Object.keys(keyEls).forEach(function (n) {
if (n !== "T" && n !== "Q"
&& keyEls[n].classList.contains("custom")) {
stray += 1;
var sEl = keyEls[n].querySelector(".k-sub");
strayList.push(n + "[sub='" + (sEl ? sEl.textContent : "")
+ "' stockSub='" + stock[n].sub + "' addr='"
+ keyEls[n].getAttribute("data-addr") + "' stockAddr='"
+ stock[n].addr + "']");
}
});
t("no stray custom marks (only T and Q); strays: "
+ strayList.join(" "), stray === 0);
resetBoard();
t("reset restores W", subOf("W") === "VALVE");
t("reset restores Q to stock VALVE", subOf("Q") === "VALVE");
t("reset clears custom marks",
document.querySelectorAll(".key.custom").length === 0);
var pre = document.createElement("pre");
pre.id = "test-out";
pre.textContent = out.join("\n");
document.body.appendChild(pre);
} catch (err) {
var pre2 = document.createElement("pre");
pre2.id = "test-out";
pre2.textContent = "HARNESS ERROR: " + err + " :: " + (err && err.stack);
document.body.appendChild(pre2);
}
"""
harness = harness.replace("__SAMPLE__", json.dumps(sample))
closer = " })();\n"
assert closer in frag, "closer not found"
test = frag.replace(closer, harness + closer, 1)
page = ("\n\n\n\n"
"test \n\n\n" + test + "\n\n\n")
out = os.path.join(SCRATCH, "controls_test.html")
io.open(out, "w", encoding="utf-8").write(page)
print("test page written:", len(page), "chars")