Table of Contents

Class UiClickThroughTests

Namespace
GrindFest
Assembly
GrindFest.dll

Clicks through the whole UI the way a player does, so "the UI works" is a measurement instead of an impression.

Why this exists: every UI bug found on 2026-09-17 was invisible to the tests that existed. The harness asks whether a panel is displayed (display:flex, non-zero rect), which a DraggablePanel parked at its x=9999 fallback satisfies - six panels were off-screen and every check said they were open. It also sends pointer events straight to an element, so it never exercises the picking and hover paths a mouse uses, and it cannot see a bar that hangs below the panel. These tests answer the three questions that were missing: is it ON SCREEN, does the click reach it, and is the console still clean afterwards (the runner fails any test that logs an error it did not declare).

Clicks go through the same helpers as the other UI tests (Ui.Click / ClickText - real UI Toolkit pointer events on a real element), so a handler that only reacts to a mouse event type the v3 bridge never forwards fails here.

public class UiClickThroughTests : DevTest
Inheritance
object
UiClickThroughTests
Inherited Members

Properties

NeedsFreshUi

True for a test that reads the OneJS UI, which makes the runner reload the whole UI before it. That reload is the only reset that reaches the state a panel keeps while it stays mounted but hidden - the Character screen's selected tab, for instance, which made two CharacterScreenUiTests fail with "Timeout waiting for 'Strength' visible" after the tab had been picked by hand before the run.

Opt-in because it is not free: measured 2026-09-16, reloading before every test left 888 s of a 1194 s suite outside the test bodies (against 119 s in the 358 s run without it), roughly 8,5 s per test. Only the tests that actually read the UI pay that.

Methods

ClickThrough_CharacterScreen_EveryTab()

The character screen's three icon buttons (there is no text to click - the row is icons only): Stats <-> Skills, the inventory, and the one that hands over to the prestige tree.

ClickThrough_DragPanelHeader_MovesPanel()

Dragging a panel by its header moves it by exactly the pointer delta.

This path was dead for every panel and nothing noticed: the drag handlers read e.position/e.localPosition, but the v3 bridge hands JS {x, y, button, pointerId} (see __dispatchEventFast in QuickJSBootstrap.js), so px/py were always 0 and the guard if (px === 0 && py === 0) return; aborted every drag. A test is the only thing that can catch it - a panel that ignores a drag looks exactly like a panel that was never dragged.

The event goes to the deepest element under the cursor point (the title Label), not to the header's own element: the handlers live on an absolutely-positioned View inside the header, and UI Toolkit bubbling is what carries the event to it. Dispatching straight at the outer header box silently tests nothing.

ClickThrough_EveryPanel_OpensOnScreenAndCloses()

Every panel, opened and closed on its own: the one path a player has to every feature, and the one that was silently broken for six of them (opened at x=9999, i.e. off-screen).

ClickThrough_Hud_HoverAndClickOrbs()

The bottom HUD: is it inside the panel, does hovering an orb show its tooltip, and does clicking one survive?

All three were broken and all three passed the old checks. The bar hung 72 px below the panel (bottom: -px(120) evaluating with the default compareOffset of 0), so the orbs were half off-screen and unpickable, and the handlers were onMouseEnter/onMouseUp, which v3 never forwards.

ClickThrough_MainMenu_EveryEntryOpensItsPanel()

A player presses ESC and clicks each entry in turn: the panel it names must open where they can see it, and closing everything must leave the menu able to reopen.

ClickThrough_Settings_EveryTab()

Clicking every Settings tab. Content has to change and nothing may throw: clicking a tab is what used to overflow the JS stack and leave the ESC menu dead for the rest of the session.