Six more menu reds: the realm's UnitPopup tables are not 1.12's

Every one asserted stock 1.12's row set. The realm's `UnitPopup.lua:75-80` adds `REPORT` and
`IGNORE` to PLAYER/PARTY/FRIEND, its own `XP` row to SELF, and `MOVE`/`MOVE_RESET` (its movable
unit frames) to SELF, PARTY and PLAYER — and it **dropped the `RESET_INSTANCES` gate**: nothing in
its 51 interface Lua rows so much as names `CanShowResetInstances` (scanned, file by file), so that
row is always there, solo included. Stock left Cancel alone in a solo SELF menu, which is why the
menu used not to open at all.

The row lists are now asserted whole and in order rather than by count and spot index, which is
what made these read as "7 != 5" instead of naming the two rows that arrived. The raid-marks row
is found by label, since its seat has moved twice now.

Also `status_bar_text_paints_the_player_numerals_but_not_the_targets`, which blamed the
`TextStatusBar` template for a leave that never hides the plate. The realm overrides that bar's own
`<OnEnter>`/`<OnLeave>` in `TargetFrame.xml:280-298`: the enter shows its two HP strings and then
borrows the parent's plate by hand, because a child that takes the mouse means `TargetFrame` never
gets an enter of its own — and the leave hides only those two strings. The plate standing after the
cursor leaves is the realm's asymmetry, not ours. Measured by wrapping the handler and watching
`GameTooltip:IsShown()` on both sides of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
2026-10-31 12:46:03 -06:00
co-authored by Claude Opus 5
parent 3a89f825bf
commit d49f8daad8
3 changed files with 130 additions and 68 deletions
@@ -55,8 +55,13 @@ fn the_three_bindings_have_the_reference_shapes() {
}
/// Through the real hit test: right-click the player portrait, click the row, answer the confirm.
///
/// The row is **not** gated on `CanShowResetInstances()`. Stock 1.12's `UnitPopup_HideButtons`
/// hid it unless that verb answered; the realm's rewrite of `UnitPopup.lua` dropped the branch
/// altogether — nothing in its 51 interface Lua rows so much as names the verb (scanned) — so
/// `RESET_INSTANCES` is an always-shown SELF row, solo included. The verb stays for addons.
#[test]
fn the_self_menu_row_gates_on_the_binding_and_confirms_before_sending() {
fn the_self_menu_reset_row_always_shows_and_confirms_before_sending() {
let _data = benilla_formats::wow_data_or_skip!();
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
@@ -123,20 +128,26 @@ fn the_self_menu_row_gates_on_the_binding_and_confirms_before_sending() {
.collect()
};
// Off, the row is hidden, not greyed (`UnitPopup.lua:379`). Solo it is the SELF menu's only
// candidate row, so with it gone the menu does not open at all.
// Solo and with the verb answering nil, the row is there all the same, and so is the menu:
// the realm's SELF list is `LOOT_METHOD, LOOT_THRESHOLD, LOOT_PROMOTE, LEAVE,
// RESET_INSTANCES, RAID_TARGET_ICON, XP, MOVE, MOVE_RESET, CANCEL` (`UnitPopup.lua:75`), and
// of those only the four group rows and the raid marks are gated off while solo.
open_self_menu(&mut s);
assert!(
s.eval::<bool>("return not DropDownList1:IsVisible()")
.unwrap(),
"solo with no lockout to reset, the SELF menu has nothing to show"
s.eval::<bool>("return DropDownList1:IsVisible()").unwrap(),
"the ungated rows open the SELF menu even solo"
);
assert!(
row_labels(&mut s).contains(&"Reset all instances".to_string()),
"the row shows with no lockout and no party: {:?}",
row_labels(&mut s)
);
s.set_can_reset_instances(true);
open_self_menu(&mut s);
assert!(
s.eval::<bool>("return DropDownList1:IsVisible()").unwrap(),
"the one showable row is enough to open the SELF menu"
"and the verb answering changes nothing about the menu"
);
let labels = row_labels(&mut s);
let row = labels
@@ -373,12 +373,34 @@ fn left_clicking_the_player_frame_targets_self() {
s.take_selection_requests().is_empty(),
"right-click queues no target"
);
// Solo, every SELF row but Cancel is gated off (`UnitPopup.lua:69`, no PvP row): no menu.
// Solo the menu still opens: the realm's SELF list (`UnitPopup.lua:75`) gates off only its
// four group rows and the raid marks, leaving `RESET_INSTANCES` (whose stock
// `CanShowResetInstances` gate the realm dropped), its own `XP` row and the two
// movable-frame rows. Stock 1.12 left nothing but Cancel here, so no menu opened.
assert!(
s.eval::<bool>("return not DropDownList1:IsVisible()")
.unwrap(),
"no SELF menu while solo"
s.eval::<bool>("return DropDownList1:IsVisible()").unwrap(),
"the realm's ungated SELF rows open the menu solo"
);
let solo: Vec<String> = (1..=s.eval::<i64>("return DropDownList1.numButtons").unwrap())
.map(|i| {
s.eval::<String>(&format!("return DropDownList1Button{i}:GetText()"))
.unwrap()
})
.collect();
assert_eq!(
solo,
[
// This fixture's player carries no name, and the title row falls back to `UNKNOWN`
// the way the reference does (`UnitPopup_ShowMenu`: `info.text = name or UNKNOWN`).
"Unknown",
"Reset all instances",
"Experience: Enabled",
"Unlock Frame",
"Reset Position",
"Cancel",
]
);
s.run("CloseDropDownMenus()").unwrap();
s.mouse_button(cx as f32, cy as f32, "LeftButton", true);
s.mouse_button(cx as f32, cy as f32, "LeftButton", false);
@@ -409,10 +431,27 @@ fn left_clicking_the_player_frame_targets_self() {
s.eval::<bool>("return DropDownList1:IsVisible()").unwrap(),
"the SELF menu opens for a party leader"
);
// The whole realm SELF list with nothing gated off but the two leader-only loot rows a
// party of two has no use for: `LOOT_PROMOTE` needs master loot, and that is the only
// absence. The last three are the realm's own additions (`UnitPopup.lua:75`).
assert_eq!(
s.eval::<i64>("return DropDownList1.numButtons").unwrap(),
6,
"title + Loot Method + Loot Threshold + Leave Party + Raid Target Icon + Cancel"
(1..=s.eval::<i64>("return DropDownList1.numButtons").unwrap())
.map(|i| s
.eval::<String>(&format!("return DropDownList1Button{i}:GetText()"))
.unwrap())
.collect::<Vec<_>>(),
[
"Unknown",
"Loot: Group Loot",
"Uncommon",
"Leave party",
"Reset all instances",
"Raid Target Icon",
"Experience: Enabled",
"Unlock Frame",
"Reset Position",
"Cancel",
]
);
// `PARTY_LEAVE` is "Leave party", lower-case (`GlobalStrings.lua:2991`).
assert_eq!(
@@ -490,13 +529,18 @@ fn raid_mark_clicks_through_the_nested_level() {
);
s.resolve();
assert_eq!(
s.eval::<String>("return DropDownList1Button5:GetText()")
.unwrap(),
"Raid Target Icon"
);
// Found by label, not by index: the realm's SELF list has grown three rows since 1.12
// (`XP`, `MOVE`, `MOVE_RESET`) and dropped the `RESET_INSTANCES` gate, so the marks row no
// longer sits at a fixed seat.
let marks = (1..=s.eval::<i64>("return DropDownList1.numButtons").unwrap())
.find(|i| {
s.eval::<String>(&format!("return DropDownList1Button{i}:GetText()"))
.unwrap()
== "Raid Target Icon"
})
.expect("the SELF menu has a Raid Target Icon row for a leader in a group");
let (rx, ry) = s
.eval::<(f64, f64)>("return DropDownList1Button5:GetCenter()")
.eval::<(f64, f64)>(&format!("return DropDownList1Button{marks}:GetCenter()"))
.unwrap();
s.mouse_move(rx as f32, ry as f32);
assert!(
@@ -1593,12 +1637,22 @@ fn status_bar_text_paints_the_player_numerals_but_not_the_targets() {
.contains("Somebody"),
"and it is the unit's own plate"
);
// …and moving off the bar leaves it up. The realm overrode this bar's own `<OnEnter>` and
// `<OnLeave>` (`TargetFrame.xml:280-298`), replacing the `TextStatusBar` template's pair: the
// enter shows the two HP strings and then borrows the parent's plate by hand
// (`TargetFrame:GetScript("OnEnter")()`, which it has to, because a child that takes the
// mouse means `TargetFrame` itself never gets an enter), while the leave hides only those two
// strings. Nothing in it hides the tooltip and the parent has no leave pending to do it, so
// the plate stands — the realm's own asymmetry, reproduced rather than papered over.
unhover(&mut s);
assert!(
s.eval::<bool>("return not GameTooltip:IsShown()").unwrap(),
"the template's OnLeave hides it (ref TextStatusBar.xml l.28-31)"
s.eval::<bool>("return GameTooltip:IsShown()").unwrap(),
"the realm's own bar OnLeave hides its HP strings and nothing else"
);
// It is cleared by whatever the cursor touches next, not by leaving this bar.
s.run("GameTooltip:Hide()").unwrap();
// Switched off, a hover still reveals the player's numerals (`TextStatusBar.lua:77-102`).
s.run("SetCVar(\"statusBarText\", \"0\", \"STATUS_BAR_TEXT\")")
.unwrap();
@@ -82,6 +82,17 @@ fn bake_strings(s: &UiScript) {
.unwrap();
}
/// Every row of an open `DropDownList1`, in order, as the menu drew them.
fn rows(s: &UiScript) -> Vec<String> {
let n = s.eval::<i64>("return DropDownList1.numButtons").unwrap();
(1..=n)
.map(|i| {
s.eval::<String>(&format!("return DropDownList1Button{i}:GetText()"))
.unwrap()
})
.collect()
}
/// Right-clicking a chat player name opens the FRIEND dropdown (`FriendsFrame_ShowDropdown`),
/// whose Invite invites by name; a left-click on the name whispers instead.
#[test]
@@ -109,23 +120,21 @@ fn chat_name_right_click_opens_the_invite_menu() {
s.eval::<bool>("return DropDownList1:IsVisible()").unwrap(),
"the chat-name right-click opens the FRIEND dropdown"
);
// Title, Whisper, Invite, Target, Cancel: the FRIEND menu (`UnitPopup.lua:74`) with its two
// guild rows hidden (no guild, no guild frame).
// The realm's FRIEND menu (`UnitPopup.lua:80`), with its two guild rows hidden (no guild, no
// guild frame). `REPORT` and `IGNORE` are the realm's own rows — stock 1.12's table has
// neither, which is what this list used to assert.
assert_eq!(
s.eval::<i64>("return DropDownList1.numButtons").unwrap(),
5,
"title + Whisper + Invite + Target + Cancel"
);
assert_eq!(
s.eval::<String>("return DropDownList1Button1:GetText()")
.unwrap(),
"Bob",
"the clicked name titles the menu"
);
assert_eq!(
s.eval::<String>("return DropDownList1Button3:GetText()")
.unwrap(),
"Invite"
rows(&s),
[
"Bob",
"Whisper",
"Invite",
"Target",
"Report",
"Ignore Player",
"Cancel",
],
"the clicked name titles the menu, then the realm's FRIEND rows in its own order"
);
// Click Invite through the real hit path → UnitPopup_OnClick → InviteByName("Bob").
@@ -206,37 +215,25 @@ fn solo_target_right_click_invites_a_player() {
s.eval::<bool>("return DropDownList1:IsVisible()").unwrap(),
"a friendly player target opens the PLAYER menu solo"
);
// Title, Whisper, Inspect, Invite, Trade, Follow, Duel, Cancel: the PLAYER menu in
// `UnitPopup.lua:72`'s order, the raid-target row hidden without a party.
// The realm's PLAYER menu (`UnitPopup.lua:78`) in its own order, with the raid-target row
// hidden without a party and the three group rows hidden solo. The last four are all the
// realm's: `REPORT` and `IGNORE`, then `MOVE`/`MOVE_RESET`, which are its movable unit frames.
assert_eq!(
s.eval::<i64>("return DropDownList1.numButtons").unwrap(),
8,
"title + Whisper + Inspect + Invite + Trade + Follow + Duel + Cancel"
);
assert_eq!(
s.eval::<String>("return DropDownList1Button3:GetText()")
.unwrap(),
"Inspect"
);
assert_eq!(
s.eval::<String>("return DropDownList1Button4:GetText()")
.unwrap(),
"Invite"
);
assert_eq!(
s.eval::<String>("return DropDownList1Button5:GetText()")
.unwrap(),
"Trade"
);
assert_eq!(
s.eval::<String>("return DropDownList1Button6:GetText()")
.unwrap(),
"Follow"
);
assert_eq!(
s.eval::<String>("return DropDownList1Button7:GetText()")
.unwrap(),
"Duel"
rows(&s),
[
"Ally",
"Whisper",
"Inspect",
"Invite",
"Trade",
"Follow",
"Duel",
"Report",
"Ignore Player",
"Unlock Frame",
"Reset Position",
"Cancel",
]
);
// Click Invite → UnitPopup_OnClick → InviteToParty("target") → InviteUnit.