the quest windows go stock, and the 20 ms frames were mis-split pairs

- the quest giver's dialog and the quest log are the reference's own `QuestFrame.xml` and `QuestLogFrame.xml`, run off the player's chain at the reference's own toc seats; our 1366-line and 1999-line copies are deleted, and the verbs the stock files call are built — the four `GameTooltip` quest-item and reward-spell setters, the reward spell itself (parsed on all three packets and dropped by the model until now), and two verbs; the detail pane could never scroll because `UpdateScrollChildRect` measured a frame late where the reference's measure is synchronous, so it measures through the installed font engine now; and ten things the reference does that ours did not, each found by a test that had enshrined ours — a shift-click never selects, an empty log hides the whole detail pane and says "No Active Quests", the abandon confirm lives in the popup registry (1944)
- the quest bindings' law, read off the binary and folded back: no 1.12 packet carries a quest background material — `GetQuestBackgroundMaterial` (`0x502230`) forks on the dialog's SOURCE object, an item answering its template's PageMaterial, a GameObject its template data, a creature nil (FrameXML's "Parchment"), the id naming a `PageTextMaterial.dbc` row whose six words are nowhere in the binary; its twin `ItemTextGetMaterial` has the same body, so the reader window already held every piece and the giver's feed runs the same resolve; `QuestChooseRewardError` is game-error row `0x98`, and the row plays `igQuestFailed` first, which the verb now queues; the reward-spell getters' third return is `Spell.dbc` Attributes bit 5 (1161 of 22357 rows set — craft-cast spells set, `Pattern:` teaching spells clear), read off the catalog now (1946)
- the "20 ms frames" of a raid, chased to their number: the probe's worst frames now carry their churn (entities, assets, first-play sound decodes, live particles, the GPU reading) and in a Blackrock raid with sound on every one of those read flat; then the same window on the PACED clock — under vsync with pipelined rendering the raw delta is mis-split into long/short pairs that sum to exact refresh multiples, so an alone Stormwind leg read 115 of 300 frames over budget with cpu and gpu both at their means, while the paced clock read 0 skipped frames in every leg; what remains real in the raid is `cpu_p99` at 22–23 ms with sound on; and every submesh the streamer spawns now carries why it is an entity, counted per reason — 5.8k resident ADT-doodad submeshes at the Stormwind auction house, 5.3k of them hidden, three prop families the retained pass declines by rule, each riding every per-`Mesh3d` sweep for ~0.4 ms of the alone frame (1947)
- a raid's dozen voices cost the mixer ~1.5 ms of every 60 Hz frame: upstream kira spatializes every frame — normalizes, the attenuation curve and a dB→amplitude `powf` per frame per spatial track, two more `powf`s for the volume tween, ~1.7 M `powf`s a second at 48 kHz × 12 tracks for gains that change imperceptibly across a chunk — so kira 0.12.1 is vendored with one patch: the spatial gains and the volume × fade amplitude are evaluated at the chunk's two ends and interpolated across its ~10.7 ms, the granularity FMOD 3, the reference's own mixer, updates them at; nothing a listener can name changes (1945); and rig poses are evaluated in parallel over rigs — each reads its shared clips and writes only its own pose, and a raid's ~400 live rigs were 0.4 ms of one thread walked serially on the main thread's critical path (1947)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-09-04 20:55:12 +07:00
co-authored by Claude Fable 5.1
parent 2f7cfcabf5
commit fd595b7119
177 changed files with 18780 additions and 3597 deletions
@@ -77,7 +77,7 @@ fn shipped_xml() -> Vec<(String, String)> {
})
.collect();
out.sort();
assert!(out.len() >= 40, "only {} xml files swept", out.len());
assert!(out.len() >= 38, "only {} xml files swept", out.len());
out
}
@@ -389,6 +389,7 @@ fn the_quest_tracker_stacks_below_the_durability_guy() {
for f in [
"Interface\\FrameXML\\Fonts.xml",
"MoneyFrame.xml",
"Interface\\FrameXML\\GlobalStrings.lua",
"UiPanels.xml",
r"Interface\FrameXML\UIPanelTemplates.lua",
r"Interface\FrameXML\UIPanelTemplates.xml",
@@ -397,9 +398,11 @@ fn the_quest_tracker_stacks_below_the_durability_guy() {
"MinimapCluster.xml",
"ScrollTemplates.xml",
"Interface\\FrameXML\\DurabilityFrame.xml",
"Interface\\FrameXML\\GlobalStrings.lua",
"QuestFrame.xml",
"QuestLogFrame.xml",
"Interface\\FrameXML\\BasicControls.xml",
"Interface\\FrameXML\\ItemButtonTemplate.xml",
"Interface\\FrameXML\\QuestFrame.xml",
"MicroMenu.xml",
"Interface\\FrameXML\\QuestLogFrame.xml",
] {
load_xml(&s, f);
}
@@ -111,6 +111,7 @@ fn an_error_toast_draws_over_an_open_panel_window() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -122,9 +123,12 @@ fn an_error_toast_draws_over_an_open_panel_window() {
// 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, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
// A left-slot panel open, and the toast raised after it — the order that must not decide.
s.eval::<()>("ShowUIPanel(QuestLogFrame)").unwrap();
@@ -17,6 +17,7 @@ fn setup() -> UiScript {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
@@ -44,7 +45,6 @@ fn setup() -> UiScript {
// drags in the whole unit-frame cluster — and it must be the REAL frames: loading only
// `PartyFrame.lua` for the two names gets "attempt to index a nil value" the first time one
// iterates. The manifest already seats these far above RaidFrame (1874).
load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua");
// The reference's own `BasicControls.xml`, which is manifest entry 3 — far above the pane
// (1874). It is here for the error/message pair and `TEXT`. NOTE: unlike `raid_tests`, this
// harness does not load `UIParent.xml`, so `UIParentLoadAddOn` — which 1881 moved there out
@@ -206,6 +206,7 @@ fn setup() -> UiScript {
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
@@ -227,7 +228,6 @@ fn setup() -> UiScript {
// drags in the whole unit-frame cluster — and it must be the REAL frames: loading only
// `PartyFrame.lua` for the two names gets "attempt to index a nil value" the first time one
// iterates. The manifest already seats these far above RaidFrame (1874).
load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua");
load_xml(&s, r"Interface\FrameXML\TextStatusBar.lua");
load_xml(&s, r"Interface\FrameXML\TextStatusBar.xml");
load_xml(&s, r"Interface\FrameXML\BuffFrame.xml");
@@ -30,6 +30,7 @@ fn setup() -> UiScript {
// row, whose OnLoad calls `SmallMoneyFrame_OnLoad` — the TOC's own order (1580's
// talent-wipe fixture hit this first).
"MoneyFrame.xml",
"Interface\\FrameXML\\GlobalStrings.lua",
"UiPanels.xml",
// The stock file's `HelpFrame_OnShow` calls `UpdateMicroButtons()` before
// `GetGMStatus()`, so without the micro row the OnShow raises and the status ask never
@@ -39,7 +40,6 @@ fn setup() -> UiScript {
"Interface\\FrameXML\\TextStatusBar.lua",
"Interface\\FrameXML\\TextStatusBar.xml",
"UIParent.xml",
"Interface\\FrameXML\\GlobalStrings.lua",
"Interface\\FrameXML\\MainMenuBar.xml",
"Interface\\FrameXML\\ActionBarFrame.xml",
"Interface\\FrameXML\\BonusActionBarFrame.xml",
@@ -718,6 +718,7 @@ fn interface_harness() -> UiScript {
&[
"Interface\\FrameXML\\Fonts.xml",
"MoneyFrame.xml",
"Interface\\FrameXML\\GlobalStrings.lua",
"UiPanels.xml",
r"Interface\FrameXML\UIPanelTemplates.lua",
r"Interface\FrameXML\UIPanelTemplates.xml",
@@ -750,7 +751,6 @@ fn interface_harness() -> UiScript {
"Interface\\FrameXML\\ColorPickerFrame.xml",
"Cooldown.xml",
"Interface\\FrameXML\\ActionButtonTemplate.xml",
"Interface\\FrameXML\\GlobalStrings.lua",
"Interface\\FrameXML\\MainMenuBar.xml",
"Interface\\FrameXML\\ActionBarFrame.xml",
"Interface\\FrameXML\\BonusActionBarFrame.xml",
@@ -765,8 +765,11 @@ fn interface_harness() -> UiScript {
"ScrollTemplates.xml",
"Interface\\FrameXML\\MerchantFrame.xml",
"Interface\\FrameXML\\GlobalStrings.lua",
"QuestFrame.xml",
"QuestLogFrame.xml",
"Interface\\FrameXML\\BasicControls.xml",
"Interface\\FrameXML\\ItemButtonTemplate.xml",
"Interface\\FrameXML\\QuestFrame.xml",
"MicroMenu.xml",
"Interface\\FrameXML\\QuestLogFrame.xml",
],
);
harness_on(s)
@@ -12,6 +12,7 @@ use super::test_ui::load_ui as load_xml;
fn load_pet_bar(s: &UiScript) {
for file in [
"MoneyFrame.xml",
"Interface\\FrameXML\\GlobalStrings.lua",
"UiPanels.xml",
"Interface\\FrameXML\\Fonts.xml",
r"Interface\FrameXML\UIPanelTemplates.lua",
@@ -21,7 +22,6 @@ fn load_pet_bar(s: &UiScript) {
"Interface\\FrameXML\\ActionButtonTemplate.xml",
"Interface\\FrameXML\\TextStatusBar.lua",
"Interface\\FrameXML\\TextStatusBar.xml",
"Interface\\FrameXML\\GlobalStrings.lua",
"Interface\\FrameXML\\MainMenuBar.xml",
"GameTooltip.xml",
"Interface\\FrameXML\\ActionBarFrame.xml",
@@ -369,6 +369,7 @@ fn pet_bar_row(with_multibar: bool) -> (usize, f32) {
s.set_screen_size(1024.0, 768.0);
for file in [
"MoneyFrame.xml",
"Interface\\FrameXML\\GlobalStrings.lua",
"UiPanels.xml",
"Interface\\FrameXML\\Fonts.xml",
r"Interface\FrameXML\UIPanelTemplates.lua",
@@ -378,7 +379,6 @@ fn pet_bar_row(with_multibar: bool) -> (usize, f32) {
"Interface\\FrameXML\\ActionButtonTemplate.xml",
"Interface\\FrameXML\\TextStatusBar.lua",
"Interface\\FrameXML\\TextStatusBar.xml",
"Interface\\FrameXML\\GlobalStrings.lua",
"Interface\\FrameXML\\MainMenuBar.xml",
"GameTooltip.xml",
"Interface\\FrameXML\\ActionBarFrame.xml",
@@ -677,6 +677,7 @@ fn load_pet_bar_with_tooltip(s: &UiScript) {
for file in [
"Interface\\FrameXML\\Fonts.xml",
"MoneyFrame.xml",
"Interface\\FrameXML\\GlobalStrings.lua",
"UiPanels.xml",
r"Interface\FrameXML\UIPanelTemplates.lua",
r"Interface\FrameXML\UIPanelTemplates.xml",
@@ -686,7 +687,6 @@ fn load_pet_bar_with_tooltip(s: &UiScript) {
"Interface\\FrameXML\\ActionButtonTemplate.xml",
"Interface\\FrameXML\\TextStatusBar.lua",
"Interface\\FrameXML\\TextStatusBar.xml",
"Interface\\FrameXML\\GlobalStrings.lua",
"Interface\\FrameXML\\MainMenuBar.xml",
"Interface\\FrameXML\\ActionBarFrame.xml",
"Interface\\FrameXML\\BonusActionBarFrame.xml",
@@ -102,7 +102,10 @@ fn setup() -> UiScript {
// 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, "QuestFrame.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.
+156 -56
View File
@@ -19,6 +19,7 @@ fn questgiver_show_hide_plays_open_and_close_kits() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -27,8 +28,11 @@ fn questgiver_show_hide_plays_open_and_close_kits() {
// MerchantFrame.xml (the same documented cross-window dep the bag tests load).
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml");
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.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");
// Hidden at load: no open sound (never transitions on startup).
assert!(
@@ -82,14 +86,18 @@ fn panel_events_show_exactly_one_child_panel_and_hide_the_others() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml");
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.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");
let panels = [
"QuestFrameGreetingPanel",
@@ -106,10 +114,15 @@ fn panel_events_show_exactly_one_child_panel_and_hide_the_others() {
active_titles: vec!["Report to Goldshire".into()],
..QuestState::default()
}));
s.fire_event(
"QUEST_GREETING",
vec![ScriptValue::Str("Deputy Willem".into())],
s.set_unit(
"npc",
Some(benilla_ui::script::UnitState {
exists: true,
name: Some("Deputy Willem".into()),
..Default::default()
}),
);
s.fire_event("QUEST_GREETING", vec![]);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
for name in panels {
assert_eq!(
@@ -132,10 +145,15 @@ fn panel_events_show_exactly_one_child_panel_and_hide_the_others() {
title: "A Threat Within".into(),
..QuestState::default()
}));
s.fire_event(
"QUEST_DETAIL",
vec![ScriptValue::Str("Deputy Willem".into())],
s.set_unit(
"npc",
Some(benilla_ui::script::UnitState {
exists: true,
name: Some("Deputy Willem".into()),
..Default::default()
}),
);
s.fire_event("QUEST_DETAIL", vec![]);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
for name in panels {
assert_eq!(
@@ -163,14 +181,18 @@ fn detail_panel_reward_grid_follows_the_refs_two_per_row_layout() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml");
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.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");
let choice = |name: &str, quality: u32| benilla_ui::script::QuestItemView {
item_id: 0,
@@ -191,10 +213,15 @@ fn detail_panel_reward_grid_follows_the_refs_two_per_row_layout() {
reward_money: 150,
..QuestState::default()
}));
s.fire_event(
"QUEST_DETAIL",
vec![ScriptValue::Str("Marshal McBride".into())],
s.set_unit(
"npc",
Some(benilla_ui::script::UnitState {
exists: true,
name: Some("Marshal McBride".into()),
..Default::default()
}),
);
s.fire_event("QUEST_DETAIL", vec![]);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
assert!(s.eval::<bool>("return QuestDetailItem1:IsShown()").unwrap());
@@ -208,7 +235,7 @@ fn detail_panel_reward_grid_follows_the_refs_two_per_row_layout() {
assert_eq!(
s.eval::<String>("return QuestDetailItemChooseText:GetText()")
.unwrap(),
"You may choose one of these rewards:"
"You will be able to choose one of these rewards:"
);
assert_eq!(
s.eval::<String>("return QuestDetailItemReceiveText:GetText()")
@@ -235,8 +262,11 @@ fn detail_panel_reward_grid_follows_the_refs_two_per_row_layout() {
// arm at all: an unmodified click must not raise and must not set an itemChoice.
s.run("QuestDetailItem1:Click()").ok();
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
// The reward panel's `itemChoice` is set only by its own OnShow (stock QuestFrame.lua:104,
// `= 0`) and a reward-row click; on the detail panel it is simply unset.
assert_eq!(
s.eval::<i64>("return QuestFrame.itemChoice").unwrap(),
s.eval::<i64>("return QuestFrameRewardPanel.itemChoice or 0")
.unwrap(),
0,
"a detail-panel row never selects a reward"
);
@@ -250,14 +280,18 @@ fn reward_panel_choice_click_selects_and_completes_with_zero_based_index() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml");
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.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");
let choice = |name: &str| benilla_ui::script::QuestItemView {
item_id: 0,
@@ -275,10 +309,15 @@ fn reward_panel_choice_click_selects_and_completes_with_zero_based_index() {
choices: vec![choice("Worn Sword"), choice("Worn Mace")],
..QuestState::default()
}));
s.fire_event(
"QUEST_COMPLETE",
vec![ScriptValue::Str("Marshal McBride".into())],
s.set_unit(
"npc",
Some(benilla_ui::script::UnitState {
exists: true,
name: Some("Marshal McBride".into()),
..Default::default()
}),
);
s.fire_event("QUEST_COMPLETE", vec![]);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
assert!(
@@ -317,24 +356,33 @@ fn greeting_goodbye_button_closes_the_window() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml");
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.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");
s.set_quest(Some(QuestState {
panel: QuestPanel::Greeting,
greeting: "Hello".into(),
..QuestState::default()
}));
s.fire_event(
"QUEST_GREETING",
vec![ScriptValue::Str("Deputy Willem".into())],
s.set_unit(
"npc",
Some(benilla_ui::script::UnitState {
exists: true,
name: Some("Deputy Willem".into()),
..Default::default()
}),
);
s.fire_event("QUEST_GREETING", vec![]);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
assert!(s.eval::<bool>("return QuestFrame:IsVisible()").unwrap());
@@ -362,14 +410,18 @@ fn detail_panel_action_buttons_resolve_to_real_onscreen_rects() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml");
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.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");
// Planted, not shipped: instant text is OFF out of the box (see the doc comment).
s.eval::<()>(r#"QUEST_FADING_DISABLE = "1""#).unwrap();
s.set_quest(Some(QuestState {
@@ -378,10 +430,15 @@ fn detail_panel_action_buttons_resolve_to_real_onscreen_rects() {
body: "Kill kobolds.".into(),
..QuestState::default()
}));
s.fire_event(
"QUEST_DETAIL",
vec![ScriptValue::Str("Deputy Willem".into())],
s.set_unit(
"npc",
Some(benilla_ui::script::UnitState {
exists: true,
name: Some("Deputy Willem".into()),
..Default::default()
}),
);
s.fire_event("QUEST_DETAIL", vec![]);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
assert!(s
.eval::<bool>("return QuestFrameAcceptButton:IsShown()")
@@ -478,14 +535,18 @@ fn write_on_still_fades_when_instant_text_is_off() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml");
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.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");
s.eval::<()>(r#"QUEST_FADING_DISABLE = "0""#).unwrap();
s.set_quest(Some(QuestState {
panel: QuestPanel::Detail,
@@ -493,10 +554,15 @@ fn write_on_still_fades_when_instant_text_is_off() {
body: "Kill kobolds.".into(),
..QuestState::default()
}));
s.fire_event(
"QUEST_DETAIL",
vec![ScriptValue::Str("Deputy Willem".into())],
s.set_unit(
"npc",
Some(benilla_ui::script::UnitState {
exists: true,
name: Some("Deputy Willem".into()),
..Default::default()
}),
);
s.fire_event("QUEST_DETAIL", vec![]);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
// Mid-write (reveal edge at char 4 of 13): the quill scratches, Accept stays dead.
@@ -543,14 +609,18 @@ fn npc_name_reaches_the_title_bar_on_open_and_on_refresh() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml");
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.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");
s.set_quest(Some(QuestState {
panel: QuestPanel::Detail,
@@ -558,10 +628,15 @@ fn npc_name_reaches_the_title_bar_on_open_and_on_refresh() {
body: "Kill things.".into(),
..QuestState::default()
}));
s.fire_event(
"QUEST_DETAIL",
vec![ScriptValue::Str("Marshal McBride".into())],
s.set_unit(
"npc",
Some(benilla_ui::script::UnitState {
exists: true,
name: Some("Marshal McBride".into()),
..Default::default()
}),
);
s.fire_event("QUEST_DETAIL", vec![]);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
assert_eq!(
s.eval::<String>("return getglobal('QuestFrameNpcNameText'):GetText() or ''")
@@ -608,16 +683,24 @@ fn npc_name_reaches_the_title_bar_on_open_and_on_refresh() {
fn greeting_panel_title_rows_grow_to_their_wrapped_titles() {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
// Stock sizes each row at show time — `SetHeight(GetTextHeight() + 2)` right after SetText
// (QuestFrame.lua:245) — which the reference can do because its text measure is synchronous.
// So is the app's (measure.rs); the harness installs the same seam's stand-in.
s.set_text_measurer(Box::new(super::FixedWidthFont(7.0)));
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml");
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.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");
s.set_quest(Some(QuestState {
panel: QuestPanel::Greeting,
@@ -629,10 +712,15 @@ fn greeting_panel_title_rows_grow_to_their_wrapped_titles() {
],
..QuestState::default()
}));
s.fire_event(
"QUEST_GREETING",
vec![ScriptValue::Str("Deputy Willem".into())],
s.set_unit(
"npc",
Some(benilla_ui::script::UnitState {
exists: true,
name: Some("Deputy Willem".into()),
..Default::default()
}),
);
s.fire_event("QUEST_GREETING", vec![]);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
let answer_measures = |s: &mut UiScript| {
@@ -665,7 +753,7 @@ fn greeting_panel_title_rows_grow_to_their_wrapped_titles() {
};
let (t1, b1, h1) = row(1);
let (t2, b2, h2) = row(2);
assert!(h1 >= 28.0 && h2 >= 28.0, "both titles wrap: {h1}, {h2}");
assert!(h1 >= 24.0 && h2 >= 24.0, "both titles wrap: {h1}, {h2}");
assert!(
(t1 - b1 - (h1 + 2.0)).abs() < 0.5,
"row 1 is its wrapped title + 2: got {}, title {h1}",
@@ -698,14 +786,18 @@ fn reward_rows_preview_and_post_without_selecting_the_choice() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml");
load_xml(&s, "ScrollTemplates.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.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");
load_xml(&s, "UIParent.xml"); // BenillaChatEdit_InsertLink lives here
load_xml(&s, "DressUpFrame.xml"); // DressUpItemLink lives here
load_xml(&s, "Interface\\FrameXML\\UIMenu.xml"); // the kit its menus build from
@@ -737,10 +829,15 @@ fn reward_rows_preview_and_post_without_selecting_the_choice() {
)],
..QuestState::default()
}));
s.fire_event(
"QUEST_COMPLETE",
vec![ScriptValue::Str("Marshal McBride".into())],
s.set_unit(
"npc",
Some(benilla_ui::script::UnitState {
exists: true,
name: Some("Marshal McBride".into()),
..Default::default()
}),
);
s.fire_event("QUEST_COMPLETE", vec![]);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
// The regression this fork could break: a PLAIN click still picks the choice (the ref's third
@@ -748,7 +845,8 @@ fn reward_rows_preview_and_post_without_selecting_the_choice() {
// move it if they leaked into the select arm.
s.run("QuestRewardItem2:Click()").unwrap();
assert_eq!(
s.eval::<i64>("return QuestFrame.itemChoice").unwrap(),
s.eval::<i64>("return QuestFrameRewardPanel.itemChoice")
.unwrap(),
2,
"a plain click still selects the reward choice"
);
@@ -772,7 +870,8 @@ fn reward_rows_preview_and_post_without_selecting_the_choice() {
"shift-click posted choice 1's link"
);
assert_eq!(
s.eval::<i64>("return QuestFrame.itemChoice").unwrap(),
s.eval::<i64>("return QuestFrameRewardPanel.itemChoice")
.unwrap(),
2,
"the shift arm returns — it must NOT also select the clicked choice"
);
@@ -803,7 +902,8 @@ fn reward_rows_preview_and_post_without_selecting_the_choice() {
"ctrl-click opened the room wearing choice 1"
);
assert_eq!(
s.eval::<i64>("return QuestFrame.itemChoice").unwrap(),
s.eval::<i64>("return QuestFrameRewardPanel.itemChoice")
.unwrap(),
2,
"the ctrl arm returns — it must NOT also select the clicked choice"
);
@@ -37,8 +37,11 @@ fn harness() -> UiScript {
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
load_xml(&s, "ScrollTemplates.xml");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\QuestTimerFrame.xml");
s
}
@@ -71,6 +71,7 @@ fn eight_entries() -> QuestLogState {
// lands, so the fixture carries that exact shape.
link: Some(HAMMER_LINK.into()),
}],
reward_spell: None,
}),
}
}
@@ -85,6 +86,7 @@ fn shipped_questlog_frame_loads_clean() {
let s = UiScript::new().unwrap();
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -95,9 +97,12 @@ fn shipped_questlog_frame_loads_clean() {
// 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, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
}
/// The whole contract in one end-to-end drive: open plays the kit and renders row 1 + the count +
@@ -111,6 +116,7 @@ fn shipped_questlog_frame_drives_end_to_end() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -121,9 +127,12 @@ fn shipped_questlog_frame_drives_end_to_end() {
// 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, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
s.set_quest_log(eight_entries());
@@ -147,7 +156,7 @@ fn shipped_questlog_frame_drives_end_to_end() {
// Row 1 carries entry 1's title (indented), the count line reads "Quests: |cffffffff8/20|r", and nothing having been
// selected, the first non-header entry auto-selects (pin §2's SetFirstValidSelection).
assert!(s
.eval::<String>("return QuestLogTitle1Text:GetText()")
.eval::<String>("return QuestLogTitle1NormalText:GetText()")
.unwrap()
.contains("Quest 1"));
assert_eq!(
@@ -194,10 +203,12 @@ fn shipped_questlog_frame_drives_end_to_end() {
.unwrap(),
"You will receive:"
);
assert_eq!(
s.eval::<String>("return QuestLogItemChooseText:GetText()")
// …hidden, not blanked: stock QuestFrameItems_Update Show()s/Hide()s the choose text
// (QuestFrame.lua:454-473) and leaves its string alone.
assert!(
!s.eval::<bool>("return QuestLogItemChooseText:IsShown()")
.unwrap(),
""
"no choices: the choose text is hidden"
);
// 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".)
@@ -220,7 +231,7 @@ fn shipped_questlog_frame_drives_end_to_end() {
})
.expect("a 'Quest 3' row text quad");
let before = s
.eval::<String>("return QuestLogTitle1Text:GetText()")
.eval::<String>("return QuestLogTitle1NormalText:GetText()")
.unwrap();
s.mouse_wheel(wx, wy, -1.0);
assert!(s.errors().is_empty(), "wheel errors: {:?}", s.errors());
@@ -230,7 +241,7 @@ fn shipped_questlog_frame_drives_end_to_end() {
// The migration reverts our step; how far a notch should travel is a look call, recorded in
// 1846 rather than re-authored back in.
assert_ne!(
s.eval::<String>("return QuestLogTitle1Text:GetText()")
s.eval::<String>("return QuestLogTitle1NormalText:GetText()")
.unwrap(),
before,
"wheel-down scrolled the list"
@@ -240,7 +251,7 @@ fn shipped_questlog_frame_drives_end_to_end() {
// Abandon, No path: marks the selection, shows the registry's ABANDON_QUEST entry on the
// shared StaticPopup engine (decision 0308 §3), but No drains nothing and hides it.
s.run("BenillaQuestLogAbandonButton_OnClick()").unwrap();
s.run("QuestLogFrameAbandonButton:Click()").unwrap();
assert!(s.eval::<bool>("return StaticPopup1:IsShown()").unwrap());
assert_eq!(
s.eval::<String>("return GetAbandonQuestName()").unwrap(),
@@ -269,7 +280,7 @@ fn shipped_questlog_frame_drives_end_to_end() {
);
// Abandon, Yes path: the pinned index (1, the selection at click time) drains, plus the kit.
s.run("BenillaQuestLogAbandonButton_OnClick()").unwrap();
s.run("QuestLogFrameAbandonButton:Click()").unwrap();
s.run("StaticPopup_OnClick(StaticPopup1, 1)").unwrap();
assert_eq!(s.take_quest_log_abandons(), vec![1]);
// Show → igMainMenuOpen; the ref OnClick runs OnAccept FIRST (the abandon kit), THEN hides
@@ -306,8 +317,12 @@ fn shift_click_toggles_the_watch_checkbox_and_the_tracker_hud() {
let _data = benilla_formats::wow_data_or_skip!();
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
// Stock seats the watch check at update time off `questNormalText:GetWidth()` (QuestLogFrame.lua:205-215),
// synchronously — the app's measure seam is synchronous too (measure.rs); its stand-in here.
s.set_text_measurer(Box::new(super::FixedWidthFont(6.0)));
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -318,11 +333,23 @@ fn shift_click_toggles_the_watch_checkbox_and_the_tracker_hud() {
// 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, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
// A shift-click reaches stock QuestLogTitleButton_OnClick's unguarded `ChatFrameEditBox:IsVisible()`
// (QuestLogFrame.lua:478) — the chat window is on the manifest long before the log (1944).
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
load_xml(&s, "Interface\\FrameXML\\UIMenu.xml");
load_xml(&s, "ChatFrame.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
s.set_quest_log(eight_entries());
// The app fires QUEST_LOG_UPDATE when the log lands; stock QuestLog_OnEvent's arm runs the
// first QuestWatch_Update, which is what hides an empty tracker.
s.fire_event("QUEST_LOG_UPDATE", vec![]);
s.run("ToggleQuestLog()").unwrap();
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
@@ -403,9 +430,12 @@ fn shift_click_toggles_the_watch_checkbox_and_the_tracker_hud() {
.unwrap(),
" - Kobold Vermin slain: 3/10"
);
// A manual watch is permanent: no auto-watch timer entry rides it.
// A manual watch is permanent: stock's shift-click inserts it with QUEST_WATCH_NO_EXPIRE
// (QuestLogFrame.lua:496), the timer that AutoQuestWatch_OnUpdate never counts down.
assert!(s
.eval::<bool>("return BENILLA_QUEST_WATCH_TIMERS[\"Quest 1\"] == nil")
.eval::<bool>(
"return QUEST_WATCH_LIST[1] ~= nil and QUEST_WATCH_LIST[1].timer == QUEST_WATCH_NO_EXPIRE"
)
.unwrap());
// Shift-click again unwatches: the checkbox clears and — nothing left watched — the whole HUD
@@ -433,6 +463,7 @@ fn watch_guards_no_op_without_erroring() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -444,9 +475,18 @@ fn watch_guards_no_op_without_erroring() {
// 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, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
// A shift-click reaches stock QuestLogTitleButton_OnClick's unguarded `ChatFrameEditBox:IsVisible()`
// (QuestLogFrame.lua:478) — the chat window is on the manifest long before the log (1944).
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
load_xml(&s, "Interface\\FrameXML\\UIMenu.xml");
load_xml(&s, "ChatFrame.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
s.set_quest_log(eight_entries());
s.run("ToggleQuestLog()").unwrap();
@@ -482,8 +522,9 @@ fn watch_guards_no_op_without_erroring() {
);
assert_eq!(
s.eval::<i64>("return GetQuestLogSelection()").unwrap(),
2,
"the click still selects"
1,
"a shift-click never selects — stock QuestLogTitleButton_OnClick's shift branch returns \
before the plain-click arm's QuestLog_SetSelection (QuestLogFrame.lua:472-500)"
);
// Fill the watch list to the cap (5) with quests 2-6 via the Lua API directly — the engine-level
@@ -532,6 +573,7 @@ fn progress_auto_watches_for_five_minutes() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -542,14 +584,22 @@ fn progress_auto_watches_for_five_minutes() {
// 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, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "UIParent.xml");
// AUTO_QUEST_WATCH is the options window's (UIOptionsFrame_Init in the reference; 1944), so the
// auto-watch needs that window loaded, as the manifest has it long before the log.
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
load_xml(&s, "KeyBindingsPage.xml");
load_xml(&s, "OptionsFrame.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
s.set_quest_log(eight_entries());
// A kill credit lands: the engine fires the event with the quest's 1-based log index.
s.fire_event(
"BENILLA_QUEST_PROGRESS",
"QUEST_WATCH_UPDATE",
vec![benilla_ui::script::ScriptValue::Int(1)],
);
assert!(s.errors().is_empty(), "auto-watch errors: {:?}", s.errors());
@@ -557,15 +607,20 @@ fn progress_auto_watches_for_five_minutes() {
assert!(s
.eval::<bool>("return QuestWatchFrame:IsVisible()")
.unwrap());
assert!(s
.eval::<bool>("return BENILLA_QUEST_WATCH_TIMERS[\"Quest 1\"] ~= nil")
.unwrap());
assert!(
s.eval::<bool>(
"return QUEST_WATCH_LIST[1] ~= nil and QUEST_WATCH_LIST[1].index == 1 \
and QUEST_WATCH_LIST[1].timer == MAX_QUEST_WATCH_TIMER"
)
.unwrap(),
"the reference's QUEST_WATCH_LIST carries the armed timer (QuestLogFrame.lua:752-769)"
);
// 299 s in it still holds; fresh progress re-arms the timer; expiry unwatches and hides.
s.tick(299.0);
assert!(s.eval::<bool>("return IsQuestWatched(1)").unwrap());
s.fire_event(
"BENILLA_QUEST_PROGRESS",
"QUEST_WATCH_UPDATE",
vec![benilla_ui::script::ScriptValue::Int(1)],
);
s.tick(299.0);
@@ -599,25 +654,35 @@ fn the_auto_watch_flag_is_the_references_uvar_and_gates_the_watch() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
// The flag is declared by the options window's init since 1944, as the reference declares it
// in UIOptionsFrame_Init — so the window that owns it loads first, as in the manifest.
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
load_xml(&s, "ScrollTemplates.xml");
load_xml(&s, "KeyBindingsPage.xml");
load_xml(&s, "OptionsFrame.xml");
load_xml(&s, "Interface\\FrameXML\\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, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
s.set_quest_log(eight_entries());
assert_eq!(s.eval::<String>("return AUTO_QUEST_WATCH").unwrap(), "1");
s.run(r#"AUTO_QUEST_WATCH = "0""#).unwrap();
s.fire_event(
"BENILLA_QUEST_PROGRESS",
"QUEST_WATCH_UPDATE",
vec![benilla_ui::script::ScriptValue::Int(1)],
);
assert!(
@@ -627,7 +692,7 @@ fn the_auto_watch_flag_is_the_references_uvar_and_gates_the_watch() {
s.run(r#"AUTO_QUEST_WATCH = "1""#).unwrap();
s.fire_event(
"BENILLA_QUEST_PROGRESS",
"QUEST_WATCH_UPDATE",
vec![benilla_ui::script::ScriptValue::Int(1)],
);
assert!(s.eval::<bool>("return IsQuestWatched(1)").unwrap());
@@ -658,8 +723,12 @@ fn the_row_tag_is_its_own_right_flush_string_and_the_state_word_wins() {
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml");
load_xml(&s, "ScrollTemplates.xml");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
let mut state = eight_entries();
// Row 1: a plain elite quest. Row 2: an elite quest that is COMPLETE — the state word wins.
@@ -695,11 +764,11 @@ fn the_row_tag_is_its_own_right_flush_string_and_the_state_word_wins() {
// The title is the bare (indented) name — the state word lives on the tag string now, so
// appending it here too would double it.
let title1 = s
.eval::<String>("return QuestLogTitle1Text:GetText()")
.eval::<String>("return QuestLogTitle1NormalText:GetText()")
.unwrap();
assert_eq!(title1, " Quest 1");
let title2 = s
.eval::<String>("return QuestLogTitle2Text:GetText()")
.eval::<String>("return QuestLogTitle2NormalText:GetText()")
.unwrap();
assert_eq!(title2, " Quest 2", "no \"(Complete)\" on the title");
@@ -722,6 +791,7 @@ fn empty_quest_log_hides_rows_and_disables_abandon() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -732,9 +802,12 @@ fn empty_quest_log_hides_rows_and_disables_abandon() {
// 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, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
s.set_quest_log(QuestLogState::default());
s.run("ToggleQuestLog()").unwrap();
@@ -742,22 +815,24 @@ 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.
// The empty log says the reference's QUESTLOG_NO_QUESTS_TEXT (GlobalStrings.lua:3225) through
// stock QuestLogNoQuestsText; "Your quest log is empty." was ours (1944).
assert_eq!(
s.eval::<String>("return QuestLogNoQuestsText:GetText()")
.unwrap(),
"Your quest log is empty."
"No Active Quests"
);
assert!(!s.eval::<bool>("return QuestLogTitle1:IsVisible()").unwrap());
assert!(!s
.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
// selection (this task's fix; QuestLogFrame.xml's header comment on the FontString).
assert_eq!(
s.eval::<String>("return QuestLogDescriptionTitle:GetText()")
// With no selection the reference hides the whole detail pane (stock
// `QuestLog_UpdateQuestDetails`: `QuestLogDetailScrollFrame:Hide()`), header included — it
// keeps its static `text="QUEST_DESCRIPTION"`; ours used to blank the header instead (1944).
assert!(
!s.eval::<bool>("return QuestLogDetailScrollFrame:IsVisible()")
.unwrap(),
""
"no selection: the detail pane is hidden, header and all"
);
assert_eq!(
s.eval::<String>("return QuestLogQuestCount:GetText()")
@@ -777,6 +852,7 @@ fn reward_rows_follow_the_refs_two_per_row_layout() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -787,9 +863,12 @@ fn reward_rows_follow_the_refs_two_per_row_layout() {
// 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, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
let mut state = eight_entries();
state.detail = Some(QuestLogDetail {
@@ -826,6 +905,7 @@ fn reward_rows_follow_the_refs_two_per_row_layout() {
usable: true,
..Default::default()
}],
reward_spell: None,
});
s.set_quest_log(state);
s.run("ToggleQuestLog()").unwrap();
@@ -850,7 +930,7 @@ fn reward_rows_follow_the_refs_two_per_row_layout() {
assert_eq!(
s.eval::<String>("return QuestLogItemChooseText:GetText()")
.unwrap(),
"You may choose one of these rewards:"
"You will be able to choose one of these rewards:"
);
// With choices present, the receive text reads "...also..." (QuestFrame.lua:461-462) — it
@@ -886,6 +966,24 @@ fn reward_rows_follow_the_refs_two_per_row_layout() {
/// ResizeChild`) well past the pane — load-bearing for the scroll/clip tests below even
/// engine-only (no font atlas, so every auto-height FontString measures 0 here — this fixture
/// overflows on the FIXED-height portions alone: 10×12px objective rows + the reward row).
/// The measure round-trip the app's resolve pass drives with its font engine: answer every
/// FontString the layout asked about with the 6px-per-glyph, 12px-per-line stand-in the
/// synchronous `FixedWidthFont(6.0)` uses, so the two agree.
fn answer_measures(s: &mut UiScript) {
let answers: Vec<(u32, f32, f32, u64)> = s
.fontstrings_needing_measure()
.into_iter()
.map(|r| {
let ink = r.text.chars().count() as f32 * 6.0;
match r.wrap_width {
Some(w) if w > 0.0 && ink > w => (r.id, w, (ink / w).ceil() * 12.0, r.key),
_ => (r.id, ink, 12.0, r.key),
}
})
.collect();
s.set_measured_text_unwrapped(&answers);
}
fn overflowing_entry() -> QuestLogState {
let objectives = (1..=10)
.map(|i| QuestLogObjectiveView {
@@ -921,6 +1019,7 @@ fn overflowing_entry() -> QuestLogState {
usable: true,
..Default::default()
}],
reward_spell: None,
}),
}
}
@@ -938,6 +1037,7 @@ fn overflowing_detail_content_clips_to_the_scrollframe_rect() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -948,9 +1048,12 @@ fn overflowing_detail_content_clips_to_the_scrollframe_rect() {
// 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, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
s.set_quest_log(overflowing_entry());
s.run("ToggleQuestLog()").unwrap();
@@ -1002,8 +1105,10 @@ fn wheel_over_the_detail_pane_changes_vertical_scroll() {
let _data = benilla_formats::wow_data_or_skip!();
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
s.set_text_measurer(Box::new(super::FixedWidthFont(6.0)));
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -1014,13 +1119,21 @@ fn wheel_over_the_detail_pane_changes_vertical_scroll() {
// 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, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
s.set_quest_log(overflowing_entry());
s.run("ToggleQuestLog()").unwrap();
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
// The detail pane's scroll range is the union of its measured content (0128): the layout's
// measure round-trip has to be answered, as the app's resolve pass answers it every frame.
s.resolve();
answer_measures(&mut s);
s.resolve(); // the measured span reaches the scroll range, and the bar, on this pass
s.resolve();
let quads = s.extract();
@@ -1053,8 +1166,10 @@ fn selection_change_resets_detail_scroll_but_a_quest_log_update_refresh_does_not
let _data = benilla_formats::wow_data_or_skip!();
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
s.set_text_measurer(Box::new(super::FixedWidthFont(6.0)));
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -1065,16 +1180,27 @@ fn selection_change_resets_detail_scroll_but_a_quest_log_update_refresh_does_not
// 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, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
s.set_quest_log(overflowing_entry());
s.run("ToggleQuestLog()").unwrap();
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
// The detail pane's scroll range is the union of its measured content (0128): the layout's
// measure round-trip has to be answered, as the app's resolve pass answers it every frame.
s.resolve();
answer_measures(&mut s);
s.resolve(); // the measured span reaches the scroll range, and the bar, on this pass
s.resolve(); // GetVerticalScrollRange (SetVerticalScroll's clamp) reads resolved rects.
s.run("QuestLogDetailScrollFrame:SetVerticalScroll(10)")
// Scroll THROUGH the bar, as the wheel and the thumb do (ScrollFrameTemplate_OnMouseWheel sets
// the bar; its OnValueChanged moves the frame): a reselect's `ScrollBar:SetValue(0)` is what
// snaps the pane back, and a bar already at 0 would fire no change over a frame-only scroll.
s.run("QuestLogDetailScrollFrameScrollBar:SetValue(10)")
.unwrap();
assert_eq!(
s.eval::<f32>("return QuestLogDetailScrollFrame:GetVerticalScroll()")
@@ -1093,8 +1219,7 @@ fn selection_change_resets_detail_scroll_but_a_quest_log_update_refresh_does_not
);
// A manual reselect (the row-click path — no doNotScroll) DOES reset it.
s.run(r#"BenillaQuestLogTitle_OnClick(QuestLogTitle1, "LeftButton")"#)
.unwrap();
s.run("QuestLogTitle1:Click()").unwrap();
assert!(s.errors().is_empty(), "click errors: {:?}", s.errors());
assert_eq!(
s.eval::<f32>("return QuestLogDetailScrollFrame:GetVerticalScroll()")
@@ -1117,6 +1242,7 @@ fn reward_row_hover_serves_the_shared_item_tooltip() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -1132,9 +1258,12 @@ fn reward_row_hover_serves_the_shared_item_tooltip() {
// 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, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
s.set_quest_log(eight_entries());
s.run("ToggleQuestLog()").unwrap();
@@ -1264,6 +1393,7 @@ fn reward_rows_preview_and_post_and_a_plain_click_stays_inert() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -1274,9 +1404,15 @@ fn reward_rows_preview_and_post_and_a_plain_click_stays_inert() {
// 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, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
// A shift-click reaches stock QuestLogTitleButton_OnClick's unguarded `ChatFrameEditBox:IsVisible()`
// (QuestLogFrame.lua:478) — the chat window is on the manifest long before the log (1944).
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
load_xml(&s, "UIParent.xml"); // BenillaChatEdit_InsertLink lives here
load_xml(&s, "DressUpFrame.xml"); // DressUpItemLink lives here
load_xml(&s, "Interface\\FrameXML\\UIMenu.xml"); // the kit its menus build from
@@ -1349,6 +1485,7 @@ fn shift_click_on_a_title_posts_the_quest_name_with_chat_open_and_watches_with_i
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -1359,9 +1496,15 @@ fn shift_click_on_a_title_posts_the_quest_name_with_chat_open_and_watches_with_i
// 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, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
// A shift-click reaches stock QuestLogTitleButton_OnClick's unguarded `ChatFrameEditBox:IsVisible()`
// (QuestLogFrame.lua:478) — the chat window is on the manifest long before the log (1944).
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
load_xml(&s, "UIParent.xml"); // BenillaChatEdit_InsertLink lives here
load_xml(&s, "Interface\\FrameXML\\UIMenu.xml"); // the kit its menus build from
load_xml(&s, "ChatFrame.xml"); // ChatFrameEditBox lives here
@@ -1445,6 +1588,7 @@ fn share_quest_needs_both_a_sharable_selection_and_a_party() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -1455,9 +1599,12 @@ fn share_quest_needs_both_a_sharable_selection_and_a_party() {
// 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, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
// Solo, quest 1 sharable and selected by the auto-selection.
s.set_quest_log(entries_sharable(&[1]));
@@ -1489,7 +1636,7 @@ fn share_quest_needs_both_a_sharable_selection_and_a_party() {
// Select an unsharable quest: party unchanged, button goes dark.
s.run("SelectQuestLogEntry(2)").unwrap();
s.run("BenillaQuestLogFrame_Update()").unwrap();
s.run("QuestLog_Update()").unwrap();
assert!(
s.eval::<bool>("return GetQuestLogPushable() == nil")
.unwrap(),
@@ -1503,7 +1650,7 @@ fn share_quest_needs_both_a_sharable_selection_and_a_party() {
// Leaving the party darkens it again from the other side.
s.run("SelectQuestLogEntry(1)").unwrap();
s.run("BenillaQuestLogFrame_Update()").unwrap();
s.run("QuestLog_Update()").unwrap();
assert!(s
.eval::<bool>("return QuestFramePushQuestButton:IsEnabled() ~= 0")
.unwrap());
@@ -1524,6 +1671,7 @@ fn share_quest_is_dark_on_an_empty_log_even_in_a_party() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -1534,9 +1682,12 @@ fn share_quest_is_dark_on_an_empty_log_even_in_a_party() {
// 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, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
s.set_quest_log(QuestLogState::default());
s.set_party(party(4));
@@ -1557,6 +1708,7 @@ fn share_quest_click_queues_the_selected_quests_id() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -1567,16 +1719,19 @@ fn share_quest_click_queues_the_selected_quests_id() {
// 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, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml");
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "MicroMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
s.set_quest_log(entries_sharable(&[1, 5]));
s.set_party(party(1));
s.run("ToggleQuestLog()").unwrap();
s.run("SelectQuestLogEntry(5)").unwrap();
s.run("BenillaQuestLogFrame_Update()").unwrap();
s.run("QuestLog_Update()").unwrap();
s.run("QuestFramePushQuestButton:Click()").unwrap();
assert_eq!(s.take_quest_log_pushes(), vec![5]);
assert!(s.take_quest_log_pushes().is_empty(), "drained");
@@ -26,6 +26,7 @@ fn setup() -> UiScript {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "GameTooltip.xml");
@@ -40,7 +41,6 @@ fn setup() -> UiScript {
// drags in the whole unit-frame cluster — and it must be the REAL frames: loading only
// `PartyFrame.lua` for the two names gets "attempt to index a nil value" the first time one
// iterates. The manifest already seats these far above RaidFrame (1874).
load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua");
// The reference's own `BasicControls.xml`, which is manifest entry 3 — far above the pane
// (1874). `UIParentLoadAddOn` used to be what this line was here for; 1881 moved that
// function to `UIParent.xml`, its home in the reference, and this harness already loads
@@ -2047,6 +2047,12 @@ mod tests {
("SHOW_COMPARE_TOOLTIP", "PaperDollFrame.lua"),
("SYSMSG", "UIErrorsFrame.lua"),
("UNIT_DEFENSE", "PetPaperDollFrame.lua"),
(
"UNIT_QUEST_LOG_CHANGED",
"QuestLogFrame.lua — a party member's quest-log fields changing (the reference \
fires it off the unit's PLAYER_QUEST_LOG_* descriptor updates); benilla's unit \
feed does not derive it yet (1944)",
),
(
"UNIT_MODEL_CHANGED",
"four files — the paperdoll model refresh",
@@ -2115,10 +2121,20 @@ mod tests {
}
let lines: Vec<&str> = text.lines().map(str::trim).collect();
for (i, t) in lines.iter().enumerate() {
// …or the value of a `match` arm (`QuestPanel::Progress =>
// "QUEST_PROGRESS",` — `ui_quest.rs`'s `panel_event`).
let (t, arm_value) = match t.find("=> \"") {
Some(k) => (t[k + 3..].trim_end_matches(','), true),
None => (*t, false),
};
let Some(lit) = t.strip_prefix('"').and_then(|x| x.strip_suffix('"'))
else {
continue;
};
if arm_value && caps(lit) {
fired.insert(lit.to_string());
continue;
}
let arm = i > 0
&& lines[i - 1].ends_with('{')
&& lines.get(i + 1).is_some_and(|n| n.starts_with('}'));
@@ -30,7 +30,7 @@ fn every_shipped_ui_xml_parses() {
// Never let the sweep pass by finding nothing — a moved assets dir would otherwise turn this
// into a test that guards zero files while staying green.
assert!(
checked >= 40,
checked >= 38, // 41 until 1944 took the two quest files stock
"only {checked} xml files swept — sweep broke"
);
}
@@ -753,7 +753,7 @@ fn no_shipped_script_sets_a_global_string_key_as_display_text() {
}
assert!(offenders.is_empty(), "{}", offenders.join("\n"));
// The sweep must never pass by finding nothing to sweep.
assert!(swept >= 40, "only {swept} xml files swept — sweep broke");
assert!(swept >= 38, "only {swept} xml files swept — sweep broke");
}
/// **The `$parentTextureFrame` idiom's contract, over the whole shipped UI**: a frame whose art is
@@ -121,6 +121,7 @@ fn shipped_spellbook_drives_end_to_end() {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
@@ -130,7 +131,6 @@ fn shipped_spellbook_drives_end_to_end() {
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.lua");
load_xml(&s, "Interface\\FrameXML\\TextStatusBar.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "Interface\\FrameXML\\MainMenuBar.xml");
load_xml(&s, "Interface\\FrameXML\\ActionBarFrame.xml");
load_xml(&s, "Interface\\FrameXML\\BonusActionBarFrame.xml");
@@ -16,6 +16,7 @@ fn taxi_script() -> UiScript {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml"); // TaxiNodeOnButtonEnter's tooltip + SetTooltipMoney
load_xml(&s, "Interface\\FrameXML\\UIErrorsFrame.xml"); // BenillaErrorsFrame_AddMessage — DrawOneHopLines' refusal
@@ -25,7 +26,6 @@ fn taxi_script() -> UiScript {
// · UIPanelTemplates (.lua then .xml) — `TaxiCloseButton` inherits `UIPanelCloseButton`,
// which lives there and NOT in our UiPanels.xml. 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\\GlobalStrings.lua");
load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.lua");
load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.xml");
load_xml(&s, "Interface\\FrameXML\\TaxiFrame.xml");
@@ -26,6 +26,7 @@ fn trainer_script() -> UiScript {
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
@@ -33,7 +34,6 @@ fn trainer_script() -> UiScript {
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml"); // the filter dropdown's kit
load_xml(&s, "ScrollTemplates.xml"); // the faux-scroll bar kit
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); // the merchant kit; the money frames are MoneyFrame.xml's (1937)
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "TrainerFrame.xml");
s.run(
"TRAINER_FILTER_AVAILABLE = 1 TRAINER_FILTER_UNAVAILABLE = 1 TRAINER_FILTER_USED = 1 \
@@ -646,13 +646,19 @@ fn shipped_target_frame_runs_the_level_law() {
load_unit_frames(&s);
// GetDifficultyColor's own load chain (the quest log window, its ref home).
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "UiPanels.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "QuestFrame.xml");
load_xml(&s, "QuestLogFrame.xml");
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.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\\Fonts.xml");
load_xml(&s, "MicroMenu.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
// The player at level 3, both feeds (the snapshot UnitLevel("player") reads; the req state
// the −1 gate and GetQuestGreenRange read) — the app keeps the two in step.
s.set_player_req_state(PlayerReqState {
@@ -466,6 +466,7 @@ fn load_pet_menu_frames(s: &UiScript) {
"Interface\\FrameXML\\Fonts.xml",
"UIParent.xml",
"MoneyFrame.xml",
"Interface\\FrameXML\\GlobalStrings.lua",
"UiPanels.xml",
r"Interface\FrameXML\UIPanelTemplates.lua",
r"Interface\FrameXML\UIPanelTemplates.xml",
@@ -483,7 +484,6 @@ fn load_pet_menu_frames(s: &UiScript) {
"Interface\\FrameXML\\PetFrame.xml",
"Cooldown.xml",
"Interface\\FrameXML\\ActionButtonTemplate.xml",
"Interface\\FrameXML\\GlobalStrings.lua",
"Interface\\FrameXML\\MainMenuBar.xml",
"Interface\\FrameXML\\ActionBarFrame.xml",
"Interface\\FrameXML\\BonusActionBarFrame.xml",