The panel registry, the map's own row, and a test that raced

`UIParent.lua`'s registry moved under three tests at once:

- `ItemTextFrame` is `pushable = 5`, so the reader shares the screen instead of replacing what is
  there; `QuestLogFrame` is `area = "doublewide"` with `width = 690`, not a left-slot window, so a
  `whileDead` open seats at the doublewide slot and leaves the left one empty; and
  `UIChildWindows` has five rows, the realm having added `ReputationDetailFrame`.
- `SetUIPanel` runs `CloseChildWindows()` for **any** slot it seats (`UIParent.lua:792-796`), not
  just the centre one, so a window merely pushed across also puts the open letter away. The test
  that pinned the stock trigger now watches both halves go.
- `user_placed_layouts()` is not empty after a manifest load, and should not be: the realm's own
  movable windows stamp themselves in their OnLoad — `PlayerFrame.xml:488-489` is
  `SetMovable(1) SetUserPlaced(true)`, the same feature as the `MOVE`/`MOVE_RESET` rows in their
  right-click menus — and `TargetFrame`, the Turtle shop, transmog and guild bank do likewise. The
  map test now asserts about its own row rather than about the file being empty.
- The blip tooltip calls `UnitName("player")` (`WorldMapFrame.lua:660`), which reads the player
  record; `load_default_ui` seats a placeholder one so the realm's files have a race and class at
  load, so a test that wants a named player names the record, after the manifest.

