Texture and FontString stop answering each other's methods

- the one shared region table splits into the client's own maps: a Texture no longer answers SetText, a FontString no longer answers SetTexture, and feature-detecting addons see the true surface
- CreateTitleRegion lands whole — object and drag — so an options window that is its own drag handle actually moves
- font-object names resolve whatever their case, and the CursorUpdate/CursorOnUpdate pair arrives for the container-item templates
- GetStringHeight is pruned (1.12 has no such method) and SetPortraitToTexture moves to the global it really is

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-08-14 19:03:34 +07:00
co-authored by Claude Fable 5
parent 56d515a374
commit 84f93c47de
32 changed files with 1109 additions and 80 deletions
@@ -408,7 +408,7 @@ fn shipped_gossip_rows_grow_to_their_wrapped_labels() {
let row = |i: u32| -> (f32, f32, f32) {
s.eval::<(f32, f32, f32)>(&format!(
"return GossipRow{i}:GetTop(), GossipRow{i}:GetBottom(), \
GossipRow{i}Label:GetStringHeight()"
GossipRow{i}Label:GetHeight()"
))
.unwrap()
};
@@ -627,7 +627,7 @@ fn greeting_panel_title_rows_grow_to_their_wrapped_titles() {
let row = |i: u32| -> (f32, f32, f32) {
s.eval::<(f32, f32, f32)>(&format!(
"local b = getglobal('QuestTitleButton{i}')\n\
return b:GetTop(), b:GetBottom(), b:GetFontString():GetStringHeight()"
return b:GetTop(), b:GetBottom(), b:GetFontString():GetHeight()"
))
.unwrap()
};
@@ -503,3 +503,106 @@ fn every_shipped_font_object_is_published_as_a_lua_global() {
assert!(height > 0.0, "{name}: height {height}");
}
}
/// **Two reference templates addons INHERIT, and the silence that hid them.**
///
/// `TargetBuffButtonTemplate` (ref `TargetFrame.xml`) and `MainMenuBarMicroButton` (ref
/// `MainMenuBarMicroButtons.xml`) are declared by the reference and were not by us. An unresolved
/// `inherits=` is **silent** — no load error, no session error, nothing in any harness column — so
/// a consumer just gets a button with no size, no hit-rect and no scripts, and nothing anywhere
/// says so. They surfaced only in the report's missing-TEMPLATES ranking.
///
/// Asserted through what a consumer actually gets: inherit the template, then read back the
/// geometry and the script the reference confers.
#[test]
fn the_inheritable_reference_templates_confer_their_shape() {
let mut s = benilla_ui::script::UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
assert!(super::load_default_ui(&s).is_empty());
// `CT_UnitFrames/CT_TargetFrame.xml:4` builds its own virtual template on top of this one, so
// the inherit has to work through a second hop as well as directly.
s.run(
r#"
BuffProbe = CreateFrame("Button", "BuffProbe", UIParent, "TargetBuffButtonTemplate")
MicroProbe = CreateFrame("Button", "MicroProbe", UIParent, "MainMenuBarMicroButton")
"#,
)
.unwrap();
s.resolve();
assert_eq!(
s.eval::<(f64, f64)>("return BuffProbe:GetWidth(), BuffProbe:GetHeight()")
.unwrap(),
(21.0, 21.0),
"the buff button carries the reference's 21x21"
);
assert!(
s.eval::<bool>("return BuffProbeIcon ~= nil").unwrap(),
"$parentIcon is the region a consumer getglobals to set the texture"
);
assert!(
s.eval::<bool>(r#"return BuffProbe:GetScript("OnEnter") ~= nil"#)
.unwrap(),
"the tooltip script comes with the template"
);
assert_eq!(
s.eval::<(f64, f64)>("return MicroProbe:GetWidth(), MicroProbe:GetHeight()")
.unwrap(),
(29.0, 58.0)
);
assert!(s
.eval::<bool>(r#"return MicroProbe:GetScript("OnEnter") ~= nil"#)
.unwrap());
assert!(s.errors().is_empty(), "no script errors: {:?}", s.errors());
}
/// **`CursorUpdate` / `CursorOnUpdate` — the inspect-cursor pair, transcribed from FrameXML.**
///
/// Both are `framexml` origin in `reference/1.12-globals.tsv` (while `ResetCursor` and
/// `ShowInspectCursor` beside them are `engine`), so they are ours to write rather than to bind.
/// Two corpus addons call `CursorUpdate` and one calls `CursorOnUpdate`; it is also one of the
/// blockers on two reference templates addons inherit, and the sourced `ContainerFrame.lua` calls
/// it from its keyring fork.
#[test]
fn the_inspect_cursor_pair_takes_both_arms() {
let mut s = benilla_ui::script::UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
assert!(super::load_default_ui(&s).is_empty());
// Both exist as functions — the shape a caller checks before hooking.
assert_eq!(
s.eval::<(String, String)>("return type(CursorUpdate), type(CursorOnUpdate)")
.unwrap(),
("function".into(), "function".into())
);
// **The no-ctrl arm, which is the one that runs in practice.** `this.hasItem` is read as a
// plain truthy field, so a frame that never sets it simply takes `ResetCursor` — this is why
// the pair is safe to ship before anything sets that field.
s.run(
r#"
CursorProbe = CreateFrame("Frame", "CursorProbe", UIParent)
this = CursorProbe
CursorUpdate()
this = nil
"#,
)
.unwrap();
assert!(
s.errors().is_empty(),
"the ResetCursor arm runs clean: {:?}",
s.errors()
);
// `CursorOnUpdate` gates on tooltip ownership, so with the tooltip unowned it must do nothing
// at all rather than reach through to the cursor.
s.run("this = CursorProbe CursorOnUpdate() this = nil")
.unwrap();
assert!(
s.errors().is_empty(),
"the unowned-tooltip gate short-circuits: {:?}",
s.errors()
);
}