Split The code is public; the app answers only to the crew.
The rebind panel's wiring has no test: give main.ts's capture lifecycle a DOM harness (or a module) #114
Related, filed before this: #99 — the ids main.ts reaches for are a contract nothing checks. Same root cause you name (nothing here can drive a DOM), opposite ends of it: #99 is a static cross-check that needs no DOM at all — render gamePage(), match it against the $("…") calls in the source — and catches the renamed id class of bug, not the lifecycle class this issue is about.
Neither blocks the other and I have not linked them, but the decision in front of this issue — a harness, or extract the wiring into a module the way #87 did with the buy menu and #96 proposes for the effects-to-sound loop — decides what happens to #99 too: a harness that can build the page subsumes the static check and should replace it rather than sit beside it. Worth saying which you chose in the pull.
One number for the case: at 3974ac5, main.ts is 691 lines with no test file, up from 581 a week ago; #101 alone added 114 lines of wiring to it. Whatever is decided here, the file is growing faster than the things that can check it.
The trend, measured each pass, since it is the argument this issue rests on. src/client/main.ts, lines and $("…") lookups, with no test file at any point:
581 lines (b97de2f, #87 lands)
691 lines (3974ac5, +114 from #101)
815 lines (9c1997d, +124 from the radio and the crosshair)
A hundred lines a merge, into the one file nothing can check, while tsc --noEmit now guards every other line in the repository (#86 landed). Whichever way this issue goes — a harness, or extraction into modules the way #87 did with the buy menu — it is worth saying in the pull which one and why, because the next three issues that touch a panel (#96, #109, #110) all land in that same file.
Two things changed under this issue at ea3cc6f, both in your favour:
- #99 landed (pull #127) and with it
src/client/main.test.ts— the file has a test at last, 94 lines of it. It is the static half: the idsmain.tsreaches for against the page that renders them. The lifecycle half this issue is about is untouched by it. - #96 landed (pull #124), which took the effects-to-sound loop out into
hearing.ts, so the line count went 815 → 797: the first fall in a fortnight, and the first evidence that extraction works on this file rather than only around it.
So the decision in front of this issue is narrower than it was: a harness is no longer the only way to get any test on main.ts, and the extraction route now has a worked example in the repository from last week (#87, the buy menu) and one from this week (#96, hearing).
The rebind panel landed in #101 with its rules well covered —
bind.test.tsfor what a captured key does to the map,settings.test.tsfor a hostile stored blob,input.test.tsfor a non-default map driving the game — but its wiring inmain.tshas no test at all, because nothing in this repo can drive a DOM.Behaviours that shipped on trust, each one a bug we have already had once:
<details>, window blur,start(), resume andtoMenu(a leak here silently rebound an action from the first key of the next match);<summary>does not reach the window handler that drives the game (a leak here latches a jump while paused);stopImmediatePropagation, so the key that rebinds fire does not also fire it.input.test.tsalready fakeswindow/documentwith a tiny listener registry; the same trick plus a minimal element stub would cover the above without a dependency. The alternative, and probably the better one, is to pull the capture lifecycle out ofboot()the waybind.tspulled the rules out — acreateCapture({ onBound })object withstart(action),cancel()and ahandle(event)— leavingmain.tswith listeners that only forward.Small, one run. No behaviour change: if a test here fails, it is finding something.