- every dialog is the reference's own: `StaticPopup.xml` + `StaticPopup.lua` load off the player's chain, and 0308's engine with its 37 dialogs leaves `UiPanels.xml`; all 76 of the reference's dialogs are the stock table's, 28 of which no file of ours had ever registered (the resurrect family among them); the nine hidden listener frames that existed only because benilla once had no `UIParent_OnEvent` come home to it verbatim from UIParent.lua, and the seven files that were nothing but a dialog and its driver are deleted; what the swap surfaced: the reference capitalises the countdown's unit word ("5 Minutes until release") where ours had lowercased it, and ESC in the typed delete confirm's box is swallowed — the entry names no escape handler; arms for events this engine never fires (PLAYER_SKINNED, TRADE_REQUEST) wait on their feeds rather than being armed blind (1960)
- the social window and the unit menus are the reference's own files: `FriendsFrame.xml` loads off the chain and our transcription is deleted — its twelve dialogs re-homed, `FRIENDLIST_SHOW` fired as the answer to an explicit `ShowFriends()`, and `Get`/`SetLookingForGroup` a client-side flag until their bodies are carved; `UnitPopup.xml` loads off the chain, our row catalogue and its driver frame go, and nine verbs are built for it — `HasFullControl` off the player-control flag the reference reads at `0x51a158`, `UnitPlayerOrPetInParty`/`InRaid` over a new unit owner, and the six pet one-shots, each pressing the bar's own reaction or command slot word through the same latch and `CMSG_PET_ACTION`, confirmed by wow-re's carve; the Guild Leave/Promote confirms 1958 believed nothing registered were our social file's all along, and the duplicate went with it (1958, 1959)
- the trainer loads on demand: our `TrainerFrame.xml` is deleted and no row replaces it — `Blizzard_TrainerUI` loads off the player's own chain through the reference's `ClassTrainerFrame_LoadUI`, and the LoadOnDemand `Blizzard_*` addons are registry rows read off the chain; the timing IS the mechanism: the list builder writes the state-filter mask to 3 on every `SMSG_TRAINER_LIST` (`0x4d75d9`) and the stock addon pushes its saved filters exactly once, on its own ADDON_LOADED — which fires on the session's first trainer, after that packet, so the saved filters win that open and an eager load would never have applied them; the loader holds an anchor to a sibling not built yet and applies it once the subtree exists; and `UNIT_FLAGS` fires only on a change between two snapshots — wow-re's trio found the per-field watch bridge runs no notify pass on the create leg, correcting 1953 (1957)
- the additive window, built and withdrawn the same hour: under `BLEND_ADD` every additive particle draw commutes with every other, so same-key additive draws can be regrouped exactly — built in the effect lane's merge walk and measured on the crowd rig (Stormwind, forty partybots): the adjacency merge already folds about half the effect items and the regroup found two more folds in a frame of ninety-three, so the pre-regroup walk is what runs; what stays: an `fx=` items/merged census on the FPS probe line, a `DRESS_PART` line per drawn part of the self body on the dress census, and the regression the A/B uncovered: 1948's stock chat migration dropped the drain's plain-line sender, so every `WOW_PROBE_CHAT` rig — the crowd rig's forty `.partybot add`s among them — had gone silent on the server; a probe's slash-less line reaches the wire as SAY again (1955)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
503 lines
20 KiB
Rust
503 lines
20 KiB
Rust
//! The two guild-charter windows (decision 1672): the guild registrar's two panels, and the
|
|
//! charter itself with its two faces.
|
|
//!
|
|
//! What these guard that the Rust-side unit tests structurally cannot: the windows are Lua over an
|
|
//! engine snapshot, so a `GetPetitionInfo` destructured in the wrong order, a name row wired to the
|
|
//! wrong index, the leader/signer swap tested the wrong way round, or a button pointed at the wrong
|
|
//! verb are all invisible to `script::petition`'s own tests and green in the parse sweep. Each test
|
|
//! below fails on exactly one of those.
|
|
//!
|
|
//! **The engine API is STOOD IN FOR here, deliberately** — `guild_tests`' convention, and for its
|
|
//! reasons: what is under test is the window and nothing else, the tests need no app feed seated,
|
|
//! and a change to the engine's plumbing cannot quietly turn one green. Two fixture shapes are the
|
|
//! ones `script::petition` promises and are easy to get wrong:
|
|
//!
|
|
//! - **era booleans are `1`/`nil`, never `true`/`false`** — `isOriginator` and `CanSignPetition`;
|
|
//! - **`GetPetitionInfo` returns SIX values in a fixed order**
|
|
//! (`petitionType, title, bodyText, maxSignatures, originatorName, isOriginator`), and the
|
|
//! fourth is the wire's *requirement*, not `MAX_PETITION_SIGNATURES`.
|
|
|
|
use benilla_ui::script::UiScript;
|
|
|
|
use super::test_ui::load_ui_strict as load_xml;
|
|
|
|
/// The charter engine API, stood in for in Lua.
|
|
///
|
|
/// One mutable table, `BenillaPetitionFixture`, is the whole model; every verb appends to `.calls`,
|
|
/// which a test drains with `BenillaPetitionCalls()`. Tests mutate the table's FIELDS — never
|
|
/// replace the table, since the closures below hold it as an upvalue.
|
|
///
|
|
/// Seeded as a **signer's** view of a charter with two of nine signatures: the case where every
|
|
/// distinction this window makes is live at once (the Sign face showing, rows both filled and
|
|
/// empty, Request still enabled).
|
|
const PETITION_FIXTURE: &str = r#"
|
|
BenillaPetitionFixture = {
|
|
petitionType = "charter",
|
|
title = "Legacy of Steel",
|
|
bodyText = "",
|
|
-- The WIRE's requirement, deliberately not 9, so a test can tell it apart from
|
|
-- MAX_PETITION_SIGNATURES.
|
|
maxSignatures = 4,
|
|
originator = "Tigole",
|
|
isOriginator = nil,
|
|
canSign = 1,
|
|
signers = { "Furor", "Kaplan" },
|
|
charterCost = 1000,
|
|
calls = {},
|
|
}
|
|
|
|
local F = BenillaPetitionFixture
|
|
|
|
function BenillaPetitionCalls()
|
|
local out = table.concat(F.calls, "|")
|
|
F.calls = {}
|
|
return out
|
|
end
|
|
|
|
local function record(call)
|
|
table.insert(F.calls, call)
|
|
end
|
|
|
|
function GetPetitionInfo()
|
|
return F.petitionType, F.title, F.bodyText, F.maxSignatures, F.originator, F.isOriginator
|
|
end
|
|
|
|
function GetNumPetitionNames() return table.getn(F.signers) end
|
|
function GetPetitionNameInfo(i) return F.signers[i] end
|
|
function CanSignPetition() return F.canSign end
|
|
function GetGuildCharterCost() return F.charterCost end
|
|
|
|
function SignPetition() record("SignPetition") end
|
|
function OfferPetition() record("OfferPetition") end
|
|
function ClosePetition() record("ClosePetition") end
|
|
function CloseGuildRegistrar() record("CloseGuildRegistrar") end
|
|
function TurnInGuildCharter() record("TurnInGuildCharter") end
|
|
function BuyGuildCharter(name) record("BuyGuildCharter:" .. name) end
|
|
function RenamePetition(name) record("RenamePetition:" .. name) end
|
|
"#;
|
|
|
|
/// The windows' manifest slice, in `benilla.toc` order, with the fixture seated first.
|
|
///
|
|
/// `MoneyFrame.xml` is load-bearing rather than incidental: `GuildRegistrarMoneyFrame` declares
|
|
/// `inherits="MoneyFrameTemplate"`, which resolves at LOAD (decision 1580), so without it the
|
|
/// registrar's price row would be a bare frame and the unknown-template guard above would fire.
|
|
fn setup() -> UiScript {
|
|
let mut s = UiScript::new().unwrap();
|
|
s.set_screen_size(1024.0, 768.0);
|
|
s.run(PETITION_FIXTURE).unwrap();
|
|
// The player's own strings: the reference's `PetitionFrame_Update` formats
|
|
// GUILD_CHARTER_TEMPLATE and reads GUILD_PETITION_*_INSTRUCTIONS and NOT_YET_SIGNED straight
|
|
// out of GlobalStrings, with no fallback of its own — `format(nil, …)` raises, and the window
|
|
// never paints.
|
|
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
|
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
|
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
|
|
load_xml(&s, "MoneyFrame.xml");
|
|
load_xml(&s, "UiPanels.xml");
|
|
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
|
|
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
|
|
load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua");
|
|
load_xml(&s, r"Interface\FrameXML\StaticPopup.xml");
|
|
// `QuestTitleButtonTemplate`, which the reference's registrar inherits for its two service
|
|
// rows — 1.12 declares it in QuestFrameTemplates.xml, an `<Include>` of QuestFrame.xml, and
|
|
// ours declares it in QuestFrame.xml directly. An unknown template is a loader WARNING, so
|
|
// without this the rows build with no art at all and nothing goes red — which is exactly the
|
|
// failure `load_ui_strict` exists to turn into a red test.
|
|
load_xml(&s, "ScrollTemplates.xml");
|
|
load_xml(&s, "UIParent.xml");
|
|
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
|
|
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
|
|
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
|
|
// `ChatFrameEditBox`, which the reference's own purchase button indexes on every click to
|
|
// decide where focus goes after the name box closes — a nil there raises before the charter is
|
|
// bought. Ours guarded it; the reference does not.
|
|
load_xml(&s, "GameTooltip.xml"); // TOOLTIP_DEFAULT_COLOR, read by the dropdown backdrops
|
|
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml"); // ChatFrame's seven dropdowns inherit its template
|
|
load_xml(&s, "Interface\\FrameXML\\UIMenu.xml"); // the kit the chat menus build from
|
|
load_xml(&s, "Interface\\FrameXML\\ChatFrame.xml");
|
|
load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.lua");
|
|
load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.xml");
|
|
load_xml(&s, "Interface\\FrameXML\\FloatingChatFrame.xml");
|
|
load_xml(&s, "Interface\\FrameXML\\GuildRegistrarFrame.xml");
|
|
load_xml(&s, "Interface\\FrameXML\\PetitionFrame.xml");
|
|
s
|
|
}
|
|
|
|
fn text(s: &UiScript, expr: &str) -> String {
|
|
s.eval::<String>(&format!("return {expr}:GetText() or \"\""))
|
|
.unwrap_or_else(|e| panic!("{expr}:GetText() — {e}"))
|
|
}
|
|
|
|
fn visible(s: &UiScript, frame: &str) -> bool {
|
|
s.eval::<bool>(&format!("return {frame}:IsVisible()"))
|
|
.unwrap_or_else(|e| panic!("{frame}:IsVisible() — {e}"))
|
|
}
|
|
|
|
fn calls(s: &UiScript) -> String {
|
|
s.eval::<String>("return BenillaPetitionCalls()").unwrap()
|
|
}
|
|
|
|
/// Open the charter window the way the engine does.
|
|
fn show_petition(s: &mut UiScript) {
|
|
s.fire_event("PETITION_SHOW", vec![]);
|
|
assert!(s.errors().is_empty(), "PETITION_SHOW: {:?}", s.errors());
|
|
}
|
|
|
|
fn show_registrar(s: &mut UiScript) {
|
|
s.fire_event("GUILD_REGISTRAR_SHOW", vec![]);
|
|
assert!(
|
|
s.errors().is_empty(),
|
|
"GUILD_REGISTRAR_SHOW: {:?}",
|
|
s.errors()
|
|
);
|
|
}
|
|
|
|
/// A signer's charter paints the header, the signed rows, the unsigned rows, and the member
|
|
/// instructions — and shows Sign, not Request/Rename.
|
|
///
|
|
/// The row assertion is the load-bearing half: rows 1..2 carry the two signers and rows 3..9 must
|
|
/// read `<not yet signed>`. A `GetPetitionNameInfo` bound 0-based would put "Kaplan" in row 1 and
|
|
/// blank row 2 while every other assertion here still passed.
|
|
#[test]
|
|
fn a_signers_charter_shows_the_sign_face_and_nine_rows() {
|
|
let _data = benilla_formats::wow_data_or_skip!();
|
|
let mut s = setup();
|
|
show_petition(&mut s);
|
|
|
|
assert!(visible(&s, "PetitionFrame"));
|
|
assert_eq!(
|
|
text(&s, "PetitionFrameNpcNameText"),
|
|
"Legacy of Steel Guild Charter",
|
|
"GUILD_CHARTER_TEMPLATE filled with the title"
|
|
);
|
|
assert_eq!(text(&s, "PetitionFrameCharterName"), "Legacy of Steel");
|
|
assert_eq!(text(&s, "PetitionFrameMasterName"), "Tigole");
|
|
// The three static labels come from GlobalStrings via `text=` and are never painted.
|
|
assert_eq!(text(&s, "PetitionFrameCharterTitle"), "Guild Name");
|
|
assert_eq!(text(&s, "PetitionFrameMasterTitle"), "Guild Master");
|
|
assert_eq!(text(&s, "PetitionFrameMemberTitle"), "Members");
|
|
|
|
assert_eq!(text(&s, "PetitionFrameMemberName1"), "Furor");
|
|
assert_eq!(text(&s, "PetitionFrameMemberName2"), "Kaplan");
|
|
for i in 3..=9 {
|
|
assert_eq!(
|
|
text(&s, &format!("PetitionFrameMemberName{i}")),
|
|
"<not yet signed>",
|
|
"row {i} is an empty seat"
|
|
);
|
|
}
|
|
|
|
// The charter's own static furniture — the three labels, the nine rows and the instructions
|
|
// all draw, not merely hold text.
|
|
for part in [
|
|
"PetitionFrameCharterTitle",
|
|
"PetitionFrameCharterName",
|
|
"PetitionFrameMasterTitle",
|
|
"PetitionFrameMasterName",
|
|
"PetitionFrameMemberTitle",
|
|
"PetitionFrameMemberName1",
|
|
"PetitionFrameMemberName9",
|
|
"PetitionFrameInstructions",
|
|
"PetitionFrameCancelButton",
|
|
"PetitionFrameCloseButton",
|
|
] {
|
|
assert!(visible(&s, part), "{part} is on screen");
|
|
}
|
|
assert!(visible(&s, "PetitionFrameSignButton"));
|
|
assert!(!visible(&s, "PetitionFrameRequestButton"));
|
|
assert!(
|
|
!visible(&s, "PetitionFrameRenameButton"),
|
|
"only the charter's owner may rename it"
|
|
);
|
|
assert_eq!(
|
|
text(&s, "PetitionFrameInstructions"),
|
|
"Click the <Sign Charter> button to become a charter member of this guild."
|
|
);
|
|
}
|
|
|
|
/// The owner's charter is the same window inside out: Request + Rename replace Sign, and the
|
|
/// instructions change.
|
|
///
|
|
/// Both halves are asserted because the two buttons share one anchor — a Show/Hide written the
|
|
/// wrong way round stacks them and the top one wins, which looks correct from a screenshot of
|
|
/// either view alone.
|
|
#[test]
|
|
fn the_owners_charter_swaps_sign_for_request_and_rename() {
|
|
let _data = benilla_formats::wow_data_or_skip!();
|
|
let mut s = setup();
|
|
s.run("BenillaPetitionFixture.isOriginator = 1").unwrap();
|
|
show_petition(&mut s);
|
|
|
|
assert!(visible(&s, "PetitionFrameRequestButton"));
|
|
assert!(visible(&s, "PetitionFrameRenameButton"));
|
|
assert!(!visible(&s, "PetitionFrameSignButton"));
|
|
assert_eq!(
|
|
text(&s, "PetitionFrameInstructions"),
|
|
"Select a player you wish to invite and click <request signature>. To create this \
|
|
guild, turn it in to the guild registrar when you have filled the charter."
|
|
);
|
|
}
|
|
|
|
/// Request Signature disables against the **wire's** requirement, never against the nine rows.
|
|
///
|
|
/// The fixture requires 4. At three signatures the button is live; at four it is not — and nine
|
|
/// rows are still painted either way. A window that compared against `MAX_PETITION_SIGNATURES`
|
|
/// would leave Request enabled on a full charter, and every other assertion in this file would
|
|
/// still pass.
|
|
#[test]
|
|
fn request_signature_disables_at_the_wires_requirement_not_at_nine() {
|
|
let _data = benilla_formats::wow_data_or_skip!();
|
|
let mut s = setup();
|
|
s.run(
|
|
r#"
|
|
BenillaPetitionFixture.isOriginator = 1
|
|
BenillaPetitionFixture.signers = { "A", "B", "C" }
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
show_petition(&mut s);
|
|
assert!(
|
|
s.eval::<bool>("return PetitionFrameRequestButton:IsEnabled() ~= 0")
|
|
.unwrap(),
|
|
"three of four signatures — still asking"
|
|
);
|
|
|
|
s.run(r#"BenillaPetitionFixture.signers = { "A", "B", "C", "D" }"#)
|
|
.unwrap();
|
|
show_petition(&mut s);
|
|
assert!(
|
|
!s.eval::<bool>("return PetitionFrameRequestButton:IsEnabled() ~= 0")
|
|
.unwrap(),
|
|
"four of four — the charter is full, though only four of nine rows are used"
|
|
);
|
|
assert_eq!(
|
|
text(&s, "PetitionFrameMemberName5"),
|
|
"<not yet signed>",
|
|
"the nine rows are unaffected by the requirement"
|
|
);
|
|
}
|
|
|
|
/// `CanSignPetition` gates the Sign button independently of which face is showing: a charter can be
|
|
/// a signer's view and still be unsignable (already signed, already guilded, full).
|
|
#[test]
|
|
fn the_sign_button_follows_can_sign_petition() {
|
|
let _data = benilla_formats::wow_data_or_skip!();
|
|
let mut s = setup();
|
|
show_petition(&mut s);
|
|
assert!(s
|
|
.eval::<bool>("return PetitionFrameSignButton:IsEnabled() ~= 0")
|
|
.unwrap());
|
|
|
|
s.run("BenillaPetitionFixture.canSign = nil").unwrap();
|
|
show_petition(&mut s);
|
|
assert!(
|
|
!s.eval::<bool>("return PetitionFrameSignButton:IsEnabled() ~= 0")
|
|
.unwrap(),
|
|
"nil, the era false, disables it"
|
|
);
|
|
assert!(
|
|
visible(&s, "PetitionFrameSignButton"),
|
|
"disabled, not hidden — hiding would expose the Request button beneath it"
|
|
);
|
|
}
|
|
|
|
/// Each button reaches its own verb, and closing the window closes the engine's session.
|
|
///
|
|
/// The `ClosePetition` half is the one worth pinning: it rides `OnHide`, so it fires however the
|
|
/// window closes, and without it the engine keeps a charter session for a window nobody can see.
|
|
#[test]
|
|
fn the_charter_buttons_reach_their_verbs_and_the_close_clears_the_session() {
|
|
let _data = benilla_formats::wow_data_or_skip!();
|
|
let mut s = setup();
|
|
show_petition(&mut s);
|
|
let _ = calls(&s);
|
|
|
|
s.run("PetitionFrameSignButton:Click()").unwrap();
|
|
assert_eq!(calls(&s), "SignPetition");
|
|
|
|
s.run("BenillaPetitionFixture.isOriginator = 1").unwrap();
|
|
show_petition(&mut s);
|
|
let _ = calls(&s);
|
|
s.run("PetitionFrameRequestButton:Click()").unwrap();
|
|
assert_eq!(calls(&s), "OfferPetition");
|
|
|
|
s.run("HideUIPanel(PetitionFrame)").unwrap();
|
|
assert!(!visible(&s, "PetitionFrame"));
|
|
assert_eq!(calls(&s), "ClosePetition", "OnHide clears the session");
|
|
}
|
|
|
|
/// The rename dialog is registered, takes the box's text, and is capped at the server's own
|
|
/// 24-character charter-name limit.
|
|
#[test]
|
|
fn rename_guild_sends_the_box_text_and_caps_at_twenty_four() {
|
|
let _data = benilla_formats::wow_data_or_skip!();
|
|
let mut s = setup();
|
|
s.run("BenillaPetitionFixture.isOriginator = 1").unwrap();
|
|
show_petition(&mut s);
|
|
let _ = calls(&s);
|
|
|
|
s.run("PetitionFrameRenameButton:Click()").unwrap();
|
|
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
|
assert_eq!(
|
|
s.eval::<i64>("return StaticPopupDialogs[\"RENAME_GUILD\"].maxLetters")
|
|
.unwrap(),
|
|
24,
|
|
"the server's MAX_CHARTER_NAME"
|
|
);
|
|
assert!(
|
|
visible(&s, "StaticPopup1"),
|
|
"the popup engine raised the dialog"
|
|
);
|
|
s.run(
|
|
r#"
|
|
StaticPopup1EditBox:SetText("Second Legacy")
|
|
StaticPopup1Button1:Click()
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(calls(&s), "RenamePetition:Second Legacy");
|
|
}
|
|
|
|
/// The registrar opens on its services list, and Purchase swaps panels **locally** — no verb, no
|
|
/// packet. Getting that wrong would send a buy on the first click of "Purchase a Guild Charter",
|
|
/// before the player has typed a name.
|
|
#[test]
|
|
fn the_registrar_opens_on_services_and_purchase_is_a_local_panel_swap() {
|
|
let _data = benilla_formats::wow_data_or_skip!();
|
|
let mut s = setup();
|
|
show_registrar(&mut s);
|
|
|
|
assert!(visible(&s, "GuildRegistrarFrame"));
|
|
assert!(visible(&s, "GuildRegistrarGreetingFrame"));
|
|
assert!(!visible(&s, "GuildRegistrarPurchaseFrame"));
|
|
// **The two service rows must be VISIBLE, not merely labelled.** This assertion is here
|
|
// because its absence shipped the bug: the rows inherited `hidden="true"` from a template
|
|
// modelled on the quest list's POOLED rows, which are shown one at a time by code. These are
|
|
// static and nothing ever calls `:Show()` on them, so both were permanently invisible — the
|
|
// window came up as a bare "Available Services" heading over blank parchment — while every
|
|
// `GetText()` assertion below passed, because a hidden button still knows its own label.
|
|
// Decision 0672's lesson in another key: a frame that loads is not a frame that draws.
|
|
for row in ["GuildRegistrarButton1", "GuildRegistrarButton2"] {
|
|
assert!(visible(&s, row), "{row} is on screen, not just loaded");
|
|
}
|
|
assert_eq!(
|
|
text(&s, "GuildRegistrarButton1"),
|
|
"Purchase a Guild Charter"
|
|
);
|
|
assert_eq!(
|
|
text(&s, "GuildRegistrarButton2"),
|
|
"Register a Guild Charter"
|
|
);
|
|
let _ = calls(&s);
|
|
|
|
s.run("GuildRegistrarButton1:Click()").unwrap();
|
|
assert!(visible(&s, "GuildRegistrarPurchaseFrame"));
|
|
assert!(!visible(&s, "GuildRegistrarGreetingFrame"));
|
|
// The purchase panel's own furniture, for the reason the services rows above are checked: a
|
|
// window whose parts load but do not draw passes every text assertion.
|
|
for part in [
|
|
"GuildRegistrarPurchaseText",
|
|
"GuildRegistrarCostLabel",
|
|
"GuildRegistrarMoneyFrame",
|
|
"GuildRegistrarFrameEditBox",
|
|
"GuildRegistrarFramePurchaseButton",
|
|
"GuildRegistrarFrameCancelButton",
|
|
] {
|
|
assert!(visible(&s, part), "{part} is on screen");
|
|
}
|
|
assert_eq!(calls(&s), "", "the swap sends nothing");
|
|
}
|
|
|
|
/// Buying sends the box's text and closes the window; registering sends the no-argument turn-in.
|
|
///
|
|
/// The close-after-buy is the reference's own behaviour and is asserted because it looks like a bug
|
|
/// otherwise: a buy is one-shot, and its answer is an item arriving rather than anything this
|
|
/// window will be told.
|
|
#[test]
|
|
fn purchase_sends_the_typed_name_and_register_turns_the_charter_in() {
|
|
let _data = benilla_formats::wow_data_or_skip!();
|
|
let mut s = setup();
|
|
show_registrar(&mut s);
|
|
s.run("GuildRegistrarButton1:Click()").unwrap();
|
|
let _ = calls(&s);
|
|
|
|
s.run(
|
|
r#"
|
|
GuildRegistrarFrameEditBox:SetText("Legacy of Steel")
|
|
GuildRegistrarFramePurchaseButton:Click()
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
calls(&s),
|
|
"BuyGuildCharter:Legacy of Steel|CloseGuildRegistrar",
|
|
"the buy, then the window's own OnHide close"
|
|
);
|
|
assert!(!visible(&s, "GuildRegistrarFrame"));
|
|
|
|
show_registrar(&mut s);
|
|
let _ = calls(&s);
|
|
s.run("GuildRegistrarButton2:Click()").unwrap();
|
|
assert_eq!(
|
|
calls(&s),
|
|
"TurnInGuildCharter",
|
|
"no argument — the engine finds the charter in the bags"
|
|
);
|
|
}
|
|
|
|
/// The price row is filled at panel-swap time from `GetGuildCharterCost()`, in copper.
|
|
///
|
|
/// Reading it at *show* time instead would paint a 0: the cost belongs to the open registrar's
|
|
/// charter list, and the greeting panel never displays one.
|
|
#[test]
|
|
fn the_charter_price_is_read_when_the_purchase_panel_opens() {
|
|
let _data = benilla_formats::wow_data_or_skip!();
|
|
let mut s = setup();
|
|
show_registrar(&mut s);
|
|
s.run("GuildRegistrarButton1:Click()").unwrap();
|
|
// 1000 copper = 10 silver: the gold slot is empty, the silver slot reads 10.
|
|
assert_eq!(text(&s, "GuildRegistrarMoneyFrameSilverButtonText"), "10");
|
|
assert_eq!(text(&s, "GuildRegistrarCostLabel"), "Cost:");
|
|
}
|
|
|
|
/// Re-opening the registrar always lands on the services list, never on a half-filled purchase
|
|
/// panel left over from last time.
|
|
#[test]
|
|
fn reopening_the_registrar_returns_to_the_services_list() {
|
|
let _data = benilla_formats::wow_data_or_skip!();
|
|
let mut s = setup();
|
|
show_registrar(&mut s);
|
|
s.run("GuildRegistrarButton1:Click()").unwrap();
|
|
assert!(visible(&s, "GuildRegistrarPurchaseFrame"));
|
|
|
|
s.fire_event("GUILD_REGISTRAR_CLOSED", vec![]);
|
|
assert!(!visible(&s, "GuildRegistrarFrame"));
|
|
show_registrar(&mut s);
|
|
assert!(visible(&s, "GuildRegistrarGreetingFrame"));
|
|
assert!(!visible(&s, "GuildRegistrarPurchaseFrame"));
|
|
}
|
|
|
|
/// Both windows are registered UIPanels — without a row, `ShowUIPanel` degrades to a bare `Show()`
|
|
/// in no slot and two left-slot windows paint over each other (B288's shape, decision 1507).
|
|
#[test]
|
|
fn both_charter_windows_are_registered_left_slot_panels() {
|
|
let _data = benilla_formats::wow_data_or_skip!();
|
|
let mut s = setup();
|
|
for frame in ["GuildRegistrarFrame", "PetitionFrame"] {
|
|
assert_eq!(
|
|
s.eval::<String>(&format!("return UIPanelWindows[\"{frame}\"].area"))
|
|
.unwrap(),
|
|
"left",
|
|
"{frame} must hold a panel row"
|
|
);
|
|
}
|
|
// And they are rivals, not neighbours: opening one seats it where the other was.
|
|
show_registrar(&mut s);
|
|
show_petition(&mut s);
|
|
assert!(visible(&s, "PetitionFrame"));
|
|
assert!(
|
|
!visible(&s, "GuildRegistrarFrame"),
|
|
"one left slot, both pushable 0"
|
|
);
|
|
}
|