the unit frames are the reference's own, and the water has motes in it
- the player, party, target and pet frames are the reference's own files (`BuffFrame`, `UnitFrame`, `CombatFeedback`, `PlayerFrame`, `PartyFrame`, `TargetFrame`, `PetFrame`, in the reference's toc order) — our 2741-line `UnitFrames.xml` and our `PartyFrame.xml` are deleted; four engine verbs built for them (`PartialPlayTime`, `NoPlayTime`, `UnitPlayerControlled`, `UnitIsCivilian`, each answering 1/nil), the whole-UI load bracketed in the counted sound-suppression scope `0x48fbf0` self-brackets (the reference fires a `PlaySound` during load and throws it away), and `GetBillingTimeRested` read off `SMSG_AUTH_RESPONSE`'s third billing field rather than hardcoded; the differences the stock files bring are pinned rather than papered — `PlayerFrame` never hides, an unresolved name is BLANK not "Unknown", the target frame has no numerals at all (a bar hover pops the unit tooltip instead) - what the swap surfaced, each a hard break under the stock files: `UnitBuff`/`UnitDebuff` returned the Classic Era ten-value tuple, name first, where 1.12 returns two and three values TEXTURE first (`0x519500`/`0x5198f0`) — a raise twice per `UNIT_AURA` on any debuffed target, and all 184 corpus call sites bind the 1.12 shape; the power events are named per resource (`UNIT_MANA`/`RAGE`/`FOCUS`/`ENERGY`/`HAPPINESS` and their MAX twins) and we fired the Era pair, so no mana, rage or energy bar could live-update; `SetRaidTarget` is the engine verb and `SetRaidTargetIcon` the FrameXML toggle wrapper — we had registered the wrapper's name on the engine's body, so every raid mark in the unit popup raised - the underwater drift cloud: the reference draws a fixed 4000-mote field whenever the camera eye is inside a liquid — not a particle system, nothing spawns, ages or dies; a population scattered once into a 30 yd camera-relative cube, advected by the camera's own motion plus a gust and wrapped, so the field stands still in the world; benilla had the murk and nothing in it; a dry frame commits no draw, and the submerged one is bounded by the reference's own 666-quad cap (633–644 of 4000 measured live, which is the expected survivor count of a uniform cube through the 90° cone — an independent check on population, distribution and cone at once) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,7 @@ fn player_unit() -> UnitState {
|
||||
class_file: Some("WARRIOR".into()),
|
||||
sex: 2,
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,9 @@ const MOB_B: u64 = 0xF130_0000_0000_0002;
|
||||
|
||||
use super::test_ui::load_ui as load_xml;
|
||||
|
||||
/// The manifest order this file actually ships in: `UiPanels.xml` for `UIFrameFade`,
|
||||
/// `UnitFrames.xml` for the `TargetFrame` the dots anchor to.
|
||||
/// The manifest order this file actually ships in: `UiPanels.xml` for `UIFrameFade`, and the
|
||||
/// reference's own `TargetFrame.xml` for the `TargetFrame` the dots anchor to — `relativeTo` is
|
||||
/// resolved at LOAD, so that file has to precede this one (1751).
|
||||
fn load_combo_frame() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
@@ -24,7 +25,15 @@ fn load_combo_frame() -> UiScript {
|
||||
load_xml(&s, "UnitPopup.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "UnitFrames.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.lua");
|
||||
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\BuffFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UnitFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\CombatFeedback.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\PlayerFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\PartyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\TargetFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\PetFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ComboFrame.xml");
|
||||
s.resolve();
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
|
||||
@@ -271,20 +271,11 @@ const KNOWN: &[Known] = &[
|
||||
under a mouse-enabled parent; `OnClick` dismisses a held spell, 0843). Unwinding \
|
||||
that is the chat window's own piece of work, not this flag's.",
|
||||
},
|
||||
Known {
|
||||
frame: "TargetofTargetHealthBar",
|
||||
flag: Flag::Mouse,
|
||||
why: "a plain StatusBar where the reference inherits TextStatusBar, whose OnEnter/OnLeave \
|
||||
show the value text. Wants the status-text arc, not the flag. (The pet page's XP bar \
|
||||
was this entry's twin until its window became the reference's own.)",
|
||||
},
|
||||
Known {
|
||||
frame: "TargetofTargetManaBar",
|
||||
flag: Flag::Mouse,
|
||||
why: "a plain StatusBar where the reference inherits TextStatusBar, whose OnEnter/OnLeave \
|
||||
show the value text. Wants the status-text arc, not the flag. (The pet page's XP bar \
|
||||
was this entry's twin until its window became the reference's own.)",
|
||||
},
|
||||
// The two `TargetofTarget*Bar` entries RETIRED here (the unit-frame migration), for the same
|
||||
// reason their twin the pet page's XP bar went: they said our bars were plain StatusBars where
|
||||
// the reference inherits `TextStatusBar` — true of our transcription, and no longer true of
|
||||
// anything. `TargetFrame.xml` is the reference's own now and its ToT bars inherit
|
||||
// `TextStatusBar` like every other unit bar. The gate found them itself, which is its job.
|
||||
Known {
|
||||
frame: "TradePlayerItem7",
|
||||
flag: Flag::Mouse,
|
||||
|
||||
@@ -268,7 +268,11 @@ fn the_open_menu_takes_the_screen_and_refuses_every_other_panel() {
|
||||
// what it needs and why, and PartyFrame's `MAX_PARTY_MEMBERS` is needed at LOAD.
|
||||
"UIDropDownMenu.xml",
|
||||
"UnitPopup.xml",
|
||||
"PartyFrame.xml",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
"Interface\\FrameXML\\TextStatusBar.xml",
|
||||
"Interface\\FrameXML\\UnitFrame.xml",
|
||||
"Interface\\FrameXML\\BuffFrame.xml",
|
||||
"Interface\\FrameXML\\PartyFrame.xml",
|
||||
"Interface\\FrameXML\\ItemButtonTemplate.xml",
|
||||
"Interface\\FrameXML\\LootFrame.xml",
|
||||
],
|
||||
@@ -598,7 +602,11 @@ fn nothing_opens_behind_the_world_map_and_escape_closes_it_first() {
|
||||
// The loot window is the reference's own since 1751 — see `test_ui::LOOT_UI` for what
|
||||
// each of these buys; `PartyFrame`'s MAX_PARTY_MEMBERS is needed at LOAD time.
|
||||
"UnitPopup.xml",
|
||||
"PartyFrame.xml",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
"Interface\\FrameXML\\TextStatusBar.xml",
|
||||
"Interface\\FrameXML\\UnitFrame.xml",
|
||||
"Interface\\FrameXML\\BuffFrame.xml",
|
||||
"Interface\\FrameXML\\PartyFrame.xml",
|
||||
"Interface\\FrameXML\\ItemButtonTemplate.xml",
|
||||
"Interface\\FrameXML\\LootFrame.xml",
|
||||
]);
|
||||
|
||||
@@ -51,6 +51,7 @@ fn target_unit() -> UnitState {
|
||||
class_file: Some("PALADIN".into()),
|
||||
sex: 2,
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,15 @@ fn the_self_menu_row_gates_on_the_binding_and_confirms_before_sending() {
|
||||
"GameTooltip.xml",
|
||||
"UIDropDownMenu.xml",
|
||||
"UnitPopup.xml",
|
||||
"UnitFrames.xml",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
"Interface\\FrameXML\\TextStatusBar.xml",
|
||||
"Interface\\FrameXML\\BuffFrame.xml",
|
||||
"Interface\\FrameXML\\UnitFrame.xml",
|
||||
"Interface\\FrameXML\\CombatFeedback.xml",
|
||||
"Interface\\FrameXML\\PlayerFrame.xml",
|
||||
"Interface\\FrameXML\\PartyFrame.xml",
|
||||
"Interface\\FrameXML\\TargetFrame.xml",
|
||||
"Interface\\FrameXML\\PetFrame.xml",
|
||||
"RaidFrame.xml",
|
||||
] {
|
||||
load_xml(&s, file);
|
||||
|
||||
@@ -381,6 +381,7 @@ pub(crate) fn seat_from_roster(
|
||||
_ => 0,
|
||||
},
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
// Nil here is not "no faction", it is a state a player character cannot be in, and
|
||||
// AceDB-2.0 concatenates it at file scope — see [`crate::ui_unit::race_faction_group`].
|
||||
faction_group: crate::ui_unit::race_faction_group(row.race).map(str::to_string),
|
||||
|
||||
@@ -154,8 +154,20 @@ end
|
||||
/// the addon harness ([`crate::addon_harness`]), which needs our entire interface under each
|
||||
/// surveyed addon.
|
||||
pub(crate) fn load_default_ui(script: &UiScript) -> Vec<String> {
|
||||
// **Silent, the way the client's own load is.** `0x48fbf0` brackets ITSELF in the counted
|
||||
// sound-suppression scope — `0x48fbfa call 0x458f50` on entry, `0x49016d call 0x458f60` on
|
||||
// exit — across the TOC walk, Bindings.xml and the AddOns, so both of its callers (login
|
||||
// `0x48f681` and `/reloadui` `0x495669`) load without a sound. The only reader of that depth
|
||||
// is `PlaySoundByName 0x458030`, which drops the call outright.
|
||||
//
|
||||
// This is the mechanism, not a workaround for one noisy handler: stock `TargetFrame_OnHide`
|
||||
// really does fire at load (the frame ships shown and its OnLoad hides it) and really does
|
||||
// call `PlaySound("INTERFACESOUND_LOSTTARGETUNIT")`. The engine throws it away. Decision 1033
|
||||
// reached the right rule from the director's ear; this is the binary agreeing.
|
||||
script.push_sound_suppression();
|
||||
let mut failures = load_manifest(script, &Addon::builtin().toc.files);
|
||||
failures.extend(bootstrap_positions(script));
|
||||
script.pop_sound_suppression();
|
||||
failures
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
//! units tall every frame, and [`seam_scale`] carries quads ×s out / mouse ÷s in. The quad pass
|
||||
//! wants y-down window px, so extraction also flips through the window height.
|
||||
//!
|
||||
//! Benilla's own **unit frames** (`assets/ui/UnitFrames.xml` — a template + the player and target
|
||||
//! instances) load at startup by default through [`benilla_ui::loader`] — the decision 0068 slice-1
|
||||
//! The **unit frames** (the reference's own `Interface\FrameXML\{PlayerFrame,PartyFrame,
|
||||
//! TargetFrame,PetFrame}.xml`, off the player's chain since 1751 — our `UnitFrames.xml`
|
||||
//! transcription is retired) load at startup through [`benilla_ui::loader`] — the decision 0068 slice-1
|
||||
//! game-shell: real FrameXML+Lua rendering unit health/power/level/name through the whole chain
|
||||
//! (snapshot → `Unit*` bindings → Lua → event → StatusBars+text → quad pass). Since captures run
|
||||
//! server-less (no real game state), `WOW_CAPTURE_UI=1` also feeds synthetic `"player"`/`"target"`
|
||||
@@ -550,6 +551,7 @@ fn demo_unit_feed(script: Option<NonSendMut<UiScript>>, mut fired: Local<VmMemo<
|
||||
class_file: Some("WARRIOR".into()),
|
||||
sex: 2,
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -730,11 +730,23 @@ fn interface_harness() -> UiScript {
|
||||
"UnitPopup.xml",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
"Interface\\FrameXML\\TextStatusBar.xml",
|
||||
"UnitFrames.xml",
|
||||
"Interface\\FrameXML\\BuffFrame.xml",
|
||||
"Interface\\FrameXML\\UnitFrame.xml",
|
||||
"Interface\\FrameXML\\CombatFeedback.xml",
|
||||
"Interface\\FrameXML\\PlayerFrame.xml",
|
||||
"Interface\\FrameXML\\PartyFrame.xml",
|
||||
"Interface\\FrameXML\\TargetFrame.xml",
|
||||
"Interface\\FrameXML\\PetFrame.xml",
|
||||
// `PartyMemberBackground`'s OnEvent sets `OpacityFrameSlider` on VARIABLES_LOADED,
|
||||
// and that slider is declared in ColorPickerFrame.xml. The reference loads them in
|
||||
// this same order (its toc: PartyFrame 45, ColorPickerFrame 84) — it works there
|
||||
// because the reader is an EVENT handler, not a load-time one, so by the time
|
||||
// VARIABLES_LOADED fires the slider exists. A test that fires the event has to have
|
||||
// loaded it too.
|
||||
"Interface\\FrameXML\\ColorPickerFrame.xml",
|
||||
"Cooldown.xml",
|
||||
"ActionBar.xml",
|
||||
"ScrollTemplates.xml",
|
||||
"Interface\\FrameXML\\BuffFrame.xml",
|
||||
"Interface\\FrameXML\\MerchantFrame.xml",
|
||||
"QuestFrame.xml",
|
||||
"QuestLogFrame.xml",
|
||||
|
||||
@@ -31,7 +31,15 @@ fn load_pet_frame() -> UiScript {
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "UIDropDownMenu.xml");
|
||||
load_xml(&s, "UnitPopup.xml");
|
||||
load_xml(&s, "UnitFrames.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.lua");
|
||||
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\BuffFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UnitFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\CombatFeedback.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\PlayerFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\PartyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\TargetFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\PetFrame.xml");
|
||||
s.set_unit(
|
||||
"player",
|
||||
Some(UnitState {
|
||||
@@ -61,6 +69,12 @@ fn pet(name: &str, health: u32, power: u32, max_power: u32, power_type: u8) -> U
|
||||
power_type,
|
||||
power,
|
||||
max_power,
|
||||
// Every token the app's feed pushes is connected (`ui_unit.rs`, "the stated
|
||||
// `is_connected` gap, closed"). It matters to the reference's own bar: stock
|
||||
// `UnitFrameManaBar_Update` (UnitFrame.lua:213) greys a DISCONNECTED unit's bar to
|
||||
// (0.5,0.5,0.5) and only reaches `UnitFrame_UpdateManaType`'s power colour otherwise, so a
|
||||
// fixture that leaves this false paints every bar grey and tests nothing about power type.
|
||||
is_connected: true,
|
||||
..UnitState::default()
|
||||
}
|
||||
}
|
||||
@@ -92,7 +106,9 @@ fn draws(s: &mut UiScript, path: &str) -> bool {
|
||||
drawn(s).iter().any(|(p, _)| p == path)
|
||||
}
|
||||
|
||||
fn debuff(spell_id: u32, name: &str, count: u8) -> AuraState {
|
||||
/// One aura on the pet's row. **Helpful**, because the row the pet frame fills is a BUFF row —
|
||||
/// see [`the_debuff_row_fills_from_the_pets_own_auras`] for the reference's own `showBuffs = 1`.
|
||||
fn pet_buff(spell_id: u32, name: &str, count: u8) -> AuraState {
|
||||
AuraState {
|
||||
spell_id,
|
||||
name: Some(name.into()),
|
||||
@@ -102,7 +118,7 @@ fn debuff(spell_id: u32, name: &str, count: u8) -> AuraState {
|
||||
// No unit but yourself carries a duration on the 1.12 wire (decision 0257 B6).
|
||||
duration: 0.0,
|
||||
expiration_time: 0.0,
|
||||
helpful: false,
|
||||
helpful: true,
|
||||
cancelable: false,
|
||||
until_cancelled: false,
|
||||
channeled: false,
|
||||
@@ -183,10 +199,28 @@ fn the_frame_answers_only_the_events_that_name_its_own_unit() {
|
||||
assert!(!s.eval::<bool>("return PetFrame:IsVisible()").unwrap());
|
||||
}
|
||||
|
||||
/// The reference's art swap (`PetFrame.lua`'s Update): a pet with no power wears the `-NoMana`
|
||||
/// The reference's art swap (`PetFrame.lua:29-33`): a pet with no power wears the `-NoMana`
|
||||
/// plate, which has no mana rail to leave empty — and the bar hides with it. A hunter's FOCUS pet
|
||||
/// takes the plain plate and the focus colour, which is the case the mana-coloured default would
|
||||
/// get wrong.
|
||||
///
|
||||
/// **Two things about the reference's wiring that our transcription did not have.**
|
||||
///
|
||||
/// *The plate's only wire is `UNIT_PET`.* `PetFrame_Update` — the sole caller of the
|
||||
/// `UnitManaMax("pet") == 0` fork — runs from `PetFrame_OnEvent`'s `UNIT_PET`/`arg1 == "player"`
|
||||
/// arm and from the frame's `OnShow` (`PetFrame.lua:46-49`, `PetFrame.xml:284-287`), and from
|
||||
/// nothing else. This fixture used to poke `UNIT_MAXPOWER`, which the stock frame does not
|
||||
/// register at all — nor could it, since the bar's own registrations are 1.12's
|
||||
/// `UNIT_MANA`/`UNIT_MAXMANA`/… (`UnitFrame.lua:190-200`) rather than our Era pair. Swapping one
|
||||
/// pet for a differently-powered one is a `UNIT_PET` on the wire, which is the honest edge here.
|
||||
///
|
||||
/// *Nothing in `PetFrame.lua` hides the bar.* The hide comes out of `TextStatusBar`: the bar
|
||||
/// inherits that template's `<OnValueChanged>` (`TextStatusBar.xml:32-34` — `PetFrame.xml:161-169`
|
||||
/// overrides only OnLoad/OnEvent), so `UnitFrameManaBar_Update`'s `SetMinMaxValues(0, 0)`
|
||||
/// (`UnitFrame.lua:211-212`) reaches `TextStatusBar_UpdateTextString`, whose `valueMax > 0` else
|
||||
/// branch is `textStatusBar:Hide()` (`TextStatusBar.lua:34/55-57`). Same visible answer as our
|
||||
/// transcription's explicit `Hide()`, by a different and much longer road — which is worth knowing,
|
||||
/// because it means the bar comes back only through a value change, never through `PetFrame.lua`.
|
||||
#[test]
|
||||
fn a_powerless_pet_wears_the_no_mana_plate() {
|
||||
let mut s = load_pet_frame();
|
||||
@@ -208,9 +242,10 @@ fn a_powerless_pet_wears_the_no_mana_plate() {
|
||||
assert!(draws(&mut s, PLAIN), "the plate with a mana rail");
|
||||
assert!(!draws(&mut s, NO_MANA));
|
||||
|
||||
// A skeleton: no power at all.
|
||||
// A skeleton: no power at all. `UNIT_PET` names the OWNER — see the doc above for why it, and
|
||||
// not a power event, is what re-cuts the plate.
|
||||
s.set_unit("pet", Some(pet("Skeleton", 100, 0, 0, 0)));
|
||||
s.fire_event("UNIT_MAXPOWER", vec![ScriptValue::Str("pet".into())]);
|
||||
s.fire_event("UNIT_PET", vec![ScriptValue::Str("player".into())]);
|
||||
assert!(
|
||||
!s.eval::<bool>("return PetFrameManaBar:IsVisible()")
|
||||
.unwrap(),
|
||||
@@ -259,8 +294,25 @@ fn the_attack_overlay_follows_its_own_two_events() {
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
/// The four debuff buttons fill from `UnitDebuff("pet", i)` in order, show a stack count only above
|
||||
/// 1, and the unused ones hide. `UNIT_AURA("pet")` is the wire.
|
||||
/// The four `$parentDebuff` buttons fill from the pet's own auras in order and the unused ones
|
||||
/// hide. `UNIT_AURA("pet")` is the wire (`PetFrame.lua:54-57`).
|
||||
///
|
||||
/// **Two things the reference does that our deleted transcription did not**, both worth stating
|
||||
/// because the frames' NAMES argue for the opposite on both counts:
|
||||
///
|
||||
/// 1. **The row shows BUFFS, not debuffs**, despite every frame in it being called
|
||||
/// `PetFrameDebuffN`. `PetFrame.lua:37`/`:56` are the only two `RefreshBuffs` calls in the whole
|
||||
/// of 1.12's FrameXML that pass `showBuffs = 1`, and that argument selects
|
||||
/// `UnitBuff(unit, i, SHOW_CASTABLE_BUFFS)` at `BuffFrame.lua:277`. (Every other caller —
|
||||
/// `PartyMemberFrame.lua:60`/`:176`/`:259`, `TargetFrame.lua:538` — passes `0`, which is the
|
||||
/// `UnitDebuff` arm at `:280`.) Our `BenillaPetFrame_UpdateDebuffs` read `UnitDebuff("pet", i)`,
|
||||
/// so a hunter whose pet was buffed saw an empty row and a poisoned pet saw icons the reference
|
||||
/// would not show.
|
||||
/// 2. **There is no stack count.** `RefreshBuffs` sets an icon and a border and nothing else
|
||||
/// (`BuffFrame.lua:287-301`), and `PartyBuffButtonTemplate` — the template all four inherit
|
||||
/// (`PetFrame.xml:173`/`:188`/`:203`/`:218`) — declares only `$parentIcon` and `$parentBorder`
|
||||
/// (`PartyFrameTemplates.xml:3-36`). `PetFrameDebuff2Count` is not a frame in the reference's
|
||||
/// house at all; the old assertion was reading a FontString our transcription invented.
|
||||
#[test]
|
||||
fn the_debuff_row_fills_from_the_pets_own_auras() {
|
||||
let mut s = load_pet_frame();
|
||||
@@ -269,7 +321,7 @@ fn the_debuff_row_fills_from_the_pets_own_auras() {
|
||||
|
||||
s.set_auras(
|
||||
"pet",
|
||||
Some(vec![debuff(1000, "Rend", 1), debuff(1001, "Sunder", 3)]),
|
||||
Some(vec![pet_buff(1000, "Rend", 1), pet_buff(1001, "Sunder", 3)]),
|
||||
);
|
||||
s.fire_event("UNIT_AURA", vec![ScriptValue::Str("pet".into())]);
|
||||
|
||||
@@ -280,15 +332,23 @@ fn the_debuff_row_fills_from_the_pets_own_auras() {
|
||||
and PetFrameDebuff2:IsVisible()
|
||||
and not PetFrameDebuff3:IsVisible()
|
||||
and not PetFrameDebuff4:IsVisible()
|
||||
and not PetFrameDebuff1Count:IsVisible()
|
||||
and PetFrameDebuff2Count:IsVisible()
|
||||
and PetFrameDebuff2Count:GetText() == "3"
|
||||
-- The reference has no count region on this template at all — not a hidden one.
|
||||
and getglobal("PetFrameDebuff1Count") == nil
|
||||
and getglobal("PetFrameDebuff2Count") == nil
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(ok, "two debuffs draw, a stack count shows only above 1");
|
||||
// …and the icons are the pet's own auras, in the pet's own order.
|
||||
assert!(draws(&mut s, "Interface\\Icons\\Spell_1000"));
|
||||
assert!(ok, "two auras draw, the other two rows stay down");
|
||||
// …and the icons are the pet's own auras, in the pet's own order. `RefreshBuffs` paints them
|
||||
// with `debuffIcon:SetTexture(debuff)` where `debuff` is `UnitBuff`'s FIRST return
|
||||
// (`BuffFrame.lua:277`/`:288`) — which on the 1.12 wire is the texture path
|
||||
// (`TargetFrame.lua:269-272` uses it the same way, and `:287-303` reads the debuff triple as
|
||||
// `texture, applications, dispelType`).
|
||||
assert!(
|
||||
draws(&mut s, "Interface\\Icons\\Spell_1000"),
|
||||
"the first buff's ICON draws — if this fails with the aura's NAME on the row instead, \
|
||||
`UnitBuff`'s first return is the Era signature's `name`, not 1.12's texture"
|
||||
);
|
||||
assert!(draws(&mut s, "Interface\\Icons\\Spell_1001"));
|
||||
|
||||
// They clear when the auras do.
|
||||
@@ -523,12 +583,24 @@ fn unit_happiness_repaints_only_the_icon() {
|
||||
/// The layering law (decision 0884), the pet frame's copy of the player/target/party tests: the
|
||||
/// frame art must draw OVER the bar fills, which only the frame LEVEL can hold — the draw layer is
|
||||
/// bucket-wide. A "simplification" back to declaration order puts the bars on top as pasted slabs.
|
||||
///
|
||||
/// **The art frame has no name in the reference, so it is reached through its texture.** Our
|
||||
/// deleted transcription gave it one (`PetFrameTextureFrame`); stock `PetFrame.xml:52-124` builds
|
||||
/// it as two ANONYMOUS `<Frame setAllPoints="true">` wrappers, one inside the other, with
|
||||
/// `PetFrameTexture` in the inner one's BACKGROUND layer. That nesting is how the reference buys
|
||||
/// the level: `PetFrame` is 2, its direct children (both bars, `PetFrame.xml:125`/`:150`) are 3,
|
||||
/// and the doubly-nested art frame is 4. The law is unchanged — the art is one level above the
|
||||
/// bars — only the way to name the frame is, so this asks the texture for its parent.
|
||||
///
|
||||
/// It is also *why* the reference needs no `PetFrameManaBar:Hide()` of its own for the
|
||||
/// [`a_powerless_pet_wears_the_no_mana_plate`] case: the `-NoMana` plate is painted a level ABOVE
|
||||
/// the mana bar and simply covers the rail.
|
||||
#[test]
|
||||
fn the_pet_art_paints_over_the_bars() {
|
||||
let s = load_pet_frame();
|
||||
let level: (i64, i64, i64) = s
|
||||
.eval(
|
||||
"return PetFrameTextureFrame:GetFrameLevel(), \
|
||||
"return PetFrameTexture:GetParent():GetFrameLevel(), \
|
||||
PetFrameHealthBar:GetFrameLevel(), \
|
||||
PetFrameManaBar:GetFrameLevel()",
|
||||
)
|
||||
|
||||
@@ -47,6 +47,7 @@ fn player_unit() -> UnitState {
|
||||
class_file: Some("HUNTER".into()),
|
||||
sex: 2,
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
..UnitState::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +142,7 @@ fn seat_a_hunter(root: &Path) -> UiScript {
|
||||
class_file: Some("HUNTER".into()),
|
||||
sex: 2,
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
faction_group: Some("Alliance".into()),
|
||||
..Default::default()
|
||||
}),
|
||||
|
||||
@@ -1044,10 +1044,11 @@ mod tests {
|
||||
/// A window that reads unblocked in [`chain_gap_report`] can still fail to swap, and the third
|
||||
/// reason found (after a missing widget type and a missing widget method) is this one: our
|
||||
/// `assets/ui` file defines a global the reference defines too, our manifest loads a CHAIN file
|
||||
/// that also defines it, and whichever lands second wins. `PartyFrame.xml` is the worked
|
||||
/// example — `UnitFrames.xml` redefines `UnitFrame_OnEvent`/`UnitFrame_Update` nine manifest
|
||||
/// lines after stock `UnitFrame.lua` defines them, so a stock party row built by the
|
||||
/// reference's own `UnitFrame_Initialize` calls OUR update and indexes a field its rows do not
|
||||
/// that also defines it, and whichever lands second wins. `PartyFrame.xml` was the worked
|
||||
/// example, and it is worth keeping in the past tense because it is what this check was built
|
||||
/// to catch: our `UnitFrames.xml` redefined `UnitFrame_OnEvent`/`UnitFrame_Update` nine
|
||||
/// manifest lines after stock `UnitFrame.lua` defined them, so a stock party row built by the
|
||||
/// reference's own `UnitFrame_Initialize` called OUR update and indexed a field its rows do not
|
||||
/// carry. It loads clean and raises on the first event.
|
||||
///
|
||||
/// Shadowing is not automatically wrong — where we ship a file the reference would have
|
||||
@@ -1062,10 +1063,11 @@ mod tests {
|
||||
/// both would declare the same frames.
|
||||
///
|
||||
/// That half doubles as the map nothing else holds: `ActionBarFrame.xml` is our
|
||||
/// `ActionBar.xml`, `FloatingChatFrame.xml` is our `ChatFrame.xml`, `PlayerFrame.xml` and
|
||||
/// `TargetFrame.xml` and `PetFrame.xml` are all our one `UnitFrames.xml`,
|
||||
/// `ActionBar.xml`, `FloatingChatFrame.xml` is our `ChatFrame.xml`,
|
||||
/// `MainMenuBarMicroButtons.xml` is our `MicroMenu.xml`, `StaticPopup.xml` is our
|
||||
/// `UiPanels.xml`. `chain_gap_report` calls several of those unblocked, and they are —
|
||||
/// `UiPanels.xml`. (The row this map used to lead with — `PlayerFrame.xml`/`TargetFrame.xml`/
|
||||
/// `PetFrame.xml` all being our one `UnitFrames.xml` — is retired: those four are the
|
||||
/// reference's own files now, 1751.) `chain_gap_report` calls several of those unblocked, and they are —
|
||||
/// individually. They just cannot load beside the file of ours already holding their names.
|
||||
///
|
||||
/// Run it before attempting a swap. It predicts which ones will fail without attempting them.
|
||||
@@ -1083,8 +1085,10 @@ mod tests {
|
||||
// and missed the case it was built for), because a reference can live in any file. A
|
||||
// declaration cannot.
|
||||
//
|
||||
// What it catches: our `UnitFrames.xml` declares `PetFrame`, and so does the stock
|
||||
// `PetFrame.xml`. Adding that stock file alongside ours would declare the name twice.
|
||||
// What it catches, in the case it was built for: our `UnitFrames.xml` declared `PetFrame`,
|
||||
// and so does the stock `PetFrame.xml`, so adding that stock file alongside ours would have
|
||||
// declared the name twice. (That pair is resolved — ours is deleted — but the check is not
|
||||
// about those two files; every window still ahead of 1751 has the same collision waiting.)
|
||||
// Several stock windows we do not ship under their own name have an equivalent of ours
|
||||
// under a different one, and `chain_gap_report` calls every one of them unblocked —
|
||||
// truthfully, because the stock file WOULD load; it just cannot load *beside* ours.
|
||||
@@ -1442,17 +1446,6 @@ mod tests {
|
||||
"the twin of the line above, `QuestLogFrame.lua:107`, called on the next line \
|
||||
(`QuestTimerFrame.lua:44`).",
|
||||
),
|
||||
(
|
||||
"UnitFrame.xml",
|
||||
"UnitPlayerControlled",
|
||||
"an engine binding, and the most reachable gap here: `UnitFrame_OnEnter` \
|
||||
(`UnitFrame.lua:62`) calls it while HOVERING any unit frame, behind \
|
||||
`SHOW_NEWBIE_TIPS == \"1\"` — which is the shipped default. wow-re has it carved \
|
||||
(`0x516410`, VERIFIED, `scratch/unit-verbs-controlled-charmed-creaturetype.md`): \
|
||||
one return, `UNIT_FIELD_FLAGS` bit 3, the DOUBLE 1.0 when set and nil when clear, \
|
||||
raising on an unrecognised token. `UnitState` carries no such flag yet, so it is \
|
||||
a field + a push + a binding — the unit frames' own slice.",
|
||||
),
|
||||
];
|
||||
|
||||
let toc = &super::super::addons::Addon::builtin().toc.files;
|
||||
|
||||
@@ -736,9 +736,10 @@ fn no_shipped_script_sets_a_global_string_key_as_display_text() {
|
||||
/// otherwise lift an ARTWORK bar fill straight over BACKGROUND art.
|
||||
///
|
||||
/// Decision 0884 pinned the layer as bucket-wide and above the frame. Two files had been getting
|
||||
/// this right for the wrong reason — `UnitFrames.xml` and `PartyFrame.xml` both *declared* the
|
||||
/// this right for the wrong reason — our `UnitFrames.xml` and `PartyFrame.xml` both *declared* the
|
||||
/// TextureFrame after the bars and leaned on the retired key's insertion-order tie-break — so both
|
||||
/// inverted the instant the key was corrected, and both reached the director's screen. The
|
||||
/// inverted the instant the key was corrected, and both reached the director's screen. (Both files
|
||||
/// are retired now, 1751; the lesson is kept because it is about the KEY, not about them.) The
|
||||
/// reference spends a real frame level on this in both of its own spellings (`TargetFrame.lua`
|
||||
/// l.32-34's explicit `SetFrameLevel(textureFrame-1)`, `PlayerFrame.xml` l.50-52's two anonymous
|
||||
/// nesting frames); benilla now uses the first, everywhere.
|
||||
@@ -760,7 +761,15 @@ fn every_texture_frame_outranks_its_status_bars() {
|
||||
assert!(failures.is_empty(), "manifest load errors: {failures:#?}");
|
||||
// Every unit-frame family painting at once: the frames hide themselves without a unit, and a
|
||||
// hidden frame emits no quads to read a level from.
|
||||
for unit in ["player", "target", "party1", "party2", "party3", "party4"] {
|
||||
for unit in [
|
||||
"player",
|
||||
"target",
|
||||
"targettarget",
|
||||
"party1",
|
||||
"party2",
|
||||
"party3",
|
||||
"party4",
|
||||
] {
|
||||
s.set_unit(
|
||||
unit,
|
||||
Some(benilla_ui::script::UnitState {
|
||||
@@ -778,6 +787,13 @@ fn every_texture_frame_outranks_its_status_bars() {
|
||||
}
|
||||
s.fire_event("PLAYER_ENTERING_WORLD", vec![]);
|
||||
s.fire_event("PARTY_MEMBERS_CHANGED", vec![]);
|
||||
// The target-of-target frame ships OFF (`SHOW_TARGET_OF_TARGET = "0"`, the reference's own
|
||||
// default, declared in OptionsFrame.xml which the manifest above has just loaded). Turn it on
|
||||
// here: it is the SECOND of the two named TextureFrames the reference spells this way, and a
|
||||
// gate that only ever sees the first covers half of what it claims to.
|
||||
s.run(r#"SHOW_TARGET_OF_TARGET = "1""#).unwrap();
|
||||
s.run("this = TargetofTargetFrame TargetofTarget_Update() this = nil")
|
||||
.unwrap();
|
||||
s.resolve();
|
||||
|
||||
// owner frame name → its (strata, level), read off the packed draw key the renderer sorts by.
|
||||
@@ -812,18 +828,22 @@ fn every_texture_frame_outranks_its_status_bars() {
|
||||
checked += 1;
|
||||
}
|
||||
}
|
||||
// Player + target (health/power) and four party members (health/mana) — never let this pass by
|
||||
// matching nothing, which is exactly how a renamed frame would silently retire the check.
|
||||
// **The predicted reduction, arrived.** This asserted 12 while the unit frames were ours,
|
||||
// because our transcription spelled the idiom with a named `$parentTextureFrame` on EVERY
|
||||
// family. The reference does not: it has exactly TWO named ones, `TargetFrameTextureFrame` and
|
||||
// `TargetofTargetTextureFrame`, both in TargetFrame.xml. `PlayerFrame`, `PetFrame` and the
|
||||
// party rows reach the same order the other way — the art inside ANONYMOUS nested
|
||||
// `<Frame setAllPoints="true">` wrappers, outranking the bars by nesting depth rather than by
|
||||
// a level on a name. There is nothing for a name sweep to look up, so those families move from
|
||||
// this assertion's cover to the loader's nesting rule.
|
||||
//
|
||||
// **This number drops to 6 the day `PartyFrame.xml` goes to the chain, and that will be a real
|
||||
// reduction rather than a rename.** Stock `PartyFrameTemplates.xml` has no
|
||||
// `$parentTextureFrame` at all: it gets the same result a different way, with the art inside a
|
||||
// pair of ANONYMOUS nested `<Frame setAllPoints="true">` wrappers, so it outranks the bars by
|
||||
// nesting depth rather than by a level on a named frame. There is nothing to look up, so the
|
||||
// party rows will move from this assertion's cover to the loader's nesting rule. Written down
|
||||
// now, while the reason is in front of somebody, rather than discovered as a number to lower.
|
||||
// The note above this line predicted the drop when PartyFrame went to the chain and called it
|
||||
// "a real reduction rather than a rename". That was right, and it under-counted: the whole kit
|
||||
// went at once, so player and pet left by the same door. Two families × (HealthBar, ManaBar,
|
||||
// ManaBar) = 6 is the reference's own coverage, and it is the floor now. Still never zero —
|
||||
// matching nothing is exactly how a renamed frame would silently retire the check.
|
||||
assert!(
|
||||
checked >= 12,
|
||||
checked >= 6,
|
||||
"only {checked} texture-frame/bar pairs checked — the name sweep found nothing"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
//! The target frame's aura rows (`assets/ui/UnitFrames.xml`, decision 0255's deferred slice)
|
||||
//! against the reference's `TargetDebuffButton_Update` law (ref-TargetFrame.lua l.263-387). The
|
||||
//! XML/Lua is the unit under test; the app-side feed (`crate::ui_aura`'s target half) is stubbed
|
||||
//! by pushing an [`AuraState`] list through [`UiScript::set_auras`] and firing the events the feed
|
||||
//! fires — `PLAYER_TARGET_CHANGED` on a switch, `UNIT_AURA "target"` on a list change.
|
||||
//! The target frame's aura rows — the **reference's own** `Interface\FrameXML\TargetFrame.xml`
|
||||
//! off the player's patch chain (decision 1751, which retired our `assets/ui/UnitFrames.xml`
|
||||
//! transcription) — against its `TargetDebuffButton_Update` law (ref TargetFrame.lua l.263-387).
|
||||
//! The stock XML/Lua is the unit under test; the app-side feed (`crate::ui_aura`'s target half) is
|
||||
//! stubbed by pushing an [`AuraState`] list through [`UiScript::set_auras`] and firing the events
|
||||
//! the feed fires — `PLAYER_TARGET_CHANGED` on a switch, `UNIT_AURA "target"` on a list change.
|
||||
//!
|
||||
//! Under test: the friend/hostile row swap (buffs first vs debuffs first), the 21→17 shrink when
|
||||
//! the debuff count reaches the wrap (6, no target-of-target frame), the dispel-tinted border, the
|
||||
//! stack count, and the hide-on-empty lifecycle. Files load in the app's own order (UnitFrames
|
||||
//! before BuffFrame), which also pins the `DebuffTypeColor` late-definition contract.
|
||||
//! stack count, and the hide-on-empty lifecycle. Files load in `benilla.toc`'s order, which is the
|
||||
//! reference's own (BuffFrame 40 → CombatFeedback 41 → UnitFrame 43 → PlayerFrame 44 → PartyFrame
|
||||
//! 45 → TargetFrame 46 → PetFrame 47), so `DebuffTypeColor` is defined ahead of the row that
|
||||
//! indexes it and `RefreshBuffs` ahead of the party rows that call it from their own OnLoad.
|
||||
|
||||
use benilla_ui::script::{AuraState, QuadContent, ScriptValue, UiScript, UnitState};
|
||||
|
||||
@@ -18,12 +21,31 @@ fn harness() -> UiScript {
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "GameTooltip.xml"); // TOOLTIP_DEFAULT_* (the dropdown kit's MenuBackdrop)
|
||||
load_xml(&s, "UIDropDownMenu.xml"); // the unit popups' kit (UnitFrames' DropDown children)
|
||||
load_xml(&s, "UIDropDownMenu.xml"); // the unit popups' kit (TargetFrameDropDown's template)
|
||||
load_xml(&s, "UnitPopup.xml");
|
||||
load_xml(&s, "Cooldown.xml");
|
||||
load_xml(&s, "ActionBar.xml"); // BENILLA_FALLBACK_ICON
|
||||
load_xml(&s, "UnitFrames.xml"); // loads BEFORE BuffFrame, as in the app
|
||||
load_xml(&s, "Interface\\FrameXML\\BuffFrame.xml"); // DebuffTypeColor
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.lua");
|
||||
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\BuffFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UnitFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\CombatFeedback.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\PlayerFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\PartyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\TargetFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\PetFrame.xml");
|
||||
|
||||
// **Settle the target-of-target frame before any of this measures a row.** Stock
|
||||
// `TargetofTargetFrame` carries no `hidden=` (ref TargetFrame.xml l.515), so it loads SHOWN,
|
||||
// and the only thing that takes it down is `TargetofTarget_Update` — which
|
||||
// `TargetFrame_OnEvent` runs *after* `TargetDebuffButton_Update` (ref TargetFrame.lua l.63-66,
|
||||
// the update sitting inside `TargetFrame_Update` at l.51). So the very first target a freshly
|
||||
// loaded client acquires lays its aura rows out for the 5-wide wrap, and only the next
|
||||
// `TargetDebuffButton_Update` corrects them. One no-target `PLAYER_TARGET_CHANGED` runs that
|
||||
// ladder to its end — exactly what deselecting once does — and leaves the frame in the state
|
||||
// it holds for every target after the first, which is the state these tests are about.
|
||||
s.fire_event("PLAYER_TARGET_CHANGED", vec![]);
|
||||
s
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,19 @@ pub(super) const LOOT_UI: &[&str] = &[
|
||||
"GameTooltip.xml", // TOOLTIP_DEFAULT_COLOR, read by the dropdown backdrop
|
||||
"UIDropDownMenu.xml", // GroupLootDropDown's OnLoad calls UIDropDownMenu_Initialize
|
||||
"UnitPopup.xml",
|
||||
"PartyFrame.xml",
|
||||
// …and what its rows' OnLoad calls: every `PartyMemberFrame<N>` and its pet frame runs
|
||||
// `UnitFrame_Initialize`, which lives in UnitFrame.lua and itself calls
|
||||
// `SetTextStatusBarText` out of TextStatusBar.lua. Naming PartyFrame without these loads
|
||||
// four rows that each raise on their own OnLoad — the loader reports it, but only because
|
||||
// `load_ui_strict` looks; a plain load would have gone quiet.
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
"Interface\\FrameXML\\TextStatusBar.xml",
|
||||
"Interface\\FrameXML\\UnitFrame.xml",
|
||||
// …and `RefreshBuffs`, which `PartyMemberFrame.lua:60` calls from each row's OnLoad. Its home
|
||||
// is BuffFrame.lua, which the chain's BuffFrame.xml sources — the reference's own toc has
|
||||
// BuffFrame at 40 and PartyFrame at 45 for exactly this reason.
|
||||
"Interface\\FrameXML\\BuffFrame.xml",
|
||||
"Interface\\FrameXML\\PartyFrame.xml",
|
||||
];
|
||||
|
||||
/// What the **character window** needs before `Interface\FrameXML\CharacterFrame.xml`,
|
||||
@@ -156,10 +168,13 @@ pub(super) const LOOT_UI: &[&str] = &[
|
||||
/// three times before doing anything else. Its `.xml` twin comes too, because the pet page's XP
|
||||
/// bar inherits the `TextStatusBar` template it declares and a missing template is a loader
|
||||
/// *warning* — the bar would load with no art and no text region and read as a pass.
|
||||
/// * **`UnitFrames.xml`** for `PlayerFrameHealthBar` / `PlayerFrameManaBar`, the frames those three
|
||||
/// calls name, plus `PetFrameHealthBar` / `PetFrameManaBar` which `CharacterFrame_OnShow` shows
|
||||
/// the text on. (Ours: the unit frames are not migrated. When they are, this entry becomes the
|
||||
/// stock `PlayerFrame.xml` / `PetFrame.xml` pair.)
|
||||
/// * **`Interface\FrameXML\PlayerFrame.xml`** for `PlayerFrameHealthBar` / `PlayerFrameManaBar`,
|
||||
/// the frames those three calls name, and **`PetFrame.xml`** for `PetFrameHealthBar` /
|
||||
/// `PetFrameManaBar`, which `CharacterFrame_OnShow` shows the text on. Both call
|
||||
/// `UnitFrame_Initialize` in their OnLoad and `PlayerFrame` also calls `CombatFeedback_Initialize`,
|
||||
/// so `UnitFrame.xml` and `CombatFeedback.xml` come first — the reference's own toc order.
|
||||
/// (This entry used to be our one `UnitFrames.xml`, and this note used to say "when they are
|
||||
/// migrated, this becomes the stock pair". They are; it did.)
|
||||
/// * **`ActionBar.xml`** for `MainMenuExpBar` — the third bar of that prefix call — and for
|
||||
/// `ShowWatchedReputationBarText` / `HideWatchedReputationBarText`, which the window's
|
||||
/// show/hide pair calls.
|
||||
@@ -199,7 +214,18 @@ pub(super) const CHARACTER_UI: &[&str] = &[
|
||||
"UnitPopup.xml",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
"Interface\\FrameXML\\TextStatusBar.xml",
|
||||
"UnitFrames.xml",
|
||||
// `UnitFrame_Initialize` and `CombatFeedback_Initialize`, which the two windows below call in
|
||||
// their OnLoad. Neither file declares a frame; both are pure script.
|
||||
"Interface\\FrameXML\\UnitFrame.xml",
|
||||
"Interface\\FrameXML\\CombatFeedback.xml",
|
||||
"Interface\\FrameXML\\PlayerFrame.xml",
|
||||
// `PetFrame.xml`'s four `PetFrameBuff*` inherit `PartyBuffButtonTemplate`, which lives here.
|
||||
// The manifest never names this file: it arrives through `PartyFrame.xml`'s
|
||||
// `<Include file="PartyFrameTemplates.xml"/>`, and the reference's own toc puts PartyFrame (45)
|
||||
// ahead of TargetFrame (46) and PetFrame (47) for exactly that reason. This kit wants the
|
||||
// template and not four party member frames, so it takes the included file directly.
|
||||
"Interface\\FrameXML\\PartyFrameTemplates.xml",
|
||||
"Interface\\FrameXML\\PetFrame.xml",
|
||||
"ActionBar.xml",
|
||||
"MicroMenu.xml",
|
||||
// The two page files these three need before the window can be OPENED, which is not the same
|
||||
|
||||
@@ -89,9 +89,31 @@ fn world_hover_seats_the_default_corner() {
|
||||
#[test]
|
||||
fn unit_frame_hover_takes_the_default_corner_and_drops_on_leave() {
|
||||
// The kit + popups precede the unit frames (their DropDown children's OnLoad), app order.
|
||||
let mut s = harness(&["UIDropDownMenu.xml", "UnitPopup.xml", "UnitFrames.xml"]);
|
||||
let mut s = harness(&[
|
||||
// The stock unit frames resolve GlobalStrings at LOAD (`CombatFeedback.lua` l.7-17,
|
||||
// `UnitFrame.lua` l.1-6) and `UnitFrame_OnEnter` passes `PARTY_OPTIONS_LABEL` /
|
||||
// `PLAYER_OPTIONS_LABEL` (l.60/63) straight into `GameTooltip:SetText`, which raises on
|
||||
// nil. The app loads this file ahead of the whole manifest; so does the fixture.
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
"Interface\\FrameXML\\TextStatusBar.xml",
|
||||
"UIDropDownMenu.xml",
|
||||
"UnitPopup.xml",
|
||||
"Interface\\FrameXML\\BuffFrame.xml",
|
||||
"Interface\\FrameXML\\UnitFrame.xml",
|
||||
"Interface\\FrameXML\\CombatFeedback.xml",
|
||||
"Interface\\FrameXML\\PlayerFrame.xml",
|
||||
"Interface\\FrameXML\\PartyFrame.xml",
|
||||
"Interface\\FrameXML\\TargetFrame.xml",
|
||||
"Interface\\FrameXML\\PetFrame.xml",
|
||||
]);
|
||||
s.set_unit("target", Some(wolf()));
|
||||
s.run("BenillaUnitFrame_OnEnter(TargetFrame)").unwrap();
|
||||
// The reference's handler takes no arguments and reads `this` (ref UnitFrame.lua l.45), which
|
||||
// is exactly how the stock frames wire it: `<OnEnter>UnitFrame_OnEnter();</OnEnter>`
|
||||
// (ref TargetFrame.xml l.505, PlayerFrame.xml l.435). Our deleted transcription's
|
||||
// `BenillaUnitFrame_OnEnter(frame)` adapter went with the file.
|
||||
s.run("this = TargetFrame UnitFrame_OnEnter() this = nil")
|
||||
.unwrap();
|
||||
assert!(s.errors().is_empty(), "hover errors: {:?}", s.errors());
|
||||
s.resolve();
|
||||
let ok: bool = s
|
||||
@@ -113,7 +135,8 @@ fn unit_frame_hover_takes_the_default_corner_and_drops_on_leave() {
|
||||
"a non-player target still gets the unit lines"
|
||||
);
|
||||
// Leave: gone on the spot, no ramp to wait out.
|
||||
s.run("BenillaUnitFrame_OnLeave(TargetFrame)").unwrap();
|
||||
s.run("this = TargetFrame UnitFrame_OnLeave() this = nil")
|
||||
.unwrap();
|
||||
let hidden: bool = s.eval("return not GameTooltip:IsShown()").unwrap();
|
||||
assert!(
|
||||
hidden,
|
||||
@@ -132,10 +155,28 @@ fn unit_frame_hover_takes_the_default_corner_and_drops_on_leave() {
|
||||
/// (the sibling test above).
|
||||
#[test]
|
||||
fn your_own_portrait_explains_the_menu_instead_of_showing_your_health() {
|
||||
let mut s = harness(&["UIDropDownMenu.xml", "UnitPopup.xml", "UnitFrames.xml"]);
|
||||
let mut s = harness(&[
|
||||
// The stock unit frames resolve GlobalStrings at LOAD (`CombatFeedback.lua` l.7-17,
|
||||
// `UnitFrame.lua` l.1-6) and `UnitFrame_OnEnter` passes `PARTY_OPTIONS_LABEL` /
|
||||
// `PLAYER_OPTIONS_LABEL` (l.60/63) straight into `GameTooltip:SetText`, which raises on
|
||||
// nil. The app loads this file ahead of the whole manifest; so does the fixture.
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
"Interface\\FrameXML\\TextStatusBar.xml",
|
||||
"UIDropDownMenu.xml",
|
||||
"UnitPopup.xml",
|
||||
"Interface\\FrameXML\\BuffFrame.xml",
|
||||
"Interface\\FrameXML\\UnitFrame.xml",
|
||||
"Interface\\FrameXML\\CombatFeedback.xml",
|
||||
"Interface\\FrameXML\\PlayerFrame.xml",
|
||||
"Interface\\FrameXML\\PartyFrame.xml",
|
||||
"Interface\\FrameXML\\TargetFrame.xml",
|
||||
"Interface\\FrameXML\\PetFrame.xml",
|
||||
]);
|
||||
s.set_unit("player", Some(wolf()));
|
||||
|
||||
s.run("BenillaUnitFrame_OnEnter(PlayerFrame)").unwrap();
|
||||
s.run("this = PlayerFrame UnitFrame_OnEnter() this = nil")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<String>("return GameTooltipTextLeft1:GetText()")
|
||||
.unwrap(),
|
||||
@@ -160,11 +201,13 @@ fn your_own_portrait_explains_the_menu_instead_of_showing_your_health() {
|
||||
"target",
|
||||
Some(UnitState {
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
name: Some("Someone".into()),
|
||||
..wolf()
|
||||
}),
|
||||
);
|
||||
s.run("BenillaUnitFrame_OnEnter(TargetFrame)").unwrap();
|
||||
s.run("this = TargetFrame UnitFrame_OnEnter() this = nil")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<String>("return GameTooltipTextLeft1:GetText()")
|
||||
.unwrap(),
|
||||
@@ -313,6 +356,8 @@ fn buff_hover_hangs_below_left_of_the_button() {
|
||||
let mut s = harness(&[
|
||||
"Cooldown.xml",
|
||||
"ActionBar.xml",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
"Interface\\FrameXML\\TextStatusBar.xml",
|
||||
"Interface\\FrameXML\\BuffFrame.xml",
|
||||
]);
|
||||
s.set_auras(
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
//! The shipped target-of-target frame, driven end to end (decision 1576) — `UnitFrames.xml`'s
|
||||
//! `TargetofTargetFrame` over synthetic `"targettarget"` snapshots and the events the app's feed
|
||||
//! fires for that token.
|
||||
//! The target-of-target frame, driven end to end (decision 1576) — the **reference's own**
|
||||
//! `TargetofTargetFrame`, declared in `Interface\FrameXML\TargetFrame.xml` (l.515-680) and
|
||||
//! driven by `TargetFrame.lua`, off the player's patch chain. Decision 1751 retired our
|
||||
//! `assets/ui/UnitFrames.xml` transcription, so what is under test here is the stock file over
|
||||
//! synthetic `"targettarget"` snapshots.
|
||||
//!
|
||||
//! Two things here are not ordinary unit-frame plumbing and are what most of these test:
|
||||
//! Three things here are not ordinary unit-frame plumbing and are what most of these test:
|
||||
//!
|
||||
//! * **The visibility law is six gates deep** — a switch, five display modes, and the four unit
|
||||
//! tests the reference wraps them in — and a frame that gets any of them wrong is either always
|
||||
//! there or never there. The mode leg is also where we knowingly leave the reference: its solo
|
||||
//! arm calls neither `Show` nor `Hide` when you are in a raid, so the frame keeps whatever state
|
||||
//! it had; ours answers the question the option asks.
|
||||
//! there or never there. The mode leg leaks by construction: its solo arm calls neither `Show`
|
||||
//! nor `Hide` when you are in a raid (ref TargetFrame.lua l.513-520), so the frame keeps
|
||||
//! whatever state it had. Our transcription closed that; the stock file is the end state, so the
|
||||
//! leak is now the behaviour under test.
|
||||
//! * **Nothing on this frame is event-driven.** Its bars carry no `OnEvent` at all (ref
|
||||
//! TargetFrame.xml l.593-625 — the health bar has only `OnValueChanged`, the mana bar no
|
||||
//! `<Scripts>` block), so the `UNIT_HEALTH`/`UNIT_MANA` registrations
|
||||
//! `UnitFrameHealthBar_Initialize`/`UnitFrameManaBar_Initialize` make (ref UnitFrame.lua
|
||||
//! l.150-151, l.190-200) are dead, and so is the frame's own `UNIT_AURA` registration (ref
|
||||
//! TargetFrame.xml l.668) — `UnitFrame_OnEvent` handles three events and that is not one of
|
||||
//! them. `TargetofTarget_OnUpdate` → `TargetofTarget_Update` is the only driver of the name, the
|
||||
//! bars, the dead word, the portrait tint and the aura row. **That is why nearly every step in
|
||||
//! this file is a `tick`, not a `fire_event`.**
|
||||
//! * **A token going away is silent.** The feed clears `"targettarget"` without an event, by the
|
||||
//! same convention `"target"` uses, so the frame's own events cannot take it down — that is what
|
||||
//! `TargetFrame_OnUpdate`'s one-compare reconcile is for, and it has a test.
|
||||
@@ -41,10 +53,10 @@ fn unit(name: &str, guid: u64, health: u32) -> UnitState {
|
||||
/// The frame's production load prefix, plus a player, a target and a target's target — the state
|
||||
/// in which everything but the switch itself already allows the frame.
|
||||
///
|
||||
/// The tail three are the target-aura tests' set and are here for the same reasons: `BuffFrame`
|
||||
/// defines `DebuffTypeColor`, which the debuff row's dispel tint indexes, and it will not TICK
|
||||
/// without `ActionBar`'s `TOOLTIP_UPDATE_TIME` — which this file's reconcile test needs, since it
|
||||
/// is the only one here that runs the clock.
|
||||
/// The tail is the target-aura tests' set and is here for the same reasons: `BuffFrame` defines
|
||||
/// `DebuffTypeColor`, which the debuff row's dispel tint indexes, and it will not TICK without
|
||||
/// `ActionBar`'s `TOOLTIP_UPDATE_TIME` — which almost every test in this file needs, because the
|
||||
/// stock frame is driven from its OnUpdate and nowhere else (see the module doc).
|
||||
fn load_tot() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
@@ -55,8 +67,21 @@ fn load_tot() -> UiScript {
|
||||
load_xml(&s, "UnitPopup.xml");
|
||||
load_xml(&s, "Cooldown.xml");
|
||||
load_xml(&s, "ActionBar.xml");
|
||||
load_xml(&s, "UnitFrames.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.lua");
|
||||
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\BuffFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UnitFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\CombatFeedback.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\PlayerFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\PartyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\TargetFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\PetFrame.xml");
|
||||
// The target-of-target pair's home is `OptionsFrame.xml` (the reference keeps them in
|
||||
// `UIOptionsFrame.lua`'s uvar defaults block, l.116-119). That file sits at the BOTTOM of our
|
||||
// manifest where the reference's sits near the top, so a test that loads the unit kit alone
|
||||
// has to state them itself — the same thing this harness already does for `DEAD`.
|
||||
s.run(r#"SHOW_TARGET_OF_TARGET = "0" SHOW_TARGET_OF_TARGET_STATE = "5""#)
|
||||
.unwrap();
|
||||
s.set_unit("player", Some(unit("Tri", 0x100, 100)));
|
||||
s.set_unit("target", Some(unit("Kobold Miner", 0x200, 80)));
|
||||
s.set_unit("targettarget", Some(unit("Tri", 0x100, 100)));
|
||||
@@ -66,7 +91,7 @@ fn load_tot() -> UiScript {
|
||||
|
||||
/// Turn the frame on the way the option row does — the write plus its `applyFunc`.
|
||||
fn switch_on(s: &mut UiScript) {
|
||||
s.run(r#"SHOW_TARGET_OF_TARGET = "1" TargetofTarget_Update()"#)
|
||||
s.run(r#"SHOW_TARGET_OF_TARGET = "1" this = TargetofTargetFrame TargetofTarget_Update() this = nil"#)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -163,37 +188,47 @@ fn the_four_unit_gates_each_take_the_frame_down() {
|
||||
|
||||
// No target of target.
|
||||
s.set_unit("targettarget", None);
|
||||
s.run("TargetofTarget_Update()").unwrap();
|
||||
s.run("this = TargetofTargetFrame TargetofTarget_Update() this = nil")
|
||||
.unwrap();
|
||||
assert!(!shown(&mut s), "nothing to show");
|
||||
s.set_unit("targettarget", Some(unit("Tri", 0x100, 100)));
|
||||
|
||||
// No target at all.
|
||||
s.set_unit("target", None);
|
||||
s.run("TargetofTarget_Update()").unwrap();
|
||||
s.run("this = TargetofTargetFrame TargetofTarget_Update() this = nil")
|
||||
.unwrap();
|
||||
assert!(!shown(&mut s), "no target, no target's target");
|
||||
s.set_unit("target", Some(unit("Kobold Miner", 0x200, 80)));
|
||||
|
||||
// The target is YOU: its target is your own target, and the frame would restate the frame it
|
||||
// hangs off.
|
||||
s.set_unit("target", Some(unit("Tri", 0x100, 100)));
|
||||
s.run("TargetofTarget_Update()").unwrap();
|
||||
s.run("this = TargetofTargetFrame TargetofTarget_Update() this = nil")
|
||||
.unwrap();
|
||||
assert!(!shown(&mut s), "self-target: nothing to add");
|
||||
s.set_unit("target", Some(unit("Kobold Miner", 0x200, 80)));
|
||||
|
||||
// A dead target.
|
||||
s.set_unit("target", Some(unit("Kobold Miner", 0x200, 0)));
|
||||
s.run("TargetofTarget_Update()").unwrap();
|
||||
s.run("this = TargetofTargetFrame TargetofTarget_Update() this = nil")
|
||||
.unwrap();
|
||||
assert!(!shown(&mut s), "a corpse is fighting nobody");
|
||||
|
||||
s.set_unit("target", Some(unit("Kobold Miner", 0x200, 80)));
|
||||
s.run("TargetofTarget_Update()").unwrap();
|
||||
s.run("this = TargetofTargetFrame TargetofTarget_Update() this = nil")
|
||||
.unwrap();
|
||||
assert!(shown(&mut s), "and back");
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// The five display modes, each against solo / party / raid. Mode 3 in a raid is the one that
|
||||
/// diverges from the reference on purpose: its ladder calls neither Show nor Hide there and the
|
||||
/// frame keeps its last state (ref l.513-520), which is a leak rather than a rule.
|
||||
/// The five display modes, each against solo / party / raid.
|
||||
///
|
||||
/// Mode 3 in a raid is not a rule the ladder states — it is the hole in it. The
|
||||
/// `SHOW_TARGET_OF_TARGET_STATE == "3"` arm acts only while `GetNumRaidMembers() == 0` (ref
|
||||
/// TargetFrame.lua l.513-520), so in a raid it calls neither Show nor Hide and the frame keeps
|
||||
/// whatever it had. It reads `false` here because the preceding `party` step left it hidden, and
|
||||
/// that sequencing is the only reason. The leak itself is
|
||||
/// [`the_solo_mode_keeps_the_frame_when_a_raid_forms`], which shows the frame first.
|
||||
#[test]
|
||||
fn the_five_modes_answer_solo_party_and_raid() {
|
||||
let mut s = load_tot();
|
||||
@@ -216,7 +251,7 @@ fn the_five_modes_answer_solo_party_and_raid() {
|
||||
for (i, (label, state)) in states.iter().enumerate() {
|
||||
s.set_party(state.clone());
|
||||
s.run(&format!(
|
||||
r#"SHOW_TARGET_OF_TARGET_STATE = "{mode}" TargetofTarget_Update()"#
|
||||
r#"SHOW_TARGET_OF_TARGET_STATE = "{mode}" this = TargetofTargetFrame TargetofTarget_Update() this = nil"#
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -230,28 +265,60 @@ fn the_five_modes_answer_solo_party_and_raid() {
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// Mode 3 in a raid, specifically as a STATE question rather than a fresh evaluation: the frame
|
||||
/// must come down when the raid forms, not keep whatever it had. This is the reference's leaked
|
||||
/// branch, and it can only be seen by showing the frame first.
|
||||
/// Mode 3 in a raid, as a STATE question rather than a fresh evaluation — the leaked branch, which
|
||||
/// can only be seen by showing the frame first.
|
||||
///
|
||||
/// **This assertion flipped with 1751.** Our deleted `UnitFrames.xml` answered the question the
|
||||
/// option asks and took the frame down; the stock file does not. `TargetofTarget_Update`'s
|
||||
/// `SHOW_TARGET_OF_TARGET_STATE == "3"` arm is `if ( GetNumRaidMembers() == 0 ) then … end` with
|
||||
/// no `else` (ref TargetFrame.lua l.513-520), so in a raid it reaches neither `Show` nor `Hide`
|
||||
/// and the frame keeps whatever it had. The stock file is the end state, so the reference's answer
|
||||
/// is the assertion.
|
||||
///
|
||||
/// Driven by a `tick`, which is the strong form: `TargetofTarget_OnUpdate` re-runs
|
||||
/// `TargetofTarget_Update` on **every** frame (ref TargetFrame.xml l.673-675 → TargetFrame.lua
|
||||
/// l.500), so this is not one stale evaluation left standing — the reference re-asks the question
|
||||
/// sixty times a second and still never takes the frame down.
|
||||
#[test]
|
||||
fn the_solo_mode_comes_down_when_a_raid_forms() {
|
||||
fn the_solo_mode_keeps_the_frame_when_a_raid_forms() {
|
||||
let mut s = load_tot();
|
||||
switch_on(&mut s);
|
||||
s.run(r#"SHOW_TARGET_OF_TARGET_STATE = "3" TargetofTarget_Update()"#)
|
||||
s.run(r#"SHOW_TARGET_OF_TARGET_STATE = "3" this = TargetofTargetFrame TargetofTarget_Update() this = nil"#)
|
||||
.unwrap();
|
||||
assert!(shown(&mut s), "solo, in solo mode");
|
||||
|
||||
s.set_party(raid(10));
|
||||
s.fire_event("RAID_ROSTER_UPDATE", vec![]);
|
||||
assert!(!shown(&mut s), "the raid forms and the frame goes away");
|
||||
s.tick(0.016);
|
||||
assert!(
|
||||
shown(&mut s),
|
||||
"the raid forms and the reference's solo arm never fires — the frame stays"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// The paint: name, health, and the powerless unit's hidden mana bar (this file's convention on
|
||||
/// every frame — the reference leaves an empty 0/0 rail drawn).
|
||||
/// The paint: name, health, and the powerless unit's mana rail — which the reference **never
|
||||
/// hides**.
|
||||
///
|
||||
/// **Two things flipped with 1751**, and both are the stock file being the end state:
|
||||
///
|
||||
/// * **The name arrives on a tick, not on the switch.** `TargetofTarget_Update` (ref
|
||||
/// TargetFrame.lua l.503-540) touches the bars, the dead word and the aura rows and never the
|
||||
/// name; the `SetText` lives in `TargetofTarget_OnUpdate`'s `CURRENT_TARGETTARGET` compare
|
||||
/// (l.494-501), off the frame's own OnUpdate. Turning the switch on alone leaves it nil.
|
||||
/// * **A powerless unit still gets a rail.** `UnitFrameManaBar_Update` (ref UnitFrame.lua
|
||||
/// l.203-224) sets 0..0 and calls neither `Show` nor `Hide` — there is no hide leg anywhere in
|
||||
/// the reference's mana path. Our deleted `UnitFrames.xml` hid the bar and this asserted that;
|
||||
/// the stock answer is an empty 0/0 rail, still drawn, so that is what is asserted now.
|
||||
///
|
||||
/// The powerless step is a `tick` for the same reason as everything else here: 1.12 has no
|
||||
/// `UNIT_MAXPOWER` event at all (the reference registers `UNIT_MAXMANA`/`RAGE`/`FOCUS`/`ENERGY`/
|
||||
/// `HAPPINESS`, ref UnitFrame.lua l.195-199), and this frame's mana bar carries no `OnEvent` to
|
||||
/// answer one with if it did (ref TargetFrame.xml l.612-625).
|
||||
#[test]
|
||||
fn the_frame_paints_its_unit() {
|
||||
let mut s = load_tot();
|
||||
switch_on(&mut s);
|
||||
s.tick(0.016);
|
||||
|
||||
assert_eq!(
|
||||
s.eval::<String>("return TargetofTargetName:GetText()")
|
||||
@@ -274,24 +341,38 @@ fn the_frame_paints_its_unit() {
|
||||
let mut powerless = unit("Skeleton", 0x100, 100);
|
||||
powerless.max_power = 0;
|
||||
s.set_unit("targettarget", Some(powerless));
|
||||
s.fire_event(
|
||||
"UNIT_MAXPOWER",
|
||||
vec![ScriptValue::Str("targettarget".into())],
|
||||
);
|
||||
s.tick(0.016);
|
||||
assert!(
|
||||
!s.eval::<bool>("return TargetofTargetManaBar:IsShown() and true or false")
|
||||
s.eval::<bool>("return TargetofTargetManaBar:IsShown() and true or false")
|
||||
.unwrap(),
|
||||
"no power, no rail"
|
||||
"no power, and the rail is STILL drawn — the reference has no hide leg"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<(f64, f64)>(
|
||||
"local _, m = TargetofTargetManaBar:GetMinMaxValues() \
|
||||
return TargetofTargetManaBar:GetValue(), m",
|
||||
)
|
||||
.unwrap(),
|
||||
(0.0, 0.0),
|
||||
"it reads 0/0"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// DEAD over a dimmed trough — and the reference's connected test, which is what tells a corpse
|
||||
/// from a linkdead player (both read zero health).
|
||||
/// from a linkdead player (both read zero health). Ref `TargetofTarget_CheckDead`, TargetFrame.lua
|
||||
/// l.559-567.
|
||||
///
|
||||
/// Every step is a `tick` rather than a `UNIT_HEALTH`: `TargetofTargetHealthBar` registers that
|
||||
/// event through `UnitFrameHealthBar_Initialize` (ref UnitFrame.lua l.150) but declares **no**
|
||||
/// `OnEvent` to answer it with (ref TargetFrame.xml l.593-611 — `OnValueChanged` and nothing
|
||||
/// else), so the registration is dead and `TargetofTarget_Update` off the frame's OnUpdate is the
|
||||
/// only thing that ever calls `TargetofTarget_CheckDead`.
|
||||
#[test]
|
||||
fn the_dead_word_needs_a_connected_corpse() {
|
||||
let mut s = load_tot();
|
||||
switch_on(&mut s);
|
||||
s.tick(0.016);
|
||||
assert!(
|
||||
!s.eval::<bool>("return TargetofTargetDeadText:IsShown() and true or false")
|
||||
.unwrap(),
|
||||
@@ -301,7 +382,7 @@ fn the_dead_word_needs_a_connected_corpse() {
|
||||
let mut corpse = unit("Tri", 0x100, 0);
|
||||
corpse.dead = true;
|
||||
s.set_unit("targettarget", Some(corpse.clone()));
|
||||
s.fire_event("UNIT_HEALTH", vec![ScriptValue::Str("targettarget".into())]);
|
||||
s.tick(0.016);
|
||||
assert!(
|
||||
s.eval::<bool>("return TargetofTargetDeadText:IsShown() and true or false")
|
||||
.unwrap(),
|
||||
@@ -311,7 +392,7 @@ fn the_dead_word_needs_a_connected_corpse() {
|
||||
let mut linkdead = corpse;
|
||||
linkdead.is_connected = false;
|
||||
s.set_unit("targettarget", Some(linkdead));
|
||||
s.fire_event("UNIT_HEALTH", vec![ScriptValue::Str("targettarget".into())]);
|
||||
s.tick(0.016);
|
||||
assert!(
|
||||
!s.eval::<bool>("return TargetofTargetDeadText:IsShown() and true or false")
|
||||
.unwrap(),
|
||||
@@ -320,18 +401,24 @@ fn the_dead_word_needs_a_connected_corpse() {
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// The portrait tint (ref TargetofTargetHealthCheck), and its PLAYERS-ONLY gate: a creature's
|
||||
/// portrait is never tinted, which is why the check has to run off the bar's own value rather
|
||||
/// than off the snapshot.
|
||||
/// The portrait tint (ref `TargetofTargetHealthCheck`, TargetFrame.lua l.569-585), and its
|
||||
/// PLAYERS-ONLY gate: a creature's portrait is never tinted, which is why the check has to run off
|
||||
/// the bar's own value rather than off the snapshot.
|
||||
///
|
||||
/// Ticks, not `UNIT_HEALTH`, and here the chain is one link longer than the dead word's: the check
|
||||
/// hangs off the health bar's `OnValueChanged` (ref TargetFrame.xml l.604-608), and the only thing
|
||||
/// that moves that value is `UnitFrameHealthBar_Update` inside `TargetofTarget_Update` — which is
|
||||
/// reached from the frame's OnUpdate and nowhere else.
|
||||
#[test]
|
||||
fn the_portrait_tints_with_a_players_state_and_never_a_creatures() {
|
||||
let mut s = load_tot();
|
||||
switch_on(&mut s);
|
||||
s.tick(0.016);
|
||||
|
||||
let mut hurt = unit("Tri", 0x100, 15);
|
||||
hurt.is_player = true;
|
||||
s.set_unit("targettarget", Some(hurt));
|
||||
s.fire_event("UNIT_HEALTH", vec![ScriptValue::Str("targettarget".into())]);
|
||||
s.tick(0.016);
|
||||
let (r, g, b) = s
|
||||
.eval::<(f64, f64, f64)>("return TargetofTargetPortrait:GetVertexColor()")
|
||||
.unwrap();
|
||||
@@ -340,7 +427,7 @@ fn the_portrait_tints_with_a_players_state_and_never_a_creatures() {
|
||||
let mut healthy = unit("Tri", 0x100, 90);
|
||||
healthy.is_player = true;
|
||||
s.set_unit("targettarget", Some(healthy));
|
||||
s.fire_event("UNIT_HEALTH", vec![ScriptValue::Str("targettarget".into())]);
|
||||
s.tick(0.016);
|
||||
let (r, g, b) = s
|
||||
.eval::<(f64, f64, f64)>("return TargetofTargetPortrait:GetVertexColor()")
|
||||
.unwrap();
|
||||
@@ -348,7 +435,7 @@ fn the_portrait_tints_with_a_players_state_and_never_a_creatures() {
|
||||
|
||||
// A creature at the same 15%: the tint must not move (the check returns before touching it).
|
||||
s.set_unit("targettarget", Some(unit("Kobold", 0x500, 15)));
|
||||
s.fire_event("UNIT_HEALTH", vec![ScriptValue::Str("targettarget".into())]);
|
||||
s.tick(0.016);
|
||||
let (r, g, b) = s
|
||||
.eval::<(f64, f64, f64)>("return TargetofTargetPortrait:GetVertexColor()")
|
||||
.unwrap();
|
||||
@@ -374,7 +461,15 @@ fn the_left_click_targets_the_unit() {
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// The four debuff buttons, dispel-tinted (ref RefreshBuffs over MAX_PARTY_DEBUFFS).
|
||||
/// The four debuff buttons, dispel-tinted (ref `RefreshBuffs` over `MAX_PARTY_DEBUFFS`,
|
||||
/// BuffFrame.lua l.262-313, called from `TargetofTarget_Update` l.538).
|
||||
///
|
||||
/// The `UNIT_AURA` fire is what the feed really does and is kept for that reason, but it is inert
|
||||
/// here: the frame registers the event in its own OnLoad (ref TargetFrame.xml l.668) and answers
|
||||
/// it with `UnitFrame_OnEvent`, which handles `UNIT_NAME_UPDATE`, `UNIT_PORTRAIT_UPDATE` and
|
||||
/// `UNIT_DISPLAYPOWER` and nothing else (ref UnitFrame.lua l.31-45). A dead registration in the
|
||||
/// stock file — the row is redrawn by `RefreshBuffs` off the OnUpdate, so the `tick` is what makes
|
||||
/// it appear.
|
||||
#[test]
|
||||
fn the_debuff_row_draws_what_the_unit_carries() {
|
||||
let mut s = load_tot();
|
||||
@@ -387,6 +482,7 @@ fn the_debuff_row_draws_what_the_unit_carries() {
|
||||
]),
|
||||
);
|
||||
s.fire_event("UNIT_AURA", vec![ScriptValue::Str("targettarget".into())]);
|
||||
s.tick(0.016);
|
||||
|
||||
let paths = drawn(&mut s);
|
||||
assert!(
|
||||
@@ -406,8 +502,13 @@ fn the_debuff_row_draws_what_the_unit_carries() {
|
||||
}
|
||||
|
||||
/// The target's OWN aura rows re-wrap around this frame: 5 to a row instead of 6, and the buffs
|
||||
/// open under the second debuff row instead of the seventh icon (ref TargetDebuffButton_Update
|
||||
/// l.310-334). Both directions — the reference only re-runs the rows on the way in.
|
||||
/// open under the second debuff row instead of the seventh icon (ref `TargetDebuffButton_Update`,
|
||||
/// TargetFrame.lua l.312-338 for the wrap and l.370-385 for the row anchors).
|
||||
///
|
||||
/// Both directions, and they are not symmetric: the reference re-runs the rows on the way **in**
|
||||
/// only. `TargetDebuffButton_Update` is called from inside `TargetofTarget_Update`'s
|
||||
/// `if ( TargetofTargetFrame:IsShown() )` (l.532-537), so turning the frame off leaves the rows
|
||||
/// wrapped short behind it.
|
||||
#[test]
|
||||
fn the_target_rows_wrap_short_while_the_frame_stands_beside_them() {
|
||||
let mut s = load_tot();
|
||||
@@ -440,13 +541,18 @@ fn the_target_rows_wrap_short_while_the_frame_stands_beside_them() {
|
||||
"and the sixth icon starts the second row"
|
||||
);
|
||||
|
||||
// Back off — the reference never re-lays the rows here, so they would stay wrapped short.
|
||||
s.run(r#"SHOW_TARGET_OF_TARGET = "0" TargetofTarget_Update()"#)
|
||||
// Back off — and the rows STAY wrapped short. **This assertion flipped with 1751.**
|
||||
// `TargetofTarget_Update` calls `TargetDebuffButton_Update` from inside
|
||||
// `if ( TargetofTargetFrame:IsShown() )` (ref TargetFrame.lua l.532-537), so the pass that
|
||||
// hides the frame is exactly the pass that does not re-lay the target's rows. They keep the
|
||||
// 5-wide wrap until something else runs the update — the next target change or `UNIT_AURA`.
|
||||
// Our deleted `UnitFrames.xml` re-laid them on the way out; the stock file is the end state.
|
||||
s.run(r#"SHOW_TARGET_OF_TARGET = "0" this = TargetofTargetFrame TargetofTarget_Update() this = nil"#)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
anchor(&mut s, "TargetFrameBuff1"),
|
||||
"TargetFrameDebuff7",
|
||||
"and back to six"
|
||||
"TargetFrameDebuff6",
|
||||
"the frame goes and the rows are left wrapped short behind it"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
@@ -2,17 +2,23 @@ use benilla_ui::script::{
|
||||
QuadContent, ScriptValue, SelectionRequest, SoundRequest, UiScript, UnitState,
|
||||
};
|
||||
|
||||
use super::test_ui::load_ui as load_xml;
|
||||
use super::test_ui::{hover, load_ui as load_xml, unhover};
|
||||
|
||||
/// The unit frames' production load prefix (ui_script/mod.rs order): fonts + UIParent +
|
||||
/// tooltip, then the dropdown kit + unit popups the frames' DropDown children initialize into.
|
||||
fn load_unit_frames(s: &UiScript) {
|
||||
// The app runs the real `Interface\FrameXML\GlobalStrings.lua` off the player's chain
|
||||
// BEFORE any XML (`load_global_strings`); this stands in for it, with the one key these
|
||||
// frames resolve at load time — `DEAD` l.898, which both dead-text FontStrings name in
|
||||
// their `text=` attribute. Without it the loader's literal fallback would leave the KEY on
|
||||
// screen, which is the bug this file guards below.
|
||||
s.run(r#"DEAD = "Dead""#).unwrap();
|
||||
// The app runs the real `Interface\FrameXML\GlobalStrings.lua` off the player's chain BEFORE
|
||||
// any XML (`load_global_strings`), so the fixture names it too. This used to be a single
|
||||
// hand-set `DEAD = "Dead"`, which was enough while the frames were OUR transcription: ours
|
||||
// carried `X = X or "…"` fallbacks and hard literals for everything else. The reference's own
|
||||
// files carry none, and they resolve GlobalStrings at LOAD in three separate places —
|
||||
// `CombatFeedback.lua` l.7-17 builds the whole `CombatFeedbackText` table out of them
|
||||
// (`TEXT(ABSORB)`, `TEXT(MISS)`, …), `UnitFrame.lua` l.1-6 builds `ManaBarColor`'s prefixes
|
||||
// the same way, and `UnitFrame_OnEnter` l.60/63 passes `PARTY_OPTIONS_LABEL` /
|
||||
// `PLAYER_OPTIONS_LABEL` straight into `GameTooltip:SetText`, which raises on nil rather than
|
||||
// drawing an empty plate. Hand-setting the union of those is a second copy of the reference's
|
||||
// own file; naming the file is the only version that cannot drift. (`DEAD` is l.898 of it.)
|
||||
load_xml(s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(s, "Fonts.xml");
|
||||
load_xml(s, "UIParent.xml");
|
||||
// The bars' numerals machinery (decision 1082), which the manifest loads immediately ahead of
|
||||
@@ -22,7 +28,13 @@ fn load_unit_frames(s: &UiScript) {
|
||||
load_xml(s, "GameTooltip.xml");
|
||||
load_xml(s, "UIDropDownMenu.xml");
|
||||
load_xml(s, "UnitPopup.xml");
|
||||
load_xml(s, "UnitFrames.xml");
|
||||
load_xml(s, "Interface\\FrameXML\\BuffFrame.xml");
|
||||
load_xml(s, "Interface\\FrameXML\\UnitFrame.xml");
|
||||
load_xml(s, "Interface\\FrameXML\\CombatFeedback.xml");
|
||||
load_xml(s, "Interface\\FrameXML\\PlayerFrame.xml");
|
||||
load_xml(s, "Interface\\FrameXML\\PartyFrame.xml");
|
||||
load_xml(s, "Interface\\FrameXML\\TargetFrame.xml");
|
||||
load_xml(s, "Interface\\FrameXML\\PetFrame.xml");
|
||||
}
|
||||
|
||||
/// Load the real `assets/ui/UnitFrames.xml` (the shipped default UI) into a bare engine and
|
||||
@@ -36,13 +48,31 @@ fn shipped_unit_frames_drive_end_to_end() {
|
||||
load_unit_frames(&s);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
|
||||
// No units yet: both frames hid themselves on their OnLoad's first update.
|
||||
let hidden: bool = s
|
||||
.eval("return not PlayerFrame:IsVisible() and not TargetFrame:IsVisible()")
|
||||
// **Only the TARGET frame hides.** Our deleted transcription hid both; the reference hides
|
||||
// exactly one. `PlayerFrame` is a plain top-level `<Button>` with no `hidden=` attribute
|
||||
// (ref PlayerFrame.xml l.4) and `PlayerFrame_Update` (ref PlayerFrame.lua l.29-37) wraps its
|
||||
// whole body in `if UnitExists("player")` without an else — nothing in the file ever calls
|
||||
// `PlayerFrame:Hide()`. That is the real client's behaviour: the player plate is up from the
|
||||
// moment the UI loads, empty until the player object arrives. `TargetFrame_Update`
|
||||
// (ref TargetFrame.lua l.38-55) is the one with the `else this:Hide()`.
|
||||
let shape: bool = s
|
||||
.eval("return PlayerFrame:IsVisible() and not TargetFrame:IsVisible()")
|
||||
.unwrap();
|
||||
assert!(hidden, "frames hide while their units don't exist");
|
||||
assert!(
|
||||
shape,
|
||||
"the player plate is always up; only the target frame hides while its unit is absent"
|
||||
);
|
||||
|
||||
// The player appears (name still unresolved), at 72/100 health, 45/80 mana.
|
||||
//
|
||||
// `PLAYER_ENTERING_WORLD` is the event, not `UNIT_HEALTH`, and the reference is why: the
|
||||
// stock bars each register their OWN events and repaint only themselves
|
||||
// (`UnitFrameHealthBar_Initialize` takes UNIT_HEALTH/UNIT_MAXHEALTH, ref UnitFrame.lua
|
||||
// l.150-151; `UnitFrameManaBar_Initialize` takes the ten UNIT_MANA/RAGE/… ones, l.189-199),
|
||||
// so UNIT_HEALTH alone would leave the mana bar at its load-time 0/0. The one handler that
|
||||
// repaints name + portrait + both bars together is `PlayerFrame_OnEvent`'s
|
||||
// PLAYER_ENTERING_WORLD arm → `UnitFrame_Update()` (ref PlayerFrame.lua l.96-100), which is
|
||||
// also what really fires when the player object arrives.
|
||||
s.set_unit(
|
||||
"player",
|
||||
Some(UnitState {
|
||||
@@ -56,10 +86,14 @@ fn shipped_unit_frames_drive_end_to_end() {
|
||||
max_power: 80,
|
||||
dead: false,
|
||||
reaction: 0,
|
||||
// Not decoration: `UnitFrameManaBar_Update`'s disconnect leg (ref UnitFrame.lua
|
||||
// l.209-212) pins a disconnected unit's power bar to MAX and greys it, and
|
||||
// `UnitState::default()` leaves this `false`.
|
||||
is_connected: true,
|
||||
..UnitState::default()
|
||||
}),
|
||||
);
|
||||
s.fire_event("UNIT_HEALTH", vec![ScriptValue::Str("player".into())]);
|
||||
s.fire_event("PLAYER_ENTERING_WORLD", vec![]);
|
||||
assert!(s.eval::<bool>("return PlayerFrame:IsVisible()").unwrap());
|
||||
let ok: bool = s
|
||||
.eval(
|
||||
@@ -71,12 +105,24 @@ fn shipped_unit_frames_drive_end_to_end() {
|
||||
return hb:GetValue() == 72 and hmax == 100
|
||||
and pb:GetValue() == 45 and pmax == 80 and pb:IsVisible()
|
||||
and b == 1 and r == 0 -- mana blue
|
||||
and PlayerName:GetText() == "Unknown"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(ok, "player frame painted from the snapshot");
|
||||
|
||||
// …and the name plate is BLANK while the name is unresolved, not the word "Unknown". That
|
||||
// word was our transcription's, twice over — a `text="Unknown"` literal on the FontString
|
||||
// (deleted UnitFrames.xml l.1364) and a `UnitName(unit) or "Unknown"` fallback (l.803). The
|
||||
// reference's `PlayerName` carries no `text=` at all (ref PlayerFrame.xml l.58) and
|
||||
// `GetUnitName` returns `UnitName`'s result unchanged (ref UnitFrame.lua l.226-236), so what
|
||||
// shows is whatever the engine's `UnitName` returns for a nameless unit — nil here.
|
||||
assert_eq!(
|
||||
s.eval::<Option<String>>("return PlayerName:GetText()")
|
||||
.unwrap(),
|
||||
None,
|
||||
"no name yet: the stock file has no \"Unknown\" literal to fall back on"
|
||||
);
|
||||
|
||||
// The name-query answer lands: UNIT_NAME_UPDATE repaints the name.
|
||||
s.set_unit(
|
||||
"player",
|
||||
@@ -91,6 +137,7 @@ fn shipped_unit_frames_drive_end_to_end() {
|
||||
max_power: 80,
|
||||
dead: false,
|
||||
reaction: 0,
|
||||
is_connected: true,
|
||||
..UnitState::default()
|
||||
}),
|
||||
);
|
||||
@@ -100,7 +147,21 @@ fn shipped_unit_frames_drive_end_to_end() {
|
||||
"Benilla"
|
||||
);
|
||||
|
||||
// A powerless wolf gets targeted: frame shows, power bar hides, health fills 30/50.
|
||||
// A powerless wolf gets targeted: frame shows, power bar runs EMPTY, health fills 30/50.
|
||||
//
|
||||
// "Empty", not hidden — and the reason is a reference quirk worth knowing. The only thing in
|
||||
// 1.12 that hides a StatusBar for having no track is `TextStatusBar_UpdateTextString`'s
|
||||
// `else textStatusBar:Hide()` (ref TextStatusBar.lua l.55-57), and that whole body sits
|
||||
// inside `if(string)` — `string` being `bar.TextString`. The reference **never declares**
|
||||
// `TargetFrameHealthBarText` / `TargetFrameManaBarText`: TargetFrame.xml l.486-487 passes
|
||||
// both names into `UnitFrame_Initialize` and neither exists anywhere in FrameXML (only the
|
||||
// player's and the pet's do — PlayerFrame.xml l.79/88, PetFrame.xml l.87/96). So
|
||||
// `SetTextStatusBarText` early-returns on the nil (ref TextStatusBar.lua l.7-10), the
|
||||
// target's bars carry no `TextString`, and the hide can never fire. It doesn't show: these
|
||||
// bars have a `<BarTexture>` and no background, so a 0/0 bar draws nothing either way.
|
||||
//
|
||||
// Our transcription hid it, because 1146 §3 *declared* the two text regions the reference
|
||||
// leaves dangling. Those are gone with the file.
|
||||
s.set_unit(
|
||||
"target",
|
||||
Some(UnitState {
|
||||
@@ -114,6 +175,7 @@ fn shipped_unit_frames_drive_end_to_end() {
|
||||
max_power: 0,
|
||||
dead: false,
|
||||
reaction: 4, // neutral
|
||||
is_connected: true,
|
||||
..UnitState::default()
|
||||
}),
|
||||
);
|
||||
@@ -122,8 +184,10 @@ fn shipped_unit_frames_drive_end_to_end() {
|
||||
let ok: bool = s
|
||||
.eval(
|
||||
r#"
|
||||
local _, pmax = TargetFrameManaBar:GetMinMaxValues()
|
||||
return TargetFrame:IsVisible()
|
||||
and not TargetFrameManaBar:IsVisible()
|
||||
and TargetFrameManaBar:GetValue() == 0 and pmax == 0
|
||||
and TargetFrameManaBar.TextString == nil -- the reference declares no text region
|
||||
and TargetName:GetText() == "Young Wolf"
|
||||
and TargetLevelText:GetText() == "3"
|
||||
and not TargetDeadText:IsShown() -- living target: no dead word
|
||||
@@ -132,7 +196,7 @@ fn shipped_unit_frames_drive_end_to_end() {
|
||||
.unwrap();
|
||||
assert!(
|
||||
ok,
|
||||
"target frame painted; powerless unit hides its power bar"
|
||||
"target frame painted; a powerless unit's power bar runs empty over an empty track"
|
||||
);
|
||||
// The select sound rides the frame's OnShow (ref TargetFrame_OnShow): a neutral (4) wolf is
|
||||
// neither UnitIsEnemy (≤2) nor UnitIsFriend (≥5) → the neutral kit.
|
||||
@@ -341,20 +405,9 @@ fn unit_combat_drives_the_player_hit_indicator() {
|
||||
fn left_clicking_the_player_frame_targets_self() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
// The SELF-menu strings the popup rows bake at UnitPopup.xml load (no GlobalStrings in a
|
||||
// bare engine — production reads the real file).
|
||||
s.run(
|
||||
r#"
|
||||
LOOT_METHOD = "Loot Method"
|
||||
LOOT_THRESHOLD = "Loot Threshold"
|
||||
LOOT_GROUP_LOOT = "Group Loot"
|
||||
PARTY_LEAVE = "Leave Party"
|
||||
RAID_TARGET_ICON = "Raid Target Icon"
|
||||
CANCEL = "Cancel"
|
||||
ITEM_QUALITY2_DESC = "Uncommon"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
// The SELF-menu strings the popup rows bake at UnitPopup.xml load arrive with
|
||||
// `load_unit_frames`' `GlobalStrings.lua`; they used to be hand-set here, and one of the
|
||||
// hand-set values was wrong (see the row assertion below).
|
||||
load_unit_frames(&s);
|
||||
|
||||
// The player must exist for the frame to be shown and mouse-hittable.
|
||||
@@ -427,10 +480,14 @@ fn left_clicking_the_player_frame_targets_self() {
|
||||
6,
|
||||
"title + Loot Method + Loot Threshold + Leave Party + Raid Target Icon + Cancel"
|
||||
);
|
||||
// `PARTY_LEAVE`, verbatim. The reference's own GlobalStrings.lua l.2991 is
|
||||
// `PARTY_LEAVE = "Leave party"` — lower-case "party". The hand-set fixture this test used to
|
||||
// open with title-cased it, so the row read back the fixture's own typo rather than the
|
||||
// client's string; naming the real file is what exposed it.
|
||||
assert_eq!(
|
||||
s.eval::<String>("return DropDownList1Button4:GetText()")
|
||||
.unwrap(),
|
||||
"Leave Party"
|
||||
"Leave party"
|
||||
);
|
||||
// The nested rows carry the expand arrow (the level-2 gate for a leader).
|
||||
assert!(
|
||||
@@ -467,21 +524,9 @@ fn left_clicking_the_player_frame_targets_self() {
|
||||
fn raid_mark_clicks_through_the_nested_level() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
s.run(
|
||||
r#"
|
||||
LOOT_METHOD = "Loot Method"
|
||||
LOOT_THRESHOLD = "Loot Threshold"
|
||||
LOOT_GROUP_LOOT = "Group Loot"
|
||||
PARTY_LEAVE = "Leave Party"
|
||||
RAID_TARGET_ICON = "Raid Target Icon"
|
||||
CANCEL = "Cancel"
|
||||
ITEM_QUALITY2_DESC = "Uncommon"
|
||||
RAID_TARGET_1 = "Star"; RAID_TARGET_2 = "Circle"; RAID_TARGET_3 = "Diamond";
|
||||
RAID_TARGET_4 = "Triangle"; RAID_TARGET_5 = "Moon"; RAID_TARGET_6 = "Square";
|
||||
RAID_TARGET_7 = "Cross"; RAID_TARGET_8 = "Skull"; NONE = "None"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
// The menu strings arrive with `load_unit_frames`' own `GlobalStrings.lua`
|
||||
// (`RAID_TARGET_ICON` l.3288, `RAID_TARGET_1..8` l.3280-3287, `NONE` l.2795), which is where
|
||||
// production reads them; they used to be hand-set here.
|
||||
load_unit_frames(&s);
|
||||
|
||||
s.set_unit(
|
||||
@@ -551,6 +596,13 @@ fn raid_mark_clicks_through_the_nested_level() {
|
||||
.unwrap();
|
||||
s.mouse_button(sx as f32, sy as f32, "LeftButton", true);
|
||||
s.mouse_button(sx as f32, sy as f32, "LeftButton", false);
|
||||
// RED while the `SetRaidTarget` engine binding is absent, and deliberately left that way.
|
||||
// `UnitPopup.xml`'s row calls `SetRaidTargetIcon(menu.unit, mark)`; the definition of that
|
||||
// used to be ours, and since the migration it is the reference's own — `TargetFrame.lua`
|
||||
// l.486-492 — whose whole body is `SetRaidTarget(unit, 0 or index)`. `SetRaidTarget` is an
|
||||
// engine verb this house does not have yet, so the click raises
|
||||
// "attempt to call global 'SetRaidTarget' (a nil value)" and no intent is queued. 1203: it
|
||||
// gets built, never stubbed.
|
||||
assert_eq!(
|
||||
s.take_party_requests(),
|
||||
vec![benilla_ui::script::PartyRequest::SetRaidTarget {
|
||||
@@ -608,6 +660,7 @@ fn shipped_target_frame_runs_the_level_law() {
|
||||
exists: true,
|
||||
level: 3,
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
health: 50,
|
||||
max_health: 50,
|
||||
..UnitState::default()
|
||||
@@ -738,6 +791,7 @@ fn shipped_target_frame_runs_the_level_law() {
|
||||
exists: true,
|
||||
level: 30,
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
health: 50,
|
||||
max_health: 50,
|
||||
..UnitState::default()
|
||||
@@ -789,6 +843,7 @@ fn pvp_icon_follows_the_three_branch_law() {
|
||||
max_health: 50,
|
||||
level: 10,
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
faction_group: Some("Alliance".into()),
|
||||
pvp,
|
||||
is_pvp_ffa: ffa,
|
||||
@@ -896,6 +951,7 @@ fn flagged_friendly_player_plate_is_green() {
|
||||
max_health: 50,
|
||||
level: 20,
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
reaction: 5,
|
||||
can_attack: false,
|
||||
faction_group: Some("Alliance".into()),
|
||||
@@ -1075,10 +1131,16 @@ fn the_ring_art_paints_over_the_bars() {
|
||||
power_type: 0,
|
||||
power: 60,
|
||||
max_power: 100,
|
||||
is_connected: true, // else the power bar takes the disconnect leg's grey max fill
|
||||
..UnitState::default()
|
||||
}),
|
||||
);
|
||||
s.fire_event("UNIT_HEALTH", vec![ScriptValue::Str("player".into())]);
|
||||
// PLAYER_ENTERING_WORLD, not UNIT_HEALTH: the reference repaints the NAME only from
|
||||
// `UnitFrame_Update` (ref UnitFrame.lua l.23-28) and on UNIT_NAME_UPDATE (l.31-34) — the
|
||||
// health bar's own event touches its bar and nothing else. `PlayerFrame_OnEvent`'s
|
||||
// PLAYER_ENTERING_WORLD arm (ref PlayerFrame.lua l.96-100) is the one that runs the full
|
||||
// repaint, and the name quad is half of what this test measures.
|
||||
s.fire_event("PLAYER_ENTERING_WORLD", vec![]);
|
||||
s.resolve();
|
||||
let quads = s.extract();
|
||||
|
||||
@@ -1131,16 +1193,28 @@ fn the_party_art_paints_over_the_bars() {
|
||||
// The loot test's prefix (`loot_tests.rs`): PartyFrame's inline <Script> reads
|
||||
// StaticPopupDialogs, which UiPanels.xml defines, and its per-member dropdown OnLoad walks the
|
||||
// whole popup kit.
|
||||
// GlobalStrings first, for the same reason `load_unit_frames` names it — the stock unit-frame
|
||||
// files resolve it at LOAD (`CombatFeedback.lua` l.7-17, `UnitFrame.lua` l.1-6).
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
load_xml(&s, "Fonts.xml");
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "UIDropDownMenu.xml");
|
||||
load_xml(&s, "UnitPopup.xml");
|
||||
// BENILLA_POWER_COLORS (the shared power-colour table) lives in UnitFrames.xml; the party
|
||||
// frames' Update reads it. The player/target frames it also defines stay hidden here — no unit.
|
||||
load_xml(&s, "UnitFrames.xml");
|
||||
load_xml(&s, "PartyFrame.xml");
|
||||
// The reference's own kit, in the manifest's order. `UIParent.xml` is not decoration here:
|
||||
// `RaiseFrameLevel`/`LowerFrameLevel` live in it (ref UIParent.lua l.1890-1896) and stock
|
||||
// `TargetofTargetTextureFrame`'s OnLoad calls one of them.
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.lua");
|
||||
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\BuffFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\UnitFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\CombatFeedback.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\PlayerFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\PartyFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\TargetFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\PetFrame.xml");
|
||||
|
||||
s.set_unit(
|
||||
"party1",
|
||||
@@ -1153,9 +1227,27 @@ fn the_party_art_paints_over_the_bars() {
|
||||
power_type: 0,
|
||||
power: 60,
|
||||
max_power: 100,
|
||||
is_connected: true,
|
||||
..UnitState::default()
|
||||
}),
|
||||
);
|
||||
// **The ROSTER, not the unit snapshot, is what shows a party row.** Stock
|
||||
// `PartyMemberFrame_UpdateMember` gates on `GetPartyMember(id)` (ref PartyMemberFrame.lua
|
||||
// l.41-57) — `UnitExists("party1")` is never consulted — and its else arm is `this:Hide()`.
|
||||
// Our deleted `PartyFrame.xml` keyed the row off the unit token, so setting `party1` alone
|
||||
// used to be enough; against the reference's file it paints nothing at all.
|
||||
s.set_party(benilla_ui::script::PartyState {
|
||||
members: vec![benilla_ui::script::PartyMemberInfo {
|
||||
name: "Onepriest".into(),
|
||||
guid: 0x0_0B12,
|
||||
}],
|
||||
leader_index: 0,
|
||||
leader_guid: 0,
|
||||
raid: Vec::new(),
|
||||
loot_method: "group".into(),
|
||||
master_looter: None,
|
||||
loot_threshold: 2,
|
||||
});
|
||||
s.fire_event("PARTY_MEMBERS_CHANGED", vec![]);
|
||||
s.resolve();
|
||||
let quads = s.extract();
|
||||
@@ -1166,14 +1258,39 @@ fn the_party_art_paints_over_the_bars() {
|
||||
s.quad_owner_name(q.target)
|
||||
.is_some_and(|n| n.starts_with("PartyMemberFrame1"))
|
||||
};
|
||||
let art = quads
|
||||
// The ART cannot be scoped that way, and the reason is the reference's own declaration:
|
||||
// `PartyMemberFrameTemplate` hangs `$parentTexture` two levels down inside a pair of
|
||||
// **anonymous** `<Frame setAllPoints="true">` wrappers (ref PartyFrameTemplates.xml
|
||||
// l.230-240). The region itself still resolves to `PartyMemberFrame1Texture` — `$parent`
|
||||
// walks to the nearest named ancestor — but the frame that OWNS the quad has no name at all,
|
||||
// so an owner-name filter can never see it. (Our deleted PartyFrame.xml gave that wrapper a
|
||||
// name, `$parentTextureFrame`, which is why this used to work.) Scoped by geometry instead:
|
||||
// rows 2-4 are hidden with no member, so exactly one UI-PartyFrame quad is drawn, and it is
|
||||
// asserted to sit on member 1's rect.
|
||||
let art_quads: Vec<_> = quads
|
||||
.iter()
|
||||
.find(|q| {
|
||||
mine(q)
|
||||
&& matches!(&q.content, QuadContent::Texture { path: Some(p), .. }
|
||||
.filter(|q| {
|
||||
matches!(&q.content, QuadContent::Texture { path: Some(p), .. }
|
||||
if p.ends_with("UI-PartyFrame"))
|
||||
})
|
||||
.expect("the party member frame art");
|
||||
.collect();
|
||||
assert_eq!(
|
||||
art_quads.len(),
|
||||
1,
|
||||
"one member in the party, one row of art drawn"
|
||||
);
|
||||
let art = art_quads[0];
|
||||
let row: Vec<f32> = s
|
||||
.eval(
|
||||
"return { PartyMemberFrame1:GetLeft(), PartyMemberFrame1:GetBottom(), \
|
||||
PartyMemberFrame1:GetRight(), PartyMemberFrame1:GetTop() }",
|
||||
)
|
||||
.unwrap();
|
||||
let r = art.rect.expect("the art quad has a resolved rect");
|
||||
assert!(
|
||||
r.left >= row[0] - 1.0 && r.right <= row[2] + 1.0 && r.top <= row[3] + 3.0,
|
||||
"the art quad sits on PartyMemberFrame1 (art {r:?}, row {row:?})"
|
||||
);
|
||||
let fill = quads
|
||||
.iter()
|
||||
.filter(|q| {
|
||||
@@ -1234,8 +1351,16 @@ fn a_feigning_target_paints_empty_bars_and_the_dead_text() {
|
||||
assert!(alive, "the control: a live hunter reads live");
|
||||
|
||||
// He feigns. Only the flag moved on the wire — the snapshot turns it into these three.
|
||||
//
|
||||
// BOTH events, because the reference's bars are independent listeners: the health bar takes
|
||||
// UNIT_HEALTH/UNIT_MAXHEALTH (ref UnitFrame.lua l.150-151) and the mana bar takes the ten
|
||||
// UNIT_MANA/RAGE/FOCUS/… ones (l.189-199) — there is no shared "repaint the frame" event, so
|
||||
// UNIT_HEALTH alone leaves the power bar showing the pre-feign number. Our deleted
|
||||
// transcription drove both bars off one update, which is why this test used to fire one event.
|
||||
// The server sends both when the dynflag lands.
|
||||
s.set_unit("target", hunter(0, 0, true));
|
||||
s.fire_event("UNIT_HEALTH", vec![ScriptValue::Str("target".into())]);
|
||||
s.fire_event("UNIT_MANA", vec![ScriptValue::Str("target".into())]);
|
||||
let (hp, hmax, mana, mmax): (f64, f64, f64, f64) = (
|
||||
s.eval("return TargetFrameHealthBar:GetValue()").unwrap(),
|
||||
s.eval("local _, m = TargetFrameHealthBar:GetMinMaxValues() return m")
|
||||
@@ -1270,6 +1395,7 @@ fn a_feigning_target_paints_empty_bars_and_the_dead_text() {
|
||||
// He stands back up: the flag clears, and nothing about the body needed restoring.
|
||||
s.set_unit("target", hunter(1200, 300, false));
|
||||
s.fire_event("UNIT_HEALTH", vec![ScriptValue::Str("target".into())]);
|
||||
s.fire_event("UNIT_MANA", vec![ScriptValue::Str("target".into())]);
|
||||
let up: bool = s
|
||||
.eval(
|
||||
r#"
|
||||
@@ -1444,10 +1570,21 @@ fn the_rest_badge_covers_the_level_number() {
|
||||
/// frames people actually watch (director report). Now the player's health and power bars carry
|
||||
/// "value / max", and the switch pins them.
|
||||
///
|
||||
/// Which bars it pins is the REFERENCE's own split, transcribed rather than chosen: 1.12 sets
|
||||
/// `textLockable` on the player's two bars, the pet's two and the XP bar, and on nothing else — so
|
||||
/// the target frame's numerals are hover-only there and stay blank here. The power bar is whatever
|
||||
/// the unit runs on; a rage unit reads "45 / 80" the same as a mana one.
|
||||
/// Which bars it reaches is the REFERENCE's own split, and running the reference's own files made
|
||||
/// it narrower than 1146 believed. `textLockable` is set on the player's two bars, the pet's two
|
||||
/// and the XP bar and nothing else — but the TARGET frame goes further than "not lockable": it has
|
||||
/// **no text regions at all**. `TargetFrame.xml` l.486-487 hands `TargetFrameHealthBarText` /
|
||||
/// `TargetFrameManaBarText` to `UnitFrame_Initialize` and neither name is declared anywhere in
|
||||
/// FrameXML (only `PlayerFrame.xml` l.79/88 and `PetFrame.xml` l.87/96 declare theirs), so
|
||||
/// `SetTextStatusBarText` early-returns on the nil (ref TextStatusBar.lua l.7-10) and those bars
|
||||
/// never get a `TextString`. 1146 §3 *added* the two regions to our transcription so a hover could
|
||||
/// reveal them; they went with the file. What 1.12 actually gives you when you hover a target bar
|
||||
/// is the unit TOOLTIP (ref TextStatusBar.xml l.16-26), asserted below.
|
||||
///
|
||||
/// The power bar's numerals also carry the resource's LABEL again — "Rage 45 / 80" — because
|
||||
/// `UnitFrame_UpdateManaType` sets the prefix on every update (ref UnitFrame.lua l.129) and that
|
||||
/// call is inside the stock file. 1147 §1 cut ours on the director's look call; the stock file has
|
||||
/// no such cut.
|
||||
#[test]
|
||||
fn status_bar_text_paints_the_player_numerals_but_not_the_targets() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
@@ -1465,6 +1602,10 @@ fn status_bar_text_paints_the_player_numerals_but_not_the_targets() {
|
||||
max_power: 80,
|
||||
dead: false,
|
||||
reaction: 4,
|
||||
// Without this `UnitFrameManaBar_Update` takes its disconnect leg (ref UnitFrame.lua
|
||||
// l.209-212) — the power bar pins to MAX and never reaches `UnitFrame_UpdateManaType`,
|
||||
// so it would read "80 / 80" with no prefix.
|
||||
is_connected: true,
|
||||
..UnitState::default()
|
||||
};
|
||||
s.set_unit("player", Some(alive(72, 45, 1))); // power_type 1 = RAGE
|
||||
@@ -1493,59 +1634,95 @@ fn status_bar_text_paints_the_player_numerals_but_not_the_targets() {
|
||||
shown(&s, "PlayerFrameHealthBarText"),
|
||||
"your own health numerals pin on"
|
||||
);
|
||||
// Bare here, and only because this fixture stops short of the character window: the
|
||||
// reference's "Health" prefix is set by `CharacterFrame_OnLoad` (ref CharacterFrame.lua l.55),
|
||||
// not by the unit frames, and the manifest loads `Interface\FrameXML\CharacterFrame.xml` far
|
||||
// below these. In a full run the player's health bar reads "Health 72 / 100".
|
||||
assert_eq!(
|
||||
text(&s, "PlayerFrameHealthBarText"),
|
||||
"72 / 100",
|
||||
"bare numbers: the reference's \"Health\"/\"Rage\" labels were cut on the \
|
||||
director's call (1147)"
|
||||
"no prefix without the character window, which is what sets HEALTH"
|
||||
);
|
||||
// The power bar's LABEL is back, and it is the unit frames' own:
|
||||
// `UnitFrame_UpdateManaType` re-sets the prefix from `ManaBarColor[UnitPowerType(unit)].prefix`
|
||||
// on every mana-bar update (ref UnitFrame.lua l.122-129), and
|
||||
// `TextStatusBar_UpdateTextString` renders `prefix .. " " .. value .. " / " .. max`
|
||||
// (ref TextStatusBar.lua l.42-46). 1147 §1 cut the three prefix calls out of OUR files on the
|
||||
// director's look call; this one lives inside the stock file, so it came back with it.
|
||||
assert_eq!(
|
||||
s.eval::<String>("return PlayerFrameManaBar.prefix")
|
||||
.unwrap(),
|
||||
"Rage",
|
||||
"the prefix follows the resource — this player runs on rage"
|
||||
);
|
||||
// The STRING, though, is still the one rendered before the switch existed. That is the
|
||||
// reference's own ordering, not a gap: `TextStatusBar_OnEvent`'s CVAR_UPDATE arm only calls
|
||||
// `TextString:Show()` (ref TextStatusBar.lua l.14-24) — it never re-renders — and the last
|
||||
// render ran from `SetValue`'s OnValueChanged during PLAYER_ENTERING_WORLD, before
|
||||
// `UnitFrame_UpdateManaType` had set the prefix (ref UnitFrame.lua l.208-215 sets the value
|
||||
// first). Its own re-render is gated on `GetCVar("statusBarText") == "1"` (l.130-132), which
|
||||
// was not yet true. So the label lands on the next repaint — asserted at the end of this test.
|
||||
assert_eq!(
|
||||
text(&s, "PlayerFrameManaBarText"),
|
||||
"45 / 80",
|
||||
"the power bar shows the number alone, whatever resource it is"
|
||||
"flipping the switch shows the string, it does not re-render it"
|
||||
);
|
||||
|
||||
// The target's stay dark with the switch ON: 1.12 never makes its bars textLockable, so the
|
||||
// option does not reach them.
|
||||
// The target's bars carry NO text region, so there is nothing for the switch to pin and
|
||||
// nothing for a hover to reveal. Asserted on the globals themselves: the reference leaves both
|
||||
// names undeclared (ref TargetFrame.xml l.486-487 passes them anyway).
|
||||
assert!(
|
||||
!shown(&s, "TargetFrameHealthBarText"),
|
||||
"the switch pins your own numbers, never the target's"
|
||||
s.eval::<bool>(
|
||||
"return TargetFrameHealthBarText == nil and TargetFrameManaBarText == nil \
|
||||
and TargetFrameHealthBar.TextString == nil and TargetFrameManaBar.TextString == nil"
|
||||
)
|
||||
.unwrap(),
|
||||
"the reference declares no numerals on the target frame, at any switch setting"
|
||||
);
|
||||
|
||||
// ...but the HOVER reveals them, which is how you read a target's health in 1.12 (1146).
|
||||
s.run("BenillaUnitFrameBar_OnEnter(TargetFrameHealthBar)")
|
||||
.unwrap();
|
||||
assert!(shown(&s, "TargetFrameHealthBarText"));
|
||||
assert_eq!(text(&s, "TargetFrameHealthBarText"), "50 / 100");
|
||||
// `this` bound, as the engine binds it for a bar's own `<OnLeave>`: the stock
|
||||
// `HideTextStatusBarText` reads `this.isZero` on one line where every other reads `bar`
|
||||
// (TextStatusBar.lua:95 — the reference's own slip; a nil index only from bare Lua).
|
||||
s.run(
|
||||
"this = TargetFrameHealthBar BenillaUnitFrameBar_OnLeave(TargetFrameHealthBar) this = nil",
|
||||
)
|
||||
.unwrap();
|
||||
// Hovering a target bar pops the unit TOOLTIP instead — the `elseif this:GetParent() ==
|
||||
// TargetFrame` arm of the template's own OnEnter (ref TextStatusBar.xml l.16-26). Driven
|
||||
// through the real pointer path, because that arm reads `this`.
|
||||
hover(&mut s, "TargetFrameHealthBar");
|
||||
assert!(
|
||||
!shown(&s, "TargetFrameHealthBarText"),
|
||||
"and go away again — the lockShow refcount balances"
|
||||
s.eval::<bool>(
|
||||
"return GameTooltip:IsShown() and GameTooltip:IsOwned(TargetFrameHealthBar)"
|
||||
)
|
||||
.unwrap(),
|
||||
"the target's bar hover is a tooltip, not numerals ({:?})",
|
||||
s.errors()
|
||||
);
|
||||
assert!(
|
||||
s.eval::<String>("return tostring(GameTooltipTextLeft1:GetText())")
|
||||
.unwrap()
|
||||
.contains("Somebody"),
|
||||
"and it is the unit's own plate"
|
||||
);
|
||||
unhover(&mut s);
|
||||
assert!(
|
||||
s.eval::<bool>("return not GameTooltip:IsShown()").unwrap(),
|
||||
"the template's OnLeave hides it (ref TextStatusBar.xml l.28-31)"
|
||||
);
|
||||
|
||||
// The same hover works with the switch OFF, which is the point of it on YOUR frame too: the
|
||||
// numbers are there when you go looking, without living on the bar.
|
||||
// The hover on YOUR bar with the switch OFF is the reveal that still exists: the numbers are
|
||||
// there when you go looking, without living on the bar. `ShowTextStatusBarText` /
|
||||
// `HideTextStatusBarText` and their `lockShow` refcount (ref TextStatusBar.lua l.77-102) are
|
||||
// the mechanism, reached through the template's OnEnter/OnLeave.
|
||||
s.run("SetCVar(\"statusBarText\", \"0\", \"STATUS_BAR_TEXT\")")
|
||||
.unwrap();
|
||||
s.tick(0.0);
|
||||
assert!(!shown(&s, "PlayerFrameHealthBarText"));
|
||||
s.run("BenillaUnitFrameBar_OnEnter(PlayerFrameHealthBar)")
|
||||
.unwrap();
|
||||
hover(&mut s, "PlayerFrameHealthBar");
|
||||
assert!(
|
||||
shown(&s, "PlayerFrameHealthBarText"),
|
||||
"hover shows them even with the option off"
|
||||
"hover shows them even with the option off ({:?})",
|
||||
s.errors()
|
||||
);
|
||||
unhover(&mut s);
|
||||
assert!(
|
||||
!shown(&s, "PlayerFrameHealthBarText"),
|
||||
"and go away again — the lockShow refcount balances"
|
||||
);
|
||||
s.run(
|
||||
"this = PlayerFrameHealthBar BenillaUnitFrameBar_OnLeave(PlayerFrameHealthBar) this = nil",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!shown(&s, "PlayerFrameHealthBarText"));
|
||||
s.run("SetCVar(\"statusBarText\", \"1\", \"STATUS_BAR_TEXT\")")
|
||||
.unwrap();
|
||||
s.tick(0.0);
|
||||
@@ -1555,10 +1732,11 @@ fn status_bar_text_paints_the_player_numerals_but_not_the_targets() {
|
||||
s.fire_event("UNIT_HEALTH", vec![ScriptValue::Str("player".into())]);
|
||||
assert_eq!(text(&s, "PlayerFrameHealthBarText"), "31 / 100");
|
||||
|
||||
// A power-type change repaints the same way (it used to re-LABEL the bar too — 1147 cut that).
|
||||
// A power-type change repaints AND re-labels — `UnitFrame_UpdateManaType` runs off
|
||||
// UNIT_DISPLAYPOWER (ref UnitFrame.lua l.36-40) and the prefix follows the new resource.
|
||||
s.set_unit("player", Some(alive(31, 60, 3)));
|
||||
s.fire_event("UNIT_DISPLAYPOWER", vec![ScriptValue::Str("player".into())]);
|
||||
assert_eq!(text(&s, "PlayerFrameManaBarText"), "60 / 80");
|
||||
assert_eq!(text(&s, "PlayerFrameManaBarText"), "Energy 60 / 80");
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,15 @@ fn load_popup_frames(s: &UiScript) {
|
||||
"UIDropDownMenu.xml",
|
||||
"UnitPopup.xml",
|
||||
"Interface\\FrameXML\\ItemRef.xml",
|
||||
"UnitFrames.xml",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
"Interface\\FrameXML\\TextStatusBar.xml",
|
||||
"Interface\\FrameXML\\BuffFrame.xml",
|
||||
"Interface\\FrameXML\\UnitFrame.xml",
|
||||
"Interface\\FrameXML\\CombatFeedback.xml",
|
||||
"Interface\\FrameXML\\PlayerFrame.xml",
|
||||
"Interface\\FrameXML\\PartyFrame.xml",
|
||||
"Interface\\FrameXML\\TargetFrame.xml",
|
||||
"Interface\\FrameXML\\PetFrame.xml",
|
||||
"ScrollTemplates.xml",
|
||||
"UIPanelTemplates.xml",
|
||||
"FriendsFrame.xml",
|
||||
@@ -54,6 +62,17 @@ fn bake_strings(s: &UiScript) {
|
||||
FOLLOW = "Follow"
|
||||
CANCEL = "Cancel"
|
||||
RAID_TARGET_ICON = "Raid Target Icon"
|
||||
-- The newbie tooltip the stock unit frame raises on a HOVER, which every test in this file
|
||||
-- takes on its way to a right-click. `UnitFrame_OnEnter` (ref `UnitFrame.lua:58-65`) runs
|
||||
-- the detailed-tip branch whenever `SHOW_NEWBIE_TIPS == "1"` — 1.12's own default
|
||||
-- (`UIOptionsFrame.lua:100`), which our `GameTooltip.xml:46` sets at load — and for a
|
||||
-- player-controlled target that is not us it calls
|
||||
-- `GameTooltip_AddNewbieTip(PLAYER_OPTIONS_LABEL, 1, 1, 1, NEWBIE_TOOLTIP_PLAYEROPTIONS)`.
|
||||
-- Both are nil in a bare harness, and `GameTooltip:SetText(nil)` raises. Verbatim from the
|
||||
-- real `Interface\FrameXML\GlobalStrings.lua` off the 1.12.1 chain (l.3081 and l.2755).
|
||||
-- Our deleted `UnitFrames.xml` never reached this arm; the stock file does.
|
||||
PLAYER_OPTIONS_LABEL = "Player Options"
|
||||
NEWBIE_TOOLTIP_PLAYEROPTIONS = "Right-click to bring up special commands for interacting with another player. You can inspect their equipment, issue a party invite, initiate a trade, or challenge a player to a duel. A group leader can promote or remove that player from the group."
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -146,6 +165,7 @@ fn solo_target_right_click_invites_a_player() {
|
||||
exists: true,
|
||||
name: Some("Me".into()),
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
..UnitState::default()
|
||||
}),
|
||||
);
|
||||
@@ -158,6 +178,7 @@ fn solo_target_right_click_invites_a_player() {
|
||||
health: 40,
|
||||
max_health: 40,
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
reaction: 5,
|
||||
..UnitState::default()
|
||||
}),
|
||||
@@ -246,6 +267,7 @@ fn solo_target_trade_click_queues_an_initiate() {
|
||||
exists: true,
|
||||
name: Some("Me".into()),
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
..UnitState::default()
|
||||
}),
|
||||
);
|
||||
@@ -257,6 +279,7 @@ fn solo_target_trade_click_queues_an_initiate() {
|
||||
health: 40,
|
||||
max_health: 40,
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
reaction: 5,
|
||||
..UnitState::default()
|
||||
}),
|
||||
@@ -314,6 +337,7 @@ fn solo_target_follow_click_queues_an_exact_by_name_follow() {
|
||||
exists: true,
|
||||
name: Some("Me".into()),
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
..UnitState::default()
|
||||
}),
|
||||
);
|
||||
@@ -325,6 +349,7 @@ fn solo_target_follow_click_queues_an_exact_by_name_follow() {
|
||||
health: 40,
|
||||
max_health: 40,
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
reaction: 5,
|
||||
..UnitState::default()
|
||||
}),
|
||||
@@ -383,6 +408,7 @@ fn solo_target_inspect_click_reaches_inspect_unit() {
|
||||
exists: true,
|
||||
name: Some("Me".into()),
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
..UnitState::default()
|
||||
}),
|
||||
);
|
||||
@@ -394,6 +420,7 @@ fn solo_target_inspect_click_reaches_inspect_unit() {
|
||||
health: 40,
|
||||
max_health: 40,
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
reaction: 5,
|
||||
..UnitState::default()
|
||||
}),
|
||||
@@ -442,7 +469,15 @@ fn load_pet_menu_frames(s: &UiScript) {
|
||||
"GameTooltip.xml",
|
||||
"UIDropDownMenu.xml",
|
||||
"UnitPopup.xml",
|
||||
"UnitFrames.xml",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
"Interface\\FrameXML\\TextStatusBar.xml",
|
||||
"Interface\\FrameXML\\BuffFrame.xml",
|
||||
"Interface\\FrameXML\\UnitFrame.xml",
|
||||
"Interface\\FrameXML\\CombatFeedback.xml",
|
||||
"Interface\\FrameXML\\PlayerFrame.xml",
|
||||
"Interface\\FrameXML\\PartyFrame.xml",
|
||||
"Interface\\FrameXML\\TargetFrame.xml",
|
||||
"Interface\\FrameXML\\PetFrame.xml",
|
||||
"Cooldown.xml",
|
||||
"ActionBar.xml",
|
||||
"PetActionBar.xml",
|
||||
@@ -495,6 +530,7 @@ fn a_pet(s: &mut UiScript, name: &str) {
|
||||
max_health: 100,
|
||||
level: 60,
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
..UnitState::default()
|
||||
}),
|
||||
);
|
||||
@@ -521,9 +557,28 @@ fn a_pet(s: &mut UiScript, name: &str) {
|
||||
|
||||
/// Open the pet menu through the real hit path, and return nothing — the assertions read
|
||||
/// `DropDownList1` afterwards.
|
||||
///
|
||||
/// **The click lands in the frame's own HIT RECT, not at its centre** — and on the reference's
|
||||
/// file those are two different places. Stock `PetFrame.xml:15-17` ships
|
||||
/// `<HitRectInsets><AbsInset left="7" right="66" top="6" bottom="7"/></HitRectInsets>` on a
|
||||
/// 128×53 button (`PetFrame.xml:4-7`), so the clickable
|
||||
/// band is the PORTRAIT half (x = left+7 … left+62) and the geometric centre (x = left+64) is two
|
||||
/// pixels outside it. Worse, the centre sits *inside* `PetFrameHealthBar` (`PetFrame.xml:125-135`:
|
||||
/// TOPLEFT 47,-22, 70×8), which is mouse-enabled by the `TextStatusBar` template's OnEnter
|
||||
/// (ref `TextStatusBar.xml:11-27`) and sits at `PetFrame.level + 1` — so under the hit law it
|
||||
/// takes the point and the click is consumed there. Unlike stock `PlayerFrame`, stock `PetFrame`
|
||||
/// ships **no** `<OnMouseUp>` forwarder on its bars, so that click is genuinely eaten in the real
|
||||
/// client too; a right-click on a pet's health bar opens nothing. Our deleted transcription had no
|
||||
/// hit-rect insets on the pet frame, which is the only reason `GetCenter()` ever worked here.
|
||||
fn right_click_the_pet_frame(s: &mut UiScript) {
|
||||
s.resolve();
|
||||
let (cx, cy) = s.eval::<(f64, f64)>("return PetFrame:GetCenter()").unwrap();
|
||||
let (cx, cy) = s
|
||||
.eval::<(f64, f64)>(
|
||||
"local il, ir, it, ib = PetFrame:GetHitRectInsets() \
|
||||
return (PetFrame:GetLeft() + il + PetFrame:GetRight() - ir) / 2, \
|
||||
(PetFrame:GetBottom() + ib + PetFrame:GetTop() - it) / 2",
|
||||
)
|
||||
.unwrap();
|
||||
s.mouse_button(cx as f32, cy as f32, "RightButton", true);
|
||||
s.mouse_button(cx as f32, cy as f32, "RightButton", false);
|
||||
s.resolve();
|
||||
@@ -759,6 +814,7 @@ fn an_idle_unit_popup_driver_parks_itself_off_the_tick() {
|
||||
exists: true,
|
||||
name: Some("Me".into()),
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
..UnitState::default()
|
||||
}),
|
||||
);
|
||||
@@ -772,6 +828,7 @@ fn an_idle_unit_popup_driver_parks_itself_off_the_tick() {
|
||||
health: 40,
|
||||
max_health: 40,
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
reaction: 5,
|
||||
..UnitState::default()
|
||||
}),
|
||||
@@ -849,6 +906,7 @@ fn an_idle_unit_popup_driver_parks_itself_off_the_tick() {
|
||||
health: 40,
|
||||
max_health: 40,
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
reaction: 5,
|
||||
is_connected: true,
|
||||
..UnitState::default()
|
||||
|
||||
Reference in New Issue
Block a user