71 more key commands, and the quest windows wear the reference's names
- the binding registry goes 130 → 201 of the client's 228 live commands: a coverage gate makes registered ∪ recorded-absent exactly the 1.12 surface, each absence naming the Lua globals whose arrival refutes it — which is how 37 rows whose mechanism had already landed came home (the keyring, the pet book, the raid tab, the six action-bar pages, both vertical multibars) — and then four mechanisms bring 71 more: the five camera views with the reference's default table and its sixteen camera CVars read out of the binary (NextView/PrevView do NOT wrap), self-cast riding UseAction's third argument with autoSelfCast a real CVar at last, the friendly target cone / assist / last-hostile as one cycler with a side (the two sides use different deadness tests on purpose, and assist neither requires an assistable target nor swings), and REPLY2 — reply to the last person you told; the friendly cone also stops borrowing the enemy side's in-combat score bonus, which would have shoved a healer targeting you to the head of every cycle - the key names close their gaps: NUMPADEQUALS, F13-F24 and BUTTON6+ were keys the binding window listed and the file saved that the dispatcher could never press — named both ways now, with the namer and the codec structurally unable to disagree again; platform= becomes the OS gate it really is (off a Mac build the iTunes-remote category never even exists), and the Mac 1.12 slice settles that its F13 IS print-screen while F14/F15 map to UNKNOWN on purpose and stay unbindable; and the last-enemy stamp gains the four conjuncts it shipped without — you resolve, are not dead, not a ghost, not mounted, and the new target is alive-or-feigning — so TargetLastEnemy can no longer land somewhere the real client's never would - the quest windows wear the reference's names: ~40 of the quest window's globals were ours (the reference's name usually sitting in the comment beside them), its two six-item reward pools merge into the reference's single ten-item run with the update function ported line-for-line, the four panels and the quest log take REAL scroll frames (the thumb drags now), the gossip window gets its greeting panel, the two mail panes' inert painted-at-rest scrollbars become real ones, the Skills detail pane nests as the reference has it, and nine private constants take their real names; the one that broke a feature detect: our QuestLogCount — a name 1.12 doesn't publish — made pfUI conclude it was running on TBC and die in that branch - an EditBox is born with its constructor's five regions (the text, three selection quads and the caret), so GetRegions answers the reference's own count and authored regions start at index 6 — and a bare <NormalTexture/> now builds a live blank texture, since the element is what creates the region, not its file=; OpacityFrame lands, its first customer having arrived; and the equip / auto-equip / use bind confirms complete the soulbind arc — three different predicates with no quality leg (borrowing the loot arm's would have silenced the confirm on every white bind-on-equip item), Accept re-issuing the original action with the reference's suppress flag because 1.12 ships no confirm packet, and the soulstone use going through the same gate Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
use benilla_ui::script::{ActionSlot, QuadContent, ScriptValue, UiScript};
|
||||
|
||||
/// The queued action ids alone — `take_action_uses` carries `UseAction`'s self-cast modifier
|
||||
/// beside the id since 1745, and every assertion in this file is about the id.
|
||||
fn action_ids(s: &mut UiScript) -> Vec<u32> {
|
||||
s.take_action_uses().into_iter().map(|u| u.action).collect()
|
||||
}
|
||||
|
||||
/// Load the real `assets/ui/ActionBar.xml` (the shipped default bar) into a bare engine and
|
||||
/// drive it with a synthetic action snapshot — the slice-1 chain minus Bevy: template
|
||||
/// expansion over 12 instances, the vanilla bonus-page formula, icon paint on events, empty
|
||||
@@ -95,7 +101,7 @@ fn shipped_action_bar_drives_end_to_end() {
|
||||
// center is (8+18, 4+18) = (26, 22).
|
||||
s.mouse_button(26.0, 22.0, "LeftButton", true);
|
||||
s.mouse_button(26.0, 22.0, "LeftButton", false);
|
||||
assert_eq!(s.take_action_uses(), vec![73]);
|
||||
assert_eq!(action_ids(&mut s), vec![73]);
|
||||
|
||||
// The keybinding entry (the app's key feed runs `ActionButtonDown/Up(i)` on the two
|
||||
// key edges — the ref's ACTIONBUTTONn binding, ActionButton.lua:15-45): UP fires UseAction
|
||||
@@ -123,7 +129,7 @@ fn shipped_action_bar_drives_end_to_end() {
|
||||
"PUSHED"
|
||||
);
|
||||
s.run("ActionButtonUp(2)").unwrap();
|
||||
assert_eq!(s.take_action_uses(), vec![74], "key '2' fires action 74");
|
||||
assert_eq!(action_ids(&mut s), vec![74], "key '2' fires action 74");
|
||||
assert_eq!(depressed(&s), 0, "key UP restores the normal state");
|
||||
|
||||
// Stance drops (offset 0): the bar re-pages to actions 1..12 — all empty here, icons clear.
|
||||
@@ -335,7 +341,7 @@ fn a_right_click_on_an_action_button_uses_the_action() {
|
||||
s.mouse_button(26.0, 22.0, "RightButton", true);
|
||||
s.mouse_button(26.0, 22.0, "RightButton", false);
|
||||
assert_eq!(
|
||||
s.take_action_uses(),
|
||||
action_ids(&mut s),
|
||||
vec![1],
|
||||
"right-click queues the same UseAction a left-click does"
|
||||
);
|
||||
@@ -1439,11 +1445,24 @@ fn bonus_bar_slides_up_with_sound_and_down_without() {
|
||||
// Keys route to the overlay immediately (ref ActionButton.lua:15-45's IsShown fork).
|
||||
s.run("ActionButtonDown(1) ActionButtonUp(1)").unwrap();
|
||||
assert_eq!(
|
||||
s.take_action_uses(),
|
||||
action_ids(&mut s),
|
||||
vec![73],
|
||||
"a key pressed mid-slide already drives the bonus page"
|
||||
);
|
||||
|
||||
// …and the same button with the SELF-CAST modifier. `SELFACTIONBUTTON1`-`12` (`ALT-1`…`ALT-=`)
|
||||
// are `ActionButtonUp(id, 1)` and nothing else, so this is the whole of what those twelve
|
||||
// bindings do that the plain twelve do not (1745).
|
||||
s.run("ActionButtonDown(1) ActionButtonUp(1, 1)").unwrap();
|
||||
assert_eq!(
|
||||
s.take_action_uses()
|
||||
.into_iter()
|
||||
.map(|u| (u.action, u.on_self))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![(73, true)],
|
||||
"ActionButtonUp's onSelf reaches UseAction's third argument"
|
||||
);
|
||||
|
||||
// Half the slide: the replica is half-risen (top = 0.5 * 43 over the bar's bottom edge at
|
||||
// y=0), the paint suppression still holds, still silent.
|
||||
s.tick(0.075);
|
||||
@@ -1536,7 +1555,7 @@ fn bonus_bar_slides_up_with_sound_and_down_without() {
|
||||
// A key mid-descent still drives the old form's page — ref GetPagedID's lastBonusBar
|
||||
// stand-in while the frame is still shown.
|
||||
s.run("ActionButtonDown(1) ActionButtonUp(1)").unwrap();
|
||||
assert_eq!(s.take_action_uses(), vec![97]);
|
||||
assert_eq!(action_ids(&mut s), vec![97]);
|
||||
s.tick(0.2);
|
||||
assert!(
|
||||
!s.eval::<bool>("return BonusActionBarFrame:IsShown()")
|
||||
|
||||
@@ -665,6 +665,10 @@ fn tab_round_trip_with_a_selected_skill_by_point() {
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
// Not optional, though this list ran without it: a MISSING template is a loader *warning*, not
|
||||
// an error, so an under-loaded list passes load_xml's assert and then fails later on geometry
|
||||
// that silently never got built. SkillDetailScrollFrame inherits UIPanelScrollFrameTemplate.
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "CharacterFrame.xml");
|
||||
load_xml(&s, "SkillFrame.xml");
|
||||
s.set_unit("player", Some(player_unit()));
|
||||
@@ -772,10 +776,10 @@ fn tab_round_trip_with_a_selected_skill_by_point() {
|
||||
// name; accepting queues the CMSG_UNLEARN_SKILL intent BY SKILL ID and removes nothing
|
||||
// locally (the server's SetSkill(id,0,0) round trip owns the removal).
|
||||
assert!(
|
||||
shown(&mut s, "SkillDetailUnlearnButton"),
|
||||
shown(&mut s, "SkillDetailStatusBarUnlearnButton"),
|
||||
"the unlearn button shows for a profession"
|
||||
);
|
||||
s.run("SkillDetailUnlearnButton:Click()").unwrap();
|
||||
s.run("SkillDetailStatusBarUnlearnButton:Click()").unwrap();
|
||||
assert!(
|
||||
shown(&mut s, "StaticPopup1"),
|
||||
"the UNLEARN_SKILL confirm opens"
|
||||
@@ -802,7 +806,7 @@ fn tab_round_trip_with_a_selected_skill_by_point() {
|
||||
let defense = text_center(&mut s, "Defense");
|
||||
click(&mut s, defense);
|
||||
assert!(
|
||||
!shown(&mut s, "SkillDetailUnlearnButton"),
|
||||
!shown(&mut s, "SkillDetailStatusBarUnlearnButton"),
|
||||
"no unlearn button for Defense"
|
||||
);
|
||||
// Restore the profession selection so the round trip below leaves familiar state.
|
||||
|
||||
@@ -130,6 +130,11 @@ fn an_error_toast_draws_over_an_open_panel_window() {
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "Cooldown.xml");
|
||||
load_xml(&s, "MerchantFrame.xml"); // BenillaMoney_* — QuestLogDetail's reward money row
|
||||
// ScrollTemplates.xml (the faux kit the list rides) + UIPanelTemplates.xml (the detail
|
||||
// pane's UIPanelScrollFrameTemplate). A MISSING template is a loader *warning*, so an
|
||||
// under-loaded list passes and then dies on the first FauxScrollFrame_Update.
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "QuestLogFrame.xml");
|
||||
|
||||
// A left-slot panel open, and the toast raised after it — the order that must not decide.
|
||||
|
||||
@@ -317,15 +317,9 @@ const KNOWN: &[Known] = &[
|
||||
why: "as FriendsFrameFriendsScrollFrame",
|
||||
},
|
||||
Known {
|
||||
frame: "OpenMailScrollFrame",
|
||||
frame: "QuestLogListScrollFrame",
|
||||
flag: Flag::Mouse,
|
||||
why: "our open-mail body is flat art plus a FontString — the render approximation \
|
||||
MailFrame.xml names; nothing scrolls it on either side",
|
||||
},
|
||||
Known {
|
||||
frame: "SendMailScrollFrame",
|
||||
flag: Flag::Mouse,
|
||||
why: "as OpenMailScrollFrame",
|
||||
why: "as FriendsFrameFriendsScrollFrame",
|
||||
},
|
||||
// ── id ─────────────────────────────────────────────────────────────────────────────────────
|
||||
//
|
||||
|
||||
@@ -200,14 +200,18 @@ fn the_capture_flow_binds_steals_and_refuses_like_112() {
|
||||
.unwrap(),
|
||||
"Unbind arms with the selection"
|
||||
);
|
||||
// The host hands back a canonical chord: F binds into slot 1, W's old seat; UP survives
|
||||
// The host hands back a canonical chord: J binds into slot 1, W's old seat; UP survives
|
||||
// in slot 2; the capture disarms; the table is LIVE and the bind COMMITTED (Save queued —
|
||||
// 1008's live-commit law, where 0997 waited for Okay).
|
||||
s.run(r#"KeyBindings_OnHostKey("F")"#).unwrap();
|
||||
//
|
||||
// **`J`, not `F`** — this leg is about the plain bind, so its key has to be one nothing else
|
||||
// holds, and `F` stopped being one when 1745 registered `ASSISTTARGET` on its byte-real
|
||||
// default. The steal path has its own leg below (`T`), which is where a taken key belongs.
|
||||
s.run(r#"KeyBindings_OnHostKey("J")"#).unwrap();
|
||||
assert!(!s.bind_capture_armed(), "a completed bind disarms");
|
||||
assert!(s
|
||||
.eval::<bool>(
|
||||
r#"local k1, k2 = GetBindingKey("MOVEFORWARD"); return k1 == "F" and k2 == "UP""#
|
||||
r#"local k1, k2 = GetBindingKey("MOVEFORWARD"); return k1 == "J" and k2 == "UP""#
|
||||
)
|
||||
.unwrap());
|
||||
assert_eq!(
|
||||
@@ -710,3 +714,34 @@ fn the_pet_lane_is_registered_under_the_action_bar_header() {
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// **Every registry body is real Lua.** 167 rows carry a Lua chunk as a Rust string literal, and
|
||||
/// nothing compiled them: a typo in one shipped as a command that errors on its first press, in a
|
||||
/// file whose tests all live one layer up at the page. `loadstring` is the whole check — running
|
||||
/// them needs the FrameXML the harness deliberately does not load — and it is exactly the check
|
||||
/// that a hand-typed string wants.
|
||||
#[test]
|
||||
fn every_command_body_compiles() {
|
||||
use crate::bindings::commands::Kind;
|
||||
|
||||
let s = UiScript::new().unwrap();
|
||||
for spec in SPECS {
|
||||
let bodies: Vec<&str> = match &spec.kind {
|
||||
Kind::Held | Kind::Host => Vec::new(),
|
||||
Kind::Edge(body) => vec![*body],
|
||||
Kind::EdgeUpDown(down, up) => vec![*down, *up],
|
||||
};
|
||||
for body in bodies {
|
||||
s.run(&format!(
|
||||
"local f, err = loadstring({body:?}); if not f then BenillaBodyError = {name:?} .. \": \" .. err end",
|
||||
body = body,
|
||||
name = spec.name
|
||||
))
|
||||
.unwrap_or_else(|e| panic!("{}: {e}", spec.name));
|
||||
}
|
||||
}
|
||||
let bad: Vec<String> = s.errors();
|
||||
assert!(bad.is_empty(), "script errors: {bad:?}");
|
||||
s.run("if BenillaBodyError then error(BenillaBodyError) end")
|
||||
.unwrap_or_else(|e| panic!("a command body is not valid Lua: {e}"));
|
||||
}
|
||||
|
||||
@@ -165,13 +165,22 @@ fn shipped_multibars_drive_end_to_end() {
|
||||
// a bonus page is active (the ref's fork: only the main bar re-pages).
|
||||
s.mouse_button(26.0, 75.0, "LeftButton", true);
|
||||
s.mouse_button(26.0, 75.0, "LeftButton", false);
|
||||
assert_eq!(s.take_action_uses(), vec![61]);
|
||||
assert_eq!(
|
||||
s.take_action_uses()
|
||||
.into_iter()
|
||||
.map(|u| u.action)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![61]
|
||||
);
|
||||
s.set_bonus_bar_offset(1);
|
||||
s.fire_event("UPDATE_BONUS_ACTIONBAR", vec![]);
|
||||
s.mouse_button(26.0, 75.0, "LeftButton", true);
|
||||
s.mouse_button(26.0, 75.0, "LeftButton", false);
|
||||
assert_eq!(
|
||||
s.take_action_uses(),
|
||||
s.take_action_uses()
|
||||
.into_iter()
|
||||
.map(|u| u.action)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![61],
|
||||
"a bonus page never re-pages a multibar"
|
||||
);
|
||||
|
||||
@@ -55,14 +55,18 @@ fn shipped_gossip_frame_drives_end_to_end() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
|
||||
// The window + its scroll frame (bar + child) + the 32-row shared pool (quest rows and option
|
||||
// rows both draw from it, decision 0088 §3 — the reference's own NUMGOSSIPBUTTONS) + the close
|
||||
// button + the GOODBYE button. The greeting and the NPC-name banner are FontString layers (the
|
||||
// real GossipGreetingText ref l.241 / GossipFrameNpcNameText ref l.170) — not their own frames.
|
||||
// UIPanelScrollFrameTemplate lives here, and the gossip scroll frame inherits it. NOT
|
||||
// optional: a missing template is a loader *warning*, not an error, so an under-loaded
|
||||
// list passes load_xml and then loses the scrollbar silently.
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
// The window + its scroll frame (bar + child) + the 32-row shared pool (quest rows and option
|
||||
// rows both draw from it, decision 0088 §3 — the reference's own NUMGOSSIPBUTTONS) + the close
|
||||
// button + the GOODBYE button. The greeting and the NPC-name banner are FontString layers (the
|
||||
// real GossipGreetingText ref l.241 / GossipFrameNpcNameText ref l.170) — not their own frames.
|
||||
assert_eq!(
|
||||
load_xml(&s, "GossipFrame.xml"),
|
||||
40,
|
||||
"window + scroll + bar (+2 arrows) + child + 32 rows + close + goodbye"
|
||||
41,
|
||||
"window + greeting panel + scroll + bar (+2 arrows) + child + 32 rows + close + goodbye"
|
||||
);
|
||||
|
||||
// Hidden by default: no gossip icon on screen.
|
||||
@@ -111,8 +115,8 @@ fn shipped_gossip_frame_drives_end_to_end() {
|
||||
// so the shared pool starts filling from option 1 at row 1 — decision 0088 §3).
|
||||
let states: (bool, bool, bool, bool) = s
|
||||
.eval(
|
||||
"return GossipRow1:IsVisible(), GossipRow1:IsEnabled() ~= 0,\n\
|
||||
GossipRow2:IsEnabled() ~= 0, GossipRow3:IsVisible()",
|
||||
"return GossipTitleButton1:IsVisible(), GossipTitleButton1:IsEnabled() ~= 0,\n\
|
||||
GossipTitleButton2:IsEnabled() ~= 0, GossipTitleButton3:IsVisible()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -233,6 +237,10 @@ fn shipped_gossip_frame_renders_quest_rows_above_options() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
|
||||
// UIPanelScrollFrameTemplate lives here, and the gossip scroll frame inherits it. NOT
|
||||
// optional: a missing template is a loader *warning*, not an error, so an under-loaded
|
||||
// list passes load_xml and then loses the scrollbar silently.
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "GossipFrame.xml");
|
||||
|
||||
s.set_gossip(Some(GossipMenu {
|
||||
@@ -269,10 +277,10 @@ fn shipped_gossip_frame_renders_quest_rows_above_options() {
|
||||
bool,
|
||||
) = s
|
||||
.eval(
|
||||
"return GossipRow1Label:GetText(), GossipRow1:IsVisible(),\n\
|
||||
GossipRow2Label:GetText(), GossipRow2:IsVisible(),\n\
|
||||
GossipRow3Label:GetText(), GossipRow3:IsVisible(),\n\
|
||||
GossipRow4:IsVisible()",
|
||||
"return GossipTitleButton1Label:GetText(), GossipTitleButton1:IsVisible(),\n\
|
||||
GossipTitleButton2Label:GetText(), GossipTitleButton2:IsVisible(),\n\
|
||||
GossipTitleButton3Label:GetText(), GossipTitleButton3:IsVisible(),\n\
|
||||
GossipTitleButton4:IsVisible()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!((r1_text.as_str(), r1_vis), ("Report to Goldshire", true));
|
||||
@@ -323,7 +331,8 @@ fn shipped_gossip_frame_renders_quest_rows_above_options() {
|
||||
// Click quest row 2 (available, "A Threat Within") → SelectGossipQuest(2) queues the seam's
|
||||
// 1-based quest-row position; the app maps it to the quest id + guid and sends
|
||||
// CMSG_QUESTGIVER_QUERY_QUEST (decision 0088 §3).
|
||||
s.run("BenillaGossipRow_OnClick(GossipRow2)").unwrap();
|
||||
s.run("BenillaGossipRow_OnClick(GossipTitleButton2)")
|
||||
.unwrap();
|
||||
assert_eq!(s.take_gossip_quest_selects(), vec![2]);
|
||||
assert!(
|
||||
s.take_gossip_selects().is_empty(),
|
||||
@@ -349,6 +358,10 @@ fn shipped_gossip_rows_grow_to_their_wrapped_labels() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
|
||||
// UIPanelScrollFrameTemplate lives here, and the gossip scroll frame inherits it. NOT
|
||||
// optional: a missing template is a loader *warning*, not an error, so an under-loaded
|
||||
// list passes load_xml and then loses the scrollbar silently.
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "GossipFrame.xml");
|
||||
|
||||
// Three long options — the shape of a real judgement/roleplay menu, every one of them wrapping
|
||||
@@ -410,8 +423,8 @@ fn shipped_gossip_rows_grow_to_their_wrapped_labels() {
|
||||
|
||||
let row = |i: u32| -> (f32, f32, f32) {
|
||||
s.eval::<(f32, f32, f32)>(&format!(
|
||||
"return GossipRow{i}:GetTop(), GossipRow{i}:GetBottom(), \
|
||||
GossipRow{i}Label:GetHeight()"
|
||||
"return GossipTitleButton{i}:GetTop(), GossipTitleButton{i}:GetBottom(), \
|
||||
GossipTitleButton{i}Label:GetHeight()"
|
||||
))
|
||||
.unwrap()
|
||||
};
|
||||
@@ -456,6 +469,10 @@ fn gossip_show_hide_plays_open_and_close_kits() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
|
||||
// UIPanelScrollFrameTemplate lives here, and the gossip scroll frame inherits it. NOT
|
||||
// optional: a missing template is a loader *warning*, not an error, so an under-loaded
|
||||
// list passes load_xml and then loses the scrollbar silently.
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "GossipFrame.xml");
|
||||
|
||||
// Hidden at load: no open sound (never transitions on startup).
|
||||
@@ -493,6 +510,10 @@ fn shipped_panel_slot_replaces_gossip_with_merchant() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
|
||||
// UIPanelScrollFrameTemplate lives here, and the gossip scroll frame inherits it. NOT
|
||||
// optional: a missing template is a loader *warning*, not an error, so an under-loaded
|
||||
// list passes load_xml and then loses the scrollbar silently.
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "GossipFrame.xml");
|
||||
load_xml(&s, "GameTooltip.xml"); // app load order: tooltip before merchant
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
@@ -575,6 +596,10 @@ fn displacing_an_npc_window_ends_the_displaced_session() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
|
||||
// UIPanelScrollFrameTemplate lives here, and the gossip scroll frame inherits it. NOT
|
||||
// optional: a missing template is a loader *warning*, not an error, so an under-loaded
|
||||
// list passes load_xml and then loses the scrollbar silently.
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "GossipFrame.xml");
|
||||
load_xml(&s, "GameTooltip.xml"); // app load order: tooltip before merchant
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
@@ -719,6 +744,10 @@ fn gossip_bank_option_hands_the_left_slot_to_the_bank() {
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
load_xml(&s, "BankFrame.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
|
||||
// UIPanelScrollFrameTemplate lives here, and the gossip scroll frame inherits it. NOT
|
||||
// optional: a missing template is a loader *warning*, not an error, so an under-loaded
|
||||
// list passes load_xml and then loses the scrollbar silently.
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "GossipFrame.xml");
|
||||
|
||||
// The gossip menu is open on the banker (its bank option showing).
|
||||
@@ -786,6 +815,7 @@ fn an_overflowing_gossip_menu_scrolls_instead_of_spilling() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml"); // UIPanelScrollFrameTemplate — see the note above
|
||||
load_xml(&s, "GossipFrame.xml");
|
||||
|
||||
// Eight wrapping options: ~4 lines each, far past the 334 px scroll frame.
|
||||
@@ -830,8 +860,8 @@ fn an_overflowing_gossip_menu_scrolls_instead_of_spilling() {
|
||||
// The child grew past the 334 px frame, so there is a range to scroll …
|
||||
let (child_h, range): (f32, f32) = s
|
||||
.eval(
|
||||
"return GossipGreetingScrollChild:GetHeight(), \
|
||||
GossipGreetingScroll:GetVerticalScrollRange()",
|
||||
"return GossipGreetingScrollChildFrame:GetHeight(), \
|
||||
GossipGreetingScrollFrame:GetVerticalScrollRange()",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
@@ -841,7 +871,7 @@ fn an_overflowing_gossip_menu_scrolls_instead_of_spilling() {
|
||||
// … and the bar is up for it (it stays hidden when everything fits — the fit case is covered by
|
||||
// `shipped_gossip_frame_drives_end_to_end`'s two-option menu).
|
||||
assert!(
|
||||
s.eval::<bool>("return GossipGreetingScrollBar:IsVisible()")
|
||||
s.eval::<bool>("return GossipGreetingScrollFrameScrollBar:IsVisible()")
|
||||
.unwrap(),
|
||||
"the scrollbar shows once the menu overflows"
|
||||
);
|
||||
@@ -852,7 +882,7 @@ fn an_overflowing_gossip_menu_scrolls_instead_of_spilling() {
|
||||
// out with: it must be the scroll frame's own rect, and rows past the bottom must be entirely
|
||||
// outside it (nothing of them survives the clip).
|
||||
let (frame_top, frame_bottom): (f32, f32) = s
|
||||
.eval("return GossipGreetingScroll:GetTop(), GossipGreetingScroll:GetBottom()")
|
||||
.eval("return GossipGreetingScrollFrame:GetTop(), GossipGreetingScrollFrame:GetBottom()")
|
||||
.unwrap();
|
||||
let painted_rows =
|
||||
|s: &mut UiScript| -> Vec<(benilla_ui::layout::Rect, benilla_ui::layout::Rect)> {
|
||||
@@ -885,9 +915,9 @@ fn an_overflowing_gossip_menu_scrolls_instead_of_spilling() {
|
||||
// Scrolling pans the content up under that clip by exactly the scroll amount. Rects are y-up
|
||||
// (`ExtractedQuad::rect`), so "up" means row 1's top EDGE VALUE grows as it slides off the top.
|
||||
let first_top =
|
||||
|s: &mut UiScript| -> f32 { s.eval::<f32>("return GossipRow1:GetTop()").unwrap() };
|
||||
|s: &mut UiScript| -> f32 { s.eval::<f32>("return GossipTitleButton1:GetTop()").unwrap() };
|
||||
let before = first_top(&mut s);
|
||||
s.run("BenillaScroll_Step(GossipGreetingScroll, 100)")
|
||||
s.run("BenillaScroll_Step(GossipGreetingScrollFrame, 100)")
|
||||
.unwrap();
|
||||
s.resolve();
|
||||
let after = first_top(&mut s);
|
||||
@@ -897,7 +927,7 @@ fn an_overflowing_gossip_menu_scrolls_instead_of_spilling() {
|
||||
);
|
||||
// The bar followed the scroll (SyncBar off OnVerticalScroll), and the rows are still clipped.
|
||||
assert_eq!(
|
||||
s.eval::<f32>("return GossipGreetingScrollBar:GetValue()")
|
||||
s.eval::<f32>("return GossipGreetingScrollFrameScrollBar:GetValue()")
|
||||
.unwrap(),
|
||||
100.0,
|
||||
"the bar seats at the scroll offset"
|
||||
@@ -932,6 +962,7 @@ fn an_addons_own_frame_registered_in_uipanelwindows_takes_the_left_slot() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml"); // UIPanelScrollFrameTemplate — see the note above
|
||||
load_xml(&s, "GossipFrame.xml");
|
||||
|
||||
// The addon's three lines, in the order an addon writes them.
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
//! of these test: `UNIT_PET` names the OWNER (`arg1 == "player"`, wow-re §9), every other `UNIT_*`
|
||||
//! names the pet itself, and a frame that mixes the two repaints off the player's health.
|
||||
|
||||
use benilla_ui::script::{AuraState, QuadContent, ScriptValue, UiScript, UnitState};
|
||||
use benilla_ui::script::{
|
||||
AuraState, QuadContent, ScriptValue, SelectionRequest, UiScript, UnitState,
|
||||
};
|
||||
|
||||
/// Load one shipped `assets/ui/<file>`, panicking on any loader error (the unit-frame tests'
|
||||
/// loader).
|
||||
@@ -322,11 +324,14 @@ fn left_clicking_the_pet_frame_targets_it() {
|
||||
s.fire_event("UNIT_PET", vec![ScriptValue::Str("player".into())]);
|
||||
|
||||
s.run("PetFrame_OnClick(\"LeftButton\")").unwrap();
|
||||
assert_eq!(s.take_target_requests(), vec!["pet".to_string()]);
|
||||
assert_eq!(
|
||||
s.take_selection_requests(),
|
||||
vec![SelectionRequest::Unit("pet".into())]
|
||||
);
|
||||
|
||||
// The right button is the deferred PET menu — it must do nothing at all, not target.
|
||||
s.run("PetFrame_OnClick(\"RightButton\")").unwrap();
|
||||
assert!(s.take_target_requests().is_empty());
|
||||
assert!(s.take_selection_requests().is_empty());
|
||||
}
|
||||
|
||||
/// **All three legs of `PetFrame_OnClick` survive the click** — B208's "dropping food from the bag
|
||||
@@ -394,7 +399,7 @@ fn every_leg_of_the_pet_frame_click_reaches_a_live_binding() {
|
||||
"a held item + a pet click queues the drop"
|
||||
);
|
||||
// …and it is the DROP leg, not the target leg — the reference's if/elseif is exclusive.
|
||||
assert!(s.take_target_requests().is_empty());
|
||||
assert!(s.take_selection_requests().is_empty());
|
||||
}
|
||||
|
||||
/// The GlobalStrings the happiness tooltip resolves by key. Loaded from the MPQ in production; the
|
||||
|
||||
@@ -100,10 +100,10 @@ fn panel_events_show_exactly_one_child_panel_and_hide_the_others() {
|
||||
load_xml(&s, "QuestFrame.xml");
|
||||
|
||||
let panels = [
|
||||
"QuestGreetingPanel",
|
||||
"QuestDetailPanel",
|
||||
"QuestProgressPanel",
|
||||
"QuestRewardPanel",
|
||||
"QuestFrameGreetingPanel",
|
||||
"QuestFrameDetailPanel",
|
||||
"QuestFrameProgressPanel",
|
||||
"QuestFrameRewardPanel",
|
||||
];
|
||||
let is_shown =
|
||||
|s: &UiScript, name: &str| s.eval::<bool>(&format!("return {name}:IsShown()")).unwrap();
|
||||
@@ -122,12 +122,12 @@ fn panel_events_show_exactly_one_child_panel_and_hide_the_others() {
|
||||
for name in panels {
|
||||
assert_eq!(
|
||||
is_shown(&s, name),
|
||||
name == "QuestGreetingPanel",
|
||||
name == "QuestFrameGreetingPanel",
|
||||
"only the greeting panel is shown after QUEST_GREETING"
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
s.eval::<String>("return QuestNpcNameText:GetText()")
|
||||
s.eval::<String>("return QuestFrameNpcNameText:GetText()")
|
||||
.unwrap(),
|
||||
"Deputy Willem"
|
||||
);
|
||||
@@ -148,7 +148,7 @@ fn panel_events_show_exactly_one_child_panel_and_hide_the_others() {
|
||||
for name in panels {
|
||||
assert_eq!(
|
||||
is_shown(&s, name),
|
||||
name == "QuestDetailPanel",
|
||||
name == "QuestFrameDetailPanel",
|
||||
"only the detail panel is shown after QUEST_DETAIL"
|
||||
);
|
||||
}
|
||||
@@ -158,10 +158,13 @@ fn panel_events_show_exactly_one_child_panel_and_hide_the_others() {
|
||||
);
|
||||
}
|
||||
|
||||
/// The reward/choice grid, ported from the ref's shared `QuestFrameItems_Update` (the SAME idiom the
|
||||
/// quest log's own reward rows already prove out) — now on REAL per-panel widgets
|
||||
/// (`QuestDetailChoice*`/`QuestDetailReward*`), exercised on the Detail panel with the
|
||||
/// fixture shape the task's capture drives: 2 choices + 1 fixed reward + money.
|
||||
/// The reward grid, ported from the ref's shared `QuestFrameItems_Update` — on ONE numbered pool
|
||||
/// per panel (`QuestDetailItem1..10`), the reference's own shape: choices first, then the
|
||||
/// reward-spell row, then fixed rewards, each positioned at runtime. The fixture is the one the
|
||||
/// task's capture drives — 2 choices + 1 fixed reward + money — so the pool reads
|
||||
/// `Item1`/`Item2` = the choices, `Item3` = the fixed reward, `Item4`+ unused. It used to run on
|
||||
/// two 6-deep pools named `QuestDetailChoice*`/`QuestDetailReward*`, names the reference does not
|
||||
/// have and a split the ref's own function cannot be written against.
|
||||
#[test]
|
||||
fn detail_panel_reward_grid_follows_the_refs_two_per_row_layout() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
@@ -198,17 +201,11 @@ fn detail_panel_reward_grid_follows_the_refs_two_per_row_layout() {
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
|
||||
assert!(s
|
||||
.eval::<bool>("return QuestDetailChoice1:IsShown()")
|
||||
.unwrap());
|
||||
assert!(s
|
||||
.eval::<bool>("return QuestDetailChoice2:IsShown()")
|
||||
.unwrap());
|
||||
assert!(!s
|
||||
.eval::<bool>("return QuestDetailChoice3:IsShown()")
|
||||
.unwrap());
|
||||
assert!(s.eval::<bool>("return QuestDetailItem1:IsShown()").unwrap());
|
||||
assert!(s.eval::<bool>("return QuestDetailItem2:IsShown()").unwrap());
|
||||
assert!(!s.eval::<bool>("return QuestDetailItem4:IsShown()").unwrap());
|
||||
assert_eq!(
|
||||
s.eval::<String>("return QuestDetailChoice1Name:GetText()")
|
||||
s.eval::<String>("return QuestDetailItem1Name:GetText()")
|
||||
.unwrap(),
|
||||
"Worn Sword"
|
||||
);
|
||||
@@ -223,14 +220,10 @@ fn detail_panel_reward_grid_follows_the_refs_two_per_row_layout() {
|
||||
"You will also receive:",
|
||||
"choices present -> the ref's \"also\" wording (QuestFrame.lua:461-462)"
|
||||
);
|
||||
assert!(s
|
||||
.eval::<bool>("return QuestDetailReward1:IsShown()")
|
||||
.unwrap());
|
||||
assert!(!s
|
||||
.eval::<bool>("return QuestDetailReward2:IsShown()")
|
||||
.unwrap());
|
||||
assert!(s.eval::<bool>("return QuestDetailItem3:IsShown()").unwrap());
|
||||
assert!(!s.eval::<bool>("return QuestDetailItem4:IsShown()").unwrap());
|
||||
assert_eq!(
|
||||
s.eval::<String>("return QuestDetailReward1Name:GetText()")
|
||||
s.eval::<String>("return QuestDetailItem3Name:GetText()")
|
||||
.unwrap(),
|
||||
"Militia Hammer"
|
||||
);
|
||||
@@ -244,7 +237,7 @@ fn detail_panel_reward_grid_follows_the_refs_two_per_row_layout() {
|
||||
// select). They DO carry an OnClick now (`QuestItem_OnClick`, the ref's own
|
||||
// QuestItemTemplate script — the ctrl/shift fork, decisions 1059/1060), but it has no select
|
||||
// arm at all: an unmodified click must not raise and must not set an itemChoice.
|
||||
s.run("QuestDetailChoice1:Click()").ok();
|
||||
s.run("QuestDetailItem1:Click()").ok();
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return QuestFrame.itemChoice").unwrap(),
|
||||
@@ -289,26 +282,26 @@ fn reward_panel_choice_click_selects_and_completes_with_zero_based_index() {
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
|
||||
assert!(
|
||||
!s.eval::<bool>("return QuestRewardChoiceHighlight:IsShown()")
|
||||
!s.eval::<bool>("return QuestRewardItemHighlight:IsShown()")
|
||||
.unwrap(),
|
||||
"no row picked yet"
|
||||
);
|
||||
// Completing without a choice picked is a no-op (ref: QuestChooseRewardError guard).
|
||||
s.run("QuestRewardCompleteButton:Click()").unwrap();
|
||||
s.run("QuestFrameCompleteQuestButton:Click()").unwrap();
|
||||
assert!(
|
||||
s.take_quest_actions().is_empty(),
|
||||
"must pick a choice first"
|
||||
);
|
||||
|
||||
s.run("QuestRewardChoice2:Click()").unwrap();
|
||||
s.run("QuestRewardItem2:Click()").unwrap();
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert!(
|
||||
s.eval::<bool>("return QuestRewardChoiceHighlight:IsShown()")
|
||||
s.eval::<bool>("return QuestRewardItemHighlight:IsShown()")
|
||||
.unwrap(),
|
||||
"picking a choice row shows the highlight"
|
||||
);
|
||||
|
||||
s.run("QuestRewardCompleteButton:Click()").unwrap();
|
||||
s.run("QuestFrameCompleteQuestButton:Click()").unwrap();
|
||||
assert_eq!(
|
||||
s.take_quest_actions(),
|
||||
vec![benilla_ui::script::QuestAction::Reward(1)],
|
||||
@@ -341,7 +334,7 @@ fn greeting_goodbye_button_closes_the_window() {
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert!(s.eval::<bool>("return QuestFrame:IsVisible()").unwrap());
|
||||
|
||||
s.run("QuestGreetingGoodbyeButton:Click()").unwrap();
|
||||
s.run("QuestFrameGreetingGoodbyeButton:Click()").unwrap();
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert!(
|
||||
!s.eval::<bool>("return QuestFrame:IsVisible()").unwrap(),
|
||||
@@ -375,10 +368,10 @@ fn detail_panel_action_buttons_resolve_to_real_onscreen_rects() {
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert!(s
|
||||
.eval::<bool>("return QuestAcceptButton:IsShown()")
|
||||
.eval::<bool>("return QuestFrameAcceptButton:IsShown()")
|
||||
.unwrap());
|
||||
assert!(s
|
||||
.eval::<bool>("return QuestDeclineButton:IsShown()")
|
||||
.eval::<bool>("return QuestFrameDeclineButton:IsShown()")
|
||||
.unwrap());
|
||||
|
||||
s.resolve();
|
||||
@@ -434,7 +427,7 @@ fn detail_panel_action_buttons_resolve_to_real_onscreen_rects() {
|
||||
"instant text keeps the writing sound: one scratch on the wake tick"
|
||||
);
|
||||
assert!(s
|
||||
.eval::<bool>("return QuestAcceptButton:IsEnabled() ~= 0")
|
||||
.eval::<bool>("return QuestFrameAcceptButton:IsEnabled() ~= 0")
|
||||
.unwrap());
|
||||
assert_eq!(
|
||||
s.eval::<f32>("return QuestDetailTextAlphaFrame:GetAlpha()")
|
||||
@@ -494,7 +487,7 @@ fn write_on_still_fades_when_instant_text_is_off() {
|
||||
"the write-on scratches the WriteQuest quill each tick"
|
||||
);
|
||||
assert!(!s
|
||||
.eval::<bool>("return QuestAcceptButton:IsEnabled() ~= 0")
|
||||
.eval::<bool>("return QuestFrameAcceptButton:IsEnabled() ~= 0")
|
||||
.unwrap());
|
||||
|
||||
// Half a second more runs the 13-char gradient off (40 chars/s): Accept wakes and the block
|
||||
@@ -502,7 +495,7 @@ fn write_on_still_fades_when_instant_text_is_off() {
|
||||
s.tick(0.5);
|
||||
assert!(s.errors().is_empty(), "tick errors: {:?}", s.errors());
|
||||
assert!(s
|
||||
.eval::<bool>("return QuestAcceptButton:IsEnabled() ~= 0")
|
||||
.eval::<bool>("return QuestFrameAcceptButton:IsEnabled() ~= 0")
|
||||
.unwrap());
|
||||
let alpha = s
|
||||
.eval::<f32>("return QuestDetailTextAlphaFrame:GetAlpha()")
|
||||
@@ -546,7 +539,7 @@ fn npc_name_reaches_the_title_bar_on_open_and_on_refresh() {
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert_eq!(
|
||||
s.eval::<String>("return getglobal('QuestNpcNameText'):GetText() or ''")
|
||||
s.eval::<String>("return getglobal('QuestFrameNpcNameText'):GetText() or ''")
|
||||
.unwrap(),
|
||||
"Marshal McBride",
|
||||
"the panel-open event's arg1 lands in the title bar"
|
||||
@@ -568,13 +561,14 @@ fn npc_name_reaches_the_title_bar_on_open_and_on_refresh() {
|
||||
);
|
||||
|
||||
// The late-name path: open with an empty name, the refresh brings it.
|
||||
s.run("getglobal('QuestNpcNameText'):SetText('')").unwrap();
|
||||
s.run("getglobal('QuestFrameNpcNameText'):SetText('')")
|
||||
.unwrap();
|
||||
s.fire_event(
|
||||
"QUEST_ITEM_UPDATE",
|
||||
vec![ScriptValue::Str("Marshal McBride".into())],
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return getglobal('QuestNpcNameText'):GetText() or ''")
|
||||
s.eval::<String>("return getglobal('QuestFrameNpcNameText'):GetText() or ''")
|
||||
.unwrap(),
|
||||
"Marshal McBride",
|
||||
"the QUEST_ITEM_UPDATE refresh also carries the name"
|
||||
@@ -718,14 +712,14 @@ fn reward_rows_preview_and_post_without_selecting_the_choice() {
|
||||
// The regression this fork could break: a PLAIN click still picks the choice (the ref's third
|
||||
// arm). Row 2 is picked here so the modified clicks below — aimed at row 1 — would visibly
|
||||
// move it if they leaked into the select arm.
|
||||
s.run("QuestRewardChoice2:Click()").unwrap();
|
||||
s.run("QuestRewardItem2:Click()").unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return QuestFrame.itemChoice").unwrap(),
|
||||
2,
|
||||
"a plain click still selects the reward choice"
|
||||
);
|
||||
assert!(s
|
||||
.eval::<bool>("return QuestRewardChoiceHighlight:IsShown()")
|
||||
.eval::<bool>("return QuestRewardItemHighlight:IsShown()")
|
||||
.unwrap());
|
||||
assert!(
|
||||
s.take_dressup_intents().is_empty(),
|
||||
@@ -735,7 +729,7 @@ fn reward_rows_preview_and_post_without_selecting_the_choice() {
|
||||
// SHIFT + chat open → the row's full escaped link, and the selection is untouched.
|
||||
assert!(s.focus_editbox("ChatFrameEditBox"));
|
||||
s.set_modifiers(true, false, false);
|
||||
s.run("QuestRewardChoice1:Click()").unwrap();
|
||||
s.run("QuestRewardItem1:Click()").unwrap();
|
||||
s.set_modifiers(false, false, false);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return ChatFrameEditBox:GetText()")
|
||||
@@ -749,10 +743,11 @@ fn reward_rows_preview_and_post_without_selecting_the_choice() {
|
||||
"the shift arm returns — it must NOT also select the clicked choice"
|
||||
);
|
||||
|
||||
// A FIXED reward row (plain QuestItemTemplate → QuestItem_OnClick) posts too.
|
||||
// A FIXED reward row posts too — same pool, same template now, and the select arm is gated
|
||||
// by `button.kind == "choice"` rather than by which pool the row came from.
|
||||
s.run("ChatFrameEditBox:SetText(\"\")").unwrap();
|
||||
s.set_modifiers(true, false, false);
|
||||
s.run("QuestRewardReward1:Click()").unwrap();
|
||||
s.run("QuestRewardItem3:Click()").unwrap();
|
||||
s.set_modifiers(false, false, false);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return ChatFrameEditBox:GetText()")
|
||||
@@ -763,7 +758,7 @@ fn reward_rows_preview_and_post_without_selecting_the_choice() {
|
||||
// CTRL → the dressing room wearing the clicked reward, and STILL no reselect. The intent pair
|
||||
// is ordered: Dress (the room was closed, so it re-dresses in the player's own gear) then TryOn.
|
||||
s.set_modifiers(false, true, false);
|
||||
s.run("QuestRewardChoice1:Click()").unwrap();
|
||||
s.run("QuestRewardItem1:Click()").unwrap();
|
||||
s.set_modifiers(false, false, false);
|
||||
assert_eq!(
|
||||
s.take_dressup_intents(),
|
||||
|
||||
@@ -102,6 +102,12 @@ fn shipped_questlog_frame_loads_clean() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
// ScrollTemplates.xml + UIPanelTemplates.xml are NOT optional: the detail pane inherits
|
||||
// UIPanelScrollFrameTemplate, and a MISSING template is a loader *warning*, not an error —
|
||||
// an under-loaded list passes load_xml and then loses the wheel, the arrows and the bar
|
||||
// silently.
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "QuestLogFrame.xml");
|
||||
}
|
||||
|
||||
@@ -117,6 +123,12 @@ fn shipped_questlog_frame_drives_end_to_end() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
// ScrollTemplates.xml + UIPanelTemplates.xml are NOT optional: the detail pane inherits
|
||||
// UIPanelScrollFrameTemplate, and a MISSING template is a loader *warning*, not an error —
|
||||
// an under-loaded list passes load_xml and then loses the wheel, the arrows and the bar
|
||||
// silently.
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "QuestLogFrame.xml");
|
||||
|
||||
s.set_quest_log(eight_entries());
|
||||
@@ -145,7 +157,8 @@ fn shipped_questlog_frame_drives_end_to_end() {
|
||||
.unwrap()
|
||||
.contains("Quest 1"));
|
||||
assert_eq!(
|
||||
s.eval::<String>("return QuestLogCount:GetText()").unwrap(),
|
||||
s.eval::<String>("return QuestLogQuestCount:GetText()")
|
||||
.unwrap(),
|
||||
"Quests: |cffffffff8/20|r"
|
||||
);
|
||||
assert_eq!(s.eval::<i64>("return GetQuestLogSelection()").unwrap(), 1);
|
||||
@@ -163,7 +176,7 @@ fn shipped_questlog_frame_drives_end_to_end() {
|
||||
);
|
||||
// The reward row (QuestFrame.xml's item-row pattern, reused) + its money.
|
||||
assert_eq!(
|
||||
s.eval::<String>("return QuestLogReward1Name:GetText()")
|
||||
s.eval::<String>("return QuestLogItem1Name:GetText()")
|
||||
.unwrap(),
|
||||
"Militia Hammer"
|
||||
);
|
||||
@@ -192,7 +205,9 @@ fn shipped_questlog_frame_drives_end_to_end() {
|
||||
.unwrap(),
|
||||
""
|
||||
);
|
||||
assert!(!s.eval::<bool>("return QuestLogChoice1:IsShown()").unwrap());
|
||||
// No choices, so the pool's first slot IS the fixed reward and the SECOND is the first
|
||||
// unused one. (One pool now — the ref's shape; there is no separate "choice row 1".)
|
||||
assert!(!s.eval::<bool>("return QuestLogItem2:IsShown()").unwrap());
|
||||
|
||||
// Wheel DOWN over an actual list ROW (delta -1) advances the faux-scroll offset by one: row 1 now
|
||||
// shows entry 2's title. Aiming over a row (not the empty margin) is the case that matters — a spin
|
||||
@@ -292,6 +307,12 @@ fn shift_click_toggles_the_watch_checkbox_and_the_tracker_hud() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
// ScrollTemplates.xml + UIPanelTemplates.xml are NOT optional: the detail pane inherits
|
||||
// UIPanelScrollFrameTemplate, and a MISSING template is a loader *warning*, not an error —
|
||||
// an under-loaded list passes load_xml and then loses the wheel, the arrows and the bar
|
||||
// silently.
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "QuestLogFrame.xml");
|
||||
|
||||
s.set_quest_log(eight_entries());
|
||||
@@ -407,6 +428,12 @@ fn watch_guards_no_op_without_erroring() {
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
load_xml(&s, "ErrorsFrame.xml"); // the guards' red-line surface
|
||||
// ScrollTemplates.xml + UIPanelTemplates.xml are NOT optional: the detail pane inherits
|
||||
// UIPanelScrollFrameTemplate, and a MISSING template is a loader *warning*, not an error —
|
||||
// an under-loaded list passes load_xml and then loses the wheel, the arrows and the bar
|
||||
// silently.
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "QuestLogFrame.xml");
|
||||
|
||||
s.set_quest_log(eight_entries());
|
||||
@@ -494,6 +521,12 @@ fn progress_auto_watches_for_five_minutes() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
// ScrollTemplates.xml + UIPanelTemplates.xml are NOT optional: the detail pane inherits
|
||||
// UIPanelScrollFrameTemplate, and a MISSING template is a loader *warning*, not an error —
|
||||
// an under-loaded list passes load_xml and then loses the wheel, the arrows and the bar
|
||||
// silently.
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "QuestLogFrame.xml");
|
||||
s.set_quest_log(eight_entries());
|
||||
|
||||
@@ -550,6 +583,12 @@ fn the_auto_watch_flag_is_the_references_uvar_and_gates_the_watch() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
// ScrollTemplates.xml + UIPanelTemplates.xml are NOT optional: the detail pane inherits
|
||||
// UIPanelScrollFrameTemplate, and a MISSING template is a loader *warning*, not an error —
|
||||
// an under-loaded list passes load_xml and then loses the wheel, the arrows and the bar
|
||||
// silently.
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "QuestLogFrame.xml");
|
||||
s.set_quest_log(eight_entries());
|
||||
assert_eq!(s.eval::<String>("return AUTO_QUEST_WATCH").unwrap(), "1");
|
||||
@@ -583,6 +622,12 @@ fn empty_quest_log_hides_rows_and_disables_abandon() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
// ScrollTemplates.xml + UIPanelTemplates.xml are NOT optional: the detail pane inherits
|
||||
// UIPanelScrollFrameTemplate, and a MISSING template is a loader *warning*, not an error —
|
||||
// an under-loaded list passes load_xml and then loses the wheel, the arrows and the bar
|
||||
// silently.
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "QuestLogFrame.xml");
|
||||
|
||||
s.set_quest_log(QuestLogState::default());
|
||||
@@ -592,13 +637,13 @@ fn empty_quest_log_hides_rows_and_disables_abandon() {
|
||||
// A bare FontString region has no Show/Hide/IsVisible in this engine (only Frames/Buttons do —
|
||||
// `region.rs`'s method table) — the empty-state message is a toggled SetText, read back here.
|
||||
assert_eq!(
|
||||
s.eval::<String>("return QuestLogEmptyText:GetText()")
|
||||
s.eval::<String>("return QuestLogNoQuestsText:GetText()")
|
||||
.unwrap(),
|
||||
"Your quest log is empty."
|
||||
);
|
||||
assert!(!s.eval::<bool>("return QuestLogTitle1:IsVisible()").unwrap());
|
||||
assert!(!s
|
||||
.eval::<bool>("return QuestLogAbandonButton:IsEnabled() ~= 0")
|
||||
.eval::<bool>("return QuestLogFrameAbandonButton:IsEnabled() ~= 0")
|
||||
.unwrap());
|
||||
// The Description header is now Lua-managed (BenillaQuestLogDetail_Clear blanks it) rather than
|
||||
// a static `text=` — it used to float "Description" over the empty-log parchment with no
|
||||
@@ -609,7 +654,8 @@ fn empty_quest_log_hides_rows_and_disables_abandon() {
|
||||
""
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return QuestLogCount:GetText()").unwrap(),
|
||||
s.eval::<String>("return QuestLogQuestCount:GetText()")
|
||||
.unwrap(),
|
||||
"Quests: |cffffffff0/20|r"
|
||||
);
|
||||
}
|
||||
@@ -626,6 +672,12 @@ fn reward_rows_follow_the_refs_two_per_row_layout() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
// ScrollTemplates.xml + UIPanelTemplates.xml are NOT optional: the detail pane inherits
|
||||
// UIPanelScrollFrameTemplate, and a MISSING template is a loader *warning*, not an error —
|
||||
// an under-loaded list passes load_xml and then loses the wheel, the arrows and the bar
|
||||
// silently.
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "QuestLogFrame.xml");
|
||||
|
||||
let mut state = eight_entries();
|
||||
@@ -669,16 +721,18 @@ fn reward_rows_follow_the_refs_two_per_row_layout() {
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
|
||||
// Both choice rows show, the rest of the 6-row cap stays hidden.
|
||||
assert!(s.eval::<bool>("return QuestLogChoice1:IsShown()").unwrap());
|
||||
assert!(s.eval::<bool>("return QuestLogChoice2:IsShown()").unwrap());
|
||||
assert!(!s.eval::<bool>("return QuestLogChoice3:IsShown()").unwrap());
|
||||
// 2 choices then 1 fixed reward, in ONE pool: Item1/Item2 are the choices, Item3 the reward,
|
||||
// Item4 the first unused slot.
|
||||
assert!(s.eval::<bool>("return QuestLogItem1:IsShown()").unwrap());
|
||||
assert!(s.eval::<bool>("return QuestLogItem2:IsShown()").unwrap());
|
||||
assert!(!s.eval::<bool>("return QuestLogItem4:IsShown()").unwrap());
|
||||
assert_eq!(
|
||||
s.eval::<String>("return QuestLogChoice1Name:GetText()")
|
||||
s.eval::<String>("return QuestLogItem1Name:GetText()")
|
||||
.unwrap(),
|
||||
"Worn Sword"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return QuestLogChoice2Name:GetText()")
|
||||
s.eval::<String>("return QuestLogItem2Name:GetText()")
|
||||
.unwrap(),
|
||||
"Worn Mace"
|
||||
);
|
||||
@@ -700,10 +754,10 @@ fn reward_rows_follow_the_refs_two_per_row_layout() {
|
||||
);
|
||||
|
||||
// One fixed reward shows, chained under the receive text (its own SetPoint target).
|
||||
assert!(s.eval::<bool>("return QuestLogReward1:IsShown()").unwrap());
|
||||
assert!(!s.eval::<bool>("return QuestLogReward2:IsShown()").unwrap());
|
||||
assert!(s.eval::<bool>("return QuestLogItem3:IsShown()").unwrap());
|
||||
assert!(!s.eval::<bool>("return QuestLogItem4:IsShown()").unwrap());
|
||||
assert_eq!(
|
||||
s.eval::<String>("return QuestLogReward1Name:GetText()")
|
||||
s.eval::<String>("return QuestLogItem3Name:GetText()")
|
||||
.unwrap(),
|
||||
"Militia Hammer"
|
||||
);
|
||||
@@ -715,7 +769,7 @@ fn reward_rows_follow_the_refs_two_per_row_layout() {
|
||||
);
|
||||
}
|
||||
|
||||
/// One entry whose selected detail overflows the 261px-tall `QuestLogDetailScroll`: 10
|
||||
/// One entry whose selected detail overflows the 261px-tall `QuestLogDetailScrollFrame`: 10
|
||||
/// objective lines (each a FIXED 12px slot, `QuestLogObjective1`'s own XML comment) plus a
|
||||
/// long description and a reward row push the child's summed height (`BenillaQuestLogDetail_
|
||||
/// ResizeChild`) well past the pane — load-bearing for the scroll/clip tests below even
|
||||
@@ -761,8 +815,8 @@ fn overflowing_entry() -> QuestLogState {
|
||||
}
|
||||
|
||||
/// The ScrollFrame clip (decision 0112 §4): every quad in the moved detail chain's subtree —
|
||||
/// `QuestLogDetailChild` and its descendants (here, a reward row's icon-slot texture) —
|
||||
/// carries `QuestLogDetailScroll`'s own resolved rect as its clip, so content that overflows
|
||||
/// `QuestLogDetailScrollChildFrame` and its descendants (here, a reward row's icon-slot texture) —
|
||||
/// carries `QuestLogDetailScrollFrame`'s own resolved rect as its clip, so content that overflows
|
||||
/// the pane never draws past its bottom edge; a sibling entirely outside the scroll child (the
|
||||
/// window's own book-icon chrome) stays unclipped, mirroring `scrollframe.rs`'s own unit test for
|
||||
/// the mechanism, just through the shipped window instead of a synthetic fixture.
|
||||
@@ -774,6 +828,12 @@ fn overflowing_detail_content_clips_to_the_scrollframe_rect() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
// ScrollTemplates.xml + UIPanelTemplates.xml are NOT optional: the detail pane inherits
|
||||
// UIPanelScrollFrameTemplate, and a MISSING template is a loader *warning*, not an error —
|
||||
// an under-loaded list passes load_xml and then loses the wheel, the arrows and the bar
|
||||
// silently.
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "QuestLogFrame.xml");
|
||||
|
||||
s.set_quest_log(overflowing_entry());
|
||||
@@ -830,6 +890,12 @@ fn wheel_over_the_detail_pane_changes_vertical_scroll() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
// ScrollTemplates.xml + UIPanelTemplates.xml are NOT optional: the detail pane inherits
|
||||
// UIPanelScrollFrameTemplate, and a MISSING template is a loader *warning*, not an error —
|
||||
// an under-loaded list passes load_xml and then loses the wheel, the arrows and the bar
|
||||
// silently.
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "QuestLogFrame.xml");
|
||||
|
||||
s.set_quest_log(overflowing_entry());
|
||||
@@ -843,14 +909,14 @@ fn wheel_over_the_detail_pane_changes_vertical_scroll() {
|
||||
let (x, y) = (scroll_rect.left + 10.0, scroll_rect.top - 10.0);
|
||||
|
||||
assert_eq!(
|
||||
s.eval::<f32>("return QuestLogDetailScroll:GetVerticalScroll()")
|
||||
s.eval::<f32>("return QuestLogDetailScrollFrame:GetVerticalScroll()")
|
||||
.unwrap(),
|
||||
0.0
|
||||
);
|
||||
s.mouse_wheel(x, y, -1.0); // WoW convention: -1 = down
|
||||
assert!(s.errors().is_empty(), "wheel errors: {:?}", s.errors());
|
||||
let after = s
|
||||
.eval::<f32>("return QuestLogDetailScroll:GetVerticalScroll()")
|
||||
.eval::<f32>("return QuestLogDetailScrollFrame:GetVerticalScroll()")
|
||||
.unwrap();
|
||||
assert!(
|
||||
after > 0.0,
|
||||
@@ -870,6 +936,12 @@ fn selection_change_resets_detail_scroll_but_a_quest_log_update_refresh_does_not
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
// ScrollTemplates.xml + UIPanelTemplates.xml are NOT optional: the detail pane inherits
|
||||
// UIPanelScrollFrameTemplate, and a MISSING template is a loader *warning*, not an error —
|
||||
// an under-loaded list passes load_xml and then loses the wheel, the arrows and the bar
|
||||
// silently.
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "QuestLogFrame.xml");
|
||||
|
||||
s.set_quest_log(overflowing_entry());
|
||||
@@ -877,9 +949,10 @@ fn selection_change_resets_detail_scroll_but_a_quest_log_update_refresh_does_not
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
s.resolve(); // GetVerticalScrollRange (SetVerticalScroll's clamp) reads resolved rects.
|
||||
|
||||
s.run("QuestLogDetailScroll:SetVerticalScroll(10)").unwrap();
|
||||
s.run("QuestLogDetailScrollFrame:SetVerticalScroll(10)")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<f32>("return QuestLogDetailScroll:GetVerticalScroll()")
|
||||
s.eval::<f32>("return QuestLogDetailScrollFrame:GetVerticalScroll()")
|
||||
.unwrap(),
|
||||
10.0
|
||||
);
|
||||
@@ -888,7 +961,7 @@ fn selection_change_resets_detail_scroll_but_a_quest_log_update_refresh_does_not
|
||||
s.fire_event("QUEST_LOG_UPDATE", vec![]);
|
||||
assert!(s.errors().is_empty(), "refresh errors: {:?}", s.errors());
|
||||
assert_eq!(
|
||||
s.eval::<f32>("return QuestLogDetailScroll:GetVerticalScroll()")
|
||||
s.eval::<f32>("return QuestLogDetailScrollFrame:GetVerticalScroll()")
|
||||
.unwrap(),
|
||||
10.0,
|
||||
"QUEST_LOG_UPDATE's data-refresh path must not yank the scroll position (pin §5)"
|
||||
@@ -899,7 +972,7 @@ fn selection_change_resets_detail_scroll_but_a_quest_log_update_refresh_does_not
|
||||
.unwrap();
|
||||
assert!(s.errors().is_empty(), "click errors: {:?}", s.errors());
|
||||
assert_eq!(
|
||||
s.eval::<f32>("return QuestLogDetailScroll:GetVerticalScroll()")
|
||||
s.eval::<f32>("return QuestLogDetailScrollFrame:GetVerticalScroll()")
|
||||
.unwrap(),
|
||||
0.0,
|
||||
"a manual reselect snaps the detail pane's scroll back to the top"
|
||||
@@ -925,6 +998,12 @@ fn reward_row_hover_serves_the_shared_item_tooltip() {
|
||||
load_xml(&s, "UIDropDownMenu.xml");
|
||||
load_xml(&s, "LootFrame.xml");
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
// ScrollTemplates.xml + UIPanelTemplates.xml are NOT optional: the detail pane inherits
|
||||
// UIPanelScrollFrameTemplate, and a MISSING template is a loader *warning*, not an error —
|
||||
// an under-loaded list passes load_xml and then loses the wheel, the arrows and the bar
|
||||
// silently.
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "QuestLogFrame.xml");
|
||||
|
||||
s.set_quest_log(eight_entries());
|
||||
@@ -1053,6 +1132,12 @@ fn reward_rows_preview_and_post_and_a_plain_click_stays_inert() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
// ScrollTemplates.xml + UIPanelTemplates.xml are NOT optional: the detail pane inherits
|
||||
// UIPanelScrollFrameTemplate, and a MISSING template is a loader *warning*, not an error —
|
||||
// an under-loaded list passes load_xml and then loses the wheel, the arrows and the bar
|
||||
// silently.
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "QuestLogFrame.xml");
|
||||
load_xml(&s, "UIParent.xml"); // BenillaChatEdit_InsertLink lives here
|
||||
load_xml(&s, "DressUpFrame.xml"); // DressUpItemLink lives here
|
||||
@@ -1062,13 +1147,13 @@ fn reward_rows_preview_and_post_and_a_plain_click_stays_inert() {
|
||||
s.run("ToggleQuestLog()").unwrap();
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert!(
|
||||
s.eval::<bool>("return QuestLogReward1:IsShown()").unwrap(),
|
||||
s.eval::<bool>("return QuestLogItem1:IsShown()").unwrap(),
|
||||
"the fixture's one fixed reward row is laid out"
|
||||
);
|
||||
let _ = s.take_dressup_intents();
|
||||
|
||||
// A PLAIN click is inert: no room, no chat text, no error (the row has no select arm).
|
||||
s.run("QuestLogReward1:Click()").unwrap();
|
||||
s.run("QuestLogItem1:Click()").unwrap();
|
||||
assert!(
|
||||
s.take_dressup_intents().is_empty(),
|
||||
"a plain click never opens the dressing room"
|
||||
@@ -1084,7 +1169,7 @@ fn reward_rows_preview_and_post_and_a_plain_click_stays_inert() {
|
||||
// SHIFT + chat open → the reward's full escaped link (ref l.547-550).
|
||||
assert!(s.focus_editbox("ChatFrameEditBox"));
|
||||
s.set_modifiers(true, false, false);
|
||||
s.run("QuestLogReward1:Click()").unwrap();
|
||||
s.run("QuestLogItem1:Click()").unwrap();
|
||||
s.set_modifiers(false, false, false);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return ChatFrameEditBox:GetText()")
|
||||
@@ -1099,7 +1184,7 @@ fn reward_rows_preview_and_post_and_a_plain_click_stays_inert() {
|
||||
|
||||
// CTRL → the room wearing the reward (ref l.543-546): re-dress first, then try it on.
|
||||
s.set_modifiers(false, true, false);
|
||||
s.run("QuestLogReward1:Click()").unwrap();
|
||||
s.run("QuestLogItem1:Click()").unwrap();
|
||||
s.set_modifiers(false, false, false);
|
||||
assert_eq!(
|
||||
s.take_dressup_intents(),
|
||||
@@ -1126,6 +1211,12 @@ fn shift_click_on_a_title_posts_the_quest_name_with_chat_open_and_watches_with_i
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
// ScrollTemplates.xml + UIPanelTemplates.xml are NOT optional: the detail pane inherits
|
||||
// UIPanelScrollFrameTemplate, and a MISSING template is a loader *warning*, not an error —
|
||||
// an under-loaded list passes load_xml and then loses the wheel, the arrows and the bar
|
||||
// silently.
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "QuestLogFrame.xml");
|
||||
load_xml(&s, "UIParent.xml"); // BenillaChatEdit_InsertLink lives here
|
||||
load_xml(&s, "ChatFrame.xml"); // ChatFrameEditBox lives here
|
||||
@@ -1210,6 +1301,12 @@ fn share_quest_needs_both_a_sharable_selection_and_a_party() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
// ScrollTemplates.xml + UIPanelTemplates.xml are NOT optional: the detail pane inherits
|
||||
// UIPanelScrollFrameTemplate, and a MISSING template is a loader *warning*, not an error —
|
||||
// an under-loaded list passes load_xml and then loses the wheel, the arrows and the bar
|
||||
// silently.
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "QuestLogFrame.xml");
|
||||
|
||||
// Solo, quest 1 sharable and selected by the auto-selection.
|
||||
@@ -1226,7 +1323,7 @@ fn share_quest_needs_both_a_sharable_selection_and_a_party() {
|
||||
"quest 1 carries the sharable bit"
|
||||
);
|
||||
assert!(
|
||||
!s.eval::<bool>("return QuestLogPushQuestButton:IsEnabled() ~= 0")
|
||||
!s.eval::<bool>("return QuestFramePushQuestButton:IsEnabled() ~= 0")
|
||||
.unwrap(),
|
||||
"solo: sharable is not enough"
|
||||
);
|
||||
@@ -1235,7 +1332,7 @@ fn share_quest_needs_both_a_sharable_selection_and_a_party() {
|
||||
s.set_party(party(2));
|
||||
s.fire_event("PARTY_MEMBERS_CHANGED", vec![]);
|
||||
assert!(
|
||||
s.eval::<bool>("return QuestLogPushQuestButton:IsEnabled() ~= 0")
|
||||
s.eval::<bool>("return QuestFramePushQuestButton:IsEnabled() ~= 0")
|
||||
.unwrap(),
|
||||
"PARTY_MEMBERS_CHANGED alone re-tests the button"
|
||||
);
|
||||
@@ -1249,7 +1346,7 @@ fn share_quest_needs_both_a_sharable_selection_and_a_party() {
|
||||
"quest 2 has no sharable bit"
|
||||
);
|
||||
assert!(
|
||||
!s.eval::<bool>("return QuestLogPushQuestButton:IsEnabled() ~= 0")
|
||||
!s.eval::<bool>("return QuestFramePushQuestButton:IsEnabled() ~= 0")
|
||||
.unwrap(),
|
||||
"in a party: an unsharable selection is still dark"
|
||||
);
|
||||
@@ -1258,12 +1355,12 @@ fn share_quest_needs_both_a_sharable_selection_and_a_party() {
|
||||
s.run("SelectQuestLogEntry(1)").unwrap();
|
||||
s.run("BenillaQuestLogFrame_Update()").unwrap();
|
||||
assert!(s
|
||||
.eval::<bool>("return QuestLogPushQuestButton:IsEnabled() ~= 0")
|
||||
.eval::<bool>("return QuestFramePushQuestButton:IsEnabled() ~= 0")
|
||||
.unwrap());
|
||||
s.set_party(PartyState::default());
|
||||
s.fire_event("PARTY_MEMBERS_CHANGED", vec![]);
|
||||
assert!(!s
|
||||
.eval::<bool>("return QuestLogPushQuestButton:IsEnabled() ~= 0")
|
||||
.eval::<bool>("return QuestFramePushQuestButton:IsEnabled() ~= 0")
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
@@ -1278,13 +1375,19 @@ fn share_quest_is_dark_on_an_empty_log_even_in_a_party() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
// ScrollTemplates.xml + UIPanelTemplates.xml are NOT optional: the detail pane inherits
|
||||
// UIPanelScrollFrameTemplate, and a MISSING template is a loader *warning*, not an error —
|
||||
// an under-loaded list passes load_xml and then loses the wheel, the arrows and the bar
|
||||
// silently.
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "QuestLogFrame.xml");
|
||||
|
||||
s.set_quest_log(QuestLogState::default());
|
||||
s.set_party(party(4));
|
||||
s.run("ToggleQuestLog()").unwrap();
|
||||
assert!(!s
|
||||
.eval::<bool>("return QuestLogPushQuestButton:IsEnabled() ~= 0")
|
||||
.eval::<bool>("return QuestFramePushQuestButton:IsEnabled() ~= 0")
|
||||
.unwrap());
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
@@ -1300,6 +1403,12 @@ fn share_quest_click_queues_the_selected_quests_id() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
// ScrollTemplates.xml + UIPanelTemplates.xml are NOT optional: the detail pane inherits
|
||||
// UIPanelScrollFrameTemplate, and a MISSING template is a loader *warning*, not an error —
|
||||
// an under-loaded list passes load_xml and then loses the wheel, the arrows and the bar
|
||||
// silently.
|
||||
load_xml(&s, "ScrollTemplates.xml");
|
||||
load_xml(&s, "UIPanelTemplates.xml");
|
||||
load_xml(&s, "QuestLogFrame.xml");
|
||||
|
||||
s.set_quest_log(entries_sharable(&[1, 5]));
|
||||
@@ -1308,7 +1417,7 @@ fn share_quest_click_queues_the_selected_quests_id() {
|
||||
|
||||
s.run("SelectQuestLogEntry(5)").unwrap();
|
||||
s.run("BenillaQuestLogFrame_Update()").unwrap();
|
||||
s.run("QuestLogPushQuestButton:Click()").unwrap();
|
||||
s.run("QuestFramePushQuestButton:Click()").unwrap();
|
||||
assert_eq!(s.take_quest_log_pushes(), vec![5]);
|
||||
assert!(s.take_quest_log_pushes().is_empty(), "drained");
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
//! sweep. Each test below fails on exactly one of those.
|
||||
|
||||
use benilla_ui::script::{
|
||||
PartyMemberInfo, PartyRequest, PartyState, RaidMemberInfo, SavedInstanceInfo, UiScript,
|
||||
PartyMemberInfo, PartyRequest, PartyState, RaidMemberInfo, SavedInstanceInfo, SelectionRequest,
|
||||
UiScript,
|
||||
};
|
||||
|
||||
/// Load one shipped `assets/ui/<file>`, panicking on any loader error **or unknown-template
|
||||
@@ -600,7 +601,10 @@ fn left_clicking_a_row_targets_its_raid_token() {
|
||||
open_raid_tab(&mut s);
|
||||
push_raid(&mut s, twelve());
|
||||
s.run("RaidGroupButton7:Click(\"LeftButton\")").unwrap();
|
||||
assert_eq!(s.take_target_requests(), vec!["raid7".to_string()]);
|
||||
assert_eq!(
|
||||
s.take_selection_requests(),
|
||||
vec![SelectionRequest::Unit("raid7".into())]
|
||||
);
|
||||
}
|
||||
|
||||
/// Right-clicking a row opens the RAID menu, and each of its four rows appears for exactly the
|
||||
|
||||
@@ -64,12 +64,17 @@ fn skill(
|
||||
fn shown_skills_page() -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
// UIPanelTemplates.xml is NOT optional even though this list ran without it for a year: a
|
||||
// MISSING template is a loader *warning*, not an error, so an under-loaded list passes the
|
||||
// assert in load_xml and then fails later on geometry that silently never got built.
|
||||
// SkillDetailScrollFrame inherits UIPanelScrollFrameTemplate from that file.
|
||||
for f in [
|
||||
"Fonts.xml",
|
||||
"MoneyFrame.xml",
|
||||
"UiPanels.xml",
|
||||
"GameTooltip.xml",
|
||||
"ScrollTemplates.xml",
|
||||
"UIPanelTemplates.xml",
|
||||
"CharacterFrame.xml",
|
||||
"SkillFrame.xml",
|
||||
] {
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
//! `TargetFrame_OnUpdate`'s one-compare reconcile is for, and it has a test.
|
||||
|
||||
use benilla_ui::script::{
|
||||
AuraState, PartyMemberInfo, PartyState, QuadContent, RaidMemberInfo, ScriptValue, UiScript,
|
||||
UnitState,
|
||||
AuraState, PartyMemberInfo, PartyState, QuadContent, RaidMemberInfo, ScriptValue,
|
||||
SelectionRequest, UiScript, UnitState,
|
||||
};
|
||||
|
||||
/// Load one shipped `assets/ui/<file>`, panicking on any loader error (the unit-frame tests').
|
||||
@@ -378,11 +378,14 @@ fn the_left_click_targets_the_unit() {
|
||||
let mut s = load_tot();
|
||||
switch_on(&mut s);
|
||||
s.run(r#"TargetofTarget_OnClick("LeftButton")"#).unwrap();
|
||||
assert_eq!(s.take_target_requests(), vec!["targettarget".to_string()]);
|
||||
assert_eq!(
|
||||
s.take_selection_requests(),
|
||||
vec![SelectionRequest::Unit("targettarget".into())]
|
||||
);
|
||||
|
||||
// A right click is not a menu here: the reference gives this frame none.
|
||||
s.run(r#"TargetofTarget_OnClick("RightButton")"#).unwrap();
|
||||
assert!(s.take_target_requests().is_empty());
|
||||
assert!(s.take_selection_requests().is_empty());
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use benilla_ui::script::{QuadContent, ScriptValue, SoundRequest, UiScript, UnitState};
|
||||
use benilla_ui::script::{
|
||||
QuadContent, ScriptValue, SelectionRequest, SoundRequest, UiScript, UnitState,
|
||||
};
|
||||
|
||||
/// Load one shipped `assets/ui/<file>` (the panel tests' loader, duplicated to stay
|
||||
/// self-contained), panicking on any loader error.
|
||||
@@ -383,7 +385,7 @@ fn left_clicking_the_player_frame_targets_self() {
|
||||
s.fire_event("UNIT_HEALTH", vec![ScriptValue::Str("player".into())]);
|
||||
s.resolve();
|
||||
assert!(
|
||||
s.take_target_requests().is_empty(),
|
||||
s.take_selection_requests().is_empty(),
|
||||
"no request before any click"
|
||||
);
|
||||
|
||||
@@ -394,7 +396,7 @@ fn left_clicking_the_player_frame_targets_self() {
|
||||
s.mouse_button(cx as f32, cy as f32, "RightButton", true);
|
||||
s.mouse_button(cx as f32, cy as f32, "RightButton", false);
|
||||
assert!(
|
||||
s.take_target_requests().is_empty(),
|
||||
s.take_selection_requests().is_empty(),
|
||||
"right-click queues no target"
|
||||
);
|
||||
// Solo, every SELF row is gated off (only CANCEL survives) — the ref opens no menu. 1.12 has
|
||||
@@ -408,8 +410,8 @@ fn left_clicking_the_player_frame_targets_self() {
|
||||
s.mouse_button(cx as f32, cy as f32, "LeftButton", true);
|
||||
s.mouse_button(cx as f32, cy as f32, "LeftButton", false);
|
||||
assert_eq!(
|
||||
s.take_target_requests(),
|
||||
vec!["player"],
|
||||
s.take_selection_requests(),
|
||||
vec![SelectionRequest::Unit("player".into())],
|
||||
"left-click queues a self-target"
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user