Files
benilla-octo/crates/benilla-app/src/ui_script/taxi_tests.rs
T
SamandClaude Fable 5.1 eadacac998 a hundred stale claims retire, and text= was never a divergence
- a comment that justifies code by an engine difference is a claim with an expiry date, and the tree had ~96 expired ones across `assets/ui`, `ui_script/` and `benilla-ui` — a hygiene pass, no behaviour change: dangling `Benilla*` names with zero definitions (`BenillaMoney_*` at thirteen sites after 1937/1962 converged the coin cluster onto the stock `SmallMoneyFrameTemplate`; `BenillaGossipRow_Resize`, `BenillaErrorsFrame_AddMessage`, the digit feed 1285 deleted, `BenillaMerchantItem_OnClick` and three more), "the shipped `assets/ui/<X>.xml`" for thirty-odd files the directory no longer holds — it holds nine — each site now naming the chain file that actually loads, `UiPanels.xml` in the present tense after 1988 took the panel manager and the dialog engine stock, and expired self-deprecation: SimpleHTML "a widget kind we do not have" (1337 built it), "benilla has no console" (1948 bound `ConsoleExec`), `Model`'s "seven verbs not published yet" and "`ClearFog` is a guess" (all 23 are published, the carve sits 500 lines below, `UNBUILT` has been `[&str; 0]` since 2027), `frame_flag_gate`'s doc describing four groups of accepted mouse divergences over a `KNOWN` list that is empty (the empty array and its enforcement stay), five comments saying key delivery is not gated on `keyboard_enabled` where `script::keyboard`'s walk filters on exactly that field (1319), `SetRaidTarget` called a verb this house lacks beside an assert that its intent IS queued (1820), two questlog tests deferring to a screenshot for `GetPoint`/`IsVisible` the regions already answer, `benilla.toc`'s "trade still needs four engine verbs" (all four bound, 1966) and its ping `Model`s "this engine draws no pixels for" (2013 renders `MinimapPing.mdx` itself), and `rig_anim`'s `set_paused` naming a booth caller it does not have; and the code the prose was propping up goes with it: `GameMenuFrame.xml`'s six `<OnLoad>` label setters, because `text=` on a `<Button>` is a global-string lookup at the loader's attribute step (0995, built 61 seconds before the file was written) — the buttons carry `text="LOGOUT"`/`"EXIT_GAME"`/`"MACROS"`/`"RETURN_TO_GAME"`/`"MAIN_MENU"` as the reference's own do; `ScriptLogFrame`'s five authored close-button lines, now `inherits="UIPanelCloseButton"` off the chain's `UIPanelTemplates.xml` seated 757 lines above it (1846); and the editbox's 22-line "gap" doc claiming only the self-acquire `autoFocus` path is built and its `false` default unverified — path 1 is 1686's `visibility_focus`, and the default is `true`, byte-read off `flags = 1` at `0x779a29`/`0x779a2e`, so the test named `autofocus_does_not_focus_on_show…` is renamed for what it actually pins (2044)
- the `text=` divergence never existed: `resolve_text`'s doc had claimed since 0068 that falling back to the raw attribute on a GlobalStrings miss was "one deliberate divergence" from a reference that draws the empty string, and read at the byte it is the reference's own behaviour — `FrameScript_GetText 0x703bf0` does return a pre-seeded empty string (`0x882748`) when the global is not a string, but that return never reaches a label, because all three `text=` readers image-wide test it and substitute the raw attribute: `Button::LoadXML` at `0x778c07` (`0x778c31 mov eax,esi`), `CSimpleFontString::LoadXML` at `0x771006` byte-identically, and `0x7292a6`, which pushes `edi` on the empty arm; so the loader changes nothing, the two test comments that had called `PetLevelText`'s "Level level race class" placeholder and `CLOSE` our divergence now name the reference's arm (a real 1.12 client draws them too), and what IS ours is the extra warning on a key-shaped miss (0983 → 0991), a strict addition; beside it, `GetEffectiveScale`'s root factor is 1 not because "benilla has no `uiScale` CVar" — 1804 registered one, default 0.9 — but because the dial is applied at the raster seam, `768/uiScale` UI units of virtual-screen height rather than a scale on `UIParent`, so every coordinate the VM hands Lua is already in those units and the reference's `GetCursorPosition()/GetEffectiveScale()` transcriptions convert correctly with a root of 1 (2044)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 19:39:30 +07:00

192 lines
9.2 KiB
Rust