And one real race, not a realm change: `a_binding_table_resolves_whatever_case_the_package_spelled_it_in`
moves `BENILLA_HOME` and `WOW_CAPTURE` without taking `ENV_LOCK`. It was the only env-moving test
in that module missing it, and the collision shows up as a *neighbour* reading the wrong roster —
which is how it surfaced here, two addons tests failing in the full run and passing alone.

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 13:58:14 -06:00
co-authored by Claude Opus 5
parent b9a58c9806
commit 34097b6164
3 changed files with 61 additions and 15 deletions
@@ -1357,6 +1357,13 @@ mod tests {
/// its hover-bind, its edit-mode escape and every bar page key.
#[test]
fn a_binding_table_resolves_whatever_case_the_package_spelled_it_in() {
// This test moves `BENILLA_HOME` and `WOW_CAPTURE`, which are process-wide: without the
// lock every other env-moving test in this module can see a home that is not its own.
// It is the one such test here that was missing it, and the collision shows up as a
// neighbour reading the wrong roster, not as a failure here.
let _l = crate::local_state::test_env::ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let tmp =
std::env::temp_dir().join(format!("benilla-addon-lcbindings-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&tmp);
+26 -11
View File
@@ -977,7 +977,9 @@ fn the_panel_registry_rows_match_the_reference_bytes() {
for probe in [
// UIParent.lua:20
"UIPanelWindows['ItemTextFrame'].area == 'left'",
"UIPanelWindows['ItemTextFrame'].pushable == 0",
// 5, not 1.12's 0: the realm made the reader pushable, so it shares the screen with
// another left-slot window instead of replacing it.
"UIPanelWindows['ItemTextFrame'].pushable == 5",
"UIPanelWindows['ItemTextFrame'].whileDead == nil",
// UIParent.lua:19
"UIPanelWindows['CharacterFrame'].pushable == 2",
@@ -985,12 +987,15 @@ fn the_panel_registry_rows_match_the_reference_bytes() {
// UIParent.lua:21 and :25
"UIPanelWindows['SpellBookFrame'].whileDead == 1",
"UIPanelWindows['QuestLogFrame'].whileDead == 1",
// UIParent.lua:45-50; `TalentFrame`'s row loads with `Blizzard_TalentUI.lua:71`.
"table.getn(UIChildWindows) == 4",
// UIParent.lua:46-52; `TalentFrame`'s row loads with `Blizzard_TalentUI.lua:71`. Five
// rows, not 1.12's four: the realm added `ReputationDetailFrame`, so a frame arriving at
// centre now puts that one away too.
"table.getn(UIChildWindows) == 5",
"UIChildWindows[1] == 'OpenMailFrame'",
"UIChildWindows[2] == 'GuildControlPopupFrame'",
"UIChildWindows[3] == 'GuildMemberDetailFrame'",
"UIChildWindows[4] == 'GuildInfoFrame'",
"UIChildWindows[5] == 'ReputationDetailFrame'",
] {
assert!(
s.eval::<bool>(&format!("return {probe}")).unwrap(),
@@ -1054,10 +1059,17 @@ fn a_dead_player_opens_only_whiledead_windows() {
s.eval::<bool>("return QuestLogFrame:IsShown()").unwrap(),
"a whileDead = 1 row opens exactly as alive"
);
// …at the **doublewide** slot: the realm's row is
// `{ area = "doublewide", pushable = 0, whileDead = 1, width = 690 }` (`UIParent.lua:22`),
// where 1.12's quest log was a plain left-slot window.
assert!(
s.eval::<bool>("return GetLeftFrame():GetName() == 'QuestLogFrame'")
s.eval::<bool>("return GetUIPanel('doublewide'):GetName() == 'QuestLogFrame'")
.unwrap(),
"and it seats normally at the left slot"
"and it seats normally, at the slot its own row names"
);
assert!(
s.eval::<bool>("return GetLeftFrame() == nil").unwrap(),
"which is not the left one"
);
assert!(
s.take_ui_errors().is_empty(),
@@ -1065,10 +1077,12 @@ fn a_dead_player_opens_only_whiledead_windows() {
);
}
/// `SetCenterFrame` hides every `UIChildWindows` frame (`UIParent.lua:839-846`), but
/// `MovePanelToCenter` seats by assignment (`:877-885`), so a frame pushed to center leaves them.
/// `SetUIPanel` hides every `UIChildWindows` frame whenever it seats one, in **any** slot
/// (`UIParent.lua:792-796`, `CloseChildWindows` at `:1289`). Stock 1.12 gated that on the centre
/// seat, so a window merely pushed across left the open letter alone; the realm's version does
/// not, which is why the second half of this test watches it go too.
#[test]
fn a_frame_arriving_at_center_puts_the_child_windows_away() {
fn a_frame_arriving_at_any_slot_puts_the_child_windows_away() {
let _data = benilla_formats::wow_data_or_skip!();
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
@@ -1103,7 +1117,8 @@ fn a_frame_arriving_at_center_puts_the_child_windows_away() {
"the arriving center frame hid the open letter (the UIChildWindows loop)"
);
// LootFrame (pushable 7) holds left; a pushable-0 window slides it to center, the letter stays.
// LootFrame (pushable 7) holds left; a pushable-0 window slides it to center — and the letter
// goes with it, because the newcomer's own seat ran the loop.
s.run(
r#"HideUIPanel(TradeFrame) HideUIPanel(MerchantFrame)
OpenMailFrame:Show()
@@ -1119,7 +1134,7 @@ fn a_frame_arriving_at_center_puts_the_child_windows_away() {
"loot slid to center (MovePanelToCenter), merchant took left"
);
assert!(
s.eval::<bool>("return OpenMailFrame:IsShown()").unwrap(),
"a PUSHED frame does not run the child-window loop — the ref's exact trigger"
!s.eval::<bool>("return OpenMailFrame:IsShown()").unwrap(),
"every seat runs the child-window loop here, not only the centre one"
);
}
@@ -532,6 +532,16 @@ fn hovering_the_player_blip_on_a_scaled_map_names_the_player() {
);
let failures = super::load_default_ui(&s);
assert!(failures.is_empty(), "manifest load errors: {failures:#?}");
// After the manifest, not before: `load_default_ui` seats a placeholder player record so the
// realm's own files have a race and a class to read at load, and `UnitName("player")` — which
// is what the blip's tooltip calls (`WorldMapFrame.lua:660`) — reads that record, not the unit
// snapshot. Naming it here is what makes the two agree.
s.set_player_record(benilla_ui::script::PlayerRecord {
name: "Probefour".into(),
race: Some(("Human".into(), "Human".into())),
class: Some(("Warrior".into(), "WARRIOR".into())),
sex: 2,
});
s.resolve();
s.run("ShowUIPanel(WorldMapFrame)").unwrap();
s.resolve();
@@ -897,9 +907,20 @@ fn a_stale_layout_row_cannot_seat_a_stock_frame() {
.unwrap(),
"and the apply's userPlaced stamp lives inside the position arm it never took"
);
// …so the row falls out of the file at this logout instead of being written again. Only this
// row: the realm stamps its own movable windows userPlaced in their OnLoad — `PlayerFrame`
// and `TargetFrame` both do `SetMovable(1) SetUserPlaced(true)` (`PlayerFrame.xml:488-489`),
// which is the same feature as the `MOVE`/`MOVE_RESET` rows in their right-click menus, and
// the Turtle shop/transmog/guild-bank windows do the same. Those five belong in the file.
assert!(
s.user_placed_layouts().is_empty(),
"so the row falls out of the file at this logout instead of being written again"
!s.user_placed_layouts()
.iter()
.any(|l| l.name == "WorldMapFrame"),
"the map must not be written back: {:?}",
s.user_placed_layouts()
.iter()
.map(|l| l.name.clone())
.collect::<Vec<_>>()
);
// The control: with the addon's `SetMovable` and `SetResizable`, the same row seats.
@@ -921,8 +942,11 @@ fn a_stale_layout_row_cannot_seat_a_stock_frame() {
"and the position arm stamps the bit it was written under"
);
let saved = s.user_placed_layouts();
assert_eq!(saved.len(), 1, "which is what puts the row back: {saved:?}");
assert_eq!(saved[0], row(), "unchanged through the round trip");
let map_row = saved
.iter()
.find(|l| l.name == "WorldMapFrame")
.unwrap_or_else(|| panic!("which is what puts the row back: {saved:?}"));
assert_eq!(*map_row, row(), "unchanged through the round trip");
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}