- the colour dialog is the reference's own `ColorPickerFrame.xml` — the window with the largest reach in the addon corpus (86 of 218 addons name it, 64 of those Dewdrop-2.0, so every Ace2 config menu's colour row opens it), and the guild charter, the guild registrar, the error toasts with the raid warning (which our copy had declared with NO scripts at all, so the big centre text and its sound had been inert scenery), the honour page, the quest timer and the stack-split spinner follow — each a toc line and a deletion - the swaps caught our own bugs on the way: the registrar's two service rows were hidden by a template modelled on the quest list's pooled rows, leaving a bare "Available Services" heading over blank parchment; the stack spinner CLAMPED a too-big digit where the reference ignores it (37 against a 5-stack leaves the 3 standing, `StackSplitFrame.lua:72-85`) — a divergence with a test defending it; and 1.12's Lua-5.0 `arg`/`arg.n` varargs turn out to work in this VM (mlua's vendored 5.1 has `LUA_COMPAT_VARARG`), which unblocks every stock file written in that idiom - 42 test modules carried five near-identical private copies of a disk-only XML loader that structurally cannot name a chain file; they are one shared reader now (733 lines out), with a strict variant that fails on an unknown-template WARNING — the case where a frame builds with none of its art — which is how the registrar bug was caught on the first window after Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
212 lines
7.4 KiB
Rust
212 lines
7.4 KiB
Rust
//! The instance-lockout **Era surface** (decision 1748): the three engine bindings, and the one
|
|
//! place in the shipped UI that reads them — the SELF menu's "Reset all instances" row and the
|
|
//! `CONFIRM_RESET_INSTANCES` dialog behind it.
|
|
//!
|
|
//! The four chat lines the family's other packets raise are engine-composed and have no Lua at
|
|
//! all (no FrameXML file mentions `RAID_INSTANCE_WELCOME` or its siblings); they are tested at the
|
|
//! composer, in `crate::ui_instance`.
|
|
|
|
use benilla_ui::script::UiScript;
|
|
|
|
use super::test_ui::load_ui as load_xml;
|
|
|
|
/// The three bindings answer with the reference's own shapes — `IsInInstance` a PAIR, both
|
|
/// readers `1`/`nil` rather than `true`/`false` (the reference pushes the double 1.0), and
|
|
/// `ResetInstances` a queue.
|
|
#[test]
|
|
fn the_three_bindings_have_the_reference_shapes() {
|
|
let mut s = UiScript::new().unwrap();
|
|
|
|
// Nothing pushed yet: the app has not said where we are.
|
|
assert_eq!(
|
|
s.eval::<(Option<f64>, String)>("return IsInInstance()")
|
|
.unwrap(),
|
|
(None, "none".into()),
|
|
"no map pushed reads as not-an-instance"
|
|
);
|
|
|
|
for (ty, inside, name) in [
|
|
(0u32, None, "none"),
|
|
(1, Some(1.0), "party"),
|
|
(2, Some(1.0), "raid"),
|
|
(3, Some(1.0), "pvp"),
|
|
// Past the reference's own `cmp esi,4; jae` guard.
|
|
(4, Some(1.0), "none"),
|
|
] {
|
|
s.set_instance_type(Some(ty));
|
|
assert_eq!(
|
|
s.eval::<(Option<f64>, String)>("return IsInInstance()")
|
|
.unwrap(),
|
|
(inside, name.into()),
|
|
"InstanceType {ty}"
|
|
);
|
|
}
|
|
|
|
assert_eq!(
|
|
s.eval::<Option<f64>>("return CanShowResetInstances()")
|
|
.unwrap(),
|
|
None,
|
|
"false by default — no bind, no dungeon behind us"
|
|
);
|
|
s.set_can_reset_instances(true);
|
|
assert_eq!(
|
|
s.eval::<Option<f64>>("return CanShowResetInstances()")
|
|
.unwrap(),
|
|
Some(1.0)
|
|
);
|
|
|
|
assert_eq!(s.take_reset_instance_asks(), 0);
|
|
s.run("ResetInstances(); ResetInstances()").unwrap();
|
|
assert_eq!(s.take_reset_instance_asks(), 2, "each call is one send");
|
|
assert_eq!(s.take_reset_instance_asks(), 0, "the drain is a take");
|
|
}
|
|
|
|
/// The whole shipped path, through the real hit test: right-click your own portrait, the row is
|
|
/// there only while `CanShowResetInstances()` is true, clicking it raises the reference's confirm,
|
|
/// and Yes is the only thing that sends.
|
|
#[test]
|
|
fn the_self_menu_row_gates_on_the_binding_and_confirms_before_sending() {
|
|
let mut s = UiScript::new().unwrap();
|
|
s.set_screen_size(1024.0, 768.0);
|
|
// The row labels a bare harness has no GlobalStrings.lua for. Verbatim 1.12 values
|
|
// (`RESET_INSTANCES` l.3342, `CONFIRM_RESET_INSTANCES` l.851, `YES` l.5463, `NO` l.2794) —
|
|
// production runs the player's own string table at boot.
|
|
s.run(
|
|
r#"
|
|
RESET_INSTANCES = "Reset all instances"
|
|
CONFIRM_RESET_INSTANCES = "Do you really want to reset all of your instances?"
|
|
YES = "Yes"
|
|
NO = "No"
|
|
CANCEL = "Cancel"
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
for file in [
|
|
"Fonts.xml",
|
|
"UIParent.xml",
|
|
"MoneyFrame.xml",
|
|
"UiPanels.xml",
|
|
"GameTooltip.xml",
|
|
"UIDropDownMenu.xml",
|
|
"UnitPopup.xml",
|
|
"UnitFrames.xml",
|
|
"RaidFrame.xml",
|
|
] {
|
|
load_xml(&s, file);
|
|
}
|
|
s.resolve();
|
|
assert!(s.errors().is_empty(), "load errors: {:?}", s.errors());
|
|
|
|
let open_self_menu = |s: &mut UiScript| {
|
|
s.run("CloseDropDownMenus()").unwrap();
|
|
s.run(r#"PlayerFrame_OnClick("RightButton")"#).unwrap();
|
|
s.resolve();
|
|
};
|
|
let row_labels = |s: &mut UiScript| -> Vec<String> {
|
|
let n = s.eval::<i64>("return DropDownList1.numButtons").unwrap();
|
|
(1..=n)
|
|
.map(|i| {
|
|
s.eval::<String>(&format!("return DropDownList1Button{i}:GetText() or \"\""))
|
|
.unwrap()
|
|
})
|
|
.collect()
|
|
};
|
|
|
|
// Off: the row is not in the menu at all — hidden, not greyed (the reference's
|
|
// `UnitPopupShown[index] = 0`). Solo, it is the ONLY row of the SELF menu that could show
|
|
// (the loot trio, Leave and the raid marks all need a party), so the menu's "nothing but
|
|
// CANCEL" early-out fires and no menu opens at all. That is the reference's behaviour too,
|
|
// and it is the sharpest possible assertion that the row is really gone.
|
|
open_self_menu(&mut s);
|
|
assert!(
|
|
s.eval::<bool>("return not DropDownList1:IsVisible()")
|
|
.unwrap(),
|
|
"solo with no lockout to reset, the SELF menu has nothing to show"
|
|
);
|
|
|
|
// On: the row appears, and with it the menu.
|
|
s.set_can_reset_instances(true);
|
|
open_self_menu(&mut s);
|
|
assert!(
|
|
s.eval::<bool>("return DropDownList1:IsVisible()").unwrap(),
|
|
"the one showable row is enough to open the SELF menu"
|
|
);
|
|
let labels = row_labels(&mut s);
|
|
let row = labels
|
|
.iter()
|
|
.position(|l| l == "Reset all instances")
|
|
.unwrap_or_else(|| panic!("the row shows once the binding says so: {labels:?}"));
|
|
|
|
// Clicking it sends NOTHING — it raises the confirm.
|
|
let button = format!("DropDownList1Button{}", row + 1);
|
|
let (cx, cy) = s
|
|
.eval::<(f64, f64)>(&format!("return {button}:GetCenter()"))
|
|
.unwrap();
|
|
s.mouse_button(cx as f32, cy as f32, "LeftButton", true);
|
|
s.mouse_button(cx as f32, cy as f32, "LeftButton", false);
|
|
s.resolve();
|
|
assert_eq!(
|
|
s.take_reset_instance_asks(),
|
|
0,
|
|
"the row itself never sends — it asks"
|
|
);
|
|
assert!(
|
|
s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap(),
|
|
"the row raises CONFIRM_RESET_INSTANCES"
|
|
);
|
|
assert_eq!(
|
|
s.eval::<String>("return StaticPopup1Text:GetText()")
|
|
.unwrap(),
|
|
"Do you really want to reset all of your instances?"
|
|
);
|
|
assert_eq!(
|
|
s.eval::<String>("return StaticPopup1Button1:GetText()")
|
|
.unwrap(),
|
|
"Yes"
|
|
);
|
|
assert_eq!(
|
|
s.eval::<String>("return StaticPopup1Button2:GetText()")
|
|
.unwrap(),
|
|
"No"
|
|
);
|
|
|
|
// No sends nothing; Yes sends exactly one.
|
|
let (nx, ny) = s
|
|
.eval::<(f64, f64)>("return StaticPopup1Button2:GetCenter()")
|
|
.unwrap();
|
|
s.mouse_button(nx as f32, ny as f32, "LeftButton", true);
|
|
s.mouse_button(nx as f32, ny as f32, "LeftButton", false);
|
|
s.resolve();
|
|
assert_eq!(
|
|
s.take_reset_instance_asks(),
|
|
0,
|
|
"No is not an answer that sends"
|
|
);
|
|
|
|
open_self_menu(&mut s);
|
|
let labels = row_labels(&mut s);
|
|
let row = labels
|
|
.iter()
|
|
.position(|l| l == "Reset all instances")
|
|
.expect("still offered");
|
|
let button = format!("DropDownList1Button{}", row + 1);
|
|
let (cx, cy) = s
|
|
.eval::<(f64, f64)>(&format!("return {button}:GetCenter()"))
|
|
.unwrap();
|
|
s.mouse_button(cx as f32, cy as f32, "LeftButton", true);
|
|
s.mouse_button(cx as f32, cy as f32, "LeftButton", false);
|
|
s.resolve();
|
|
let (yx, yy) = s
|
|
.eval::<(f64, f64)>("return StaticPopup1Button1:GetCenter()")
|
|
.unwrap();
|
|
s.mouse_button(yx as f32, yy as f32, "LeftButton", true);
|
|
s.mouse_button(yx as f32, yy as f32, "LeftButton", false);
|
|
s.resolve();
|
|
assert_eq!(
|
|
s.take_reset_instance_asks(),
|
|
1,
|
|
"Yes is the one call that sends CMSG_RESET_INSTANCES"
|
|
);
|
|
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
|
}
|