//! The shipped taxi-map window driven end-to-end, engine-only (no Bevy): the real
//! `Interface\FrameXML\TaxiFrame.xml` fed a synthetic two-node snapshot (the [`crate::ui_taxi`]
//! feed's exact output shape). Covers what only a runtime load exercises: the Lua parses and every
//! referenced global resolves (including the static node-button pool and the runtime-created
//! route-line textures — TaxiFrame.xml's own header note on both), `TAXIMAP_OPENED` shows the
//! window with the flight master's name and paints the node buttons at their pushed positions, a
//! click on a node drains through `TakeTaxiNode`, and `TAXIMAP_CLOSED` hides it.
use benilla_ui::script::{ScriptValue, TaxiNodeType, TaxiUiNode, TaxiUiState, UiScript};
use super::test_ui::load_ui as load_xml;
/// Load the taxi window + its deps into a fresh script, screen sized.
fn taxi_script() -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua");
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, r"Interface\FrameXML\UIParent.xml");
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua");
load_xml(&s, r"Interface\FrameXML\StaticPopup.xml");
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml"); // TaxiNodeOnButtonEnter's tooltip + SetTooltipMoney
load_xml(&s, "Interface\\FrameXML\\UIErrorsFrame.xml"); // `UIErrorsFrame:AddMessage` — DrawOneHopLines' refusal
// Three the reference's own TaxiFrame leans on that our transcription did not:
// · GlobalStrings — `ERR_TAXINOPATHS` is a GlobalString, and `AddMessage(nil)` draws an
// empty line rather than raising, so its absence is silent.
// · UIPanelTemplates (.lua then .xml) — `TaxiCloseButton` inherits `UIPanelCloseButton`,
// which the chain declares there. Without it the close button loads as a
// bare Button with no handler and a click does nothing at all.
load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.lua");
load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.xml");
load_xml(&s, "Interface\\FrameXML\\TaxiFrame.xml");
s
}
/// Seat the flight master as the `"npc"` unit. Stock `TaxiFrame_OnEvent` fills its label from
/// `UnitName("npc")` (TaxiFrame.lua:27), where our deleted transcription read the `TAXIMAP_OPENED`
/// argument — so a fixture that only fires the event leaves the reference's label blank.
fn seat_flight_master(s: &mut UiScript, name: &str) {
let npc = benilla_ui::script::UnitState {
name: Some(name.into()),
..Default::default()
};
s.set_unit("npc", Some(npc));
}
/// A two-node snapshot: Stormwind (Current) and the verified Sentinel Hill hop (Reachable, 110
/// copper, one route segment) — [`crate::ui_taxi::build_nodes`]'s exact real-data shape.
fn menu() -> TaxiUiState {
TaxiUiState {
art: "Interface\\TaxiFrame\\TAXIMAP0".into(),
nodes: vec![
TaxiUiNode {
name: "Stormwind, Elwynn".into(),
node_type: TaxiNodeType::Current,
pos: (0.43, 0.33),
cost: 0,
routes: vec![],
},
TaxiUiNode {
name: "Sentinel Hill, Westfall".into(),
node_type: TaxiNodeType::Reachable,
pos: (0.41, 0.25),
cost: 110,
routes: vec![[0.43, 0.33, 0.41, 0.25]],
},
],
}
}
/// The whole taxi window minus Bevy: hidden by default, `TAXIMAP_OPENED` shows it with the flight
/// master's name (the event-arg deviation TaxiFrame.xml's header note flags), paints exactly the
/// pushed node buttons (the rest of the 50-slot static pool stays hidden — the CreateFrame/template
/// deviation), a click on the reachable node drains through `TakeTaxiNode`, and `TAXIMAP_CLOSED`
/// hides it again.
#[test]
fn shipped_taxi_frame_drives_end_to_end() {
let mut s = taxi_script();
assert!(!s.eval::<bool>("return TaxiFrame:IsVisible()").unwrap());
s.set_taxi(Some(menu()));
seat_flight_master(&mut s, "Dungar Longdrink");
s.fire_event(
"TAXIMAP_OPENED",
vec![ScriptValue::Str("Dungar Longdrink".into())],
);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
assert!(s.eval::<bool>("return TaxiFrame:IsVisible()").unwrap());
assert_eq!(
// The reference names the flight-master label `TaxiMerchant` and fills it from
// `UnitName("npc")` (TaxiFrame.lua:27) — NOT from the event's argument, which is what our
// transcription's `TaxiNameText` read. So the fixture seats the npc unit above.
s.eval::<String>("return TaxiMerchant:GetText()").unwrap(),
"Dungar Longdrink"
);
// The two pushed nodes show, and there is no third button AT ALL — the reference builds its
// node buttons on demand, one `CreateFrame("Button", "TaxiButton"..i, TaxiRouteMap,
// "TaxiButtonTemplate")` per node (TaxiFrame.lua:39), where our transcription pre-made a fixed
// pool and hid the tail. Same thing on screen, a different mechanism behind it — so the
// expectation moves from "hidden" to "absent".
assert!(s.eval::<bool>("return TaxiButton1:IsVisible()").unwrap());
assert!(s.eval::<bool>("return TaxiButton2:IsVisible()").unwrap());
assert!(s.eval::<bool>("return TaxiButton3 == nil").unwrap());
assert!(s.eval::<bool>("return TaxiButton50 == nil").unwrap());
// A click on node 2 (Sentinel Hill) drains through TakeTaxiNode.
s.resolve();
let (cx, cy) = s
.eval::<(f32, f32)>("return TaxiButton2:GetCenter()")
.unwrap();
s.mouse_button(cx, cy, "LeftButton", true);
s.mouse_button(cx, cy, "LeftButton", false);
assert_eq!(s.take_taxi_node(), vec![2]);
assert!(s.take_taxi_node().is_empty(), "drained");
// TAXIMAP_CLOSED hides the window.
s.set_taxi(None);
s.fire_event("TAXIMAP_CLOSED", vec![]);
assert!(!s.eval::<bool>("return TaxiFrame:IsVisible()").unwrap());
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}
/// The "no single-hop destination" refusal (ref `DrawOneHopLines`, fired from `OnShow`): a map
/// with only the `Current` node (no `Reachable` neighbor at all) hits `numSingleHops == 0` and
/// calls `UIErrorsFrame:AddMessage(ERR_TAXINOPATHS, …)` (stock `TaxiFrame.lua:161-162`) then
/// hides the window — driven end-to-end because the message frame's Lua seam is what the
/// refusal is visible through.
#[test]
fn no_single_hop_destination_posts_the_error_and_closes() {
let mut s = taxi_script();
s.set_taxi(Some(TaxiUiState {
art: "Interface\\TaxiFrame\\TAXIMAP0".into(),
nodes: vec![TaxiUiNode {
name: "Stormwind, Elwynn".into(),
node_type: TaxiNodeType::Current,
pos: (0.43, 0.33),
cost: 0,
routes: vec![],
}],
}));
seat_flight_master(&mut s, "Dungar Longdrink");
s.fire_event(
"TAXIMAP_OPENED",
vec![ScriptValue::Str("Dungar Longdrink".into())],
);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
// OnShow ran DrawOneHopLines, which found zero single-hop nodes: the red error line posted
// and the window hid itself again — the reference's own reaction to a dead-end flight point.
assert!(!s.eval::<bool>("return TaxiFrame:IsVisible()").unwrap());
s.resolve();
let quads = s.extract();
let has_refusal = quads.iter().any(|q| match &q.content {
benilla_ui::script::QuadContent::Text { text: Some(t), .. } => {
t.contains("don\u{2019}t know any flight locations")
}
_ => false,
});
assert!(
has_refusal,
"ERR_TAXINOPATHS posted to UIErrorsFrame: {quads:?}"
);
}
/// The close button's own click path: `CloseTaxiMap()` queues the intent this engine drains, and
/// the window hides immediately (no one-frame lag) — the Gossip/Trainer close-button precedent.
#[test]
fn close_button_queues_the_intent_and_hides() {
let mut s = taxi_script();
s.set_taxi(Some(menu()));
seat_flight_master(&mut s, "Dungar Longdrink");
s.fire_event(
"TAXIMAP_OPENED",
vec![ScriptValue::Str("Dungar Longdrink".into())],
);
assert!(s.eval::<bool>("return TaxiFrame:IsVisible()").unwrap());
// The reference's close button is a plain `UIPanelCloseButton` (TaxiFrame.xml:133) — it has no
// handler of its own, so the click goes through the template's, which hides the parent panel.
// Ours carried a named `BenillaTaxiCloseButton_OnClick`; that went with the file.
s.run("TaxiCloseButton:Click()").unwrap();
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
assert!(s.take_taxi_close());
assert!(!s.eval::<bool>("return TaxiFrame:IsVisible()").unwrap());
}