the loading screen lets nothing through, and the micro buttons go stock
- units could be hovered through the loading screen — the sword cursor gave it away: the world pick chain has no state gate, one of fifty-one input sites, so a gate per consumer is a rule that regresses; the cut is at the source instead — one `PreUpdate` system empties the button planes, drains the raw queues, zeroes the accumulators and blanks the window's cursor position while the cover is up (the alt-tabbed-away state every consumer already handles); a press under the cover never happened, a held button is released once, a gesture in flight is cancelled, and a wall test counts the channels; the reference does the same — its raise registers a veto stub (`0x406a50`) on five event-bus categories — and one gap closed with it: the reference parks the plain arrow and drops any item overlay before the screen goes up, where benilla kept a held item's icon over the loading art (1986, 1990) - the micro-button row is the reference's own `MainMenuBarMicroButtons.xml`, our transcription deleted — no engine verb was missing, and the whole engine-side cost is one alias, `SoundOptionsFrame = OptionsFrame`, because the stock file reads the reference's three options windows and ours is the one window for all three; what the stock file brings back: `UpdateTalentButton`'s own −2 re-seat (the five buttons after the talent button sit 1 px further right — the reference's own pixel), the raw binding token in the hover label (`(C)`, `(SHIFT-P)`), the sixty-second talent-button pulse on a ding while the sheet is closed (0304's scope-out retired), the keyring arm, and "Abilities" on the spellbook hover for a character with no spells; `BenillaActionBarArt_SeatAbove` goes with the file — a child is born one level above its parent (`SetParent` `0x76ab65`), the seat it set by hand (1987) - hovering the player arrow on the stock world map showed an empty tooltip: narrower than 4:3 the map is scaled under 1, and our `MouseIsOver` had dropped the reference's division by `GetEffectiveScale` as "always 1" — the frame getters answer in the frame's own units, `GetCursorPosition` in the screen's; the division is back, and the region getters divide by their owner's scale too; and the ready check times out: the stock `UIParent.xml` ticks `CheckReadyCheckTime` every frame, the one unbound name in that file's way, so the mechanism behind it is built — a 30 s deadline and a "has not answered" flag per roster entry, the leader getting no popup while members print "%s has initiated a ready check" and get it, and on expiry `RAID_MEMBERS_AFK` with the unanswered names or `READY_CHECK_NO_AFK`, the last answer closing the check at once (1985, 1989) - a review of the floor grind closed eight stale-input and bypassed-side-effect siblings the gates had left open: a never-placed card places on a still frame, the dormant-emitter skip sits below the emitter's lifetime blocks, PVS changes (not only spawns) un-still the gates, the appear ramp's retirement and a far-side unmark come due, a host born under a parked camera is judged, the projection counts as a still input, a fresh palette range is zeroed, the stable feed runs once more after a close, the unit publisher's viewer query is filtered, solo UI materials are reused by key, and the draw-list cache compares exactly; and three doc links the chat window's stock move (1948) left dangling point at what replaced them — the colour table and the tell ring are the stock Lua's now — which is what had the doc-links gate (1925) red on main Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -414,7 +414,7 @@ fn the_quest_tracker_stacks_below_the_durability_guy() {
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\ItemButtonTemplate.xml",
|
||||
"Interface\\FrameXML\\QuestFrame.xml",
|
||||
"MicroMenu.xml",
|
||||
r"Interface\FrameXML\MainMenuBarMicroButtons.xml",
|
||||
"Interface\\FrameXML\\QuestLogFrame.xml",
|
||||
] {
|
||||
load_xml(&s, f);
|
||||
|
||||
@@ -131,7 +131,7 @@ fn an_error_toast_draws_over_an_open_panel_window() {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
|
||||
// A left-slot panel open, and the toast raised after it — the order that must not decide.
|
||||
|
||||
@@ -574,10 +574,10 @@ fn the_shipped_frames_carry_the_references_flags() {
|
||||
// frames are the reference's own now), which took the paired count from the low 400s to
|
||||
// the high 150s; 1966 (TradeFrame), 1968 (GameTooltip), 1969 (DressUpFrame) and 1970
|
||||
// (MailFrame) to the high 80s, and 1980 (WorldMapFrame, whose 50-odd blip frames were
|
||||
// most of what was left) to the high teens. The floor guards the pairing, not the
|
||||
// census — it comes down with every window that migrates, and reaches zero with the
|
||||
// last file of ours that declares a reference-named frame.
|
||||
compared > 10,
|
||||
// most of what was left) to the high teens, and 1987 (the micro row) to nine. The floor
|
||||
// guards the pairing, not the census — it comes down with every window that migrates,
|
||||
// and reaches zero with the last file of ours that declares a reference-named frame.
|
||||
compared > 5,
|
||||
"only {compared} frames compared — the pairing broke, and the sweep guards nothing"
|
||||
);
|
||||
assert!(
|
||||
|
||||
@@ -577,7 +577,7 @@ fn the_world_map_cannot_open_behind_the_menu_and_gives_its_slot_back() {
|
||||
"Interface\\FrameXML\\MainMenuBar.xml",
|
||||
"Interface\\FrameXML\\ActionBarFrame.xml",
|
||||
"Interface\\FrameXML\\BonusActionBarFrame.xml",
|
||||
"MicroMenu.xml",
|
||||
r"Interface\FrameXML\MainMenuBarMicroButtons.xml",
|
||||
r"Interface\FrameXML\WorldMapFrame.xml",
|
||||
]);
|
||||
|
||||
@@ -638,7 +638,7 @@ fn nothing_opens_behind_the_world_map_and_escape_closes_it_first() {
|
||||
"Interface\\FrameXML\\MainMenuBar.xml",
|
||||
"Interface\\FrameXML\\ActionBarFrame.xml",
|
||||
"Interface\\FrameXML\\BonusActionBarFrame.xml",
|
||||
"MicroMenu.xml",
|
||||
r"Interface\FrameXML\MainMenuBarMicroButtons.xml",
|
||||
r"Interface\FrameXML\WorldMapFrame.xml",
|
||||
"Interface\\FrameXML\\MerchantFrame.xml",
|
||||
// The loot window is the reference's own since 1751 — see `test_ui::LOOT_UI` for what
|
||||
|
||||
@@ -38,7 +38,7 @@ fn setup() -> UiScript {
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
// The stock file's `HelpFrame_OnShow` calls `UpdateMicroButtons()` before
|
||||
// `GetGMStatus()`, so without the micro row the OnShow raises and the status ask never
|
||||
// happens. Ours never called it. MicroMenu needs the bar's button kit under it.
|
||||
// happens. Ours never called it. The row needs the bar's button kit under it.
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
@@ -47,7 +47,7 @@ fn setup() -> UiScript {
|
||||
"Interface\\FrameXML\\MainMenuBar.xml",
|
||||
"Interface\\FrameXML\\ActionBarFrame.xml",
|
||||
"Interface\\FrameXML\\BonusActionBarFrame.xml",
|
||||
"MicroMenu.xml",
|
||||
r"Interface\FrameXML\MainMenuBarMicroButtons.xml",
|
||||
// `TicketStatusFrame_OnEvent` re-anchors TemporaryEnchantFrame before it arms the repoll
|
||||
// timer, so without BuffFrame the handler raises and `refreshTime` is never set. This is
|
||||
// the one ordering requirement benilla.toc already calls out for this window.
|
||||
|
||||
@@ -108,7 +108,7 @@ fn harness_with(player: &str) -> UiScript {
|
||||
r"Interface\FrameXML\UIPanelTemplates.xml",
|
||||
// `ClassTrainerListScrollFrameTemplate` — the icon chooser's scroll frame inherits it.
|
||||
r"Interface\FrameXML\ClassTrainerFrameTemplates.xml",
|
||||
"MicroMenu.xml", // stock `MacroFrame_OnShow`/`_OnHide` drive the micro button
|
||||
r"Interface\FrameXML\MainMenuBarMicroButtons.xml", // stock `MacroFrame_OnShow`/`_OnHide` drive the micro button
|
||||
] {
|
||||
super::test_ui::load_ui(&s, file);
|
||||
}
|
||||
|
||||
@@ -1,85 +1,119 @@
|
||||
//! The shipped `assets/ui/MicroMenu.xml` — the eight micro buttons in the main bar's right-hand
|
||||
//! recess — loaded behind `ActionBar.xml` (their anchor target `MainMenuBarArtFrame` and the
|
||||
//! `BenillaActionBarArt_SeatAbove` seating helper) into a bare engine.
|
||||
//! The stock `Interface\FrameXML\MainMenuBarMicroButtons.xml` — the eight micro buttons in the
|
||||
//! main bar's right-hand recess — off the player's chain behind the stock bar (their anchor
|
||||
//! target `MainMenuBarArtFrame`) into a bare engine (decision 1987).
|
||||
//!
|
||||
//! What these guard, in order: the row's ref geometry (29×58 at art-frame BOTTOMLEFT +(552,2), a
|
||||
//! 26 px stride) — which is the whole point of the slice, since the gap the row fills is exactly
|
||||
//! that panel of empty bar art; the `HitRectInsets` header that keeps a button's transparent top
|
||||
//! from eating the mouse; `UpdateMicroButtons` tracking a panel's visibility; and
|
||||
//! `UpdateTalentButton`'s level-10 gate closing the row up behind the hidden talent button.
|
||||
//! What these guard, in order: the row's geometry (29×58 at art-frame BOTTOMLEFT +(552,2), a
|
||||
//! 26 px stride, and the reference's own 1 px nudge once the talent gate has run); the
|
||||
//! `HitRectInsets` header that keeps a button's transparent top from eating the mouse; the
|
||||
//! character button's portrait crop; `UpdateTalentButton`'s level-10 gate closing the row up
|
||||
//! behind the hidden talent button; the two-line hover with the bound key in its label; and,
|
||||
//! under the whole shipped manifest, `UpdateMicroButtons` following a real panel and the
|
||||
//! level-up pulse the reference hangs on the talent button.
|
||||
|
||||
use benilla_ui::script::{QuadContent, ScriptValue, TexCoords, UiScript};
|
||||
use benilla_ui::script::{QuadContent, ScriptValue, TexCoords, UiScript, UnitState};
|
||||
|
||||
/// Load `ActionBar.xml` then `MicroMenu.xml` into a 1024×768 engine, asserting both load clean.
|
||||
/// 1024 wide is deliberate: the 1024-wide bar then centers at x=0, so every ref offset below is
|
||||
/// The eight, in row order.
|
||||
const ROW: [&str; 8] = [
|
||||
"CharacterMicroButton",
|
||||
"SpellbookMicroButton",
|
||||
"TalentMicroButton",
|
||||
"QuestLogMicroButton",
|
||||
"SocialsMicroButton",
|
||||
"WorldMapMicroButton",
|
||||
"MainMenuMicroButton",
|
||||
"HelpMicroButton",
|
||||
];
|
||||
|
||||
/// The stock bar, then the stock row, into a 1024×768 engine, asserting both load clean. 1024
|
||||
/// wide is deliberate: the 1024-wide bar then centers at x=0, so every reference offset below is
|
||||
/// also an absolute screen coordinate.
|
||||
fn harness() -> UiScript {
|
||||
harness_with(&[])
|
||||
}
|
||||
|
||||
/// …with more shipped files behind the row — the tooltip test needs the real plate.
|
||||
fn harness_with(extra: &[&str]) -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
let files: Vec<&str> = [
|
||||
for file in [
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
"Cooldown.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
"Interface\\FrameXML\\TextStatusBar.xml",
|
||||
"UIParent.xml",
|
||||
// The eight labels and their NEWBIE_TOOLTIP_* lines, read at each button's OnLoad…
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
// …through `TEXT()`.
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\MainMenuBar.xml",
|
||||
r"Interface\FrameXML\MoneyFrame.lua",
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
"Interface\\FrameXML\\ActionBarFrame.xml",
|
||||
"Interface\\FrameXML\\BonusActionBarFrame.xml",
|
||||
"MicroMenu.xml",
|
||||
]
|
||||
.into_iter()
|
||||
.chain(extra.iter().copied())
|
||||
.collect();
|
||||
// Through `test_ui::load_ui`, not off disk: the list above now names a chain file, and a
|
||||
// reader that joins `assets/ui` cannot resolve one (the wall 1838 hit, and 1887 again).
|
||||
for file in files {
|
||||
r"Interface\FrameXML\MainMenuBarMicroButtons.xml",
|
||||
] {
|
||||
super::test_ui::load_ui(&s, file);
|
||||
}
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
s
|
||||
}
|
||||
|
||||
/// The row's geometry, quoted from ref-MainMenuBarMicroButtons.xml: CharacterMicroButton at the
|
||||
/// art frame's BOTTOMLEFT +(552,2), each button 29×58, the rest chained BOTTOMLEFT-to-previous-
|
||||
/// BOTTOMRIGHT +(-3,0) ⇒ a 26 px stride, so the eight run x 552..757 — inside the 1024-wide bar,
|
||||
/// between the page arrows (x≈522) and the bag cluster at the far right.
|
||||
/// Set the player's level and fire the `UNIT_LEVEL` the talent gate listens for.
|
||||
fn set_player_level(s: &mut UiScript, level: u32) {
|
||||
s.set_unit(
|
||||
"player",
|
||||
Some(UnitState {
|
||||
exists: true,
|
||||
level,
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
s.fire_event("UNIT_LEVEL", vec![ScriptValue::Str("player".into())]);
|
||||
}
|
||||
|
||||
fn left_of(s: &UiScript, name: &str) -> f64 {
|
||||
s.eval::<f64>(&format!("return {name}:GetLeft()"))
|
||||
.unwrap_or_else(|e| panic!("{name}: {e}"))
|
||||
}
|
||||
|
||||
/// The row's geometry, the reference's own numbers: CharacterMicroButton at the art frame's
|
||||
/// BOTTOMLEFT +(552,2), each button 29×58, the rest chained BOTTOMLEFT-to-previous-BOTTOMRIGHT
|
||||
/// +(-3,0) ⇒ a 26 px stride, so the eight run x 552..757 as declared — between the page arrows
|
||||
/// (x≈522) and the bag cluster at the far right. **As declared** is the operative phrase: the XML
|
||||
/// seats the quest-log button at −3 and `UpdateTalentButton` re-seats it at −2
|
||||
/// (`MainMenuBarMicroButtons.lua:139`), so the first level pass at 10+ nudges the tail right by
|
||||
/// one pixel. A real row is 1 px wider past its first `PLAYER_ENTERING_WORLD` than its XML
|
||||
/// says; that is the client's, and it is asserted here as the reference's law rather than
|
||||
/// sanded off (our retired file wrote −3 both ways).
|
||||
#[test]
|
||||
fn the_micro_row_sits_where_the_reference_puts_it() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = harness();
|
||||
// Past level 10 so all eight are shown and the row is unbroken.
|
||||
set_player_level(&mut s, 10);
|
||||
s.resolve();
|
||||
|
||||
let names = [
|
||||
"CharacterMicroButton",
|
||||
"SpellbookMicroButton",
|
||||
"TalentMicroButton",
|
||||
"QuestLogMicroButton",
|
||||
"SocialsMicroButton",
|
||||
"WorldMapMicroButton",
|
||||
"MainMenuMicroButton",
|
||||
"HelpMicroButton",
|
||||
];
|
||||
for (i, name) in names.iter().enumerate() {
|
||||
// As declared, before any level pass: a clean 26 px stride.
|
||||
for (i, name) in ROW.iter().enumerate() {
|
||||
let (left, bottom, w, h) = s
|
||||
.eval::<(f64, f64, f64, f64)>(&format!(
|
||||
"return {name}:GetLeft(), {name}:GetBottom(), {name}:GetWidth(), {name}:GetHeight()"
|
||||
))
|
||||
.unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||
assert_eq!((w, h), (29.0, 58.0), "{name} size");
|
||||
assert_eq!(left, 552.0 + 26.0 * i as f64, "{name} left edge");
|
||||
assert_eq!(
|
||||
left,
|
||||
552.0 + 26.0 * i as f64,
|
||||
"{name} left edge, as declared"
|
||||
);
|
||||
assert_eq!(bottom, 2.0, "{name} sits 2 above the bar's bottom");
|
||||
}
|
||||
|
||||
// Past level 10 the gate re-seats the quest-log button at −2: the tail moves 1 px right.
|
||||
set_player_level(&mut s, 60);
|
||||
s.resolve();
|
||||
for (i, name) in ROW.iter().enumerate().skip(3) {
|
||||
assert_eq!(
|
||||
left_of(&s, name),
|
||||
553.0 + 26.0 * i as f64,
|
||||
"{name} after the talent gate's own −2"
|
||||
);
|
||||
}
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
@@ -88,8 +122,8 @@ fn the_micro_row_sits_where_the_reference_puts_it() {
|
||||
/// bar's XP strip and the sliver of world above it.
|
||||
#[test]
|
||||
fn the_transparent_top_of_a_micro_button_does_not_capture_the_mouse() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = harness();
|
||||
set_player_level(&mut s, 10);
|
||||
s.resolve();
|
||||
|
||||
// Mid-button horizontally (552 + 14), inside the art band (y 2..42) ⇒ captures.
|
||||
@@ -98,7 +132,7 @@ fn the_transparent_top_of_a_micro_button_does_not_capture_the_mouse() {
|
||||
Some("CharacterMicroButton"),
|
||||
"the art band takes the mouse"
|
||||
);
|
||||
// Same column, in the dead 18-unit header (y 42..60) ⇒ nothing of ours captures it.
|
||||
// Same column, in the dead 18-unit header (y 42..60) ⇒ nothing of the row captures it.
|
||||
assert_ne!(
|
||||
s.hit_test_name(566.0, 50.0).as_deref(),
|
||||
Some("CharacterMicroButton"),
|
||||
@@ -107,13 +141,14 @@ fn the_transparent_top_of_a_micro_button_does_not_capture_the_mouse() {
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// The character button's face: the region binds the SAME `"player"` portrait slot the unit frame
|
||||
/// samples, and carries the ref's crop window out to the renderer. Both halves matter — the crop is
|
||||
/// what makes an 18×25 rectangle show a face instead of the whole square bake squashed into it, and
|
||||
/// the extract path used to pin portrait quads to full UVs and drop it (fixed with this slice).
|
||||
/// Pushing the button swaps the window and dims it (ref CharacterMicroButton_SetPushed).
|
||||
/// The character button's face: `MicroButtonPortrait` binds the SAME `"player"` portrait slot
|
||||
/// the unit frame samples, and carries the reference's crop window out to the renderer. Both
|
||||
/// halves matter — the crop is what makes an 18×25 rectangle show a face instead of the whole
|
||||
/// square bake squashed into it. Pushing the button swaps the window and dims it
|
||||
/// (`CharacterMicroButton_SetPushed`, `MainMenuBarMicroButtons.lua:109-117`).
|
||||
#[test]
|
||||
fn the_character_button_carries_the_player_portrait_through_the_reference_crop() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = harness();
|
||||
s.resolve();
|
||||
|
||||
@@ -136,8 +171,8 @@ fn the_character_button_carries_the_player_portrait_through_the_reference_crop()
|
||||
assert_eq!(unit, "player");
|
||||
assert!(
|
||||
circular,
|
||||
"the ref's round stencil lives in the bake's own UV space, so the crop below yields a \
|
||||
rectangular slice OF a masked face — not an ellipse fitted to this 18x25 region"
|
||||
"the reference's round stencil lives in the bake's own UV space, so the crop below yields \
|
||||
a rectangular slice OF a masked face — not an ellipse fitted to this 18x25 region"
|
||||
);
|
||||
let round4 = |c: TexCoords| match c {
|
||||
TexCoords::Rect(e) => e.map(|v| (v * 10_000.0).round() / 10_000.0),
|
||||
@@ -146,7 +181,7 @@ fn the_character_button_carries_the_player_portrait_through_the_reference_crop()
|
||||
assert_eq!(
|
||||
round4(coords.expect("a crop window")),
|
||||
[0.2, 0.8, 0.0666, 0.9],
|
||||
"the normal window (ref CharacterMicroButton_SetNormal)"
|
||||
"the normal window (CharacterMicroButton_SetNormal)"
|
||||
);
|
||||
|
||||
s.run("CharacterMicroButton_SetPushed()").unwrap();
|
||||
@@ -155,7 +190,7 @@ fn the_character_button_carries_the_player_portrait_through_the_reference_crop()
|
||||
assert_eq!(
|
||||
round4(coords.expect("a crop window")),
|
||||
[0.2666, 0.8666, 0.0, 0.8333],
|
||||
"the held-down window (ref CharacterMicroButton_SetPushed)"
|
||||
"the held-down window (CharacterMicroButton_SetPushed)"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<f64>("return MicroButtonPortrait:GetAlpha()")
|
||||
@@ -166,50 +201,12 @@ fn the_character_button_carries_the_player_portrait_through_the_reference_crop()
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// `UpdateMicroButtons` (ref MainMenuBarMicroButtons.lua l.20-84): a button is PUSHED exactly
|
||||
/// while its panel is up. Driven here with a stand-in frame under the real panel's name rather
|
||||
/// than the whole character window — this is the visibility contract, not that window's.
|
||||
#[test]
|
||||
fn a_micro_button_pushes_while_its_panel_is_open() {
|
||||
let s = harness();
|
||||
s.run(
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "CharacterFrame")
|
||||
f:SetPoint("CENTER", 0, 0); f:SetSize(100, 100); f:Hide()
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let state = |s: &UiScript| {
|
||||
s.eval::<String>("return CharacterMicroButton:GetButtonState()")
|
||||
.unwrap()
|
||||
};
|
||||
s.run("UpdateMicroButtons()").unwrap();
|
||||
assert_eq!(state(&s), "NORMAL", "closed panel ⇒ button up");
|
||||
|
||||
s.run("CharacterFrame:Show(); UpdateMicroButtons()")
|
||||
.unwrap();
|
||||
assert_eq!(state(&s), "PUSHED", "open panel ⇒ button held down");
|
||||
|
||||
s.run("CharacterFrame:Hide(); UpdateMicroButtons()")
|
||||
.unwrap();
|
||||
assert_eq!(state(&s), "NORMAL", "closed again ⇒ button pops back");
|
||||
|
||||
// A button whose panel doesn't exist at all (the three unwired ones, and any panel a test
|
||||
// harness didn't load) never pushes — and never errors.
|
||||
assert_eq!(
|
||||
s.eval::<String>("return SocialsMicroButton:GetButtonState()")
|
||||
.unwrap(),
|
||||
"NORMAL"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// `UpdateTalentButton` (ref l.133-141): under level 10 the talent button is hidden and the quest
|
||||
/// log button slides onto its seat, so the row has no hole in it; at 10 the button returns and the
|
||||
/// tail shifts back out by one stride.
|
||||
/// `UpdateTalentButton` (l.133-141): under level 10 the talent button is hidden and the quest
|
||||
/// log button slides onto its seat, so the row has no hole in it; at 10 the button returns and
|
||||
/// the tail shifts back out — by one stride less two, the gate's own −2.
|
||||
#[test]
|
||||
fn the_talent_button_appears_at_level_ten_and_the_row_closes_up_below_it() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = harness();
|
||||
|
||||
set_player_level(&mut s, 9);
|
||||
@@ -220,13 +217,9 @@ fn the_talent_button_appears_at_level_ten_and_the_row_closes_up_below_it() {
|
||||
"no talents before 10"
|
||||
);
|
||||
// The quest log button takes the talent button's own seat — slot 3, not slot 4.
|
||||
assert_eq!(left_of(&s, "QuestLogMicroButton"), 552.0 + 26.0 * 2.0);
|
||||
assert_eq!(
|
||||
s.eval::<f64>("return QuestLogMicroButton:GetLeft()")
|
||||
.unwrap(),
|
||||
552.0 + 26.0 * 2.0
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<f64>("return HelpMicroButton:GetLeft()").unwrap(),
|
||||
left_of(&s, "HelpMicroButton"),
|
||||
552.0 + 26.0 * 6.0,
|
||||
"the whole tail moves up one slot with it"
|
||||
);
|
||||
@@ -239,51 +232,84 @@ fn the_talent_button_appears_at_level_ten_and_the_row_closes_up_below_it() {
|
||||
"the button returns at 10"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<f64>("return QuestLogMicroButton:GetLeft()")
|
||||
.unwrap(),
|
||||
552.0 + 26.0 * 3.0
|
||||
left_of(&s, "QuestLogMicroButton"),
|
||||
553.0 + 26.0 * 3.0,
|
||||
"…and the tail goes back out, at the gate's own −2"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// The hover (ref MainMenuBarMicroButton's OnEnter, l.13): every button's plate is the ref's
|
||||
/// TWO-line `GameTooltip_AddNewbieTip` — the label, then that button's own `NEWBIE_TOOLTIP_*`
|
||||
/// explanation in gold. 1.12 ships detailed tips ON (`SHOW_NEWBIE_TIPS = "1"`, ref
|
||||
/// UIOptionsFrame.lua l.100), so the paragraph is the DEFAULT hover; this file used to open-code
|
||||
/// the tips-off branch and show a bare label. Decision 0661.
|
||||
///
|
||||
/// Checked across all eight, because the explanation is threaded per button through OnLoad and a
|
||||
/// single missing argument is invisible until someone hovers that one button.
|
||||
/// The hover (the template's OnEnter, `MainMenuBarMicroButtons.xml:12-14`): every button's plate
|
||||
/// is the reference's TWO-line `GameTooltip_AddNewbieTip` — the label, then that button's own
|
||||
/// `NEWBIE_TOOLTIP_*` explanation in gold. 1.12 ships detailed tips ON (`SHOW_NEWBIE_TIPS = "1"`,
|
||||
/// `UIOptionsFrame.lua:100`; ours sits in OptionsFrame.xml's uvar block), so the paragraph is the
|
||||
/// DEFAULT hover (decision 0661). Through the engine's own hover, because the stock OnEnter reads
|
||||
/// the firing frame off `this`. For the bound ones the label carries the key the way the
|
||||
/// reference prints it — `GetBindingKey`'s raw token in `NORMAL_FONT_COLOR_CODE` parentheses
|
||||
/// (`MicroButtonTooltipText`, l.11-18) — re-read on `UPDATE_BINDINGS`, which is the event
|
||||
/// `bindings.rs` fires after every (re)load of the table.
|
||||
#[test]
|
||||
fn every_micro_button_hovers_with_its_reference_explanation() {
|
||||
let mut s = harness_with(&["UIParent.xml"]);
|
||||
set_player_level(&mut s, 10);
|
||||
// 1.12 ships detailed tips ON — `SHOW_NEWBIE_TIPS = "1"` is UIOptionsFrame_Init's (ref
|
||||
// UIOptionsFrame.lua l.100; ours sits in OptionsFrame.xml's uvar block, 1968), and a harness
|
||||
// without the options file says so itself, the way the reference's tooltip would read it.
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = harness();
|
||||
s.run("SHOW_NEWBIE_TIPS = \"1\"").unwrap();
|
||||
// A key the character button's label can carry: the registry, then the reference's own
|
||||
// `SetBinding`, then the event the row re-reads its labels on.
|
||||
s.register_bindings(&crate::bindings::registry_commands());
|
||||
assert_eq!(
|
||||
s.eval::<Option<i64>>("return SetBinding(\"C\", \"TOGGLECHARACTER0\")")
|
||||
.unwrap(),
|
||||
Some(1)
|
||||
);
|
||||
s.fire_event("UPDATE_BINDINGS", vec![]);
|
||||
s.resolve();
|
||||
|
||||
for (button, newbie) in [
|
||||
("CharacterMicroButton", "NEWBIE_TOOLTIP_CHARACTER"),
|
||||
("SpellbookMicroButton", "NEWBIE_TOOLTIP_SPELLBOOK"),
|
||||
("TalentMicroButton", "NEWBIE_TOOLTIP_TALENTS"),
|
||||
("QuestLogMicroButton", "NEWBIE_TOOLTIP_QUESTLOG"),
|
||||
("SocialsMicroButton", "NEWBIE_TOOLTIP_SOCIAL"),
|
||||
("WorldMapMicroButton", "NEWBIE_TOOLTIP_WORLDMAP"),
|
||||
("MainMenuMicroButton", "NEWBIE_TOOLTIP_MAINMENU"),
|
||||
("HelpMicroButton", "NEWBIE_TOOLTIP_HELP"),
|
||||
for (button, label, newbie) in [
|
||||
(
|
||||
"CharacterMicroButton",
|
||||
"CHARACTER_BUTTON",
|
||||
"NEWBIE_TOOLTIP_CHARACTER",
|
||||
),
|
||||
// The spellbook's own OnEnter (l.79-87) picks its label by `PlayerHasSpells()`.
|
||||
(
|
||||
"SpellbookMicroButton",
|
||||
"PlayerHasSpells() and SPELLBOOK_ABILITIES_BUTTON or ABILITYBOOK_BUTTON",
|
||||
"NEWBIE_TOOLTIP_SPELLBOOK",
|
||||
),
|
||||
(
|
||||
"TalentMicroButton",
|
||||
"TALENTS_BUTTON",
|
||||
"NEWBIE_TOOLTIP_TALENTS",
|
||||
),
|
||||
(
|
||||
"QuestLogMicroButton",
|
||||
"QUESTLOG_BUTTON",
|
||||
"NEWBIE_TOOLTIP_QUESTLOG",
|
||||
),
|
||||
(
|
||||
"SocialsMicroButton",
|
||||
"SOCIAL_BUTTON",
|
||||
"NEWBIE_TOOLTIP_SOCIAL",
|
||||
),
|
||||
(
|
||||
"WorldMapMicroButton",
|
||||
"WORLDMAP_BUTTON",
|
||||
"NEWBIE_TOOLTIP_WORLDMAP",
|
||||
),
|
||||
(
|
||||
"MainMenuMicroButton",
|
||||
"MAINMENU_BUTTON",
|
||||
"NEWBIE_TOOLTIP_MAINMENU",
|
||||
),
|
||||
("HelpMicroButton", "HELP_BUTTON", "NEWBIE_TOOLTIP_HELP"),
|
||||
] {
|
||||
// A hover is a handler: the reference's helper reads the firing frame off `this`.
|
||||
s.run(&format!("this = {button} BenillaMicroButton_OnEnter(this)"))
|
||||
.unwrap();
|
||||
super::test_ui::hover(&mut s, button);
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return GameTooltip:NumLines()").unwrap(),
|
||||
2,
|
||||
"{button}: the label, then the explanation"
|
||||
);
|
||||
// Line 1 is the button's own label (plus a `|cff…(key)|r` suffix on the five bound ones).
|
||||
let label = s.eval::<String>(&format!("return {button}.label")).unwrap();
|
||||
let label = s.eval::<String>(&format!("return {label}")).unwrap();
|
||||
let line1 = s
|
||||
.eval::<String>("return GameTooltipTextLeft1:GetText()")
|
||||
.unwrap();
|
||||
@@ -295,26 +321,130 @@ fn every_micro_button_hovers_with_its_reference_explanation() {
|
||||
s.eval::<String>("return GameTooltipTextLeft2:GetText()")
|
||||
.unwrap(),
|
||||
s.eval::<String>(&format!("return {newbie}")).unwrap(),
|
||||
"{button}: line 2 is the ref's {newbie}, verbatim"
|
||||
"{button}: line 2 is the reference's {newbie}, verbatim"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return GameTooltip.default").unwrap(),
|
||||
1,
|
||||
"{button}: the default-corner anchor, not ANCHOR_RIGHT off the button"
|
||||
);
|
||||
super::test_ui::unhover(&mut s);
|
||||
}
|
||||
|
||||
// The bound one carries its key the reference's way: the raw token, gold, in parentheses,
|
||||
// after a space — `MicroButtonTooltipText`'s own concatenation.
|
||||
super::test_ui::hover(&mut s, "CharacterMicroButton");
|
||||
let line1 = s
|
||||
.eval::<String>("return GameTooltipTextLeft1:GetText()")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
line1,
|
||||
s.eval::<String>(
|
||||
"return CHARACTER_BUTTON .. \" \" .. NORMAL_FONT_COLOR_CODE .. \"(C)\" .. FONT_COLOR_CODE_CLOSE"
|
||||
)
|
||||
.unwrap(),
|
||||
"the key read on UPDATE_BINDINGS"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// Set the player's level and fire the `UNIT_LEVEL` the talent gate listens for.
|
||||
fn set_player_level(s: &mut UiScript, level: u32) {
|
||||
/// Under the whole shipped manifest — the only VM in which every panel `UpdateMicroButtons`
|
||||
/// (l.20-84) reads exists — a button is PUSHED exactly while its panel is up, the character
|
||||
/// button's face dims with it, and the level-up pulse the reference hangs on the talent button
|
||||
/// (`TalentMicroButton_OnEvent`, l.119-124: sixty seconds, unless the character sheet is open)
|
||||
/// is live — 0304's "no microbutton pulse" scope-out, retired by the file that owns the pulse.
|
||||
#[test]
|
||||
fn a_micro_button_pushes_while_its_panel_is_open_and_the_talent_button_pulses_on_a_ding() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
// A player always exists by the time the manifest loads (1051/1848) — with a race and a
|
||||
// class, which the sheet's own `PaperDollFrame_SetLevel` formats into its title on show.
|
||||
s.set_unit(
|
||||
"player",
|
||||
Some(benilla_ui::script::UnitState {
|
||||
Some(UnitState {
|
||||
exists: true,
|
||||
level,
|
||||
name: Some("Probefour".into()),
|
||||
level: 60,
|
||||
race: Some("Night Elf".into()),
|
||||
race_file: Some("NightElf".into()),
|
||||
class: Some("Warrior".into()),
|
||||
class_file: Some("WARRIOR".into()),
|
||||
sex: 2,
|
||||
is_player: true,
|
||||
player_controlled: true,
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
s.fire_event("UNIT_LEVEL", vec![ScriptValue::Str("player".into())]);
|
||||
// The reference's strings, which the app runs ahead of the manifest (lifecycle.rs) — the
|
||||
// ding's chat lines below format `SPELL_STAT0_NAME`..`4` through them.
|
||||
super::test_ui::load_ui(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
assert!(super::load_default_ui(&s).is_empty());
|
||||
s.resolve();
|
||||
|
||||
let state = |s: &UiScript, button: &str| {
|
||||
s.eval::<String>(&format!("return {button}:GetButtonState()"))
|
||||
.unwrap()
|
||||
};
|
||||
let face = |s: &UiScript| {
|
||||
s.eval::<f64>("return MicroButtonPortrait:GetAlpha()")
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(state(&s, "CharacterMicroButton"), "NORMAL");
|
||||
assert_eq!(face(&s), 1.0);
|
||||
// The button's own click (its OnClick, l.49-51) — the sheet's OnShow calls back.
|
||||
s.run("ToggleCharacter(\"PaperDollFrame\")").unwrap();
|
||||
assert_eq!(
|
||||
state(&s, "CharacterMicroButton"),
|
||||
"PUSHED",
|
||||
"open sheet ⇒ button held down"
|
||||
);
|
||||
assert_eq!(
|
||||
face(&s),
|
||||
0.5,
|
||||
"…and the face dims (CharacterMicroButton_SetPushed)"
|
||||
);
|
||||
s.run("ToggleCharacter(\"PaperDollFrame\")").unwrap();
|
||||
assert_eq!(state(&s, "CharacterMicroButton"), "NORMAL");
|
||||
assert_eq!(face(&s), 1.0);
|
||||
|
||||
// The reference's own full-screen panel; its OnShow/OnHide call back the same way.
|
||||
s.run("ToggleWorldMap()").unwrap();
|
||||
assert_eq!(state(&s, "WorldMapMicroButton"), "PUSHED");
|
||||
s.run("ToggleWorldMap()").unwrap();
|
||||
assert_eq!(state(&s, "WorldMapMicroButton"), "NORMAL");
|
||||
|
||||
// A ding with the sheet closed: sixty seconds on the pulse list (UiPanels.xml's
|
||||
// SetButtonPulse, the reference's UIParent.lua machinery). The nine args are the ones
|
||||
// `ui_unit` fires (level, health, power, talent points, five stats).
|
||||
s.fire_event(
|
||||
"PLAYER_LEVEL_UP",
|
||||
vec![
|
||||
ScriptValue::Int(61),
|
||||
ScriptValue::Int(12),
|
||||
ScriptValue::Int(0),
|
||||
ScriptValue::Int(1),
|
||||
ScriptValue::Int(1),
|
||||
ScriptValue::Int(1),
|
||||
ScriptValue::Int(1),
|
||||
ScriptValue::Int(0),
|
||||
ScriptValue::Int(0),
|
||||
],
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<f64>("return TalentMicroButton.pulseTimeLeft")
|
||||
.unwrap(),
|
||||
60.0,
|
||||
"the sixty-second pulse"
|
||||
);
|
||||
assert!(
|
||||
s.eval::<bool>(
|
||||
"for _, b in ipairs(PULSEBUTTONS) do if b == TalentMicroButton then return true end end \
|
||||
return false"
|
||||
)
|
||||
.unwrap(),
|
||||
"on the pulse list"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
@@ -796,7 +796,7 @@ fn interface_harness() -> UiScript {
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\ItemButtonTemplate.xml",
|
||||
"Interface\\FrameXML\\QuestFrame.xml",
|
||||
"MicroMenu.xml",
|
||||
r"Interface\FrameXML\MainMenuBarMicroButtons.xml",
|
||||
"Interface\\FrameXML\\QuestLogFrame.xml",
|
||||
],
|
||||
);
|
||||
|
||||
@@ -692,7 +692,7 @@ fn tooltip_line1(s: &UiScript) -> String {
|
||||
///
|
||||
/// The exact string is pinned, colour codes and all, because the ref's concatenation puts the
|
||||
/// colour BEFORE the space (`Attack|cffffd200 (CTRL-1)|r`, not `Attack |cffffd200(CTRL-1)|r` —
|
||||
/// which is what MicroMenu.xml's different-shaped version of the same idea produces).
|
||||
/// which is what the stock `MicroButtonTooltipText` — the same idea, the other shape — produces).
|
||||
#[test]
|
||||
fn token_tooltips_name_their_binding_and_pet_spells_do_not() {
|
||||
let mut s = hovered_pet_bar();
|
||||
|
||||
@@ -44,7 +44,7 @@ fn harness() -> UiScript {
|
||||
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestTimerFrame.xml");
|
||||
s
|
||||
|
||||
@@ -105,7 +105,7 @@ fn shipped_questlog_frame_loads_clean() {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ fn shipped_questlog_frame_drives_end_to_end() {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
|
||||
s.set_quest_log(eight_entries());
|
||||
@@ -349,7 +349,7 @@ fn shift_click_toggles_the_watch_checkbox_and_the_tracker_hud() {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
// A shift-click reaches stock QuestLogTitleButton_OnClick's unguarded `ChatFrameEditBox:IsVisible()`
|
||||
// (QuestLogFrame.lua:478) — the chat window is on the manifest long before the log (1944).
|
||||
load_xml(&s, "UIParent.xml");
|
||||
@@ -498,7 +498,7 @@ fn watch_guards_no_op_without_erroring() {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
// A shift-click reaches stock QuestLogTitleButton_OnClick's unguarded `ChatFrameEditBox:IsVisible()`
|
||||
// (QuestLogFrame.lua:478) — the chat window is on the manifest long before the log (1944).
|
||||
load_xml(&s, "UIParent.xml");
|
||||
@@ -619,7 +619,7 @@ fn progress_auto_watches_for_five_minutes() {
|
||||
load_xml(&s, "OptionsFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
s.set_quest_log(eight_entries());
|
||||
|
||||
@@ -705,7 +705,7 @@ fn the_auto_watch_flag_is_the_references_uvar_and_gates_the_watch() {
|
||||
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
s.set_quest_log(eight_entries());
|
||||
assert_eq!(s.eval::<String>("return AUTO_QUEST_WATCH").unwrap(), "1");
|
||||
@@ -761,7 +761,7 @@ fn the_row_tag_is_its_own_right_flush_string_and_the_state_word_wins() {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
|
||||
let mut state = eight_entries();
|
||||
@@ -844,7 +844,7 @@ fn empty_quest_log_hides_rows_and_disables_abandon() {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
|
||||
s.set_quest_log(QuestLogState::default());
|
||||
@@ -909,7 +909,7 @@ fn reward_rows_follow_the_refs_two_per_row_layout() {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
|
||||
let mut state = eight_entries();
|
||||
@@ -1098,7 +1098,7 @@ fn overflowing_detail_content_clips_to_the_scrollframe_rect() {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
|
||||
s.set_quest_log(overflowing_entry());
|
||||
@@ -1173,7 +1173,7 @@ fn wheel_over_the_detail_pane_changes_vertical_scroll() {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
|
||||
s.set_quest_log(overflowing_entry());
|
||||
@@ -1238,7 +1238,7 @@ fn selection_change_resets_detail_scroll_but_a_quest_log_update_refresh_does_not
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
|
||||
s.set_quest_log(overflowing_entry());
|
||||
@@ -1320,7 +1320,7 @@ fn reward_row_hover_serves_the_shared_item_tooltip() {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
|
||||
s.set_quest_log(eight_entries());
|
||||
@@ -1475,7 +1475,7 @@ fn reward_rows_preview_and_post_and_a_plain_click_stays_inert() {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
// A shift-click reaches stock QuestLogTitleButton_OnClick's unguarded `ChatFrameEditBox:IsVisible()`
|
||||
// (QuestLogFrame.lua:478) — the chat window is on the manifest long before the log (1944).
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
|
||||
@@ -1574,7 +1574,7 @@ fn shift_click_on_a_title_posts_the_quest_name_with_chat_open_and_watches_with_i
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
// A shift-click reaches stock QuestLogTitleButton_OnClick's unguarded `ChatFrameEditBox:IsVisible()`
|
||||
// (QuestLogFrame.lua:478) — the chat window is on the manifest long before the log (1944).
|
||||
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
|
||||
@@ -1684,7 +1684,7 @@ fn share_quest_needs_both_a_sharable_selection_and_a_party() {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
|
||||
// Solo, quest 1 sharable and selected by the auto-selection.
|
||||
@@ -1771,7 +1771,7 @@ fn share_quest_is_dark_on_an_empty_log_even_in_a_party() {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
|
||||
s.set_quest_log(QuestLogState::default());
|
||||
@@ -1812,7 +1812,7 @@ fn share_quest_click_queues_the_selected_quests_id() {
|
||||
load_xml(&s, "UIParent.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
|
||||
s.set_quest_log(entries_sharable(&[1, 5]));
|
||||
|
||||
@@ -80,6 +80,7 @@ fn push_raid(s: &mut UiScript, raid: Vec<RaidMemberInfo>) {
|
||||
members,
|
||||
leader_index: 0,
|
||||
leader_guid: 0, // the player leads; their guid is unset in this fixture
|
||||
own_guid: 0,
|
||||
raid,
|
||||
loot_method: "group".into(),
|
||||
master_looter: None,
|
||||
|
||||
@@ -1116,12 +1116,13 @@ mod tests {
|
||||
/// do not ship under its own name usually has a counterpart of ours under a different one, and
|
||||
/// both would declare the same frames.
|
||||
///
|
||||
/// That half doubles as the map nothing else holds: `FloatingChatFrame.xml` is our `ChatFrame.xml`,
|
||||
/// `MainMenuBarMicroButtons.xml` is our `MicroMenu.xml`, `StaticPopup.xml` is our
|
||||
/// `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.
|
||||
/// That half used to double as the map nothing else held — `FloatingChatFrame.xml` was our
|
||||
/// `ChatFrame.xml`, `MainMenuBarMicroButtons.xml` our `MicroMenu.xml`, `StaticPopup.xml` our
|
||||
/// `UiPanels.xml`'s dialog half, `PlayerFrame.xml`/`TargetFrame.xml`/`PetFrame.xml` our one
|
||||
/// `UnitFrames.xml`. Every one of those is the reference's own file now (1751; the micro row
|
||||
/// last, 1987), so the frame half names nothing today. It stays because a stock window
|
||||
/// declaring a frame one of ours still holds is the first thing a swap has to rule out —
|
||||
/// such a pair can load individually and cannot load side by side.
|
||||
///
|
||||
/// Run it before attempting a swap. It predicts which ones will fail without attempting them.
|
||||
#[test]
|
||||
@@ -1419,8 +1420,8 @@ mod tests {
|
||||
/// FrameXML function is invisible to both, because loading a file never runs the body that
|
||||
/// calls it: `LootFrame.xml` shipped load-clean and raised at `LootFrame.lua:85` the first
|
||||
/// time it met real data, and stock `CharacterFrame.xml` would have raised on the first tab
|
||||
/// HOVER, because `MicroButtonTooltipText` did not exist here (it does now — `MicroMenu.xml`,
|
||||
/// our `MainMenuBarMicroButtons.xml` counterpart, is where the reference declares it).
|
||||
/// HOVER, because `MicroButtonTooltipText` did not exist here (it does now — the stock
|
||||
/// `MainMenuBarMicroButtons.xml` declares it, off the chain since 1987).
|
||||
///
|
||||
/// So: for every chain `.xml` in the manifest, census the bare `Name(` call sites across it
|
||||
/// and its `.lua`, subtract what those two define themselves, keep the names the reference's
|
||||
|
||||
@@ -625,10 +625,10 @@ fn every_archive_path_a_shipped_lua_chunk_names_survives_its_own_escaping() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Never let the sweep pass by matching nothing (19 after 1971, 8 after 1980; the floor
|
||||
// follows the census down as windows migrate).
|
||||
// Never let the sweep pass by matching nothing (19 after 1971, 8 after 1980, 4 after 1987;
|
||||
// the floor follows the census down as windows migrate).
|
||||
assert!(
|
||||
paths.len() >= 5,
|
||||
paths.len() >= 4,
|
||||
"only {} archive paths swept out of the shipped Lua",
|
||||
paths.len()
|
||||
);
|
||||
@@ -1375,9 +1375,10 @@ fn a_cinematic_leaves_nothing_of_the_interface_on_screen() {
|
||||
let before = names.iter().filter(|n| visible(&s, n)).count();
|
||||
assert!(
|
||||
// 50 until 1938 took the three bar files stock, 20 until 1974 took the minimap cluster
|
||||
// (its 22 named frames were most of what this sweep counted); the floor follows the
|
||||
// census of OUR files down, and 12 is what is left — the glue and dev frames.
|
||||
before > 10,
|
||||
// (its 22 named frames were most of what this sweep counted), 12 until 1987 took the
|
||||
// micro row; the floor follows the census of OUR files down, and 2 is what is left —
|
||||
// the glue and dev frames.
|
||||
before >= 2,
|
||||
"only {before} frames visible before the cinematic — the sweep found no interface to \
|
||||
hide, so it would pass no matter what the cascade did"
|
||||
);
|
||||
@@ -1448,8 +1449,9 @@ fn every_declared_parent_really_attaches() {
|
||||
let declared = shipped_frame_parents();
|
||||
assert!(
|
||||
// A sanity floor for the scan, not a census — the declarations retire with the files
|
||||
// that carry them (1751); 67 before 1938, 30 after 1956, 14 after 1970.
|
||||
declared.len() >= 10,
|
||||
// that carry them (1751); 67 before 1938, 30 after 1956, 14 after 1970, 2 after 1987
|
||||
// (the micro row's eight were most of what was left).
|
||||
declared.len() >= 2,
|
||||
"only {} parent declarations found — the scan broke",
|
||||
declared.len()
|
||||
);
|
||||
|
||||
@@ -114,7 +114,7 @@ pub(super) fn spellbook_ui(w: f32, h: f32) -> UiScript {
|
||||
"KeyBindingsPage.xml",
|
||||
"OptionsFrame.xml",
|
||||
"Interface\\FrameXML\\MultiActionBars.xml",
|
||||
"MicroMenu.xml",
|
||||
r"Interface\FrameXML\MainMenuBarMicroButtons.xml",
|
||||
"Interface\\FrameXML\\SpellBookFrame.xml",
|
||||
"SpellBookAdapters.xml",
|
||||
] {
|
||||
@@ -703,7 +703,7 @@ fn the_macro_editor_takes_a_shift_click_and_only_a_shift_click() {
|
||||
r"Interface\FrameXML\UIPanelTemplates.xml",
|
||||
// The icon chooser's scroll frame inherits `ClassTrainerListScrollFrameTemplate`.
|
||||
r"Interface\FrameXML\ClassTrainerFrameTemplates.xml",
|
||||
"MicroMenu.xml",
|
||||
r"Interface\FrameXML\MainMenuBarMicroButtons.xml",
|
||||
"Interface\\FrameXML\\ActionButtonTemplate.xml",
|
||||
"Interface\\FrameXML\\TextStatusBar.lua",
|
||||
"Interface\\FrameXML\\TextStatusBar.xml",
|
||||
|
||||
@@ -70,6 +70,14 @@ fn load_entry(s: &UiScript, entry: &str, strict_templates: bool, no_warnings: bo
|
||||
"{entry}: loader errors: {:?}",
|
||||
report.errors
|
||||
);
|
||||
if path
|
||||
.rsplit('/')
|
||||
.next()
|
||||
.is_some_and(|leaf| leaf.eq_ignore_ascii_case("MainMenuBarMicroButtons.xml"))
|
||||
{
|
||||
s.run(MICRO_BUTTON_STAND_INS)
|
||||
.expect("the micro-button stand-ins");
|
||||
}
|
||||
if no_warnings {
|
||||
assert!(
|
||||
report.warnings.is_empty(),
|
||||
@@ -92,6 +100,39 @@ fn load_entry(s: &UiScript, entry: &str, strict_templates: bool, no_warnings: bo
|
||||
report.frames
|
||||
}
|
||||
|
||||
/// **What a kit owes the stock micro-button row.** `UpdateMicroButtons`
|
||||
/// (`MainMenuBarMicroButtons.lua:20-84`) reads ten panels, `KeyRingButton` and `IsBagOpen`
|
||||
/// UNGUARDED — the reference's own contract, because in the client every one of them is up
|
||||
/// before anything can be shown. A kit is a prefix of the manifest and stops wherever its window
|
||||
/// does, so [`load_entry`] wraps the function the moment the row loads: on its FIRST call — a
|
||||
/// panel's OnShow, by which time the kit is complete — the wrapper seats a hidden, unnamed
|
||||
/// stand-in under each name the kit never declared (a hidden frame is what a closed panel
|
||||
/// answers), then runs the reference's own body. First use rather than load time, and unnamed,
|
||||
/// because both registries keep the first writer: the arena's name map
|
||||
/// (`named_registry_is_non_overwriting`) and `_G` itself — a named frame is published
|
||||
/// non-overwriting (RF-0023: a `lua_gettable` nil check on GLOBALSINDEX, which is why a
|
||||
/// metatable fallback would block the real publish just the same). A stand-in seated before the
|
||||
/// real declaration shadows the real frame for good; the first cut did exactly that, 126
|
||||
/// failures, every tabbed panel's `numTabs` read off the stand-in. `fire_chat_login`'s
|
||||
/// `UIOptionsFrame` stand-in is the same move for the chat files. Seated here rather than at the
|
||||
/// kits' seventy-odd consumers because the dependency is the row's, not any one window's — and
|
||||
/// the shipped manifest never needs it. `tests/common/mod.rs` carries the same chunk for the
|
||||
/// integration tests, which cannot reach this module (decision 1987).
|
||||
pub(super) const MICRO_BUTTON_STAND_INS: &str = r#"
|
||||
local real = UpdateMicroButtons
|
||||
function UpdateMicroButtons()
|
||||
for _, name in ipairs({ "CharacterFrame", "SpellBookFrame", "QuestLogFrame", "GameMenuFrame",
|
||||
"OptionsFrame", "SoundOptionsFrame", "UIOptionsFrame", "FriendsFrame", "WorldMapFrame",
|
||||
"HelpFrame" }) do
|
||||
if not getglobal(name) then local f = CreateFrame("Frame") f:Hide() setglobal(name, f) end
|
||||
end
|
||||
if not KeyRingButton then KeyRingButton = CreateFrame("Button") KeyRingButton:Hide() end
|
||||
if not KEYRING_CONTAINER then KEYRING_CONTAINER = -2 end
|
||||
if not IsBagOpen then function IsBagOpen() return nil end end
|
||||
return real()
|
||||
end
|
||||
"#;
|
||||
|
||||
/// One file's bytes, from whichever store its path names — the chain for a path, this crate's
|
||||
/// `assets/ui` for a bare name. Also the `<Include>` / `<Script file=>` provider, which is why it
|
||||
/// takes an already-resolved path in either space.
|
||||
@@ -222,9 +263,12 @@ pub(super) const LOOT_UI: &[&str] = &[
|
||||
/// `HonorLevelText` and `HonorGuildText` "while we're at it" (`PaperDollFrame.lua:100`/`:120`).
|
||||
/// Nothing touches them at load, so leaving this out loads clean and then raises the first time
|
||||
/// the window is SHOWN. It sits below the character block in the manifest for the same reason.
|
||||
/// * **`MicroMenu.xml`** — `UpdateMicroButtons` (called by both `CharacterFrame_OnShow` and
|
||||
/// `_OnHide`) and `MicroButtonTooltipText` (all five tab hovers). A tab hover is the only thing
|
||||
/// that reaches the second one, so its absence is invisible until a test hovers a tab.
|
||||
/// * **`MainMenuBarMicroButtons.xml`** — `UpdateMicroButtons` (called by both
|
||||
/// `CharacterFrame_OnShow` and `_OnHide`) and `MicroButtonTooltipText` (all five tab hovers).
|
||||
/// A tab hover is the only thing that reaches the second one, so its absence is invisible
|
||||
/// until a test hovers a tab. The reference's own file since 1987; the panels its
|
||||
/// `UpdateMicroButtons` reads unguarded and this kit stops short of are
|
||||
/// [`MICRO_BUTTON_STAND_INS`]'s.
|
||||
///
|
||||
/// Needs client data, like its three siblings: open with `benilla_formats::wow_data_or_skip!()`.
|
||||
pub(super) const CHARACTER_UI: &[&str] = &[
|
||||
@@ -272,7 +316,7 @@ pub(super) const CHARACTER_UI: &[&str] = &[
|
||||
"Interface\\FrameXML\\MainMenuBar.xml",
|
||||
"Interface\\FrameXML\\ActionBarFrame.xml",
|
||||
"Interface\\FrameXML\\BonusActionBarFrame.xml",
|
||||
"MicroMenu.xml",
|
||||
r"Interface\FrameXML\MainMenuBarMicroButtons.xml",
|
||||
// The two page files these three need before the window can be OPENED, which is not the same
|
||||
// as before it can load: `CHARACTERFRAME_SUBFRAMES` lists all five pages by name and
|
||||
// `CharacterFrame_ShowSubFrame` calls `getglobal(value):Hide()` on each one it is not showing
|
||||
@@ -331,7 +375,7 @@ pub(super) const SOCIAL_UI: &[&str] = &[
|
||||
"KeyBindingsPage.xml",
|
||||
"OptionsFrame.xml",
|
||||
"Interface\\FrameXML\\MultiActionBars.xml",
|
||||
"MicroMenu.xml",
|
||||
r"Interface\FrameXML\MainMenuBarMicroButtons.xml",
|
||||
"Interface\\FrameXML\\UnitPopup.xml",
|
||||
"Interface\\FrameXML\\UIMenu.xml",
|
||||
"Interface\\FrameXML\\ChatFrame.xml",
|
||||
@@ -393,7 +437,7 @@ pub(super) const BAG_UI: &[&str] = &[
|
||||
"Interface\\FrameXML\\BonusActionBarFrame.xml",
|
||||
// `UpdateMicroButtons` — the KEYRING's own OnShow/OnHide calls it (ContainerFrame.lua l.117,
|
||||
// l.137), because in the reference the keyring's existence moves the micro-button row.
|
||||
"MicroMenu.xml",
|
||||
r"Interface\FrameXML\MainMenuBarMicroButtons.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
r"Interface\FrameXML\UIPanelTemplates.xml",
|
||||
// The dialog engine, after the UIPanelCloseButton it inherits (1960).
|
||||
|
||||
@@ -71,7 +71,7 @@ fn trainer_script_base() -> UiScript {
|
||||
"KeyBindingsPage.xml",
|
||||
"OptionsFrame.xml",
|
||||
"Interface\\FrameXML\\MultiActionBars.xml",
|
||||
"MicroMenu.xml",
|
||||
r"Interface\FrameXML\MainMenuBarMicroButtons.xml",
|
||||
"Interface\\FrameXML\\MerchantFrame.xml",
|
||||
"Interface\\FrameXML\\ClassTrainerFrameTemplates.xml",
|
||||
] {
|
||||
|
||||
@@ -284,3 +284,42 @@ fn the_font_path_globals_are_the_references_own_four() {
|
||||
"|cffffff9a"
|
||||
);
|
||||
}
|
||||
|
||||
/// `MouseIsOver` divides the cursor by the frame's effective scale, as the reference does
|
||||
/// (UIParent.lua l.1389-1390): a frame scaled to 0.5 whose screen footprint holds the cursor
|
||||
/// answers 1 — with the division written out (the old "the scale is the constant 1" note) it
|
||||
/// answered nil under the very cursor that had just entered it (decision 1985).
|
||||
#[test]
|
||||
fn mouse_is_over_reads_a_scaled_frame_in_its_own_units() {
|
||||
let mut s = ui_parent();
|
||||
s.run(
|
||||
r#"Scaled = CreateFrame("Frame", "Scaled", UIParent) Scaled:SetWidth(200) Scaled:SetHeight(100)
|
||||
Scaled:SetPoint("BOTTOMLEFT", 100, 100) Scaled:SetScale(0.5)"#,
|
||||
)
|
||||
.unwrap();
|
||||
s.resolve();
|
||||
// The frame's screen footprint is its own-unit box times its effective scale (offsets and
|
||||
// size both scale); the probe sits at its centre, then just past its right edge.
|
||||
let (l, r, b, t, eff) = s
|
||||
.eval::<(f64, f64, f64, f64, f64)>(
|
||||
"return Scaled:GetLeft(), Scaled:GetRight(), Scaled:GetBottom(), Scaled:GetTop(), Scaled:GetEffectiveScale()",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
(eff - 0.5).abs() < 1e-6 && (r - l - 200.0).abs() < 1e-3,
|
||||
"own units: {l}..{r} at {eff}"
|
||||
);
|
||||
let (cx, cy) = (((l + r) * 0.5 * eff) as f32, ((b + t) * 0.5 * eff) as f32);
|
||||
s.mouse_move(cx, cy);
|
||||
assert_eq!(
|
||||
s.eval::<Option<i64>>("return MouseIsOver(Scaled)").unwrap(),
|
||||
Some(1),
|
||||
"the cursor at the scaled footprint's centre ({cx}, {cy})"
|
||||
);
|
||||
s.mouse_move((r * eff) as f32 + 10.0, cy);
|
||||
assert_eq!(
|
||||
s.eval::<Option<i64>>("return MouseIsOver(Scaled)").unwrap(),
|
||||
None,
|
||||
"past the scaled footprint (inside the unscaled one) is outside"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -469,6 +469,7 @@ fn left_clicking_the_player_frame_targets_self() {
|
||||
// The player's own guid, which this fixture leaves unset — spelled out because a bare 0
|
||||
// is also the reference's "ungrouped" sentinel and this party has a member.
|
||||
leader_guid: 0,
|
||||
own_guid: 0,
|
||||
raid: Vec::new(),
|
||||
loot_method: "group".into(),
|
||||
master_looter: None,
|
||||
@@ -551,6 +552,7 @@ fn raid_mark_clicks_through_the_nested_level() {
|
||||
}],
|
||||
leader_index: 0, // we lead — the mark rows are leader-gated
|
||||
leader_guid: 0, // the player's own guid; this fixture leaves it unset
|
||||
own_guid: 0,
|
||||
raid: Vec::new(),
|
||||
loot_method: "group".into(),
|
||||
master_looter: None,
|
||||
@@ -663,7 +665,7 @@ fn shipped_target_frame_runs_the_level_law() {
|
||||
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
||||
load_xml(&s, "MicroMenu.xml");
|
||||
load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
||||
// The player at level 3, both feeds (the snapshot UnitLevel("player") reads; the req state
|
||||
// the −1 gate and GetQuestGreenRange read) — the app keeps the two in step.
|
||||
@@ -1270,6 +1272,7 @@ fn the_party_art_paints_over_the_bars() {
|
||||
}],
|
||||
leader_index: 0,
|
||||
leader_guid: 0,
|
||||
own_guid: 0,
|
||||
raid: Vec::new(),
|
||||
loot_method: "group".into(),
|
||||
master_looter: None,
|
||||
@@ -1999,6 +2002,7 @@ fn the_player_frame_wears_the_leader_and_master_looter_icons() {
|
||||
leader_index,
|
||||
// Follows `leader_index`: 0 = the player (unset here), else the member who leads.
|
||||
leader_guid: if leader_index == 0 { 0 } else { 0x7A17 },
|
||||
own_guid: 0,
|
||||
raid: Vec::new(),
|
||||
loot_method: method.into(),
|
||||
master_looter,
|
||||
|
||||
@@ -537,3 +537,54 @@ fn the_maps_own_furniture_survives_the_hide_that_showing_it_performs() {
|
||||
assert_eq!(visible(&s, "BlackoutWorld"), 0, "with the blackout down");
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// **Hovering the player arrow on a scaled map names the player** (director report: an empty
|
||||
/// tooltip). On a window narrower than 4:3 the stock `SetupFullscreenScale` scales the map
|
||||
/// under 1 (0.802 here); `WorldMapUnit_OnEnter` then asks `MouseIsOver(WorldMapPlayer)` for the
|
||||
/// text, which read the cursor against the blip's own-unit edges without the reference's scale
|
||||
/// division and answered nil — a tooltip with no lines (decision 1985).
|
||||
#[test]
|
||||
fn hovering_the_player_blip_on_a_scaled_map_names_the_player() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(821.0, 768.0);
|
||||
s.set_unit(
|
||||
"player",
|
||||
Some(benilla_ui::script::UnitState {
|
||||
exists: true,
|
||||
name: Some("Probefour".into()),
|
||||
level: 60,
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
let failures = super::load_default_ui(&s);
|
||||
assert!(failures.is_empty(), "manifest load errors: {failures:#?}");
|
||||
s.resolve();
|
||||
s.run("ShowUIPanel(WorldMapFrame)").unwrap();
|
||||
s.resolve();
|
||||
assert!(
|
||||
s.eval::<f64>("return WorldMapFrame:GetScale()").unwrap() < 0.99,
|
||||
"the narrow window scales the map under 1"
|
||||
);
|
||||
s.set_world_map_feed(None, Some((0.5, 0.5)), 0.0, None, Vec::new(), Vec::new());
|
||||
update(&mut s);
|
||||
s.resolve();
|
||||
let (px, py, eff) = s
|
||||
.eval::<(f64, f64, f64)>(
|
||||
"local x, y = WorldMapPlayer:GetCenter() return x, y, WorldMapPlayer:GetEffectiveScale()",
|
||||
)
|
||||
.unwrap();
|
||||
s.mouse_move((px * eff) as f32, (py * eff) as f32);
|
||||
assert_eq!(
|
||||
s.hit_test_name((px * eff) as f32, (py * eff) as f32)
|
||||
.as_deref(),
|
||||
Some("WorldMapPlayer")
|
||||
);
|
||||
assert!(s.eval::<bool>("return WorldMapTooltip:IsShown()").unwrap());
|
||||
assert_eq!(
|
||||
s.eval::<String>("return WorldMapTooltipTextLeft1:GetText()")
|
||||
.unwrap(),
|
||||
"Probefour"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user