test: v0.4.0 focus/drag/resize assertions

Adds 46 new W.3 assertions covering: focus(id) reorders within open_order;
focus(hud) no-op; raise alias; lower(id) to bottom; get_focused tier
priority + nil-when-empty; click-to-focus auto-routing; drag updates
bounds_override; drag screen-clamp on all four edges; resize updates
bounds_override; min_size + max_size clamps; dispatch-time drag-start +
resize-start consume the click; chromeless drag-start excluded;
bounds_override survives drag-end and is respected by point_in_any_panel.

Bumps panel-test 0.3.0 -> 0.4.0 + panel-dep 0.4.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Calic
2026-06-15 03:04:45 +02:00
parent 07f7967543
commit f4e4b34903
3 changed files with 398 additions and 14 deletions

367
init.lua
View File

@@ -1,11 +1,16 @@
-- lib-core.panel-test v0.3.0 — Panel test suite
-- lib-core.panel-test v0.4.0 — Panel test suite
-- Exercises: registry lifecycle, theme, errors, pause-gate, context-menu
-- dispatch, multi-active model, layout-slots, hit-test reverse-order,
-- z_tiers, persistent windows, input_block routing,
-- panel.point_in_any_panel.
-- panel.point_in_any_panel, focus/raise/lower API, click-to-focus,
-- drag-by-title-bar (with screen-clamp), resize-by-SE-corner (with min/max
-- size constraints).
-- 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
-- exercised via panel._dispatch_event_for_test(event).
-- exercised via panel._dispatch_event_for_test(event). Drag/resize state
-- is exercised via panel._test_simulate_drag_{start,move,end} and
-- panel._test_simulate_resize_{start,move,end} backdoors (engine.input
-- is not poll-able from a test-lib context).
local panel = require("lib-core.panel")
local T = engine.test
@@ -602,6 +607,362 @@ function M.run_tests(_ctx)
T.assert(not normal_clicked_m,
"modal swallow: normal-tier widget does NOT receive click while top-tier all-modal is open")
panel._test_reset_all()
-- ==================================================================
-- W.3 v0.4.0 — Focus + Drag + Resize
-- ==================================================================
-- ------------------------------------------------------------------
-- W.3 #1: focus(id) reorders within open_order
-- ------------------------------------------------------------------
panel.register("w_fA", make_widget({ title = "A" }))
panel.register("w_fB", make_widget({ title = "B" }))
panel.open("w_fA")
panel.open("w_fB")
T.equals(panel._test_get_focused_id(), "w_fB",
"W.3 B focused initially (last-opened)")
panel.focus("w_fA")
T.equals(panel._test_get_focused_id(), "w_fA",
"W.3 focus(A) reorders so A is now focused")
local fo = panel._test_get_open_ids()
T.equals(fo[#fo], "w_fA",
"W.3 focus moves widget to end of open_order")
panel._test_reset_all()
-- ------------------------------------------------------------------
-- W.3 #2: focus on hud-tier widget is a no-op
-- ------------------------------------------------------------------
panel.register("w_hud_f", make_widget({ title = "Hud" }),
{ z_tier = "hud", layout = "top" })
panel.register("w_norm_f", make_widget({ title = "Norm" }),
{ z_tier = "normal", layout = "bottom" })
panel.open("w_hud_f")
panel.open("w_norm_f")
-- normal-tier opened last → focused
T.equals(panel._test_get_focused_id(), "w_norm_f",
"W.3 normal-tier focused after open")
panel.focus("w_hud_f") -- should be no-op
T.equals(panel._test_get_focused_id(), "w_norm_f",
"W.3 focus(hud-tier widget) is a no-op (hud never focusable)")
panel._test_reset_all()
-- ------------------------------------------------------------------
-- W.3 #3: raise alias = focus
-- ------------------------------------------------------------------
panel.register("w_rA", make_widget({ title = "A" }))
panel.register("w_rB", make_widget({ title = "B" }))
panel.open("w_rA")
panel.open("w_rB")
panel.raise("w_rA")
T.equals(panel._test_get_focused_id(), "w_rA",
"W.3 raise(id) is alias for focus(id)")
panel._test_reset_all()
-- ------------------------------------------------------------------
-- W.3 #4: lower(id) moves to bottom of open_order
-- ------------------------------------------------------------------
panel.register("w_lA", make_widget({ title = "A" }))
panel.register("w_lB", make_widget({ title = "B" }))
panel.open("w_lA")
panel.open("w_lB")
panel.lower("w_lB") -- B was focused; lower → bottom
local lo = panel._test_get_open_ids()
T.equals(lo[1], "w_lB",
"W.3 lower(B) moves B to bottom of open_order")
T.equals(panel._test_get_focused_id(), "w_lA",
"W.3 after lower(B), A is focused (last in order)")
panel._test_reset_all()
-- ------------------------------------------------------------------
-- W.3 #5: get_focused() prioritizes top-tier over normal
-- ------------------------------------------------------------------
panel.register("w_gn", make_widget({ title = "N" }),
{ z_tier = "normal", layout = "left" })
panel.register("w_gt", make_widget({ title = "T" }),
{ z_tier = "top", layout = "center" })
panel.open("w_gt")
panel.open("w_gn")
-- gn opened last; but top-tier gt should win for get_focused
T.equals(panel.get_focused(), "w_gt",
"W.3 get_focused() returns top-tier widget even when normal-tier is last-opened")
panel._test_reset_all()
-- ------------------------------------------------------------------
-- W.3 #6: get_focused() returns last-opened when no top-tier
-- ------------------------------------------------------------------
panel.register("w_g1", make_widget({ title = "1" }))
panel.register("w_g2", make_widget({ title = "2" }))
panel.open("w_g1")
panel.open("w_g2")
T.equals(panel.get_focused(), "w_g2",
"W.3 get_focused() returns last-opened when no top-tier window")
panel._test_reset_all()
-- ------------------------------------------------------------------
-- W.3 #7: get_focused() returns nil when nothing open
-- ------------------------------------------------------------------
T.assert(panel.get_focused() == nil,
"W.3 get_focused() returns nil when no windows open")
-- ------------------------------------------------------------------
-- W.3 #8: click on window auto-focuses
-- ------------------------------------------------------------------
panel.register("w_acA", make_widget({ title = "A" }), { layout = "left-half" })
panel.register("w_acB", make_widget({ title = "B" }), { layout = "right-half" })
panel.open("w_acA")
panel.open("w_acB")
T.equals(panel._test_get_focused_id(), "w_acB",
"W.3 B initial-focused")
-- Click in A's left-half → auto-focus A
local sw_ac, sh_ac = panel._test_get_screen_size()
panel._dispatch_event_for_test({
kind = "click", button = "left",
x = sw_ac * 0.25, y = sh_ac * 0.5,
})
T.equals(panel._test_get_focused_id(), "w_acA",
"W.3 click on A auto-focuses A")
panel._test_reset_all()
-- ------------------------------------------------------------------
-- W.3 #9: focus(hud-tier) does NOT promote hud above normal-tier
-- ------------------------------------------------------------------
-- focus() is the modder-facing API for "bring to top within tier".
-- The contract is: hud-tier widgets are NEVER promoted via focus()
-- (they are cosmetic overlays). Verify by opening normal, then hud
-- (hud is naturally last-opened by virtue of open ordering), then
-- explicitly call focus on each and verify the open_order tail.
panel.register("w_hf_norm", make_widget({ title = "N" }),
{ z_tier = "normal", layout = "right-half" })
panel.register("w_hf_hud", make_widget({ title = "H" }),
{ z_tier = "hud", input_block = "self", layout = "left-half" })
panel.open("w_hf_hud") -- opens first
panel.open("w_hf_norm") -- opens last → last in open_order
T.equals(panel._test_get_focused_id(), "w_hf_norm",
"W.3 normal opened last → last in open_order")
-- focus(hud-tier) is a no-op: order is preserved.
panel.focus("w_hf_hud")
T.equals(panel._test_get_focused_id(), "w_hf_norm",
"W.3 focus(hud-tier widget) is a no-op (order unchanged)")
-- Click on hud-tier should also be a no-op for focus (auto-focus
-- internally calls M.focus, which is no-op for hud).
local sw_hf, sh_hf = panel._test_get_screen_size()
panel._dispatch_event_for_test({
kind = "click", button = "left",
x = sw_hf * 0.25, y = sh_hf * 0.5,
})
T.equals(panel._test_get_focused_id(), "w_hf_norm",
"W.3 click on hud-tier does NOT change focus (auto-focus no-op for hud)")
panel._test_reset_all()
-- ------------------------------------------------------------------
-- W.3 #10: drag updates bounds_override
-- ------------------------------------------------------------------
panel.register("w_d", make_widget({ title = "D" }), {
draggable = true,
layout = function(_sw, _sh) return {x=100, y=100, w=200, h=150} end,
})
panel.open("w_d")
-- Start drag at title-bar click point (110, 105) — 10px inside top-left
panel._test_simulate_drag_start("w_d", 110, 105)
T.equals(panel._test_get_dragging_id(), "w_d",
"W.3 drag-start sets dragging_id")
-- Move mouse +50 +30 → window moves from (100,100) to (150,130)
panel._test_simulate_drag_move(160, 135)
local bd = panel._test_get_window_bounds("w_d")
T.equals(bd.x, 150, "W.3 drag updates x (+50)")
T.equals(bd.y, 130, "W.3 drag updates y (+30)")
T.equals(bd.w, 200, "W.3 drag preserves w")
T.equals(bd.h, 150, "W.3 drag preserves h")
panel._test_simulate_drag_end()
T.assert(panel._test_get_dragging_id() == nil,
"W.3 drag-end clears dragging_id")
panel._test_reset_all()
-- ------------------------------------------------------------------
-- W.3 #11: drag screen-clamp keeps title-bar visible (32px minimum)
-- ------------------------------------------------------------------
panel.register("w_dc", make_widget({ title = "DC" }), {
draggable = true,
layout = function(_sw, _sh) return {x=100, y=100, w=200, h=150} end,
})
panel.open("w_dc")
panel._test_simulate_drag_start("w_dc", 110, 110)
-- Try to drag way off-screen left + up
panel._test_simulate_drag_move(-5000, -5000)
local bdc = panel._test_get_window_bounds("w_dc")
local sw_dc, sh_dc = panel._test_get_screen_size()
-- x clamp: at most (-w + 32) = -200 + 32 = -168
T.assert(bdc.x >= -200 + 32 - 0.01,
"W.3 drag x-clamp keeps 32px visible on right edge when pushed left")
-- y clamp: at most 0 (title-bar top must stay on-screen)
T.assert(bdc.y >= 0 - 0.01,
"W.3 drag y-clamp prevents title-bar from leaving top of screen")
-- Try drag way off-screen right + down
panel._test_simulate_drag_move(99999, 99999)
bdc = panel._test_get_window_bounds("w_dc")
-- x clamp: at most (sw - 32)
T.assert(bdc.x <= sw_dc - 32 + 0.01,
"W.3 drag x-clamp keeps 32px visible on left edge when pushed right")
T.assert(bdc.y <= sh_dc - 32 + 0.01,
"W.3 drag y-clamp keeps title-bar reachable when pushed down")
panel._test_simulate_drag_end()
panel._test_reset_all()
-- ------------------------------------------------------------------
-- W.3 #12: resize updates bounds (SE-corner)
-- ------------------------------------------------------------------
panel.register("w_r", make_widget({ title = "R" }), {
resizable = true,
layout = function(_sw, _sh) return {x=100, y=100, w=200, h=150} end,
})
panel.open("w_r")
-- Start resize at SE-corner (~ 300, 250); use the actual coords
panel._test_simulate_resize_start("w_r", 300, 250)
T.equals(panel._test_get_resizing_id(), "w_r",
"W.3 resize-start sets resizing_id")
-- Drag SE-corner +50 +40 → w becomes 250, h becomes 190
panel._test_simulate_resize_move(350, 290)
local br = panel._test_get_window_bounds("w_r")
T.equals(br.w, 250, "W.3 resize updates w (+50)")
T.equals(br.h, 190, "W.3 resize updates h (+40)")
T.equals(br.x, 100, "W.3 resize preserves x")
T.equals(br.y, 100, "W.3 resize preserves y")
panel._test_simulate_resize_end()
T.assert(panel._test_get_resizing_id() == nil,
"W.3 resize-end clears resizing_id")
panel._test_reset_all()
-- ------------------------------------------------------------------
-- W.3 #13: resize respects min_size constraint
-- ------------------------------------------------------------------
panel.register("w_rmin", make_widget({ title = "Rmin" }), {
resizable = true,
min_size = { w = 180, h = 120 },
layout = function(_sw, _sh) return {x=100, y=100, w=200, h=150} end,
})
panel.open("w_rmin")
panel._test_simulate_resize_start("w_rmin", 300, 250)
-- Try to shrink way past min — should clamp to min_size
panel._test_simulate_resize_move(0, 0)
local brmin = panel._test_get_window_bounds("w_rmin")
T.equals(brmin.w, 180, "W.3 resize clamps to min_size.w")
T.equals(brmin.h, 120, "W.3 resize clamps to min_size.h")
panel._test_simulate_resize_end()
panel._test_reset_all()
-- ------------------------------------------------------------------
-- W.3 #14: resize respects max_size constraint
-- ------------------------------------------------------------------
panel.register("w_rmax", make_widget({ title = "Rmax" }), {
resizable = true,
max_size = { w = 250, h = 180 },
layout = function(_sw, _sh) return {x=100, y=100, w=200, h=150} end,
})
panel.open("w_rmax")
panel._test_simulate_resize_start("w_rmax", 300, 250)
-- Try to grow way past max — should clamp
panel._test_simulate_resize_move(99999, 99999)
local brmax = panel._test_get_window_bounds("w_rmax")
T.equals(brmax.w, 250, "W.3 resize clamps to max_size.w")
T.equals(brmax.h, 180, "W.3 resize clamps to max_size.h")
panel._test_simulate_resize_end()
panel._test_reset_all()
-- ------------------------------------------------------------------
-- W.3 #15: drag-start via _dispatch_event on title-bar click
-- ------------------------------------------------------------------
-- This exercises the real dispatch-time drag-start detection rather
-- than the simulate-backdoor.
local handler_fired_d = false
panel.register("w_dd", make_widget({
title = "DD",
handle_input = function(_ctx, _ev) handler_fired_d = true end,
}), {
draggable = true,
layout = function(_sw, _sh) return {x=100, y=100, w=200, h=150} end,
})
panel.open("w_dd")
-- Click in the title-bar area (top of window) → drag-start, handler NOT invoked
panel._dispatch_event_for_test({
kind = "click", button = "left",
x = 150, y = 108, -- well inside title-bar (font_size_title=18 + padding=8*2 = 34 px)
})
T.equals(panel._test_get_dragging_id(), "w_dd",
"W.3 dispatch-time drag-start sets dragging_id")
T.assert(not handler_fired_d,
"W.3 dispatch-time drag-start consumes the click (handler NOT invoked)")
panel._test_simulate_drag_end()
panel._test_reset_all()
-- ------------------------------------------------------------------
-- W.3 #16: resize-start via _dispatch_event on SE-corner click
-- ------------------------------------------------------------------
local handler_fired_r = false
panel.register("w_rr", make_widget({
title = "RR",
handle_input = function(_ctx, _ev) handler_fired_r = true end,
}), {
resizable = true,
layout = function(_sw, _sh) return {x=100, y=100, w=200, h=150} end,
})
panel.open("w_rr")
-- Click in SE-corner (12x12 zone at right-bottom)
panel._dispatch_event_for_test({
kind = "click", button = "left",
x = 295, y = 245, -- inside 12px SE handle (bottom-right = 300, 250)
})
T.equals(panel._test_get_resizing_id(), "w_rr",
"W.3 dispatch-time resize-start sets resizing_id")
T.assert(not handler_fired_r,
"W.3 dispatch-time resize-start consumes the click (handler NOT invoked)")
panel._test_simulate_resize_end()
panel._test_reset_all()
-- ------------------------------------------------------------------
-- W.3 #17: chromeless widget does NOT drag-start on top-area click
-- ------------------------------------------------------------------
-- chromeless has no defined title-bar. Drag must NOT engage.
local hf_chrome_d = false
panel.register("w_chrome_d", make_widget({
title = "CD",
handle_input = function(_ctx, _ev) hf_chrome_d = true end,
}), {
chromeless = true,
draggable = true, -- modder set both; chromeless takes precedence
layout = function(_sw, _sh) return {x=100, y=100, w=200, h=150} end,
})
panel.open("w_chrome_d")
-- Click in what WOULD be the title-bar area on a chromed widget
panel._dispatch_event_for_test({
kind = "click", button = "left", x = 150, y = 108,
})
T.assert(panel._test_get_dragging_id() == nil,
"W.3 chromeless widget does NOT drag-start (no title-bar)")
T.assert(hf_chrome_d,
"W.3 chromeless widget's handler receives the click (no drag intercept)")
panel._test_reset_all()
-- ------------------------------------------------------------------
-- W.3 #18: bounds_override survives the layout-fn (drag persists)
-- ------------------------------------------------------------------
-- Once bounds_override is set, the layout-fn no longer drives bounds.
panel.register("w_bo", make_widget({ title = "BO" }), {
draggable = true,
layout = function(_sw, _sh) return {x=100, y=100, w=200, h=150} end,
})
panel.open("w_bo")
panel._test_simulate_drag_start("w_bo", 110, 110)
panel._test_simulate_drag_move(310, 310)
panel._test_simulate_drag_end()
local bbo = panel._test_get_window_bounds("w_bo")
T.equals(bbo.x, 300, "W.3 bounds_override survives after drag-end (x)")
T.equals(bbo.y, 300, "W.3 bounds_override survives after drag-end (y)")
-- Verify point_in_any_panel uses override too
T.assert(panel.point_in_any_panel(350, 350),
"W.3 point_in_any_panel respects bounds_override")
T.assert(not panel.point_in_any_panel(150, 150),
"W.3 point_in_any_panel does NOT report old layout-fn position")
panel._test_reset_all()
end
return M