- the world map's arrow shows on every zone sheet, and alt-clicking a town jumps there: the stock file creates the player arrow as a `Model` child of `WorldMapFrame` and lays a zone sheet's explored-area overlays as ARTWORK textures on `WorldMapDetailFrame` — one `(strata, level)` bucket, where the draw layer outranks the frame — and benilla drew a model's scene at the frame's own layer-0 slot, so every overlay painted over it (the continent sheet has no overlays, which is why it was the only sheet with an arrow); wow-re's carve: a layer batch carries three sub-arrays — quads, the string batch, a render-callback list — that `0x76fb00` drains in that order, and `0x76d160` registers a model's callback only for layer 2 (`0x76d17f cmp ebx,2`), so a model's scene is ARTWORK content, neither a separate pass nor a layer-0 slot; a `ModelPane` quad now keys at the ARTWORK content rank, which re-orders the pet page's happiness icon (a sibling's BACKGROUND quad) under the pane's scene — the reference's own order, harmless because the bake is transparent where the model is not; a model against a LATER-linked ARTWORK font string in the same bucket is still ordered by link stamp, named not built; and the alt-click jump, which required the topmost frame under the cursor to be `WorldMapButton` itself while the stock file parents every POI icon and blip to that button, now walks up from whatever was hit to the button and takes its rect — the map's own chrome still answers nothing (1992) - `UIParent` is the reference's own: `UIParent.xml` loads off the chain at its toc's slot, right after `WorldFrame.xml`, sourcing `UIParent.lua` and `LocaleProperties.lua` itself, and our `UIParent.xml` + `UiPanels.xml` — 3086 lines of transcribed `UIParent.lua` — are deleted; the one unbound verb the stock file ticks, `CheckReadyCheckTime`, was already 1989's; back from the reference: all sixty-six registered events and their arms (`PLAYER_CONTROL_LOST`/`_GAINED`, `RAID_ROSTER_UPDATE`, the trade-request pair, `PLAYER_SKINNED`, `GOSSIP_ENTER_CODE`), the ESC ladder as written — StaticPopup, the options window's Cancel, the game menu, `CloseMenus`, `SpellStopCasting`, `SpellStopTargeting`, `CloseAllWindows`, `ClearTarget`, else open — so ESC no longer clears a held cursor item or cancels the colour picker, two arms ours had grown that the reference does not have; the fade/flash/pulse kits walked from `UIParent`'s own OnUpdate in place of three 1×1 driver frames of ours, so a cinematic's `UIParent:Hide()` stops the walks as it does there; the managed bottom-stack positions without our `UIParent_RegisterManagedPositionListener` extension, which no shipped file registered any more; `LowHealthFrame`/`OutOfControlFrame`; and the `firstTimeLoaded` close on re-show; what stayed ours moved out — the window tab template and its OnUpdate settle (1004's async-measure adapter) and `BENILLA_FALLBACK_ICON` to `ScrollTemplates.xml`, 0561's bag verbs to `ContainerFrameAdapters`, the script log's slot row to its own file, `OptionsFrameCancel = OptionsFrameCloseButton` beside the two options aliases — and the Auction/Inspect/Macro/Talent panel rows, verbatim copies of the LoadOnDemand addons' own registrations, go; the reference also decides that the area spirit healer's dialog accepts on show with one CANCEL button, that losing control closes every window bar the reference's exceptions, and that the bottom-bar clearance reads the saved globals `SHOW_MULTI_ACTIONBAR_1`/`_2` rather than the bars' shown state; three integration binaries that kept private copies of the manifest loader use the shared one now (1988) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
231 lines
10 KiB
Rust
231 lines
10 KiB
Rust
//! `CreateFrame`'s fourth argument against the **real** `assets/ui` templates — the corpus idiom,
|
|
//! end to end.
|
|
//!
|
|
//! The unit tests for the runtime template path live in
|
|
//! `benilla-ui/src/script/tests/create_frame_template.rs` and drive synthetic templates. This one
|
|
//! exists because the thing an addon actually types is
|
|
//!
|
|
//! ```lua
|
|
//! local tab = CreateFrame("Button", "MyTab", UIParent, "TabButtonTemplate")
|
|
//! getglobal("MyTab".."Text"):SetText("Hi")
|
|
//! ```
|
|
//!
|
|
//! against a template *we* wrote, loaded the way the client loads it — six hundred lines of real
|
|
//! FrameXML above it, a font registry, an anchor graph — not a four-line fixture. Every assertion
|
|
//! below is a global an addon would reach for.
|
|
|
|
mod common;
|
|
|
|
use benilla_ui::script::UiScript;
|
|
|
|
/// The prefix of `benilla.toc`'s load order these templates need: the font registry, the panel kit,
|
|
/// UIParent (the parent every addon passes), and the faux-scroll kit.
|
|
///
|
|
/// **Two of the templates under test are the REFERENCE's since 1860** — `FauxScrollFrameTemplate`
|
|
/// and `TabButtonTemplate` were ours until the dead-copy sweep, and both now come off the player's
|
|
/// chain from `Interface\FrameXML\UIPanelTemplates.xml`, seated below `UiPanels.xml` exactly as
|
|
/// the manifest seats it. So this list carries the chain pair and the loader below has to be able
|
|
/// to READ a chain entry, which a disk-only provider under `assets/ui` cannot.
|
|
const FILES: &[&str] = &[
|
|
"Interface\\FrameXML\\Fonts.xml",
|
|
// A REGRESSION GUARD, not a dependency (1923). FadingFrame.xml's entire body is a single
|
|
// `<Script file="FadingFrame.lua"/>`, so it loads correctly ONLY if that relative reference
|
|
// resolves against the document's own directory. Load it through the path-less `loader::load`
|
|
// and the base is "", the ref stays bare, the provider misses, and the assert below fires —
|
|
// which is exactly what it did before this file passed the path.
|
|
//
|
|
// It is self-contained on BOTH axes, and both had to be checked: zero `inherits=`, AND its Lua
|
|
// has no file-scope statements at all — only function definitions. The first guard tried here
|
|
// was CombatFeedback.xml, which also has zero `inherits=` but whose Lua calls `TEXT()` at file
|
|
// scope (l.7); that global is BasicControls.xml's, so it needed a dependency after all.
|
|
// `UnitFrame.lua` fails the same way (`TEXT(MANA)`). Structural dependencies are not the only
|
|
// kind.
|
|
"Interface\\FrameXML\\FadingFrame.xml",
|
|
r"Interface\FrameXML\MoneyFrame.lua",
|
|
r"Interface\FrameXML\MoneyFrame.xml",
|
|
r"Interface\FrameXML\UIParent.xml",
|
|
r"Interface\FrameXML\UIPanelTemplates.lua",
|
|
r"Interface\FrameXML\UIPanelTemplates.xml",
|
|
"Interface\\FrameXML\\GlobalStrings.lua",
|
|
"Interface\\FrameXML\\BasicControls.xml", // `TEXT`, which StaticPopup.lua reads at file scope
|
|
"Interface\\FrameXML\\LocaleProperties.lua",
|
|
"Interface\\FrameXML\\StaticPopup.xml", // the dialog engine (1960)
|
|
"ScrollTemplates.xml",
|
|
];
|
|
|
|
fn load_ui(script: &UiScript) {
|
|
for file in FILES {
|
|
common::load_ui(script, file);
|
|
}
|
|
}
|
|
|
|
/// The line an addon writes, and the globals it reads on the next one.
|
|
///
|
|
/// `TabButtonTemplate` (UiPanels.xml) is a `<Button>` carrying a `<Size>`, six `<Layers>` slices,
|
|
/// a `<ButtonText name="$parentText">`, a `<HighlightTexture name="$parentHighlightTexture">`, the
|
|
/// three state fonts and an `<OnUpdate>` — i.e. every decoration pass at once. Instantiating it as
|
|
/// `BenillaTemplateProbeTab` must publish `BenillaTemplateProbeTab*`, and must publish nothing
|
|
/// named after the template.
|
|
#[test]
|
|
fn a_real_template_reaches_an_addon_through_create_frame() {
|
|
let _data = benilla_formats::wow_data_or_skip!();
|
|
let mut s = UiScript::new().unwrap();
|
|
s.set_screen_size(1024.0, 768.0);
|
|
load_ui(&s);
|
|
// Clear anything the UI load itself had to say; what follows is this call's alone.
|
|
let _ = s.take_warnings();
|
|
let _ = s.take_errors();
|
|
|
|
s.run(
|
|
r#"ProbeTab = CreateFrame("Button", "BenillaTemplateProbeTab", UIParent, "TabButtonTemplate")"#,
|
|
)
|
|
.expect("the corpus idiom must not error");
|
|
|
|
// The template's authored `<Size>` is 115x32 in BOTH our retired copy and the reference's, but
|
|
// the reference's `<OnLoad>` calls `PanelTemplates_TabResize(0)` — it FITS the tab to its text
|
|
// on load, and a freshly created tab has none, so what survives is the two end caps. Ours left
|
|
// the fit to an OnUpdate settle (1004), which is why this used to read the pre-fit 115. The
|
|
// height is untouched by the fit and still the template's (1860).
|
|
let (w, h) = s
|
|
.eval::<(f32, f32)>("return ProbeTab:GetWidth(), ProbeTab:GetHeight()")
|
|
.unwrap();
|
|
assert_eq!(h, 32.0, "the template's own <Size> height");
|
|
let caps = s
|
|
.eval::<f32>("return 2 * BenillaTemplateProbeTabLeft:GetWidth()")
|
|
.unwrap();
|
|
// `caps` plus the empty label's one-unit floor (`FONTSTRING_MIN_SPAN`) — the point is that it
|
|
// collapsed to its end caps, nowhere near the authored 115.
|
|
assert!(
|
|
w >= caps && w <= caps + 1.5,
|
|
"an unlabelled tab fits down to its two end caps: {w} vs {caps}"
|
|
);
|
|
assert_eq!(
|
|
s.eval::<String>("return ProbeTab:GetParent():GetName()")
|
|
.unwrap(),
|
|
"UIParent",
|
|
"the parent argument, not the template's idea of one"
|
|
);
|
|
|
|
// The globals an addon addresses the parts by — every one named against the INSTANCE. The
|
|
// reference's own kit does exactly this (`getglobal(tabName.."Text")` in PanelTemplates).
|
|
for suffix in [
|
|
"Text", // <ButtonText name="$parentText">
|
|
"HighlightTexture", // <HighlightTexture name="$parentHighlightTexture">
|
|
"Left", // <Layers> slices
|
|
"Middle",
|
|
"Right",
|
|
"LeftDisabled",
|
|
] {
|
|
assert!(
|
|
s.eval::<bool>(&format!(
|
|
r#"return getglobal("BenillaTemplateProbeTab{suffix}") ~= nil"#
|
|
))
|
|
.unwrap(),
|
|
"BenillaTemplateProbeTab{suffix} must exist"
|
|
);
|
|
assert!(
|
|
s.eval::<bool>(&format!(
|
|
r#"return getglobal("TabButtonTemplate{suffix}") == nil"#
|
|
))
|
|
.unwrap(),
|
|
"nothing may be published under the TEMPLATE's name (TabButtonTemplate{suffix})"
|
|
);
|
|
}
|
|
|
|
// The label is a real FontString the addon can drive.
|
|
s.run(r#"getglobal("BenillaTemplateProbeTabText"):SetText("Hi")"#)
|
|
.unwrap();
|
|
assert_eq!(
|
|
s.eval::<String>(r#"return getglobal("BenillaTemplateProbeTabText"):GetText()"#)
|
|
.unwrap(),
|
|
"Hi"
|
|
);
|
|
|
|
// The template's <Scripts> reached the frame. **`OnLoad`, not `OnUpdate`, since 1860** — the
|
|
// OnUpdate was our own text-fit settle (1004), and the reference's `TabButtonTemplate` declares
|
|
// exactly one handler: an `<OnLoad>` that calls `PanelTemplates_TabResize(0)` and sizes the
|
|
// highlight. That it ran is what the collapsed width above already proves.
|
|
assert!(
|
|
s.eval::<bool>(r#"return ProbeTab:GetScript("OnLoad") ~= nil"#)
|
|
.unwrap(),
|
|
"the template's OnLoad is installed"
|
|
);
|
|
|
|
assert!(s.take_errors().is_empty());
|
|
assert_eq!(
|
|
s.take_warnings(),
|
|
Vec::<String>::new(),
|
|
"a template that resolves cleanly has nothing to report"
|
|
);
|
|
}
|
|
|
|
/// The template the corpus actually asks for, and the handler that proves the naming rule.
|
|
///
|
|
/// `FauxScrollFrameTemplate` is the most-instantiated template in the 218-addon vanilla corpus that
|
|
/// benilla declares at all — 15 `CreateFrame` call sites across AckisRecipeList, FonzAppraiser,
|
|
/// Leader, Optional and oRA2. Its `<OnLoad>` is `ScrollFrame_OnLoad`, whose very first line is
|
|
///
|
|
/// ```lua
|
|
/// getglobal(this:GetName() .. "ScrollBarScrollDownButton"):Disable()
|
|
/// ```
|
|
///
|
|
/// so the handler **dies on a nil index** unless the caller's own name won all the way down: the
|
|
/// template's nested `<Slider name="$parentScrollBar">` had to become `<caller>ScrollBar`, and
|
|
/// *that* slider's own inherited `$parentScrollDownButton` had to become
|
|
/// `<caller>ScrollBarScrollDownButton`. Two levels of `$parent`, composed through a runtime
|
|
/// template, checked by shipped code rather than by an assertion we wrote to match.
|
|
#[test]
|
|
fn the_corpus_favourite_template_composes_parent_two_levels_deep() {
|
|
let _data = benilla_formats::wow_data_or_skip!();
|
|
let mut s = UiScript::new().unwrap();
|
|
s.set_screen_size(1024.0, 768.0);
|
|
load_ui(&s);
|
|
let _ = s.take_warnings();
|
|
let _ = s.take_errors();
|
|
|
|
s.run(
|
|
// **A `ScrollFrame`, not a `Frame`, since 1860.** `FauxScrollFrameTemplate` is now the
|
|
// reference's own and the reference declares it as a `<ScrollFrame>` carrying a
|
|
// `<ScrollChild>`; `framexml::merge` takes the OVERRIDING node's tag, so asking for a
|
|
// "Frame" keeps the frame a Frame and the ScrollFrame-only parts — the scroll child among
|
|
// them — cannot apply. Our retired copy was a plain `<Frame>`, which is why the corpus
|
|
// idiom used to read that way here.
|
|
r#"Scroller = CreateFrame("ScrollFrame", "BenillaTemplateProbeScroll", UIParent, "FauxScrollFrameTemplate")"#,
|
|
)
|
|
.expect("the corpus's most-used template must not error");
|
|
|
|
// `ScrollFrame_OnLoad` ran to completion — it set `this.offset`, its last statement, which it
|
|
// cannot reach if either getglobal above returned nil.
|
|
assert_eq!(
|
|
s.eval::<f32>("return Scroller.offset").unwrap(),
|
|
0.0,
|
|
"the template's OnLoad ran to its last line"
|
|
);
|
|
for suffix in [
|
|
"ScrollBar",
|
|
"ScrollBarScrollUpButton",
|
|
"ScrollBarScrollDownButton",
|
|
] {
|
|
assert!(
|
|
s.eval::<bool>(&format!(
|
|
r#"return getglobal("BenillaTemplateProbeScroll{suffix}") ~= nil"#
|
|
))
|
|
.unwrap(),
|
|
"BenillaTemplateProbeScroll{suffix} must exist"
|
|
);
|
|
}
|
|
assert_eq!(
|
|
s.eval::<String>("return BenillaTemplateProbeScrollScrollBar:GetParent():GetName()")
|
|
.unwrap(),
|
|
"BenillaTemplateProbeScroll"
|
|
);
|
|
assert!(
|
|
s.eval::<bool>(r#"return getglobal("FauxScrollFrameTemplateScrollBar") == nil"#)
|
|
.unwrap(),
|
|
"nothing may be published under the template's own name"
|
|
);
|
|
|
|
assert!(s.take_errors().is_empty());
|
|
assert_eq!(s.take_warnings(), Vec::<String>::new());
|
|
}
|