From bf5e368c4e0b97ad3fdcda175a2c36b568612af3 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Fri, 5 Jun 2026 19:47:22 -0700 Subject: [PATCH] tests: paint-gate re-showable menus + drain synth plays (sync audit) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ports the dasImgui sync-audit (PR #181) to the node-editor integration suite. These tests drive the async live API; the gaps were leaning on a poll window to absorb an in-flight synth gesture, and gating re-showable popups on registry existence rather than actual paint. - imgui_editor_playwright: add ne_wait_visible — the paint-gated peer of ne_wait_widget (present AND rendered-this-frame). Existence-only can stale-pass on a cached menu entry before the popup repaints (the documented popup re-open race; wait_for_visible closes it). - test_shader_graph (context_menu / new_node_drag): drain the synth right-click with wait_for_mouse_idle before the menu wait, and switch the two re-showable BG_CTX context-menu waits to ne_wait_visible. - test_context_menus: switch the NODE_MENU / BG_MENU item waits to ne_wait_visible (the local right_click wrapper already drains the mouse timeline). - test_delete_and_select: drain the synth click (wait_for_mouse_idle) and the Delete key (wait_for_key_idle) before their effect polls, instead of relying on the 6s poll window to absorb the in-flight gesture. Pure hardening — every change adds a wait or strengthens existence->paint, never removes one. Full suite green 3x headless (23/23, 4 workers); lint + format clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- daslib/imgui_editor_playwright.das | 7 +++++++ tests/integration/test_context_menus.das | 4 ++-- tests/integration/test_delete_and_select.das | 2 ++ tests/integration/test_shader_graph.das | 10 ++++++---- utils/node_editor2rst.das | 2 +- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/daslib/imgui_editor_playwright.das b/daslib/imgui_editor_playwright.das index a2cff19..28e4dac 100644 --- a/daslib/imgui_editor_playwright.das +++ b/daslib/imgui_editor_playwright.das @@ -112,6 +112,13 @@ def public ne_wait_widget(s : EditorSession; subpath : string; timeout_sec : flo return wait_for_widget(s.app, "{s.canvas}/{subpath}", timeout_sec) } +def public ne_wait_visible(s : EditorSession; subpath : string; timeout_sec : float = 6.0f) : JsonValue? { + //! Poll until `{canvas}/{subpath}` is present AND painted this frame. Use for re-showable + //! widgets (context-menu items, popups): existence-only (ne_wait_widget) can stale-pass on a + //! cached registry entry before the menu actually repaints. + return wait_for_visible(s.app, "{s.canvas}/{subpath}", timeout_sec) +} + def public ne_wait_selected(s : EditorSession; id : int; timeout_sec : float = 5.0f) : JsonValue? { //! Poll until node `id` is selected. Gate a selection-dependent chord (Ctrl+D / Ctrl+C) on //! the click's real effect, not a frame/time guess. Returns the snapshot, or null on timeout. diff --git a/tests/integration/test_context_menus.das b/tests/integration/test_context_menus.das index 24a0e1f..1e7dd3d 100644 --- a/tests/integration/test_context_menus.das +++ b/tests/integration/test_context_menus.das @@ -48,7 +48,7 @@ def test_context_menus(t : T?) { // ---- node menu: right-click node A's body, pick "Delete node" ---- right_click(d, node_center(snap, "{canvas}/node_1")) - var menuSnap = ne_wait_widget(s, "NODE_MENU/DEL_ITEM", 6.0f) + var menuSnap = ne_wait_visible(s, "NODE_MENU/DEL_ITEM", 6.0f) t |> success(menuSnap != null, "right-click on node opened its context menu") click(d, del_item) var gone = wait_until_sec(d, 6.0f) $(var sn) { @@ -58,7 +58,7 @@ def test_context_menus(t : T?) { // ---- background menu: right-click empty canvas, pick "Node" ---- right_click(d, (720.0, 430.0)) - var bgSnap = ne_wait_widget(s, "BG_MENU/ADD_ITEM", 6.0f) + var bgSnap = ne_wait_visible(s, "BG_MENU/ADD_ITEM", 6.0f) t |> success(bgSnap != null, "right-click on empty canvas opened the background menu") click(d, add_item) var spawned = ne_wait_widget(s, "node_200", 6.0f) diff --git a/tests/integration/test_delete_and_select.das b/tests/integration/test_delete_and_select.das index 6289273..4dcaa27 100644 --- a/tests/integration/test_delete_and_select.das +++ b/tests/integration/test_delete_and_select.das @@ -37,6 +37,7 @@ def test_delete_and_select(t : T?) { var ev : array ev |> click_at(0, (title_x, title_y), 200, 0) post_command(d, "imgui_mouse_play", JV((events = ev))) + wait_for_mouse_idle(d) let selected = wait_until_sec(d, 6.0f) $(var sn) { return find_widget(sn, "{canvas}/node_2")?["payload"]?["selected"] ?? false } @@ -46,6 +47,7 @@ def test_delete_and_select(t : T?) { let del = int(ImGuiKey.Delete) post_command(d, "imgui_key_press", JV((key = del))) post_command(d, "imgui_key_release", JV((key = del))) + wait_for_key_idle(d, 4.0f) var gone = wait_until_sec(d, 6.0f) $(var sn) { return !widget_exists(sn, "{canvas}/node_2") } diff --git a/tests/integration/test_shader_graph.das b/tests/integration/test_shader_graph.das index 11657e9..2dcba95 100644 --- a/tests/integration/test_shader_graph.das +++ b/tests/integration/test_shader_graph.das @@ -324,9 +324,11 @@ def test_shader_graph(t : T?) { var ev : array ev |> click_at(0, (900.0, 450.0), 150, 1) post_command(d, "imgui_mouse_play", JV((events = ev))) + wait_for_mouse_idle(d) - // The right-click opens the popup → its menu_labels register. - var snapMenu = ne_wait_widget(s, "BG_CTX/CREATE_TEX", 6.0f) + // The right-click popup is re-showable — gate on actual paint, not stale registry + // existence (ne_wait_widget can pass on a cached menu entry from a prior open). + var snapMenu = ne_wait_visible(s, "BG_CTX/CREATE_TEX", 6.0f) t |> success(snapMenu != null, "right-click opened the Create-Node menu") if (snapMenu != null) { t |> success(widget_exists(snapMenu, "{s.canvas}/BG_CTX/CREATE_ADD"), @@ -356,8 +358,8 @@ def test_shader_graph(t : T?) { // Inject a drag-release from pin 8 (Multiply's output) dropped at canvas (600,450). ne_new_node_drag(s, 8, 600.0, 450.0) - // The release opens the Create+Connect menu → its items register at full path. - var snapMenu = ne_wait_widget(s, "BG_CTX/CREATE_ADD", 6.0f) + // The release opens the Create+Connect menu (re-showable popup) → gate on paint. + var snapMenu = ne_wait_visible(s, "BG_CTX/CREATE_ADD", 6.0f) t |> success(snapMenu != null, "drag-release opened the create-node menu") if (snapMenu != null) { // Telemetry: the editor recorded the drag (source pin + drop point). diff --git a/utils/node_editor2rst.das b/utils/node_editor2rst.das index 14cf9a6..7ab9e67 100644 --- a/utils/node_editor2rst.das +++ b/utils/node_editor2rst.das @@ -47,7 +47,7 @@ def document_module_imgui_editor_playwright() { group_by_regex("Session", mod, %regex~^ne_open$%%), group_by_regex("Actions", mod, %regex~^(ne_select_node|ne_select_link|ne_clear_selection|ne_shortcut|ne_add_link|ne_delete_node|ne_delete_link|ne_move_node|ne_new_node_drag|ne_flow)$%%), group_by_regex("Snapshots & queries", mod, %regex~^(ne_snapshot|ne_node_count|ne_payload|ne_node|ne_node_exists|ne_node_selected)$%%), - group_by_regex("Polling / await", mod, %regex~^(ne_wait_widget|ne_wait_selected|ne_wait_payload_str|ne_wait_shortcut)$%%), + group_by_regex("Polling / await", mod, %regex~^(ne_wait_widget|ne_wait_visible|ne_wait_selected|ne_wait_payload_str|ne_wait_shortcut)$%%), hide_group(group_by_regex("Internal", mod, %regex~^(_|priv_).*%%)) ) document("Editor playwright — node-editor-aware test layer over imgui_playwright (EditorSession + ne_* helpers)", mod, "imgui_editor_playwright.rst", groups)