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>
969 lines
44 KiB
Lua
969 lines
44 KiB
Lua
-- 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, 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). 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
|
|
|
|
local M = {}
|
|
|
|
-- -----------------------------------------------------------------------
|
|
-- Shared dummy widget factories
|
|
-- -----------------------------------------------------------------------
|
|
|
|
local function make_widget(overrides)
|
|
local w = {
|
|
title = "Test Widget",
|
|
render = function(_ctx) end,
|
|
handle_input = function(_ctx, _event) end,
|
|
}
|
|
if overrides then
|
|
for k, v in pairs(overrides) do w[k] = v end
|
|
end
|
|
return w
|
|
end
|
|
|
|
-- -----------------------------------------------------------------------
|
|
-- Test suite
|
|
-- -----------------------------------------------------------------------
|
|
|
|
function M.run_tests(_ctx)
|
|
|
|
-- ==================================================================
|
|
-- v0.1.1 baseline tests (bw-compat verification)
|
|
-- ==================================================================
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- 1. register + open / close round-trip
|
|
-- ------------------------------------------------------------------
|
|
panel._test_reset_all()
|
|
panel.register("w1", make_widget())
|
|
panel.open("w1")
|
|
T.assert(panel.is_open(), "register + open makes is_open() true")
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- 2. close after open works
|
|
-- ------------------------------------------------------------------
|
|
panel.close()
|
|
T.assert(not panel.is_open(), "close() after open makes is_open() false")
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- 3. toggle twice → opens then closes
|
|
-- ------------------------------------------------------------------
|
|
panel.toggle("w1")
|
|
T.assert(panel.is_open(), "toggle (first) opens panel")
|
|
panel.toggle("w1")
|
|
T.assert(not panel.is_open(), "toggle (second) closes panel")
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- 4. open("unknown_id") → loud-error
|
|
-- ------------------------------------------------------------------
|
|
local ok_unknown = pcall(panel.open, "does_not_exist")
|
|
T.assert(not ok_unknown,
|
|
"panel.open with unknown widget_id raises an error")
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- 5. set_theme with valid override + get_theme reads merged
|
|
-- ------------------------------------------------------------------
|
|
panel.set_theme({ padding = 16 })
|
|
local t = panel.get_theme()
|
|
T.equals(t.padding, 16, "set_theme override is visible via get_theme")
|
|
|
|
-- Restore for subsequent tests
|
|
panel.set_theme({ padding = 8 })
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- 6. set_theme with unknown key → loud-error
|
|
-- ------------------------------------------------------------------
|
|
local ok_bad_key = pcall(panel.set_theme, { nonexistent_key = 99 })
|
|
T.assert(not ok_bad_key,
|
|
"set_theme with unknown key raises an error")
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- 7. register without render function → loud-error
|
|
-- ------------------------------------------------------------------
|
|
local ok_no_render = pcall(panel.register, "w_bad", {
|
|
title = "Bad",
|
|
handle_input = function() end,
|
|
-- render intentionally missing
|
|
})
|
|
T.assert(not ok_no_render,
|
|
"panel.register without render function raises an error")
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- 8. pause_on_open=true widget makes is_pausing()=true; close → false
|
|
-- ------------------------------------------------------------------
|
|
panel.register("w_pause", make_widget({ pause_on_open = true }))
|
|
panel.open("w_pause")
|
|
T.assert(panel.is_pausing(),
|
|
"is_pausing() is true when active widget has pause_on_open=true")
|
|
panel.close()
|
|
T.assert(not panel.is_pausing(),
|
|
"is_pausing() is false after close()")
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- 9. show_context_menu + simulated click on action row → callback invoked
|
|
-- ------------------------------------------------------------------
|
|
|
|
-- Need an active widget for context-menu dispatch path
|
|
panel.open("w1")
|
|
|
|
local callback_fired = false
|
|
|
|
-- The context-menu renders at a known position; compute the click coords:
|
|
-- show_context_menu is called at (100, 100). With padding=8, row_height=24:
|
|
-- row 1 spans y=100..(100+24). Use x=120, y=110 (inside row 1).
|
|
panel.show_context_menu(100, 100, {
|
|
{
|
|
label = "Do Thing",
|
|
callback = function(_info)
|
|
callback_fired = true
|
|
end,
|
|
},
|
|
})
|
|
|
|
-- Simulate a left-click inside the first action row
|
|
panel._dispatch_event_for_test({
|
|
kind = "click",
|
|
x = 120,
|
|
y = 110,
|
|
button = "left",
|
|
})
|
|
|
|
T.assert(callback_fired,
|
|
"context-menu action callback is invoked on click inside its row")
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- 10. non-pause widget: is_pausing() = false
|
|
-- ------------------------------------------------------------------
|
|
panel.close() -- close w1 from test 9
|
|
panel.open("w1") -- w1 has no pause_on_open
|
|
T.assert(not panel.is_pausing(),
|
|
"is_pausing() is false for widget without pause_on_open")
|
|
|
|
-- Cleanup
|
|
panel.close()
|
|
panel.unregister("w1")
|
|
panel.unregister("w_pause")
|
|
|
|
-- ==================================================================
|
|
-- v0.2.0 — Multi-Active + Layout-Slots + Bw-Compat-Shim
|
|
-- ==================================================================
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- W.0 #1: bw-compat register(id, widget) without opts uses center default
|
|
-- ------------------------------------------------------------------
|
|
panel._test_reset_all()
|
|
panel.register("w_bc1", make_widget({ title = "BC" }))
|
|
panel.open("w_bc1")
|
|
T.assert(panel.is_open(), "is_open() any-open bw-compat")
|
|
T.assert(panel.is_open("w_bc1"), "is_open(id) per-id")
|
|
local bounds_bc = panel._test_get_window_bounds("w_bc1")
|
|
T.assert(bounds_bc ~= nil, "bounds resolved for open window")
|
|
-- center slot: x = sw*0.25, w = sw*0.50, h = sh*0.60
|
|
T.assert(bounds_bc.x > 0, "default center slot x > 0")
|
|
T.assert(bounds_bc.w > 0, "default center slot w > 0")
|
|
panel._test_reset_all()
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- W.0 #2: multi-active open(A) + open(B) both stay open
|
|
-- ------------------------------------------------------------------
|
|
panel.register("w_mA", make_widget({ title = "A" }))
|
|
panel.register("w_mB", make_widget({ title = "B" }))
|
|
panel.open("w_mA")
|
|
panel.open("w_mB")
|
|
T.assert(panel.is_open("w_mA"), "A still open after opening B")
|
|
T.assert(panel.is_open("w_mB"), "B is open")
|
|
local open_ids = panel._test_get_open_ids()
|
|
T.equals(#open_ids, 2, "two windows open")
|
|
T.equals(open_ids[1], "w_mA", "A first in open_order")
|
|
T.equals(open_ids[2], "w_mB", "B second in open_order")
|
|
T.equals(panel._test_get_focused_id(), "w_mB",
|
|
"B (last-opened) is focused")
|
|
panel._test_reset_all()
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- W.0 #3: close(id) per-id closes only that one
|
|
-- ------------------------------------------------------------------
|
|
panel.register("w_cA", make_widget({ title = "A" }))
|
|
panel.register("w_cB", make_widget({ title = "B" }))
|
|
panel.open("w_cA")
|
|
panel.open("w_cB")
|
|
panel.close("w_cA")
|
|
T.assert(not panel.is_open("w_cA"), "A closed by per-id close")
|
|
T.assert(panel.is_open("w_cB"), "B still open after per-id close(A)")
|
|
T.equals(panel._test_get_focused_id(), "w_cB",
|
|
"B is focused after A closed")
|
|
panel._test_reset_all()
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- W.0 #4: close() bw-compat closes the focused (last-opened)
|
|
-- ------------------------------------------------------------------
|
|
panel.register("w_fA", make_widget({ title = "A" }))
|
|
panel.register("w_fB", make_widget({ title = "B" }))
|
|
panel.open("w_fA")
|
|
panel.open("w_fB")
|
|
panel.close() -- no arg
|
|
T.assert(not panel.is_open("w_fB"), "B (focused) closed by close()")
|
|
T.assert(panel.is_open("w_fA"), "A still open after close() (bw-compat)")
|
|
T.equals(panel._test_get_focused_id(), "w_fA",
|
|
"A is now focused after focused-close")
|
|
panel._test_reset_all()
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- W.0 #5: is_open() bw-compat = any-open
|
|
-- ------------------------------------------------------------------
|
|
panel.register("w_io", make_widget({ title = "X" }))
|
|
T.assert(not panel.is_open(), "is_open() false when nothing open")
|
|
panel.open("w_io")
|
|
T.assert(panel.is_open(), "is_open() true after open (bw-compat any)")
|
|
panel.close()
|
|
T.assert(not panel.is_open(), "is_open() false after close")
|
|
panel._test_reset_all()
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- W.0 #6: layout left-half resolves correctly
|
|
-- ------------------------------------------------------------------
|
|
panel.register("w_lh", make_widget({ title = "L" }), { layout = "left-half" })
|
|
panel.open("w_lh")
|
|
local b_lh = panel._test_get_window_bounds("w_lh")
|
|
local sw_lh, sh_lh = panel._test_get_screen_size()
|
|
T.assert(b_lh.x == 0, "left-half x = 0")
|
|
T.assert(math.abs(b_lh.w - sw_lh * 0.5) < 0.01, "left-half w = sw/2")
|
|
T.assert(math.abs(b_lh.h - sh_lh) < 0.01, "left-half h = sh")
|
|
panel._test_reset_all()
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- W.0 #7: layout right-half resolves correctly
|
|
-- ------------------------------------------------------------------
|
|
panel.register("w_rh", make_widget({ title = "R" }), { layout = "right-half" })
|
|
panel.open("w_rh")
|
|
local b_rh = panel._test_get_window_bounds("w_rh")
|
|
local sw_rh = panel._test_get_screen_size()
|
|
T.assert(math.abs(b_rh.x - sw_rh * 0.5) < 0.01, "right-half x = sw/2")
|
|
T.assert(math.abs(b_rh.w - sw_rh * 0.5) < 0.01, "right-half w = sw/2")
|
|
panel._test_reset_all()
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- W.0 #8: layout center default
|
|
-- ------------------------------------------------------------------
|
|
panel.register("w_c", make_widget({ title = "C" })) -- no opts = center
|
|
panel.open("w_c")
|
|
local b_c = panel._test_get_window_bounds("w_c")
|
|
local sw_c, sh_c = panel._test_get_screen_size()
|
|
T.assert(math.abs(b_c.x - sw_c * 0.25) < 0.01, "center x = sw*0.25")
|
|
T.assert(math.abs(b_c.y - sh_c * 0.20) < 0.01, "center y = sh*0.20")
|
|
T.assert(math.abs(b_c.w - sw_c * 0.50) < 0.01, "center w = sw*0.50")
|
|
T.assert(math.abs(b_c.h - sh_c * 0.60) < 0.01, "center h = sh*0.60")
|
|
panel._test_reset_all()
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- W.0 #9: layout custom function
|
|
-- ------------------------------------------------------------------
|
|
panel.register("w_cu", make_widget({ title = "Cu" }), {
|
|
layout = function(_sw, _sh)
|
|
return { x = 10, y = 20, w = 100, h = 50 }
|
|
end,
|
|
})
|
|
panel.open("w_cu")
|
|
local b_cu = panel._test_get_window_bounds("w_cu")
|
|
T.equals(b_cu.x, 10, "custom layout x = 10")
|
|
T.equals(b_cu.y, 20, "custom layout y = 20")
|
|
T.equals(b_cu.w, 100, "custom layout w = 100")
|
|
T.equals(b_cu.h, 50, "custom layout h = 50")
|
|
panel._test_reset_all()
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- W.0 #10: Loud-Error on unknown layout-slot
|
|
-- ------------------------------------------------------------------
|
|
local ok_bog, err_bog = pcall(panel.register, "w_bog",
|
|
make_widget({ title = "X" }), { layout = "bogus" })
|
|
T.assert(not ok_bog, "register with unknown layout slot errors")
|
|
T.assert(err_bog ~= nil and string.find(err_bog, "panel%.register") ~= nil,
|
|
"error message mentions panel.register")
|
|
T.assert(err_bog ~= nil and string.find(err_bog, "bogus") ~= nil,
|
|
"error message mentions the bad slot name")
|
|
panel._test_reset_all()
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- W.0 #11: hit-test reverse-open-order
|
|
-- ------------------------------------------------------------------
|
|
local captured_a, captured_b = nil, nil
|
|
local w_hA = make_widget({
|
|
title = "A",
|
|
handle_input = function(_ctx, event) captured_a = event end,
|
|
})
|
|
local w_hB = make_widget({
|
|
title = "B",
|
|
handle_input = function(_ctx, event) captured_b = event end,
|
|
})
|
|
panel.register("w_hA", w_hA, { layout = "left-half" })
|
|
panel.register("w_hB", w_hB, { layout = "right-half" })
|
|
panel.open("w_hA")
|
|
panel.open("w_hB")
|
|
local sw_h, sh_h = panel._test_get_screen_size()
|
|
-- Click in left half: hits A only (B's bounds are right-half so click misses)
|
|
panel._dispatch_event_for_test({
|
|
kind = "click", button = "left",
|
|
x = sw_h * 0.25, y = sh_h * 0.5,
|
|
})
|
|
T.assert(captured_a ~= nil, "A captured click in its left-half bounds")
|
|
T.assert(captured_b == nil, "B did not capture click outside its bounds")
|
|
-- Reset captures, click in right half
|
|
captured_a, captured_b = nil, nil
|
|
panel._dispatch_event_for_test({
|
|
kind = "click", button = "left",
|
|
x = sw_h * 0.75, y = sh_h * 0.5,
|
|
})
|
|
T.assert(captured_b ~= nil, "B captured click in its right-half bounds")
|
|
T.assert(captured_a == nil, "A did not capture click outside its bounds")
|
|
panel._test_reset_all()
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- W.0 #12: is_pausing() checks ALL open windows, not just focused
|
|
-- ------------------------------------------------------------------
|
|
panel.register("w_np", make_widget({ title = "NP" })) -- non-pausing
|
|
panel.register("w_p", make_widget({ title = "P", pause_on_open = true }))
|
|
panel.open("w_p")
|
|
panel.open("w_np") -- focused = non-pausing
|
|
T.assert(panel.is_pausing(),
|
|
"is_pausing() true when a non-focused open window has pause_on_open")
|
|
panel.close("w_p")
|
|
T.assert(not panel.is_pausing(),
|
|
"is_pausing() false after closing the pausing window (np still open)")
|
|
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()
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- W.2 #12: chromeless=true suppresses phantom close-button hit-test
|
|
-- ------------------------------------------------------------------
|
|
-- Regression: chromeless widgets draw no X close-button but the
|
|
-- dispatch loop used to hit-test a phantom 26x26 zone at the top-
|
|
-- right. A click there would close the widget. Verify the click
|
|
-- now reaches the widget instead.
|
|
local chrome_clicked = false
|
|
panel.register("w_chrome", make_widget({
|
|
title = "ChromelessW",
|
|
handle_input = function(_ctx, _ev) chrome_clicked = true end,
|
|
}), { chromeless = true, layout = "center" })
|
|
panel.open("w_chrome")
|
|
local b_chrome = panel._test_get_window_bounds("w_chrome")
|
|
-- Click in the top-right area where the phantom close-button would sit.
|
|
panel._dispatch_event_for_test({
|
|
kind = "click", button = "left",
|
|
x = b_chrome.x + b_chrome.w - 10,
|
|
y = b_chrome.y + 10,
|
|
})
|
|
T.assert(chrome_clicked,
|
|
"chromeless: top-right click reaches widget (no phantom close-button)")
|
|
T.assert(panel.is_open("w_chrome"),
|
|
"chromeless: widget remains open after top-right click")
|
|
panel._test_reset_all()
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- W.2 #13: top-tier input_block="all" swallows outside-clicks before
|
|
-- they can reach a normal-tier widget behind the modal.
|
|
-- ------------------------------------------------------------------
|
|
local normal_clicked_m = false
|
|
panel.register("w_norm_m", make_widget({
|
|
title = "N",
|
|
handle_input = function(_ctx, _ev) normal_clicked_m = true end,
|
|
}), { z_tier = "normal", layout = "left-half" })
|
|
panel.register("w_modal_m", make_widget({
|
|
title = "M",
|
|
handle_input = function(_ctx, _ev) end,
|
|
}), { z_tier = "top", input_block = "all", layout = "right-half" })
|
|
panel.open("w_norm_m")
|
|
panel.open("w_modal_m")
|
|
-- Click in left-half (inside normal's bounds, outside modal's bounds).
|
|
-- The modal must swallow it before normal can claim it.
|
|
local sw_mm, sh_mm = panel._test_get_screen_size()
|
|
panel._dispatch_event_for_test({
|
|
kind = "click", button = "left",
|
|
x = sw_mm * 0.25, y = sh_mm * 0.5,
|
|
})
|
|
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
|