fix(qt): a submenu stays open while the pointer is over its rows

CardMenu's pair-hover check read the popup's HoverHandler, which lives on
the background and never fires over a row: the rows' MouseAreas take the
hover events, so the panel only counted as hovered on its 5px padding.
Crossing from that padding into the child's first row read as "left both
menus" and the 350 ms leave timer closed the pair — the Copy submenu
closed the whole context menu as soon as it was entered.

A menu is now hovered while its panel OR one of its rows is (one hot row
at a time, held by reference so a destroyed row cannot leave it stuck),
and the pair check reads that on both menus. scripts/test-card-menu-qml.sh
drives the pointer through the parent row, the child's rows, back to a
plain row and out, and joins the runtime gate and CI.

cc
This commit is contained in:
vicrodh committed 2026-09-14 00:39:47 -06:00
1 parent e278d931b7
commit a75d7b0087
5 files changed
+125 -4

No files matched your search

+81
View File
@@ -0,0 +1,81 @@
// CardMenu submenus: hovering the child must keep BOTH menus open (the
// rows' MouseAreas must count as menu hover), moving to a plain row in the
// parent closes the child only, and leaving both closes both.
import QtQuick
import QtTest
import "../../crates/qbz-qt/qml/controls"
Item {
id: root
width: 700; height: 500
Item { id: anchorItem; x: 120; y: 80; width: 10; height: 10 }
CardMenu {
id: menu
entries: [
{ "label": "One", "icon": "", "action": "one" },
{ "label": "Copy", "icon": "", "action": "copy", "submenu": [
{ "label": "Sub A", "icon": "", "action": "sub-a" },
{ "label": "Sub B", "icon": "", "action": "sub-b" }
] },
{ "label": "Three", "icon": "", "action": "three" }
]
}
SignalSpy { id: picks; target: menu; signalName: "picked" }
TestCase {
name: "CardMenuSubmenu"
when: windowShown
// Row i's centre inside a menu (5px padding, 33px rows).
function rowY(m, i) { return m.y + 5 + 33 * i + 16 }
function test_a_hover_keeps_the_pair_open() {
menu.openAtCursor(anchorItem, 0, 0)
wait(50)
verify(menu.opened)
mouseMove(root, menu.x + 40, rowY(menu, 1))
wait(120)
verify(menu.subOpen, "hovering the Copy row opens the child")
var sub = menu._sub
verify(sub.opened)
mouseMove(root, sub.x + 40, rowY(sub, 0))
wait(600)
verify(menu.opened, "the parent stays open while the child's first row is hovered")
verify(sub.opened, "the child stays open while its first row is hovered")
mouseMove(root, sub.x + 40, rowY(sub, 1))
wait(600)
verify(menu.opened && sub.opened, "still open on the child's second row")
// Back onto a plain parent row: the child goes away, the parent stays.
mouseMove(root, menu.x + 40, rowY(menu, 0))
wait(120)
verify(menu.opened)
verify(!menu.subOpen, "a plain row puts the child away")
// Leave both: both close.
mouseMove(root, menu.x + 40, rowY(menu, 1))
wait(120)
verify(menu.subOpen)
mouseMove(root, 650, 450)
wait(700)
verify(!menu.opened, "leaving both closes the parent")
}
function test_b_picking_in_the_child_closes_everything_and_forwards() {
picks.clear()
menu.openAtCursor(anchorItem, 0, 0)
wait(50)
mouseMove(root, menu.x + 40, rowY(menu, 1))
wait(120)
var sub = menu._sub
verify(sub && sub.opened)
mouseMove(root, sub.x + 40, rowY(sub, 1))
wait(100)
mouseClick(root, sub.x + 40, rowY(sub, 1))
wait(100)
compare(picks.count, 1)
compare(picks.signalArguments[0][0], "sub-b")
verify(!menu.opened)
verify(!sub.opened)
}
}
}
+1
View File
@@ -10,6 +10,7 @@ bash scripts/test-text-edit-menu-qml.sh
bash scripts/test-compact-seek-qml.sh
bash scripts/test-library-folders-qml.sh
bash scripts/test-array-model-qml.sh
bash scripts/test-card-menu-qml.sh
bash scripts/test-orbit-qml.sh
node scripts/test_qt_local_views.mjs
node scripts/test_qt_library.mjs
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# CardMenu submenu regression: hovering the child keeps the pair open,
# leaving both closes both, a pick in the child forwards and closes all.
set -euo pipefail
cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.."
if [[ -n "${QT_ROOT_DIR:-}" ]]; then
qt_card_menu_bins="$QT_ROOT_DIR/bin"
else
qt_card_menu_qmake="${QMAKE:-}"
if [[ -z "$qt_card_menu_qmake" ]]; then
qt_card_menu_qmake="$(command -v qmake6 || command -v qmake)"
fi
qt_card_menu_bins="$("$qt_card_menu_qmake" -query QT_INSTALL_BINS)"
fi
QT_QPA_PLATFORM=offscreen QT_QUICK_BACKEND=software \
"$qt_card_menu_bins/qmltestrunner" \
-input scripts/qml-tests/tst_card_menu.qml \
-import scripts/qml-tests/imports