Files
benilla-octo/crates/benilla-app/src/ui_script/delete_item_tests.rs
T
Sam SungandClaude Opus 5.5 eba558a5d2 Comments in benilla-app, the UI script layer: one line of fact each, no history, no record numbers (#184)
Part of #41: the first of five `benilla-app` pieces. This one is the UI script layer: `ui_script` with its test harnesses, the text engine and layout (`ui_text`), the UI pass and the UI model panes. Its comments are rewritten to the rule in `docs/METHOD.md`, which is what the code does and the reference fact behind it, in a line. 22,041 comment lines become 7,133.

- **The mechanical strip.** The citations that were only a private record number go first, and each edit is shown to be a pure removal.
- **Wrong facts corrected.** The rewrite corrects the comments that stated wrong facts. Among them:
  - On a fresh login the reference fires `PLAYER_LOGIN` and `PLAYER_ENTERING_WORLD` from the player's create (`0x5deb60` → `0x4908c0`); only a `/reload` goes through `0x490168`. The comments now state both paths.
  - `ComputeStep` (`0x5ca2d0`) applies negative pair kerns and rounds up. It does have a kerning term.
  - A fallback icon global and the retired `UiPanels.xml` panels no longer exist. The comments that named them now describe the stock FrameXML mechanism that replaced them.
  - Stock FrameXML line citations are checked against the stock 1.12 files and corrected, and several misattributed files are fixed (`MainMenuBarArtFrame` lives in `MainMenuBar.xml`).
  - Dozens more turned up on the way: docs sitting on the wrong item, removed functions, stale counts and links.
- **Deviation labels.** "Deviation:" now marks only a choice with a reason. An unbuilt behaviour says so plainly.
- **Verification.** Every file strips to identical code, and `cargo fmt --check` is clean. Five second readers compared every file old against new, restored the facts the rewrite had dropped, and checked each new citation against the code, the stock files, vmangos and the reference.

Behaviour differences the rewrite surfaced are filed as their own issues (#183 among them). The comments here state what the code does. String literals are untouched; they are #92.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 08:20:21 +07:00

491 lines
18 KiB
Rust

//! The delete confirm: the world drop's `DELETE_ITEM_CONFIRM` raises the stock `DELETE_ITEM`
//! popup, or `DELETE_GOOD_ITEM` for quality 3 and up (`UIParent.lua:344-352`). The fixtures are
//! real 1.12 items: Tough Jerky (117, quality 1) and Flurry Axe (871, quality 4).
use benilla_ui::script::{ContainerSlot, ContainerState, UiScript};
use super::test_ui::{
bag_open, bag_slot_button, load_ui as load_xml, load_world_frame, world_click, BAG_UI,
};
/// A backpack holding a 5-stack of `item_id` at `quality`, the parameter the popup forks on.
fn one_item_backpack(item_id: u32, name: &str, quality: u32) -> ContainerState {
let mut slots = std::collections::HashMap::new();
slots.insert(
1,
ContainerSlot {
duration_ms: None,
petition: None,
already_bound: false,
bar_placeable: true,
durability: None,
texture: Some("Interface\\Icons\\INV_Misc_Food_16".into()),
count: 5,
quality: Some(quality),
item_id,
link: Some(format!("|cffffffff|Hitem:{item_id}|h[{name}]|h|r")),
locked: false,
equip_slots: Vec::new(),
cooldown: None,
readable: false,
creator: None,
flags: 0,
enchants: Vec::new(),
},
);
ContainerState {
name: Some("Backpack".into()),
num_slots: 16,
slots,
}
}
/// The stock popup stack and the world frame the drop is clicked on.
fn setup() -> 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, r"Interface\FrameXML\UIParent.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua");
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua");
// The stock `DELETE_GOOD_ITEM`'s `OnHide` hands focus back to `ChatFrameEditBox`.
load_xml(&s, r"Interface\FrameXML\UIMenu.xml");
load_xml(&s, r"Interface\FrameXML\ChatFrame.xml");
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml"); // TOOLTIP_DEFAULT_COLOR, for dropdowns
load_xml(&s, r"Interface\FrameXML\UIDropDownMenu.xml");
load_xml(&s, r"Interface\FrameXML\StaticPopup.xml");
load_xml(&s, r"Interface\FrameXML\FloatingChatFrame.xml"); // declares ChatFrameEditBox
// The drop is a click on the full-screen world frame, so it must be loaded.
load_world_frame(&s);
s.set_money(0);
s
}
/// The stock bag windows (`BAG_UI`) and the world frame, for the test that watches a bag repaint.
fn bag_setup() -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
for file in BAG_UI {
load_xml(&s, file);
}
// `ContainerFrame_Update` reads `MerchantFrame:IsShown()` on any slot the tooltip owns.
load_xml(&s, "ScrollTemplates.xml"); // our scroll kits
load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml");
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml");
load_world_frame(&s); // the drop target
// `BAG_UI` leaves out `CharacterFrame.xml`, so `PaperDollFrame` has no hidden parent and would
// cover the world; the real client never shows it here.
s.run("PaperDollFrame:Hide()")
.expect("the paper doll the missing CharacterFrame would have hidden");
s.set_money(0);
s
}
/// Drops Tough Jerky on the world: a full left click, press and release both on the world frame,
/// fires `DELETE_ITEM_CONFIRM(name, quality)`.
fn pick_up_and_drop_in_world(s: &mut UiScript) {
drop_in_world(s, 117, "Tough Jerky", 1);
}
/// The world drop for any fixture item; its quality (`arg2`) picks the popup.
fn drop_in_world(s: &mut UiScript, item_id: u32, name: &str, quality: u32) {
s.set_container(0, Some(one_item_backpack(item_id, name, quality)));
s.run("PickupContainerItem(0, 1)").unwrap();
assert!(s.cursor_item().is_some(), "fixture: the item is held");
world_click(s);
s.tick(0.01); // flush the queued DELETE_ITEM_CONFIRM into UIParent's OnEvent
}
#[test]
fn delete_item_confirm_shows_the_real_strings_and_yes_deletes() {
benilla_formats::wow_data_or_skip!();
let mut s = setup();
pick_up_and_drop_in_world(&mut s);
assert!(
s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap(),
"the confirm popup shows on DELETE_ITEM_CONFIRM"
);
assert_eq!(
s.eval::<String>("return StaticPopup1Text:GetText()")
.unwrap(),
"Do you want to destroy Tough Jerky?",
"the real GlobalStrings DELETE_ITEM text, formatted with the item name"
);
assert_eq!(
s.eval::<String>("return StaticPopup1Button1:GetText()")
.unwrap(),
"Yes"
);
assert_eq!(
s.eval::<String>("return StaticPopup1Button2:GetText()")
.unwrap(),
"No"
);
// Yes runs `OnAccept`, `DeleteCursorItem()`; a destroy count of 0 is the whole stack.
s.run("StaticPopup_OnClick(StaticPopup1, 1)").unwrap();
assert!(
!s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap(),
"Yes hides the popup"
);
assert!(s.cursor_item().is_none(), "DeleteCursorItem cleared it");
assert_eq!(s.take_container_destroys(), vec![(0, 1, 0)]);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}
/// No runs `OnCancel` (`ClearCursor()`) and destroys nothing; only the clear's `ITEM_LOCK_CHANGED`
/// can repaint the open bag, un-darkening the slot (`ContainerFrame.lua:39-42`, `:246`).
#[test]
fn delete_item_confirm_no_clears_without_destroying() {
let _data = benilla_formats::wow_data_or_skip!();
let mut s = bag_setup();
s.set_container(0, Some(one_item_backpack(117, "Tough Jerky", 1)));
s.run("ToggleBackpack()").unwrap();
assert!(bag_open(&s, 0), "the backpack window is up");
// `ContainerFrame_GenerateFrame` numbers buttons backwards (`ContainerFrame.lua:426-428`).
let slot1 = bag_slot_button(&s, 0, 1);
pick_up_and_drop_in_world(&mut s);
assert!(
desaturated(&mut s, &slot1),
"held on the cursor, the source slot draws dark (ref SetItemButtonDesaturated(_, locked))"
);
// Count repaints. The 1.12 client's Lua 5.0 has no `...` as a value: forward `arg` by `unpack`.
s.run(
"repaints = 0\n\
local real = ContainerFrame_Update\n\
ContainerFrame_Update = function(...) repaints = repaints + 1; \
return real(unpack(arg, 1, arg.n)) end",
)
.unwrap();
s.run("StaticPopup_OnClick(StaticPopup1, 2)").unwrap();
assert!(
!s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap(),
"No hides the popup"
);
assert!(s.cursor_item().is_none(), "ClearCursor cleared it");
assert!(s.take_container_destroys().is_empty(), "No never destroys");
s.tick(0.01); // ITEM_LOCK_CHANGED fires from the pending queue at the next tick
assert!(
s.eval::<i64>("return repaints").unwrap() >= 1,
"the clear's ITEM_LOCK_CHANGED repaints the bag (the stuck-darkened slot, 0218)"
);
assert!(
!s.eval::<bool>("local _, _, locked = GetContainerItemInfo(0, 1) return locked")
.unwrap(),
"the source slot reads unlocked again"
);
assert!(
!desaturated(&mut s, &slot1),
"…and the repaint un-darkened it — the 0218 stuck-dark slot"
);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}
/// Whether `button`'s icon draws greyscale, off the quad stream: `SetDesaturated` has no getter.
fn desaturated(s: &mut UiScript, button: &str) -> bool {
s.resolve();
s.extract()
.into_iter()
.filter(|q| s.quad_owner_name(q.target).as_deref() == Some(button))
.any(|q| match &q.content {
benilla_ui::script::QuadContent::Texture {
path: Some(p),
desaturated,
..
} => p.contains("INV_Misc_Food_16") && *desaturated,
_ => false,
})
}
/// ESC runs stock `StaticPopup_EscapePressed`: `DELETE_ITEM` is `hideOnEscape`, so its `OnCancel`
/// runs as No would (`StaticPopup.lua:1879-1893`).
#[test]
fn escape_closes_the_delete_confirm_popup() {
benilla_formats::wow_data_or_skip!();
let mut s = setup();
pick_up_and_drop_in_world(&mut s);
s.run("ToggleGameMenu()").unwrap();
assert!(
!s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap(),
"ESC closes the confirm popup"
);
assert!(s.cursor_item().is_none());
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}
/// The stock `DELETE_ITEM`'s `OnUpdate` hides it once the cursor empties by any path
/// (`StaticPopup.lua:669-673`); the poll is the entry's own.
#[test]
fn the_delete_entry_polls_itself_hidden_and_other_dialogs_are_untouched() {
benilla_formats::wow_data_or_skip!();
let mut s = setup();
pick_up_and_drop_in_world(&mut s);
assert!(s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap());
s.run("ClearCursor()").unwrap();
s.tick(0.01);
assert!(
!s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap(),
"the DELETE_ITEM OnUpdate poll auto-hides once the cursor is empty"
);
s.run(
r#"StaticPopupDialogs["TEST_UNRELATED"] = { text = "unrelated?", button1 = "Yes",
button2 = "No", timeout = 0, whileDead = 1 }
StaticPopup_Show("TEST_UNRELATED")"#,
)
.unwrap();
assert!(s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap());
s.fire_event("CURSOR_UPDATE", vec![]);
s.tick(0.01);
assert!(
s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap(),
"an unrelated dialog is never touched by the delete-confirm poll"
);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}
/// Quality 3 and up raises `DELETE_GOOD_ITEM` instead (`UIParent.lua:346-350`).
#[test]
fn a_rare_payload_raises_the_typed_confirm_with_okay_disabled() {
benilla_formats::wow_data_or_skip!();
let mut s = setup();
drop_in_world(&mut s, 871, "Flurry Axe", 4);
assert_eq!(
s.eval::<String>("return StaticPopup1.which").unwrap(),
"DELETE_GOOD_ITEM",
"quality 4 forks to the typed-confirmation variant"
);
assert_eq!(
s.eval::<String>("return StaticPopup1Text:GetText()")
.unwrap(),
"Do you want to destroy Flurry Axe?\n\nType \"DELETE\" into the field to confirm.",
"the real GlobalStrings DELETE_GOOD_ITEM text, formatted with the item name"
);
assert!(
s.eval::<bool>("return StaticPopup1EditBox:IsShown()")
.unwrap(),
"hasEditBox raises the narrow box"
);
assert_eq!(
s.focused_editbox_name().as_deref(),
Some("StaticPopup1EditBox"),
"the entry's OnShow focuses the box, so the player can type straight away"
);
assert_eq!(
s.eval::<i64>("return StaticPopup1Button1:IsEnabled()")
.unwrap(),
0,
"OKAY starts disabled — nothing has been typed yet"
);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}
/// `EditBoxOnTextChanged` compares the `strupper` of the text with `DELETE_ITEM_CONFIRM_STRING`
/// (`StaticPopup.lua:718-723`).
#[test]
fn typing_the_confirm_word_enables_okay_and_untyping_it_disables_again() {
benilla_formats::wow_data_or_skip!();
let mut s = setup();
drop_in_world(&mut s, 871, "Flurry Axe", 4);
// `OnTextChanged` fires at the drain, so this ticks before reading the button.
let enabled = |s: &mut UiScript| {
s.tick(0.0);
s.eval::<i64>("return StaticPopup1Button1:IsEnabled()")
.unwrap()
== 1
};
s.run(r#"StaticPopup1EditBox:SetText("DELET")"#).unwrap();
assert!(!enabled(&mut s), "a prefix of the word is not the word");
// Typed key by key in lower case, through the engine's input path.
s.run(r#"StaticPopup1EditBox:SetText("")"#).unwrap();
for c in ["d", "e", "l", "e", "t", "e"] {
assert!(s.char_input(c), "the focused box takes the keystroke");
}
assert_eq!(
s.eval::<String>("return StaticPopup1EditBox:GetText()")
.unwrap(),
"delete"
);
assert!(
enabled(&mut s),
"the ref compares through strupper, so lower case passes"
);
s.run(r#"StaticPopup1EditBox:SetText("deletex")"#).unwrap();
assert!(!enabled(&mut s), "one char past the word disables again");
s.run(r#"StaticPopup1EditBox:SetText("DELETE")"#).unwrap();
assert!(enabled(&mut s));
s.run("StaticPopup_OnClick(StaticPopup1, 1)").unwrap();
assert!(
!s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap(),
"OKAY hides the popup"
);
assert!(s.cursor_item().is_none(), "DeleteCursorItem cleared it");
assert_eq!(s.take_container_destroys(), vec![(0, 1, 0)]);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}
/// Stock `EditBoxOnEnterPressed` destroys only while OKAY is enabled (`StaticPopup.lua:712-717`).
#[test]
fn enter_in_the_box_destroys_only_once_okay_is_enabled() {
benilla_formats::wow_data_or_skip!();
let mut s = setup();
drop_in_world(&mut s, 871, "Flurry Axe", 4);
s.run(r#"StaticPopup1EditBox:SetText("DEL")"#).unwrap();
assert!(s.key_input("ENTER"), "the focused box consumes ENTER");
assert!(
s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap(),
"Enter with OKAY disabled is inert — the dialog stays up"
);
assert!(s.cursor_item().is_some(), "and the item is still held");
assert!(s.take_container_destroys().is_empty());
s.run(r#"StaticPopup1EditBox:SetText("DELETE")"#).unwrap();
s.tick(0.0); // the write only marks the box; the drain enables OKAY
assert!(s.key_input("ENTER"));
assert!(
!s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap(),
"Enter with OKAY enabled destroys and hides"
);
assert!(s.cursor_item().is_none());
assert_eq!(s.take_container_destroys(), vec![(0, 1, 0)]);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}
/// The stock `OnHide` empties the box, so the next raise opens blank and disarmed.
#[test]
fn no_on_the_typed_confirm_clears_and_leaves_the_box_empty_for_next_time() {
benilla_formats::wow_data_or_skip!();
let mut s = setup();
drop_in_world(&mut s, 871, "Flurry Axe", 4);
s.run(r#"StaticPopup1EditBox:SetText("DELETE")"#).unwrap();
s.run("StaticPopup_OnClick(StaticPopup1, 2)").unwrap();
assert!(!s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap());
assert!(s.cursor_item().is_none(), "ClearCursor cleared it");
assert!(s.take_container_destroys().is_empty(), "No never destroys");
assert_eq!(
s.eval::<String>("return StaticPopup1EditBox:GetText()")
.unwrap(),
"",
"OnHide empties the box"
);
drop_in_world(&mut s, 871, "Flurry Axe", 4);
assert_eq!(
s.eval::<String>("return StaticPopup1EditBox:GetText()")
.unwrap(),
""
);
assert_eq!(
s.eval::<i64>("return StaticPopup1Button1:IsEnabled()")
.unwrap(),
0,
"and OKAY is disabled again"
);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}
/// Stock `DELETE_GOOD_ITEM` has no `EditBoxOnEscapePressed`, so ESC in its box does nothing
/// (`StaticPopup.lua:1817-1822`).
#[test]
fn escape_in_the_typed_confirms_box_is_swallowed_as_the_reference_leaves_it() {
benilla_formats::wow_data_or_skip!();
let mut s = setup();
drop_in_world(&mut s, 871, "Flurry Axe", 4);
assert_eq!(
s.focused_editbox_name().as_deref(),
Some("StaticPopup1EditBox")
);
assert!(s.key_input("ESCAPE"), "the focused box consumes ESCAPE");
assert!(
s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap(),
"the dialog stays: the entry names no escape handler"
);
assert_eq!(
s.focused_editbox_name().as_deref(),
Some("StaticPopup1EditBox"),
"and the box keeps focus"
);
assert!(s.cursor_item().is_some(), "the item is still on the cursor");
assert!(s.take_container_destroys().is_empty(), "ESC never destroys");
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}
#[test]
fn the_plain_arm_shows_no_edit_box() {
benilla_formats::wow_data_or_skip!();
let mut s = setup();
pick_up_and_drop_in_world(&mut s); // Tough Jerky, quality 1
assert_eq!(
s.eval::<String>("return StaticPopup1.which").unwrap(),
"DELETE_ITEM"
);
assert!(
!s.eval::<bool>("return StaticPopup1EditBox:IsShown()")
.unwrap(),
"no hasEditBox on the plain entry"
);
assert_eq!(
s.eval::<i64>("return StaticPopup1Button1:IsEnabled()")
.unwrap(),
1,
"and YES is live immediately"
);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}
/// The world drop under the whole shipped manifest: a fixture is a subset, and can miss the file
/// that breaks the drop.
#[test]
fn the_world_drop_survives_the_whole_shipped_interface() {
let _data = benilla_formats::wow_data_or_skip!();
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
let failures = super::load_default_ui(&s);
assert!(failures.is_empty(), "manifest load errors: {failures:#?}");
s.set_money(0);
s.set_container(0, Some(one_item_backpack(117, "Tough Jerky", 1)));
s.resolve();
s.run("PickupContainerItem(0, 1)").unwrap();
assert!(s.cursor_item().is_some(), "fixture: the item is held");
world_click(&mut s);
s.tick(0.01);
assert!(
s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap(),
"clicking the ground with an item held raises the destroy confirm"
);
assert_eq!(
s.eval::<String>("return StaticPopup1Text:GetText()")
.unwrap(),
"Do you want to destroy Tough Jerky?"
);
s.run("StaticPopup1Button1:Click()").unwrap();
assert!(s.cursor_item().is_none(), "the cursor is empty after Yes");
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}