diff --git a/crates/benilla-app/src/ui_script/test_ui.rs b/crates/benilla-app/src/ui_script/test_ui.rs
index fcb42d9c..ee6f5f13 100644
--- a/crates/benilla-app/src/ui_script/test_ui.rs
+++ b/crates/benilla-app/src/ui_script/test_ui.rs
@@ -346,9 +346,27 @@ pub(super) const UIPARENT_STAND_INS: &str = r#"
-- The `isVar` rows are skipped: those keys are global NUMBERS the pass writes
-- (CONTAINER_OFFSET_X/Y, PETACTIONBAR_YPOS…), and a frame seated under one of those names
-- would be arithmetic's problem two files later.
+ -- …except the two chat windows, and the exception is the point rather than a special
+ -- case. The pass reads a managed row ONLY through `frame = _G[index]; if ( frame )`
+ -- (UIParent.lua:1962) and compares at l.2024 with `frame == ChatFrame1`, which a nil
+ -- global answers without raising -- so these two never need a stand-in. Every other row
+ -- can be reached unguarded by the custom-positioning tail (`MultiBarBottomLeft:IsShown()`
+ -- l.2040, `PetActionBarFrame:IsShown()` l.2065, `MultiBarBottomRight:IsShown()` l.2070),
+ -- which is why the rest of the table still seats.
+ --
+ -- And seating these two is not merely unnecessary, it is destructive: `FloatingChatFrame`
+ -- builds the real ChatFrame1..7 twelve toc rows AFTER this pass first runs (it runs at
+ -- load, from ShapeshiftBar_OnLoad, BonusActionBarFrame.lua:109 -> :157), a frame's publish
+ -- to _G is non-overwriting (0x701bd0), so a stand-in seated here shadows the real window
+ -- for the rest of the VM's life while the real one -- correctly built, correctly parented
+ -- -- is unreachable by name. FloatingChatFrame.lua then reads the stand-in back:
+ -- FCF_SetWindowName concatenating its nil GetName (l.693), FCF_SaveDock handing
+ -- SetChatWindowDocked window 0 (l.1284), and FCF_UpdateButtonSide doing arithmetic on its
+ -- unresolvable GetRight (l.1019). That cost 136 of this crate's tests.
+ local chat_windows = { ChatFrame1 = true, ChatFrame2 = true }
local named = {}
for name, row in pairs(UIPARENT_MANAGED_FRAME_POSITIONS) do
- if not row.isVar then table.insert(named, name) end
+ if not row.isVar and not chat_windows[name] then table.insert(named, name) end
-- …and every row's ANCHOR TARGET (`anchorTo`, l.1668), which is a different set: the
-- keys are the frames being MOVED, the targets are what they move relative to, and
-- several targets (`ActionButton1`, `MainMenuBarArtFrame`) are declared in files a
diff --git a/crates/benilla-ui/src/loader/tests.rs b/crates/benilla-ui/src/loader/tests.rs
index a82708c5..93d25767 100644
--- a/crates/benilla-ui/src/loader/tests.rs
+++ b/crates/benilla-ui/src/loader/tests.rs
@@ -942,6 +942,84 @@ mod loader_tests {
);
}
+ /// …and when the chain's **base** template came from an earlier document, which is the last
+ /// difference between the two tests above and the realm's real shape:
+ /// `FloatingChatFrameTemplate` carries the `parent=` and inherits `ChatFrameTemplate`, which
+ /// `ChatFrame.xml` declared one toc row earlier.
+ #[test]
+ fn a_parent_survives_an_inherit_chain_whose_base_is_in_an_earlier_document() {
+ let s = UiScript::new().unwrap();
+ let first = load(
+ &s,
+ &parse(
+ r#"
+
+
+
+ "#,
+ ),
+ &no_files,
+ );
+ assert!(first.errors.is_empty(), "{:?}", first.errors);
+ let second = load(
+ &s,
+ &parse(
+ r#"
+
+
+
+
+
+ "#,
+ ),
+ &no_files,
+ );
+ assert!(second.errors.is_empty(), "{:?}", second.errors);
+ assert_eq!(
+ s.eval::(
+ "return tostring(LateLeaf:GetParent() and LateLeaf:GetParent():GetName())"
+ )
+ .unwrap(),
+ "HostBox3",
+ "a cross-document base must not cost the middle template its parent="
+ );
+ }
+
+ /// …and for a ``, which is the kind `ChatFrame1` actually is. Kept as
+ /// its own case because the three above all used ``, and a kind with its own
+ /// materialise path is the one place a dropped attribute would not show.
+ #[test]
+ fn a_scrolling_message_frame_takes_its_templates_parent() {
+ let s = UiScript::new().unwrap();
+ let doc = parse(
+ r#"
+
+
+
+
+
+
+ "#,
+ );
+ let mut s = s;
+ s.set_screen_size(1024.0, 768.0);
+ let report = load(&s, &doc, &no_files);
+ assert!(report.errors.is_empty(), "{:?}", report.errors);
+ assert_eq!(
+ s.eval::(
+ "return tostring(SmfLeaf:GetParent() and SmfLeaf:GetParent():GetName())"
+ )
+ .unwrap(),
+ "HostBox4"
+ );
+ assert_eq!(
+ s.eval::("return tostring(SmfLeaf:GetRight())")
+ .unwrap(),
+ "40",
+ "with a parent the anchor resolves, which is what FCF_UpdateButtonSide needs"
+ );
+ }
+
/// A `.toc` line hands a listed `Bindings.xml` to `0x6ede10` (from `0x6edd51`), so each
/// `` logs as an unknown frame type; `0x51f400` reads the bindings regardless.
#[test]