test: v0.3.0 z_tier + persistent + input_block assertions
Add 25 new W.2 assertions covering panel v0.3.0 features: - persistent flag auto-opens at register-time - z_tier render-order hud->normal->top - within-tier open-order preserved - input_block='none' passes through (no handler call) - input_block='self' hit-tests within bounds - input_block='all' swallows outside-clicks (modal block) - persistent + close() no-op when focused is persistent - close(id) explicit DOES close persistent - Loud-Error on unknown z_tier - Loud-Error on unknown input_block - default input_block per tier (hud=none, normal=self, top=all) - point_in_any_panel public API Total: 79 assertions pass. Manifest bumped to 0.3.0 + panel-dep to 0.3.0.
This commit is contained in:
41
README.md
41
README.md
@@ -1,12 +1,18 @@
|
|||||||
# lib-core.panel-test
|
# lib-core.panel-test
|
||||||
|
|
||||||
Test-module for `lib-core.panel`. 10 assertions covering registry lifecycle,
|
Test-module for `lib-core.panel`. v0.3.0 expands to 79 assertions:
|
||||||
theme validation, error handling, pause-gate, and context-menu dispatch.
|
- v0.1.x: registry lifecycle, theme validation, error handling,
|
||||||
|
pause-gate, context-menu dispatch.
|
||||||
|
- v0.2.0 (W.0): multi-active model, 11 layout-slots, custom-fn layout,
|
||||||
|
hit-test reverse-open-order, bw-compat shim.
|
||||||
|
- v0.3.0 (W.2): z-tiers (hud/normal/top), persistent windows,
|
||||||
|
input_block routing (none/self/all), default-input_block per tier,
|
||||||
|
panel.point_in_any_panel public API.
|
||||||
|
|
||||||
**Version:** 0.1.0
|
**Version:** 0.3.0
|
||||||
**Lib-ID:** lib-core.panel-test
|
**Lib-ID:** lib-core.panel-test
|
||||||
**Requires:** lib-core.panel v0.1.0
|
**Requires:** lib-core.panel v0.3.0
|
||||||
**Tags:** test, panel, ui
|
**Tags:** test, panel, ui, window-manager
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
@@ -17,10 +23,11 @@ SPOREL_LIBS_DIR=~/Projects/Sporel/sporel-libs SPOREL_CI=1 SPOREL_LOG_STDERR=1 \
|
|||||||
./build/Sporel.exe --test-libs=lib-core.panel-test 2>&1 | tail -15
|
./build/Sporel.exe --test-libs=lib-core.panel-test 2>&1 | tail -15
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: `pass=10 fail=0`.
|
Expected: `pass=79 fail=0`.
|
||||||
|
|
||||||
## Assertion Summary
|
## Assertion Summary (v0.3.0)
|
||||||
|
|
||||||
|
**v0.1.x baseline (10 assertions):**
|
||||||
1. register + open / close round-trip
|
1. register + open / close round-trip
|
||||||
2. close after open works
|
2. close after open works
|
||||||
3. toggle twice opens then closes
|
3. toggle twice opens then closes
|
||||||
@@ -31,3 +38,23 @@ Expected: `pass=10 fail=0`.
|
|||||||
8. pause_on_open=true widget makes is_pausing() true; close makes it false
|
8. pause_on_open=true widget makes is_pausing() true; close makes it false
|
||||||
9. context-menu action callback invoked on click inside its row
|
9. context-menu action callback invoked on click inside its row
|
||||||
10. non-pause widget: is_pausing() is false
|
10. non-pause widget: is_pausing() is false
|
||||||
|
|
||||||
|
**v0.2.0 W.0 multi-active + layout-slots (~44 assertions):** bw-compat
|
||||||
|
shim, multi-active open(A)+open(B), per-id close, focused-close,
|
||||||
|
layout-slot resolution (left-half, right-half, center, custom fn),
|
||||||
|
loud-error on unknown slot, hit-test reverse-open-order, is_pausing
|
||||||
|
across all open windows.
|
||||||
|
|
||||||
|
**v0.3.0 W.2 z-tiers + persistent + input-block (25 assertions):**
|
||||||
|
- persistent flag auto-opens at register-time + is in open_order
|
||||||
|
- z_tier render-order is hud → normal → top
|
||||||
|
- within z_tier, open-order preserved
|
||||||
|
- input_block="none" passes through (no handler call)
|
||||||
|
- input_block="self" hit-tests within bounds (inside vs outside)
|
||||||
|
- input_block="all" swallows outside-clicks (modal block)
|
||||||
|
- persistent + close() no-op when focused is persistent
|
||||||
|
- close(id) explicit DOES close persistent (modder-controlled)
|
||||||
|
- Loud-Error on unknown z_tier
|
||||||
|
- Loud-Error on unknown input_block
|
||||||
|
- default input_block per tier (hud=none, normal=self, top=all)
|
||||||
|
- point_in_any_panel public API (inside / outside / none open)
|
||||||
|
|||||||
219
init.lua
219
init.lua
@@ -1,6 +1,8 @@
|
|||||||
-- lib-core.panel-test v0.2.0 — Panel test suite
|
-- lib-core.panel-test v0.3.0 — Panel test suite
|
||||||
-- Exercises: registry lifecycle, theme, errors, pause-gate, context-menu
|
-- Exercises: registry lifecycle, theme, errors, pause-gate, context-menu
|
||||||
-- dispatch, multi-active model, layout-slots, hit-test reverse-order.
|
-- dispatch, multi-active model, layout-slots, hit-test reverse-order,
|
||||||
|
-- z_tiers, persistent windows, input_block routing,
|
||||||
|
-- panel.point_in_any_panel.
|
||||||
-- Render paths are NOT tested here: engine.render.* is blocked in test-mode
|
-- Render paths are NOT tested here: engine.render.* is blocked in test-mode
|
||||||
-- (assert_in_render_phase guard in engine_lua_render.c). Input dispatch is
|
-- (assert_in_render_phase guard in engine_lua_render.c). Input dispatch is
|
||||||
-- exercised via panel._dispatch_event_for_test(event).
|
-- exercised via panel._dispatch_event_for_test(event).
|
||||||
@@ -335,6 +337,219 @@ function M.run_tests(_ctx)
|
|||||||
T.assert(not panel.is_pausing(),
|
T.assert(not panel.is_pausing(),
|
||||||
"is_pausing() false after closing the pausing window (np still open)")
|
"is_pausing() false after closing the pausing window (np still open)")
|
||||||
panel._test_reset_all()
|
panel._test_reset_all()
|
||||||
|
|
||||||
|
-- ==================================================================
|
||||||
|
-- W.2 v0.3.0 — Z-Tiers + Persistent + Input-Block
|
||||||
|
-- ==================================================================
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
-- W.2 #1: persistent flag auto-opens at register-time
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
panel.register("w_persist", make_widget({ title = "P" }),
|
||||||
|
{ persistent = true, layout = "top" })
|
||||||
|
T.assert(panel.is_open("w_persist"),
|
||||||
|
"W.2 persistent=true makes widget open immediately after register")
|
||||||
|
local persist_ids = panel._test_get_open_ids()
|
||||||
|
T.equals(persist_ids[1], "w_persist",
|
||||||
|
"W.2 persistent widget is in open_order")
|
||||||
|
panel._test_reset_all()
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
-- W.2 #2: z_tier render-order is hud → normal → top
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
-- Custom render that records call-order via test backdoor. We can't
|
||||||
|
-- exercise render() directly (engine.render.* blocked in test-mode);
|
||||||
|
-- instead we verify the per-tier iteration logic via the test backdoor
|
||||||
|
-- _test_get_open_ids and confirm windows persist across z_tiers.
|
||||||
|
-- For the actual render-order claim, panel._test_get_render_order()
|
||||||
|
-- is added in W.2 (returns the would-be iteration sequence).
|
||||||
|
panel.register("w_hud", make_widget({ title = "Hud" }),
|
||||||
|
{ z_tier = "hud", layout = "bottom" })
|
||||||
|
panel.register("w_n", make_widget({ title = "Normal" }),
|
||||||
|
{ z_tier = "normal", layout = "left" })
|
||||||
|
panel.register("w_top", make_widget({ title = "Top" }),
|
||||||
|
{ z_tier = "top", layout = "center" })
|
||||||
|
panel.open("w_hud")
|
||||||
|
panel.open("w_n")
|
||||||
|
panel.open("w_top")
|
||||||
|
local render_order = panel._test_get_render_order()
|
||||||
|
T.equals(render_order[1], "w_hud",
|
||||||
|
"W.2 hud-tier widget renders first")
|
||||||
|
T.equals(render_order[2], "w_n",
|
||||||
|
"W.2 normal-tier widget renders second")
|
||||||
|
T.equals(render_order[3], "w_top",
|
||||||
|
"W.2 top-tier widget renders third")
|
||||||
|
panel._test_reset_all()
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
-- W.2 #3: within z_tier, open-order is preserved in render
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
panel.register("w_n1", make_widget({ title = "N1" }),
|
||||||
|
{ z_tier = "normal", layout = "left-half" })
|
||||||
|
panel.register("w_n2", make_widget({ title = "N2" }),
|
||||||
|
{ z_tier = "normal", layout = "right-half" })
|
||||||
|
panel.open("w_n1")
|
||||||
|
panel.open("w_n2")
|
||||||
|
local ro2 = panel._test_get_render_order()
|
||||||
|
T.equals(ro2[1], "w_n1",
|
||||||
|
"W.2 within same tier: earlier-opened renders first")
|
||||||
|
T.equals(ro2[2], "w_n2",
|
||||||
|
"W.2 within same tier: later-opened renders second (= on top)")
|
||||||
|
panel._test_reset_all()
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
-- W.2 #4: input_block="none" passes through (no handler call)
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
local n_calls = 0
|
||||||
|
panel.register("w_pass", make_widget({
|
||||||
|
title = "Pass",
|
||||||
|
handle_input = function(_ctx, _ev) n_calls = n_calls + 1 end,
|
||||||
|
}), {
|
||||||
|
z_tier = "hud",
|
||||||
|
input_block = "none",
|
||||||
|
layout = function(_sw, _sh) return { x = 0, y = 0, w = 1280, h = 720 } end,
|
||||||
|
})
|
||||||
|
-- HUD covers the entire screen; despite click landing inside, the
|
||||||
|
-- "none" input_block skips hit-test entirely.
|
||||||
|
panel._dispatch_event_for_test({
|
||||||
|
kind = "click", button = "left", x = 100, y = 100,
|
||||||
|
})
|
||||||
|
T.equals(n_calls, 0,
|
||||||
|
"W.2 input_block='none' skips hit-test (handler not invoked)")
|
||||||
|
panel._test_reset_all()
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
-- W.2 #5: input_block="self" hit-tests within bounds
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
local hit_self = nil
|
||||||
|
panel.register("w_self", make_widget({
|
||||||
|
title = "Self",
|
||||||
|
handle_input = function(_ctx, _ev) hit_self = "yes" end,
|
||||||
|
}), {
|
||||||
|
z_tier = "normal",
|
||||||
|
input_block = "self",
|
||||||
|
layout = "left-half",
|
||||||
|
})
|
||||||
|
panel.open("w_self")
|
||||||
|
local sw_s, sh_s = panel._test_get_screen_size()
|
||||||
|
-- Click within left-half → hit
|
||||||
|
panel._dispatch_event_for_test({
|
||||||
|
kind = "click", button = "left",
|
||||||
|
x = sw_s * 0.25, y = sh_s * 0.5,
|
||||||
|
})
|
||||||
|
T.equals(hit_self, "yes",
|
||||||
|
"W.2 input_block='self' routes click inside bounds to widget")
|
||||||
|
-- Click in right-half → miss; widget should NOT see the event
|
||||||
|
hit_self = nil
|
||||||
|
panel._dispatch_event_for_test({
|
||||||
|
kind = "click", button = "left",
|
||||||
|
x = sw_s * 0.75, y = sh_s * 0.5,
|
||||||
|
})
|
||||||
|
T.assert(hit_self == nil,
|
||||||
|
"W.2 input_block='self' does NOT route click outside bounds")
|
||||||
|
panel._test_reset_all()
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
-- W.2 #6: input_block="all" swallows outside-clicks (modal)
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
local hit_modal = false
|
||||||
|
panel.register("w_modal", make_widget({
|
||||||
|
title = "Modal",
|
||||||
|
handle_input = function(_ctx, _ev) hit_modal = true end,
|
||||||
|
}), {
|
||||||
|
z_tier = "top",
|
||||||
|
input_block = "all",
|
||||||
|
layout = "center",
|
||||||
|
})
|
||||||
|
panel.open("w_modal")
|
||||||
|
-- Click outside the center (at 0,0) — modal swallows; handler not invoked.
|
||||||
|
panel._dispatch_event_for_test({
|
||||||
|
kind = "click", button = "left", x = 0, y = 0,
|
||||||
|
})
|
||||||
|
T.assert(not hit_modal,
|
||||||
|
"W.2 input_block='all' outside-click does NOT reach handler")
|
||||||
|
-- Sanity: click inside DOES reach the handler
|
||||||
|
hit_modal = false
|
||||||
|
local sw_m, sh_m = panel._test_get_screen_size()
|
||||||
|
panel._dispatch_event_for_test({
|
||||||
|
kind = "click", button = "left",
|
||||||
|
x = sw_m * 0.5, y = sh_m * 0.5,
|
||||||
|
})
|
||||||
|
T.assert(hit_modal,
|
||||||
|
"W.2 input_block='all' inside-click DOES reach handler")
|
||||||
|
panel._test_reset_all()
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
-- W.2 #7: persistent + close() no-op when focused is persistent
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
panel.register("w_pe", make_widget({ title = "Pe" }),
|
||||||
|
{ persistent = true, layout = "left" })
|
||||||
|
T.assert(panel.is_open("w_pe"),
|
||||||
|
"W.2 persistent open at register-time")
|
||||||
|
panel.close() -- bw-compat no-arg close: should be NO-OP for persistent
|
||||||
|
T.assert(panel.is_open("w_pe"),
|
||||||
|
"W.2 persistent: close() (no arg) is no-op when focused is persistent")
|
||||||
|
panel.close("w_pe") -- explicit per-id close: DOES close persistent
|
||||||
|
T.assert(not panel.is_open("w_pe"),
|
||||||
|
"W.2 persistent: close(id) explicit DOES close persistent")
|
||||||
|
panel._test_reset_all()
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
-- W.2 #8: Loud-Error on unknown z_tier
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
local ok_zt, err_zt = pcall(panel.register, "w_zt",
|
||||||
|
make_widget({ title = "X" }), { z_tier = "bogus" })
|
||||||
|
T.assert(not ok_zt,
|
||||||
|
"W.2 register with unknown z_tier raises an error")
|
||||||
|
T.assert(err_zt ~= nil and string.find(err_zt, "z_tier") ~= nil,
|
||||||
|
"W.2 unknown-z_tier error message mentions 'z_tier'")
|
||||||
|
panel._test_reset_all()
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
-- W.2 #9: Loud-Error on unknown input_block
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
local ok_ib, err_ib = pcall(panel.register, "w_ib",
|
||||||
|
make_widget({ title = "X" }), { input_block = "modal" })
|
||||||
|
T.assert(not ok_ib,
|
||||||
|
"W.2 register with unknown input_block raises an error")
|
||||||
|
T.assert(err_ib ~= nil and string.find(err_ib, "input_block") ~= nil,
|
||||||
|
"W.2 unknown-input_block error message mentions 'input_block'")
|
||||||
|
panel._test_reset_all()
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
-- W.2 #10: default input_block per tier (hud=none, normal=self, top=all)
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
panel.register("w_h_def", make_widget({ title = "H" }),
|
||||||
|
{ z_tier = "hud", layout = "top" })
|
||||||
|
panel.register("w_n_def", make_widget({ title = "N" }),
|
||||||
|
{ z_tier = "normal", layout = "bottom" })
|
||||||
|
panel.register("w_t_def", make_widget({ title = "T" }),
|
||||||
|
{ z_tier = "top", layout = "center" })
|
||||||
|
panel.open("w_h_def")
|
||||||
|
panel.open("w_n_def")
|
||||||
|
panel.open("w_t_def")
|
||||||
|
T.equals(panel._test_get_input_block("w_h_def"), "none",
|
||||||
|
"W.2 hud default input_block = 'none'")
|
||||||
|
T.equals(panel._test_get_input_block("w_n_def"), "self",
|
||||||
|
"W.2 normal default input_block = 'self'")
|
||||||
|
T.equals(panel._test_get_input_block("w_t_def"), "all",
|
||||||
|
"W.2 top default input_block = 'all'")
|
||||||
|
panel._test_reset_all()
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
-- W.2 #11: point_in_any_panel public API
|
||||||
|
-- ------------------------------------------------------------------
|
||||||
|
T.assert(not panel.point_in_any_panel(100, 100),
|
||||||
|
"W.2 point_in_any_panel false when nothing open")
|
||||||
|
panel.register("w_pia", make_widget({ title = "PIA" }),
|
||||||
|
{ layout = "left-half" })
|
||||||
|
panel.open("w_pia")
|
||||||
|
local sw_p, sh_p = panel._test_get_screen_size()
|
||||||
|
T.assert(panel.point_in_any_panel(sw_p * 0.25, sh_p * 0.5),
|
||||||
|
"W.2 point_in_any_panel true when point inside open panel")
|
||||||
|
T.assert(not panel.point_in_any_panel(sw_p * 0.75, sh_p * 0.5),
|
||||||
|
"W.2 point_in_any_panel false when point outside any panel")
|
||||||
|
panel._test_reset_all()
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"id": "lib-core.panel-test",
|
"id": "lib-core.panel-test",
|
||||||
"role": "test",
|
"role": "test",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"api_min": "0.1",
|
"api_min": "0.1",
|
||||||
"deps": [
|
"deps": [
|
||||||
{"id": "lib-core.panel", "version": "0.2.0"}
|
{"id": "lib-core.panel", "version": "0.3.0"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user