diff --git a/crates/benilla-app/src/ui_script/multibar_stance_tests.rs b/crates/benilla-app/src/ui_script/multibar_stance_tests.rs index d98cf998..8c837c46 100644 --- a/crates/benilla-app/src/ui_script/multibar_stance_tests.rs +++ b/crates/benilla-app/src/ui_script/multibar_stance_tests.rs @@ -275,7 +275,6 @@ fn shipped_stance_bar_drives_end_to_end() { load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, "KeyBindingsPage.xml"); load_xml(&s, "OptionsFrame.xml"); - load_xml(&s, "Interface\\FrameXML\\MultiActionBars.xml"); show_bars(&s, &[1]); assert!( s.eval::("return ShapeshiftBarFrame ~= nil and ShapeshiftButton10 ~= nil") @@ -550,7 +549,6 @@ fn the_vertical_multibars_exist_hidden_on_the_reference_pages() { load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, "KeyBindingsPage.xml"); load_xml(&s, "OptionsFrame.xml"); - load_xml(&s, "Interface\\FrameXML\\MultiActionBars.xml"); for bar in ["MultiBarRight", "MultiBarLeft"] { assert!( @@ -605,7 +603,6 @@ fn every_extra_bar_stays_down_until_its_own_toggle_is_set() { load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, "KeyBindingsPage.xml"); load_xml(&s, "OptionsFrame.xml"); - load_xml(&s, "Interface\\FrameXML\\MultiActionBars.xml"); const BARS: [&str; 4] = [ "MultiBarBottomLeft", @@ -682,7 +679,6 @@ fn raising_a_bottom_bar_moves_the_managed_bottom_stack() { load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, "KeyBindingsPage.xml"); load_xml(&s, "OptionsFrame.xml"); - load_xml(&s, "Interface\\FrameXML\\MultiActionBars.xml"); load_xml(&s, "Interface\\FrameXML\\CastingBarFrame.xml"); // Read the row's y off the anchor, so a hidden cast bar answers too. @@ -741,7 +737,6 @@ fn viewable_action_bar_pages_follow_the_bar_toggles() { load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, "KeyBindingsPage.xml"); load_xml(&s, "OptionsFrame.xml"); - load_xml(&s, "Interface\\FrameXML\\MultiActionBars.xml"); let viewable = |s: &UiScript| { s.eval::( @@ -803,7 +798,6 @@ fn the_grid_option_holds_the_extra_bars_empty_wells_open() { load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, "KeyBindingsPage.xml"); load_xml(&s, "OptionsFrame.xml"); - load_xml(&s, "Interface\\FrameXML\\MultiActionBars.xml"); show_bars(&s, &[1]); let well = |s: &UiScript| { @@ -884,7 +878,6 @@ fn a_held_payload_ghosts_the_empty_wells_it_opens() { load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, "KeyBindingsPage.xml"); load_xml(&s, "OptionsFrame.xml"); - load_xml(&s, "Interface\\FrameXML\\MultiActionBars.xml"); s.set_action( 1, Some(ActionSlot { @@ -939,21 +932,26 @@ fn a_held_payload_ghosts_the_empty_wells_it_opens() { 23, "11 empty main wells swap art + MultiBarBottomLeft's 12 appear" ); - // **Known red, and precisely located.** The eleven main-bar rings carry the half alpha; the - // twelve MultiBar ones do not, and the Lua state is right — `GetVertexColor()` on - // `MultiBarBottomLeftButton1NormalTexture` answers 0.5. The global is a **different object** - // than `MultiBarBottomLeftButton1:GetNormalTexture()`, though the live region's own - // `GetName()` is that very name, so the tint lands on something nothing draws. + // This assertion was red for a while on a wrong diagnosis worth leaving written down, because + // the symptom is a good imitation of a loader bug. The twelve MultiBar rings were not taking + // the tint, and `_G["MultiBarBottomLeftButton1NormalTexture"]` was a **different object** from + // `MultiBarBottomLeftButton1:GetNormalTexture()` while both answered that same `GetName()` — + // which reads exactly like the state-texture publish naming the wrong region, and which I + // wrote up as a template-depth defect in `loader::widgets`. // - // The fork is the depth of template inheritance, not the widget: `ActionButton1` is one level - // (`ActionBarButtonTemplate`) and is fine; a MultiBar button is two - // (`MultiBar1ButtonTemplate` -> `ActionBarButtonTemplate` -> `ActionButtonTemplate`) and is - // not. Neither of the two outer templates declares a `` of its own, so the - // duplicate comes out of the loader's expansion, not the markup. + // It was neither. **This test loaded `MultiActionBars.xml` twice** — once through + // `load_action_bar`, where it belongs by toc order, and once again in its own list, left + // behind when the first was added. Two loads build two sets of MultiBar buttons, and the two + // `_G` publishes disagree about a collision: a frame's is non-overwriting (`0x701bd0`), so + // `MultiBarBottomLeftButton1` stayed the **first** set's button, while the loader's + // state-texture publish is a plain `globals().set`, so the region name became the **second** + // set's texture. Every write through the global landed on the hidden second set. // - // Left failing deliberately rather than relaxed: it is a real defect with real reach — every - // `_G[name .. "NormalTexture"]` write in FrameXML and in an addon, and pfUI makes many — and - // the fix belongs in `loader::widgets`'s state-texture path with its own tests. + // A minimal three-level template chain does *not* reproduce it (`loader/tests.rs`), which is + // what finally killed the depth story. The inconsistency between the two publishes is real + // but out of reach of any single load, so it is recorded here rather than guessed at: the + // reference's region path is not disassembled, and making regions non-overwriting to match + // frames would be a change with no evidence behind it. assert!( ghosts.iter().all(|a| *a == 0.5), "every grid ring is the ref's half-alpha ghost, not an opaque plate: {ghosts:?}" @@ -1022,7 +1020,6 @@ fn a_bar_toggle_sends_the_byte_its_globals_pack_to() { load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, "KeyBindingsPage.xml"); load_xml(&s, "OptionsFrame.xml"); - load_xml(&s, "Interface\\FrameXML\\MultiActionBars.xml"); let _ = s.take_action_bar_toggle_sends(); // The Options row's sequence: assign, update the bars, send. @@ -1099,7 +1096,6 @@ fn the_shipped_setter_passes_exactly_four_arguments() { load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, "KeyBindingsPage.xml"); load_xml(&s, "OptionsFrame.xml"); - load_xml(&s, "Interface\\FrameXML\\MultiActionBars.xml"); // Lua 5.0's `arg.n`: `...` as a value is not in the 1.12 client's grammar. s.run( @@ -1145,7 +1141,6 @@ fn the_stance_bar_sits_where_the_pass_puts_it() { load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, "KeyBindingsPage.xml"); load_xml(&s, "OptionsFrame.xml"); - load_xml(&s, "Interface\\FrameXML\\MultiActionBars.xml"); load_xml(&s, "Interface\\FrameXML\\Cooldown.xml"); load_xml(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml"); s.set_shapeshift_forms(vec![ShapeshiftFormView { @@ -1206,7 +1201,6 @@ fn the_stance_shelf_follows_the_bottom_left_bar() { load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, "KeyBindingsPage.xml"); load_xml(&s, "OptionsFrame.xml"); - load_xml(&s, "Interface\\FrameXML\\MultiActionBars.xml"); load_xml(&s, "Interface\\FrameXML\\Cooldown.xml"); load_xml(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml"); @@ -1270,15 +1264,21 @@ fn the_stance_shelf_follows_the_bottom_left_bar() { } assert_eq!(ring(&s), 50.0, "raised rings are the ref's 50"); - // A third form learned while raised: `ShapeshiftBar_Update` shows the middle strip regardless - // (BonusActionBarFrame.lua:169-175) and nothing runs the pass on that event, so the strip - // draws over the row until the next pass (UIParent.lua:1706-1717). + // A third form learned while raised. `ShapeshiftBar_Update`'s form-count arm *does* show the + // middle strip — and then its own last line runs the manage pass, which takes it straight back + // down because the bottom-left bar is up (`BonusActionBarFrame.lua`'s `ShapeshiftBar_Update` + // ends `ShapeshiftBar_UpdateState(); UIParent_ManageFramePositions();`, and the pass's + // shapeshift arm hides all three strips under `if ( MultiBarBottomLeft:IsShown() )`). So the + // strip never draws across the row, not even for a frame: the show and the hide are inside one + // call. This assertion used to read the other way, on a note that "nothing runs the pass on + // that event" — the file's last line says otherwise, and that was the whole of this red. s.set_shapeshift_forms(vec![form(2457), form(71), form(2458)]); s.fire_event("UPDATE_SHAPESHIFT_FORMS", vec![]); assert!( - shown(&s, "ShapeshiftBarMiddle"), - "Update shows the strip; only the pass takes it down" + !shown(&s, "ShapeshiftBarMiddle"), + "Update shows the strip and its own trailing pass hides it again" ); + // And an explicit pass is idempotent on that state. s.run("UIParent_ManageFramePositions()").unwrap(); assert!(!shown(&s, "ShapeshiftBarMiddle")); assert_eq!(ring(&s), 50.0); @@ -1318,7 +1318,6 @@ fn the_stance_shelf_is_as_long_as_the_form_count() { load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, "KeyBindingsPage.xml"); load_xml(&s, "OptionsFrame.xml"); - load_xml(&s, "Interface\\FrameXML\\MultiActionBars.xml"); load_xml(&s, "Interface\\FrameXML\\Cooldown.xml"); load_xml(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml"); show_bars(&s, &[]); // unraised: the shelf art is the state under test @@ -1418,7 +1417,6 @@ fn an_extra_bars_empty_well_keeps_its_bound_hotkey_label() { load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, "KeyBindingsPage.xml"); load_xml(&s, "OptionsFrame.xml"); - load_xml(&s, "Interface\\FrameXML\\MultiActionBars.xml"); show_bars(&s, &[1]); s.set_action( 61, @@ -1500,7 +1498,6 @@ fn the_middle_strip_tiles_along_its_length_only() { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); load_action_bar(&s); - load_xml(&s, "Interface\\FrameXML\\MultiActionBars.xml"); show_bars(&s, &[]); // bars down: the shelf is the state under test let form = |id: u32| ShapeshiftFormView { @@ -1567,7 +1564,6 @@ fn a_forms_state_change_leaves_the_shelf_down_over_the_raised_bar() { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); load_action_bar(&s); - load_xml(&s, "Interface\\FrameXML\\MultiActionBars.xml"); // A recorder for the two events a push may announce. s.run( "STANCE_LOG = {} local f = CreateFrame('Frame') \ @@ -1721,8 +1717,11 @@ fn a_forms_state_change_leaves_the_shelf_down_over_the_raised_bar() { ); assert_eq!(log(&s), ""); - // A fourth stance learned is a list edge: `ShapeshiftBar_Update` shows the strip over the - // raised bar until the next pass. + // A fourth stance learned is a list edge. `ShapeshiftBar_Update` shows the strip for the form + // count and then runs the manage pass on its own last line, which takes it down again under + // the raised bar — so the shelf is never seen across the row. Same correction as + // `the_stance_shelf_follows_the_bottom_left_bar`'s: the "until the next pass" reading ignored + // `ShapeshiftBar_Update`'s trailing `UIParent_ManageFramePositions()`. assert_eq!( push_forms( &mut s, @@ -1738,8 +1737,8 @@ fn a_forms_state_change_leaves_the_shelf_down_over_the_raised_bar() { ); assert_eq!(log(&s), "UPDATE_SHAPESHIFT_FORMS"); assert!( - shown(&s, "ShapeshiftBarMiddle"), - "the learn edge: Update shows the strip" + !shown(&s, "ShapeshiftBarMiddle"), + "the learn edge shows the strip and hides it again in the same call" ); s.run("UIParent_ManageFramePositions()").unwrap(); assert!(!shown(&s, "ShapeshiftBarMiddle")); diff --git a/crates/benilla-ui/src/loader/tests.rs b/crates/benilla-ui/src/loader/tests.rs index 93d25767..4657feb0 100644 --- a/crates/benilla-ui/src/loader/tests.rs +++ b/crates/benilla-ui/src/loader/tests.rs @@ -14,6 +14,82 @@ mod loader_tests { framexml::parse(text).expect("valid FrameXML") } + /// A state texture's `$parent` name points at the live slot however deep the template chain + /// is, and whether the button is top-level or a named parent's child. + /// + /// Written after a wrong diagnosis: `_G["MultiBarBottomLeftButton1NormalTexture"]` really was + /// a different object from that button's `GetNormalTexture()`, and the obvious reading was + /// that `expand`'s merge duplicates the base's `` once per level, so the + /// publish and the final slot drift apart. It does not — merge concatenates children and only + /// the base of the real chain (`ActionButtonTemplate`) declares one, so there is exactly one + /// declaration at any depth. The real cause was the test loading `MultiActionBars.xml` twice + /// (`multibar_stance_tests::a_held_payload_ghosts_the_empty_wells_it_opens` carries the + /// write-up). This test is what rules the loader out, so it stays. + #[test] + fn a_state_textures_name_follows_the_live_slot_at_any_template_depth() { + let mut s = UiScript::new().unwrap(); + s.set_screen_size(800.0, 600.0); + let doc = parse( + r#" + + + + + + + + + + this.mid = 1 + + + this.outer = 1 + + + + + + + + + + + + + + + + "#, + ); + let report = load(&s, &doc, &no_files); + assert!(report.errors.is_empty(), "{:?}", report.errors); + for button in ["OneHop", "ThreeHop", "DepthHostKid"] { + assert!( + s.eval::(&format!( + "return _G['{button}NormalTexture'] == _G['{button}']:GetNormalTexture()" + )) + .unwrap(), + "{button}: the published name is not the live normal texture" + ); + assert_eq!( + s.eval::(&format!( + "return _G['{button}']:GetNormalTexture():GetName()" + )) + .unwrap(), + format!("{button}NormalTexture"), + "{button}: the live region does not answer its own published name" + ); + // And the paint reached it, so this is not two agreeing empties. + assert_eq!( + s.eval::(&format!("return _G['{button}NormalTexture']:GetTexture()")) + .unwrap(), + "Interface\\Base", + "{button}: the base template's art" + ); + } + } + /// `CMinimap::LoadXML 0x4ee2b0`: `minimapArrowModel` goes to engine children 1-8 (`0x4ee170`), /// `minimapPlayerModel` to child 9 alone (`[Minimap+0x338]`, `0x4ee260`); without the /// attributes the defaults are `0x84c768` and `0x8453c0`. `Minimap.xml:97` sets both.