the mailbox takes typing again, and events carry their real arguments
- the mail window's send tab takes typing again: the subject, body and money boxes were taking no input while the recipient box swallowed every key, and a sent letter clears its subject and item icon instead of putting them straight back (2145) - seven events fire the way the reference does: UNIT_DYNAMIC_FLAGS fires at all, swapping two identical stacks between bank slots fires PLAYERBANKSLOTS_CHANGED twice, the pet sheet's training points repaint, and ITEM_LOCK_CHANGED loses its invented arguments (2140) - nine widget methods 1.12 addons probe for are in: ShaguTweaks stops recolouring textures the reference leaves alone, pfUI and _Nameplates stop raising on a tooltip's GetAnchorType, and 63 AceEvent addons get RegisterAllEvents (2142) - six post-1.12 methods leave the widget surface (SetSize, SetShown, HasFocus, SetFormattedText and two tooltip verbs), so an addon reaching for a later client's API finds what a real 1.12 client gives it (2142, 2146) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,7 @@ fn setpoint_explicit_nil_relative_to_keeps_offsets() {
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Nil")
|
||||
f:SetPoint("TOPLEFT", nil, "TOPLEFT", 40, -40)
|
||||
f:SetSize(300, 200)
|
||||
f:SetWidth(300); f:SetHeight(200)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -40,7 +40,7 @@ fn setpoint_resolve_size_and_rect() {
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Sized")
|
||||
f:SetPoint("TOPLEFT", 10, -5) -- relativeTo = screen (default), relativePoint = TOPLEFT
|
||||
f:SetSize(200, 50)
|
||||
f:SetWidth(200); f:SetHeight(50)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -68,7 +68,7 @@ fn setpoint_resolve_size_and_rect() {
|
||||
#[test]
|
||||
fn getwidth_falls_back_to_explicit_size_before_resolve() {
|
||||
let s = script();
|
||||
// No SetPoint ⇒ unresolvable; GetWidth returns the explicit SetSize value.
|
||||
// No SetPoint ⇒ unresolvable; GetWidth returns the explicit SetWidth value.
|
||||
let w: f32 = s
|
||||
.eval(r#"local f = CreateFrame("Frame"); f:SetWidth(123); return f:GetWidth()"#)
|
||||
.unwrap();
|
||||
|
||||
@@ -13,7 +13,7 @@ fn backdrop_installs_and_extracts_pieces_with_colors() {
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Plate")
|
||||
f:SetPoint("TOPLEFT", nil, "TOPLEFT", 100, -100)
|
||||
f:SetSize(200, 100)
|
||||
f:SetWidth(200); f:SetHeight(100)
|
||||
f:SetBackdrop({
|
||||
bgFile = "bg", edgeFile = "edge", tile = true,
|
||||
tileSize = 16, edgeSize = 16,
|
||||
@@ -129,7 +129,7 @@ fn get_backdrop_round_trips_through_set_backdrop() {
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "BC2Plate")
|
||||
f:SetPoint("CENTER")
|
||||
f:SetSize(54, 54)
|
||||
f:SetWidth(54); f:SetHeight(54)
|
||||
f:SetBackdrop({ bgFile = "bg", edgeFile = "edge", tile = true,
|
||||
tileSize = 32, edgeSize = 32,
|
||||
insets = { left = 11, right = 12, top = 12, bottom = 11 } })
|
||||
@@ -166,7 +166,7 @@ fn set_backdrop_nil_tears_down() {
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Plate2")
|
||||
f:SetPoint("CENTER")
|
||||
f:SetSize(100, 100)
|
||||
f:SetWidth(100); f:SetHeight(100)
|
||||
f:SetBackdrop({ bgFile = "bg", edgeFile = "edge" })
|
||||
f:SetBackdrop(nil)
|
||||
"#,
|
||||
|
||||
@@ -34,7 +34,7 @@ fn button_state_textures_switch_with_interaction() {
|
||||
s.run(
|
||||
r#"
|
||||
local b = CreateFrame("Button", "StateBtn")
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetSize(100, 100)
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetWidth(100); b:SetHeight(100)
|
||||
b:SetNormalTexture("Interface\\N.blp")
|
||||
b:SetPushedTexture("Interface\\P.blp")
|
||||
b:SetDisabledTexture("Interface\\D.blp")
|
||||
@@ -110,7 +110,7 @@ fn a_state_with_no_texture_leaves_the_shown_one_standing() {
|
||||
s.run(
|
||||
r#"
|
||||
local b = CreateFrame("Button", "StickyBtn")
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetSize(100, 100)
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetWidth(100); b:SetHeight(100)
|
||||
b:SetNormalTexture("Interface\\N.blp")
|
||||
"#,
|
||||
)
|
||||
@@ -162,7 +162,7 @@ fn a_state_with_no_texture_leaves_the_shown_one_standing() {
|
||||
s.run(
|
||||
r#"
|
||||
local b = CreateFrame("Button", "BornDeadBtn")
|
||||
b:SetPoint("BOTTOMLEFT", 200, 0); b:SetSize(100, 100)
|
||||
b:SetPoint("BOTTOMLEFT", 200, 0); b:SetWidth(100); b:SetHeight(100)
|
||||
b:Disable()
|
||||
b:SetNormalTexture("Interface\\N.blp")
|
||||
"#,
|
||||
@@ -191,7 +191,7 @@ fn disabling_a_button_takes_its_whole_highlight_layer() {
|
||||
s.run(
|
||||
r#"
|
||||
local b = CreateFrame("Button", "LayerBtn")
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetSize(100, 100)
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetWidth(100); b:SetHeight(100)
|
||||
b:SetNormalTexture("Interface\\N.blp")
|
||||
b:SetHighlightTexture("Interface\\H.blp")
|
||||
local own = b:CreateTexture(nil, "HIGHLIGHT")
|
||||
@@ -271,7 +271,7 @@ fn any_registered_mouse_button_shows_the_pushed_texture() {
|
||||
r#"
|
||||
local function slot(name, x)
|
||||
local b = CreateFrame("Button", name)
|
||||
b:SetPoint("BOTTOMLEFT", x, 0); b:SetSize(100, 100)
|
||||
b:SetPoint("BOTTOMLEFT", x, 0); b:SetWidth(100); b:SetHeight(100)
|
||||
b:SetNormalTexture("Interface\\" .. name .. "N.blp")
|
||||
b:SetPushedTexture("Interface\\" .. name .. "P.blp")
|
||||
return b
|
||||
@@ -331,7 +331,7 @@ fn set_button_state_drives_the_pushed_visual() {
|
||||
s.run(
|
||||
r#"
|
||||
local b = CreateFrame("Button", "PushBtn")
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetSize(100, 100)
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetWidth(100); b:SetHeight(100)
|
||||
b:SetNormalTexture("Interface\\N.blp")
|
||||
b:SetPushedTexture("Interface\\P.blp")
|
||||
"#,
|
||||
@@ -375,7 +375,7 @@ fn disabled_button_swallows_clicks_checkbutton_toggles_before_onclick() {
|
||||
r#"
|
||||
clicks, seen_checked = 0, nil
|
||||
local cb = CreateFrame("CheckButton", "Toggler")
|
||||
cb:SetPoint("BOTTOMLEFT", 0, 0); cb:SetSize(100, 100)
|
||||
cb:SetPoint("BOTTOMLEFT", 0, 0); cb:SetWidth(100); cb:SetHeight(100)
|
||||
cb:SetScript("OnClick", function(self, button, down)
|
||||
clicks = clicks + 1
|
||||
seen_checked = self:GetChecked()
|
||||
@@ -422,7 +422,7 @@ fn default_registration_is_left_click_only_right_click_reaches_nothing() {
|
||||
r#"
|
||||
clicks = 0
|
||||
local btn = CreateFrame("Button", "Vendor")
|
||||
btn:SetPoint("BOTTOMLEFT", 0, 0); btn:SetSize(100, 100)
|
||||
btn:SetPoint("BOTTOMLEFT", 0, 0); btn:SetWidth(100); btn:SetHeight(100)
|
||||
btn:SetScript("OnClick", function(self, button, down) clicks = clicks + 1 end)
|
||||
"#,
|
||||
)
|
||||
@@ -445,7 +445,7 @@ fn register_for_clicks_grows_right_click_and_carries_the_button_name() {
|
||||
r#"
|
||||
clicks, click_btn, arg1_btn = 0, nil, nil
|
||||
local btn = CreateFrame("Button", "Vendor")
|
||||
btn:SetPoint("BOTTOMLEFT", 0, 0); btn:SetSize(100, 100)
|
||||
btn:SetPoint("BOTTOMLEFT", 0, 0); btn:SetWidth(100); btn:SetHeight(100)
|
||||
btn:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
||||
btn:SetScript("OnClick", function(self, button, down)
|
||||
clicks = clicks + 1
|
||||
@@ -478,7 +478,7 @@ fn down_registration_fires_on_press_and_toggles_checked_once() {
|
||||
r#"
|
||||
clicks, seen_down = 0, nil
|
||||
local cb = CreateFrame("CheckButton", "QuickSell")
|
||||
cb:SetPoint("BOTTOMLEFT", 0, 0); cb:SetSize(100, 100)
|
||||
cb:SetPoint("BOTTOMLEFT", 0, 0); cb:SetWidth(100); cb:SetHeight(100)
|
||||
cb:RegisterForClicks("LeftButtonDown")
|
||||
cb:SetScript("OnClick", function(self, button, down)
|
||||
clicks = clicks + 1
|
||||
@@ -509,9 +509,9 @@ fn highlight_is_additive_and_state_textures_fill_then_anchor() {
|
||||
s.run(
|
||||
r#"
|
||||
local b = CreateFrame("Button", "AddBtn")
|
||||
b:SetPoint("BOTTOMLEFT", 100, 100); b:SetSize(36, 36)
|
||||
b:SetPoint("BOTTOMLEFT", 100, 100); b:SetWidth(36); b:SetHeight(36)
|
||||
b:SetNormalTexture("Interface\\Ring.blp")
|
||||
b:GetNormalTexture():SetSize(64, 64)
|
||||
local nt = b:GetNormalTexture(); nt:SetWidth(64); nt:SetHeight(64)
|
||||
b:SetHighlightTexture("Interface\\Hi.blp")
|
||||
"#,
|
||||
)
|
||||
@@ -533,7 +533,7 @@ fn highlight_is_additive_and_state_textures_fill_then_anchor() {
|
||||
));
|
||||
// A fresh state texture gets the creation-path implicit SetAllPoints (decision 1310 — the
|
||||
// reference's string setters anchor a freshly built texture to the button outright), whose
|
||||
// two corners pin all four edges: the later SetSize(64) is structurally unread and the ring
|
||||
// two corners pin all four edges: the later 64px size is structurally unread and the ring
|
||||
// FILLS the 36px button.
|
||||
let r = find("Interface\\Ring.blp").rect.unwrap();
|
||||
assert_eq!(
|
||||
@@ -608,7 +608,7 @@ fn button_label_repaints_by_state_font_object() {
|
||||
s.run(
|
||||
r#"
|
||||
b = CreateFrame("Button", "FontBtn")
|
||||
b:SetPoint("CENTER", 0, 0); b:SetSize(100, 20)
|
||||
b:SetPoint("CENTER", 0, 0); b:SetWidth(100); b:SetHeight(20)
|
||||
b:SetText("Label")
|
||||
b:SetTextFontObject("GoldFont")
|
||||
b:SetDisabledFontObject("GrayFont")
|
||||
@@ -696,7 +696,7 @@ fn a_locked_or_hovered_button_wears_its_highlight_font_over_its_normal_color() {
|
||||
s.run(
|
||||
r#"
|
||||
b = CreateFrame("Button", "RowBtn")
|
||||
b:SetPoint("BOTTOMLEFT", 100, 100); b:SetSize(100, 20)
|
||||
b:SetPoint("BOTTOMLEFT", 100, 100); b:SetWidth(100); b:SetHeight(20)
|
||||
b:SetText("Rough Copper Vest")
|
||||
b:SetTextFontObject("RowNormal")
|
||||
b:SetHighlightFontObject("RowHighlight")
|
||||
@@ -1103,7 +1103,7 @@ fn a_button_labels_own_setfont_survives_the_state_font_repoint() {
|
||||
s.run(
|
||||
r#"
|
||||
b = CreateFrame("Button", "SkinnedBtn")
|
||||
b:SetPoint("CENTER", 0, 0); b:SetSize(100, 20)
|
||||
b:SetPoint("CENTER", 0, 0); b:SetWidth(100); b:SetHeight(20)
|
||||
b:SetText("Label")
|
||||
b:SetTextFontObject("TemplateFont")
|
||||
b:GetFontString():SetFont("Interface\\Addons\\Skin\\Fonts\\porky.ttf", 18, "OUTLINE")
|
||||
@@ -1189,7 +1189,7 @@ fn a_state_texture_slot_takes_an_object_and_a_nil() {
|
||||
s.run(
|
||||
r#"
|
||||
b = CreateFrame("Button", "SlotBtn")
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetSize(100, 100)
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetWidth(100); b:SetHeight(100)
|
||||
b:SetNormalTexture("Interface\\N.blp")
|
||||
-- Bongos' own idiom: build the highlight yourself and hand the object over.
|
||||
hl = b:CreateTexture()
|
||||
@@ -1260,7 +1260,7 @@ fn an_unlocked_scripted_push_is_released_by_the_next_mouse_release() {
|
||||
s.run(
|
||||
r#"
|
||||
row = CreateFrame("Button", "TabletRow")
|
||||
row:SetPoint("BOTTOMLEFT", 0, 0); row:SetSize(100, 100)
|
||||
row:SetPoint("BOTTOMLEFT", 0, 0); row:SetWidth(100); row:SetHeight(100)
|
||||
row:SetNormalTexture("Interface\\RowN.blp")
|
||||
row:SetPushedTexture("Interface\\RowP.blp")
|
||||
row:SetButtonState("PUSHED") -- Tablet-2.0's call, verbatim: no lock argument
|
||||
@@ -1301,10 +1301,10 @@ fn a_locked_state_ignores_the_mouse_and_enable_disable_clears_the_lock() {
|
||||
s.run(
|
||||
r#"
|
||||
micro = CreateFrame("Button", "MicroButton")
|
||||
micro:SetPoint("BOTTOMLEFT", 0, 0); micro:SetSize(100, 100)
|
||||
micro:SetPoint("BOTTOMLEFT", 0, 0); micro:SetWidth(100); micro:SetHeight(100)
|
||||
micro:SetButtonState("PUSHED", 1) -- MainMenuBarMicroButtons.lua, verbatim
|
||||
pin = CreateFrame("Button", "PinnedNormal")
|
||||
pin:SetPoint("BOTTOMLEFT", 200, 0); pin:SetSize(100, 100)
|
||||
pin:SetPoint("BOTTOMLEFT", 200, 0); pin:SetWidth(100); pin:SetHeight(100)
|
||||
pin:SetButtonState("NORMAL", 1)
|
||||
"#,
|
||||
)
|
||||
@@ -1367,7 +1367,7 @@ fn the_hover_is_not_an_input_to_the_press_state() {
|
||||
s.run(
|
||||
r#"
|
||||
b = CreateFrame("Button", "HeldButton")
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetSize(100, 100)
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetWidth(100); b:SetHeight(100)
|
||||
b:SetNormalTexture("Interface\\HeldN.blp")
|
||||
b:SetPushedTexture("Interface\\HeldP.blp")
|
||||
"#,
|
||||
@@ -1416,7 +1416,7 @@ fn hiding_a_held_button_un_presses_it() {
|
||||
s.run(
|
||||
r#"
|
||||
h = CreateFrame("Button", "HidButton")
|
||||
h:SetPoint("BOTTOMLEFT", 0, 0); h:SetSize(100, 100)
|
||||
h:SetPoint("BOTTOMLEFT", 0, 0); h:SetWidth(100); h:SetHeight(100)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1438,3 +1438,86 @@ fn hiding_a_held_button_un_presses_it() {
|
||||
s.mouse_button(50.0, 50.0, "LeftButton", false);
|
||||
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
||||
}
|
||||
|
||||
/// `Button:GetTextColor()` — **FOUR** values, r/g/b/a (`0x781100`, table `0x879d00`, argc 1,
|
||||
/// arity 4, kinds `(number,number,number,number)`). Three is the plausible wrong answer, and the
|
||||
/// shapes table flags the name `name-not-unique` because seven tables register it — this asserts
|
||||
/// the BUTTON one, on a Button, and its two inheritance legs.
|
||||
///
|
||||
/// Completeness rather than a live break: no corpus site has a Button receiver today (every
|
||||
/// measured `GetTextColor` is on a FontString or a font object). It is here because the reference
|
||||
/// registers it, so the widget shape gate can cover it from now on.
|
||||
#[test]
|
||||
fn button_get_text_color_answers_four_values_through_the_state_font() {
|
||||
let s = script();
|
||||
s.register_font_object(
|
||||
"BtnGold",
|
||||
FontObject {
|
||||
color: Some([1.0, 0.82, 0.0, 1.0]),
|
||||
height: Some(12.0),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
Plain = CreateFrame("Button", "PlainBtn")
|
||||
Themed = CreateFrame("Button", "ThemedBtn")
|
||||
Themed:SetTextFontObject("BtnGold")
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return select('#', PlainBtn:GetTextColor())")
|
||||
.unwrap(),
|
||||
4,
|
||||
"arity 4 — not 3, the plausible wrong answer"
|
||||
);
|
||||
let kinds: String = s
|
||||
.eval(
|
||||
r#"local r,g,b,a = PlainBtn:GetTextColor()
|
||||
return type(r)..","..type(g)..","..type(b)..","..type(a)"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(kinds, "number,number,number,number");
|
||||
|
||||
// A button with nothing set anywhere is the untinted white every other colour getter here
|
||||
// (`GetVertexColor`, `FontString:GetTextColor`) answers.
|
||||
let plain: Vec<f32> = (1..=4)
|
||||
.map(|i| {
|
||||
s.eval::<f32>(&format!("return select({i}, PlainBtn:GetTextColor())"))
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(plain, vec![1.0, 1.0, 1.0, 1.0]);
|
||||
|
||||
// With no local colour it reads THROUGH what the normal state inherits — the same leg
|
||||
// `Button:GetFont` takes, and the one a stock `GameMenuButtonTemplate` button relies on.
|
||||
let themed: Vec<f32> = (1..=4)
|
||||
.map(|i| {
|
||||
s.eval::<f32>(&format!("return select({i}, ThemedBtn:GetTextColor())"))
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(themed, vec![1.0, 0.82, 0.0, 1.0]);
|
||||
|
||||
// A local SetTextColor wins, alpha included, and round-trips.
|
||||
s.run("ThemedBtn:SetTextColor(0.1, 0.2, 0.3, 0.4)").unwrap();
|
||||
let set: Vec<f32> = (1..=4)
|
||||
.map(|i| {
|
||||
s.eval::<f32>(&format!("return select({i}, ThemedBtn:GetTextColor())"))
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(set, vec![0.1, 0.2, 0.3, 0.4]);
|
||||
|
||||
// A CheckButton reaches it through Button's table, as it does the rest of the trio.
|
||||
assert_eq!(
|
||||
s.eval::<i64>(
|
||||
r#"local c = CreateFrame("CheckButton", "ChkColorBtn")
|
||||
return select('#', c:GetTextColor())"#
|
||||
)
|
||||
.unwrap(),
|
||||
4
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ fn end_to_end_two_frame_tree_extracts_in_zkey_order() {
|
||||
-- same strata/level) with a texture and a fontstring. ~30 lines of ordinary FrameXML Lua.
|
||||
local parent = CreateFrame("Frame", "Root")
|
||||
parent:SetPoint("TOPLEFT", 0, 0) -- anchored to the screen root
|
||||
parent:SetSize(400, 300)
|
||||
parent:SetWidth(400); parent:SetHeight(300)
|
||||
-- SetAllPoints on each region: a templateless Lua region gets NO implicit anchor
|
||||
-- (decision 1310 — rect-less, never drawn), so real addon code anchors it, and so do we.
|
||||
local pbg = parent:CreateTexture(nil, "BACKGROUND")
|
||||
@@ -23,7 +23,7 @@ fn end_to_end_two_frame_tree_extracts_in_zkey_order() {
|
||||
|
||||
local child = CreateFrame("Frame", "Leaf", parent)
|
||||
child:SetPoint("TOPLEFT", parent, "TOPLEFT", 10, -10)
|
||||
child:SetSize(100, 50)
|
||||
child:SetWidth(100); child:SetHeight(50)
|
||||
local cbg = child:CreateTexture(nil, "ARTWORK")
|
||||
cbg:SetTexture("Interface\\Child.blp")
|
||||
cbg:SetAllPoints()
|
||||
|
||||
@@ -270,3 +270,112 @@ fn mid_dispatch_removal_of_the_next_stops_and_append_is_visited() {
|
||||
"a tail-append during dispatch is still visited this dispatch"
|
||||
);
|
||||
}
|
||||
|
||||
/// `Frame:RegisterAllEvents()` — the frame's `OnEvent` receives every event dispatched
|
||||
/// (`0x774c20`, table `0x878ec0`, argc 1, arity 0), and `UnregisterAllEvents` clears that state
|
||||
/// along with the per-event ones.
|
||||
///
|
||||
/// **The clearing half is the load-bearing one.** AceEvent-2.0 — shipped by 63 vanilla addons and
|
||||
/// 6 of the top 20 — turns all-events on once (`AceEvent.frame:RegisterAllEvents()`), deliberately
|
||||
/// stops calling `frame:UnregisterEvent(event)` while it is on, and gets back to per-event
|
||||
/// registration by calling `frame:UnregisterAllEvents()` and then re-registering each event it
|
||||
/// still wants. A `RegisterAllEvents` that survived that call would leave every Ace2 addon on the
|
||||
/// whole event stream for the session.
|
||||
#[test]
|
||||
fn register_all_events_takes_every_event_and_unregister_all_clears_it() {
|
||||
let mut s = script();
|
||||
s.run(
|
||||
r#"
|
||||
seen = {}
|
||||
All = CreateFrame("Frame", "AllEv")
|
||||
All:SetScript("OnEvent", function() table.insert(seen, event) end)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Arity 0 — the verb answers nothing.
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return select('#', All:RegisterAllEvents())")
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
|
||||
// Anything dispatched now reaches it, including an event no name list could have enumerated.
|
||||
for ev in [
|
||||
"PLAYER_LOGIN",
|
||||
"UNIT_HEALTH",
|
||||
"SOME_SERVER_EVENT_NOBODY_LISTED",
|
||||
] {
|
||||
s.fire_event(ev, vec![]);
|
||||
}
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return table.getn(seen)").unwrap(),
|
||||
3,
|
||||
"every event dispatched, whatever its name"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return seen[3]").unwrap(),
|
||||
"SOME_SERVER_EVENT_NOBODY_LISTED"
|
||||
);
|
||||
|
||||
// Registering twice is a no-op, and a frame holding BOTH an all-events registration and a
|
||||
// RegisterEvent for the same event is one listener, not two — the same rule `RegisterEvent`'s
|
||||
// own `if not already in the list` holds one level down.
|
||||
s.run(r#"seen = {}; All:RegisterAllEvents(); All:RegisterEvent("UNIT_HEALTH")"#)
|
||||
.unwrap();
|
||||
s.fire_event("UNIT_HEALTH", vec![]);
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return table.getn(seen)").unwrap(),
|
||||
1,
|
||||
"fired once, not twice"
|
||||
);
|
||||
|
||||
// UnregisterAllEvents clears BOTH: the explicit UNIT_HEALTH registration and the all-events one.
|
||||
s.run("seen = {}; All:UnregisterAllEvents()").unwrap();
|
||||
for ev in ["UNIT_HEALTH", "PLAYER_LOGIN"] {
|
||||
s.fire_event(ev, vec![]);
|
||||
}
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return table.getn(seen)").unwrap(),
|
||||
0,
|
||||
"the all-events registration does not outlive UnregisterAllEvents"
|
||||
);
|
||||
|
||||
// …and the AceEvent path back: re-register the individual events it still wants.
|
||||
s.run(r#"All:RegisterEvent("UNIT_HEALTH")"#).unwrap();
|
||||
for ev in ["UNIT_HEALTH", "PLAYER_LOGIN"] {
|
||||
s.fire_event(ev, vec![]);
|
||||
}
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return table.getn(seen)").unwrap(),
|
||||
1,
|
||||
"back to exactly one event"
|
||||
);
|
||||
assert!(s.take_errors().is_empty());
|
||||
}
|
||||
|
||||
/// An all-events frame dispatches AFTER the event's own listeners — where it would sit if the
|
||||
/// registration were expanded into every per-event list, since it joined later than they did.
|
||||
/// Cross-frame order is a law consumers depend on (the two ZoneText frames writing one FontString);
|
||||
/// it does not stop being one because a listener asked for everything.
|
||||
#[test]
|
||||
fn an_all_events_listener_runs_after_the_events_own() {
|
||||
let mut s = script();
|
||||
s.run(
|
||||
r#"
|
||||
order = {}
|
||||
Named = CreateFrame("Frame", "NamedEv")
|
||||
Named:SetScript("OnEvent", function() table.insert(order, "named") end)
|
||||
Named:RegisterEvent("PLAYER_LOGIN")
|
||||
|
||||
Everything = CreateFrame("Frame", "EveryEv")
|
||||
Everything:SetScript("OnEvent", function() table.insert(order, "all") end)
|
||||
Everything:RegisterAllEvents()
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
s.fire_event("PLAYER_LOGIN", vec![]);
|
||||
assert_eq!(s.eval::<String>("return order[1]").unwrap(), "named");
|
||||
assert_eq!(s.eval::<String>("return order[2]").unwrap(), "all");
|
||||
assert_eq!(s.eval::<i64>("return table.getn(order)").unwrap(), 2);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
//! Small frame-API wins: GetLocale, SetAllPoints, SetFormattedText, SetShown, and GetPoint readback.
|
||||
//! Small frame-API wins: GetLocale, SetAllPoints, and GetPoint readback.
|
||||
|
||||
use super::common::script;
|
||||
|
||||
/// Two era names, asserted together because neither earns a file of its own.
|
||||
///
|
||||
/// This test carried two more legs until decision 2142, `SetFormattedText` and `SetShown`, and
|
||||
/// both went with the verbs: neither is in a 1.12 method table. What the first of them actually
|
||||
/// proved — that `%N$s` reorders rather than consumes — is `string.format`'s own behaviour and is
|
||||
/// pinned where it lives, in [`crate::strings`].
|
||||
#[test]
|
||||
fn consensus_api_small_wins() {
|
||||
fn get_locale_and_set_all_points() {
|
||||
let mut s = script();
|
||||
s.set_screen_size(800.0, 600.0);
|
||||
// GetLocale — benilla is enUS-data-only.
|
||||
@@ -12,13 +18,11 @@ fn consensus_api_small_wins() {
|
||||
s.run(
|
||||
r#"
|
||||
p = CreateFrame("Frame", "SAP_Parent")
|
||||
p:SetPoint("BOTTOMLEFT", 100, 100); p:SetSize(200, 50)
|
||||
p:SetPoint("BOTTOMLEFT", 100, 100); p:SetWidth(200); p:SetHeight(50)
|
||||
c = CreateFrame("Frame", "SAP_Child", p)
|
||||
c:SetAllPoints() -- default: the parent
|
||||
f = CreateFrame("Frame", "SAP_Free")
|
||||
f:SetAllPoints("SAP_Parent") -- by name
|
||||
fs = p:CreateFontString(nil, "OVERLAY")
|
||||
fs:SetFormattedText("%2$s %1$s (%3$d)", "world", "hello", 7)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -34,20 +38,6 @@ fn consensus_api_small_wins() {
|
||||
)
|
||||
.unwrap();
|
||||
assert!(ok, "SetAllPoints matched the target rect");
|
||||
|
||||
// SetFormattedText routes through the positional-aware format.
|
||||
assert_eq!(
|
||||
s.eval::<String>("return fs:GetText()").unwrap(),
|
||||
"hello world (7)"
|
||||
);
|
||||
|
||||
// SetShown: truthy shows, nil/false hides.
|
||||
s.run("p:SetShown(false)").unwrap();
|
||||
assert!(!s.eval::<bool>("return p:IsShown()").unwrap());
|
||||
s.run("p:SetShown(1)").unwrap();
|
||||
assert!(s.eval::<bool>("return p:IsShown()").unwrap());
|
||||
s.run("p:SetShown(nil)").unwrap();
|
||||
assert!(!s.eval::<bool>("return p:IsShown()").unwrap());
|
||||
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ fn enable_mouse_gates_hit_testing() {
|
||||
-- Two full-screen frames. `b` is created later (drawn on top) but mouse-disabled, so it is
|
||||
-- transparent to hits; the enabled frame behind it (`a`) must capture.
|
||||
local a = CreateFrame("Frame", "A")
|
||||
a:SetPoint("BOTTOMLEFT", 0, 0); a:SetSize(800, 600); a:EnableMouse(true)
|
||||
a:SetPoint("BOTTOMLEFT", 0, 0); a:SetWidth(800); a:SetHeight(600); a:EnableMouse(true)
|
||||
a:SetScript("OnEnter", function(self) who = self:GetName() end)
|
||||
local b = CreateFrame("Frame", "B")
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetSize(800, 600); b:EnableMouse(false)
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetWidth(800); b:SetHeight(600); b:EnableMouse(false)
|
||||
b:SetScript("OnEnter", function(self) who = self:GetName() end)
|
||||
-- 1/nil, not a boolean (1830): this is the exact comparison shape that inverts, so the
|
||||
-- test asserts it rather than leaning on truthiness.
|
||||
@@ -51,10 +51,10 @@ fn hit_order_is_strata_then_level_then_the_earlier_linked_frame() {
|
||||
r#"
|
||||
who = nil
|
||||
local a = CreateFrame("Frame", "A")
|
||||
a:SetPoint("BOTTOMLEFT", 0, 0); a:SetSize(800, 600); a:EnableMouse(true)
|
||||
a:SetPoint("BOTTOMLEFT", 0, 0); a:SetWidth(800); a:SetHeight(600); a:EnableMouse(true)
|
||||
a:SetScript("OnEnter", function(self) who = self:GetName() end)
|
||||
local b = CreateFrame("Frame", "B")
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetSize(800, 600); b:EnableMouse(true)
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetWidth(800); b:SetHeight(600); b:EnableMouse(true)
|
||||
b:SetScript("OnEnter", function(self) who = self:GetName() end)
|
||||
"#,
|
||||
)
|
||||
@@ -96,7 +96,7 @@ fn mouse_move_fires_enter_then_leave_across_a_boundary_with_correct_self() {
|
||||
enters, leaves = 0, 0
|
||||
enter_self_ok, leave_self_ok = false, false
|
||||
local a = CreateFrame("Frame", "A") -- left half only
|
||||
a:SetPoint("BOTTOMLEFT", 0, 0); a:SetSize(400, 600); a:EnableMouse(true)
|
||||
a:SetPoint("BOTTOMLEFT", 0, 0); a:SetWidth(400); a:SetHeight(600); a:EnableMouse(true)
|
||||
a:SetScript("OnEnter", function(self) enters = enters + 1; enter_self_ok = (self == a) end)
|
||||
a:SetScript("OnLeave", function(self) leaves = leaves + 1; leave_self_ok = (self == a) end)
|
||||
"#,
|
||||
@@ -127,10 +127,10 @@ fn onclick_fires_on_press_release_same_frame_not_when_release_lands_elsewhere()
|
||||
r#"
|
||||
clicks_a, clicks_b, click_btn = 0, 0, nil
|
||||
local a = CreateFrame("Frame", "A") -- left half
|
||||
a:SetPoint("BOTTOMLEFT", 0, 0); a:SetSize(400, 600); a:EnableMouse(true)
|
||||
a:SetPoint("BOTTOMLEFT", 0, 0); a:SetWidth(400); a:SetHeight(600); a:EnableMouse(true)
|
||||
a:SetScript("OnClick", function(self, button, down) clicks_a = clicks_a + 1; click_btn = button end)
|
||||
local b = CreateFrame("Frame", "B") -- right half
|
||||
b:SetPoint("BOTTOMLEFT", 400, 0); b:SetSize(400, 600); b:EnableMouse(true)
|
||||
b:SetPoint("BOTTOMLEFT", 400, 0); b:SetWidth(400); b:SetHeight(600); b:EnableMouse(true)
|
||||
b:SetScript("OnClick", function(self, button, down) clicks_b = clicks_b + 1 end)
|
||||
"#,
|
||||
)
|
||||
@@ -168,14 +168,14 @@ fn hidden_or_effective_hidden_frame_never_captures() {
|
||||
entered = false
|
||||
-- own shown = false
|
||||
local own = CreateFrame("Frame", "OwnHidden")
|
||||
own:SetPoint("BOTTOMLEFT", 0, 0); own:SetSize(800, 600); own:EnableMouse(true)
|
||||
own:SetPoint("BOTTOMLEFT", 0, 0); own:SetWidth(800); own:SetHeight(600); own:EnableMouse(true)
|
||||
own:SetScript("OnEnter", function() entered = true end)
|
||||
own:Hide()
|
||||
-- effective-hidden: child is shown but its parent is hidden
|
||||
local parent = CreateFrame("Frame", "Par")
|
||||
parent:SetPoint("BOTTOMLEFT", 0, 0); parent:SetSize(800, 600)
|
||||
parent:SetPoint("BOTTOMLEFT", 0, 0); parent:SetWidth(800); parent:SetHeight(600)
|
||||
local child = CreateFrame("Frame", "Ch", parent)
|
||||
child:SetPoint("BOTTOMLEFT", 0, 0); child:SetSize(800, 600); child:EnableMouse(true)
|
||||
child:SetPoint("BOTTOMLEFT", 0, 0); child:SetWidth(800); child:SetHeight(600); child:EnableMouse(true)
|
||||
child:SetScript("OnEnter", function() entered = true end)
|
||||
parent:Hide()
|
||||
"#,
|
||||
@@ -203,7 +203,7 @@ fn mouse_wheel_passes_delta_to_the_captured_frame() {
|
||||
r#"
|
||||
wheel = nil
|
||||
local a = CreateFrame("Frame", "A")
|
||||
a:SetPoint("BOTTOMLEFT", 0, 0); a:SetSize(800, 600); a:EnableMouse(true)
|
||||
a:SetPoint("BOTTOMLEFT", 0, 0); a:SetWidth(800); a:SetHeight(600); a:EnableMouse(true)
|
||||
-- The wheel is its own index and its own flag: SetScript never auto-enables (that law is
|
||||
-- XML-load-time only), so a runtime-created frame needs this explicitly, exactly as it
|
||||
-- needs EnableMouse. Mouse-enablement alone puts nothing in the wheel index.
|
||||
@@ -231,7 +231,7 @@ fn hit_rect_insets_shrink_the_mouse_rect_only() {
|
||||
r#"
|
||||
entered = false
|
||||
local a = CreateFrame("Frame", "A")
|
||||
a:SetPoint("BOTTOMLEFT", 0, 0); a:SetSize(100, 100); a:EnableMouse(true)
|
||||
a:SetPoint("BOTTOMLEFT", 0, 0); a:SetWidth(100); a:SetHeight(100); a:EnableMouse(true)
|
||||
a:SetScript("OnEnter", function() entered = true end)
|
||||
a:SetHitRectInsets(5, 5, 18, 5)
|
||||
local l, r, t, b = a:GetHitRectInsets()
|
||||
@@ -375,7 +375,7 @@ fn a_second_fast_click_fires_on_double_click_instead_of_the_second_on_click() {
|
||||
r#"
|
||||
clicks, doubles, dblbtn = 0, 0, nil
|
||||
local b = CreateFrame("Button", "DblB")
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetSize(200, 200); b:EnableMouse(true)
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetWidth(200); b:SetHeight(200); b:EnableMouse(true)
|
||||
b:SetScript("OnClick", function(self, button) clicks = clicks + 1 end)
|
||||
b:SetScript("OnDoubleClick", function(self, button) doubles = doubles + 1 dblbtn = button end)
|
||||
"#,
|
||||
@@ -432,7 +432,7 @@ fn a_frame_with_no_double_click_handler_keeps_every_rapid_on_click() {
|
||||
r#"
|
||||
clicks = 0
|
||||
local b = CreateFrame("Button", "NoDbl")
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetSize(200, 200); b:EnableMouse(true)
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetWidth(200); b:SetHeight(200); b:EnableMouse(true)
|
||||
b:SetScript("OnClick", function() clicks = clicks + 1 end)
|
||||
"#,
|
||||
)
|
||||
@@ -465,9 +465,9 @@ fn the_double_click_boundary_is_time_and_frame_and_clicks_pair_up() {
|
||||
f:SetScript("OnDoubleClick", function() doubles = doubles + 1 end)
|
||||
end
|
||||
local a = CreateFrame("Button", "DblA")
|
||||
a:SetPoint("BOTTOMLEFT", 0, 0); a:SetSize(100, 100); a:EnableMouse(true); watch(a)
|
||||
a:SetPoint("BOTTOMLEFT", 0, 0); a:SetWidth(100); a:SetHeight(100); a:EnableMouse(true); watch(a)
|
||||
local b = CreateFrame("Button", "DblBB")
|
||||
b:SetPoint("BOTTOMLEFT", 300, 0); b:SetSize(100, 100); b:EnableMouse(true); watch(b)
|
||||
b:SetPoint("BOTTOMLEFT", 300, 0); b:SetWidth(100); b:SetHeight(100); b:EnableMouse(true); watch(b)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -522,7 +522,7 @@ fn a_multi_registered_button_pairs_a_left_click_with_a_right_one() {
|
||||
r#"
|
||||
doubles, dblbtn = 0, nil
|
||||
local b = CreateFrame("Button", "MixDbl")
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetSize(200, 200); b:EnableMouse(true)
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetWidth(200); b:SetHeight(200); b:EnableMouse(true)
|
||||
b:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
||||
b:SetScript("OnDoubleClick", function(self, button) doubles = doubles + 1 dblbtn = button end)
|
||||
"#,
|
||||
@@ -555,7 +555,7 @@ fn a_half_finished_double_click_survives_the_cursor_leaving_the_window() {
|
||||
r#"
|
||||
doubles = 0
|
||||
local b = CreateFrame("Button", "LeaveDbl")
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetSize(200, 200); b:EnableMouse(true)
|
||||
b:SetPoint("BOTTOMLEFT", 0, 0); b:SetWidth(200); b:SetHeight(200); b:EnableMouse(true)
|
||||
b:SetScript("OnDoubleClick", function() doubles = doubles + 1 end)
|
||||
"#,
|
||||
)
|
||||
@@ -585,7 +585,7 @@ fn set_script_on_double_click_is_accepted_because_something_fires_it() {
|
||||
r#"
|
||||
ran = false
|
||||
Btn = CreateFrame("Button", "DblAccepted")
|
||||
Btn:SetPoint("BOTTOMLEFT", 0, 0); Btn:SetSize(100, 100); Btn:EnableMouse(true)
|
||||
Btn:SetPoint("BOTTOMLEFT", 0, 0); Btn:SetWidth(100); Btn:SetHeight(100); Btn:EnableMouse(true)
|
||||
Btn:SetScript("OnDoubleClick", function() ran = true end)
|
||||
"#,
|
||||
)
|
||||
@@ -625,8 +625,8 @@ fn the_unfired_script_kinds_still_raise_rather_than_silently_accepting() {
|
||||
"OnAttributeChanged",
|
||||
// Real 1.12 slots we do not fire, and zero corpus call sites. (`OnUpdateModel` and
|
||||
// `OnAnimFinished` left this list with decision 2007: the tick's model pass fires
|
||||
// them — `script::tests::model_clock`.)
|
||||
"OnHorizontalScroll",
|
||||
// them — `script::tests::model_clock`. `OnHorizontalScroll` left it with the ScrollFrame's
|
||||
// horizontal offset pair, which fires it — `script::tests::scrollframe`.)
|
||||
"OnHyperlinkEnter",
|
||||
"OnMessageScrollChanged",
|
||||
"OnInputLanguageChanged",
|
||||
@@ -653,9 +653,9 @@ fn hiding_the_hovered_frame_fires_its_onleave_before_onhide() {
|
||||
r#"
|
||||
log = {}
|
||||
local win = CreateFrame("Frame", "Win")
|
||||
win:SetPoint("BOTTOMLEFT", 0, 0); win:SetSize(800, 600)
|
||||
win:SetPoint("BOTTOMLEFT", 0, 0); win:SetWidth(800); win:SetHeight(600)
|
||||
local slot = CreateFrame("Button", "Slot", win)
|
||||
slot:SetPoint("BOTTOMLEFT", 100, 100); slot:SetSize(100, 100); slot:EnableMouse(true)
|
||||
slot:SetPoint("BOTTOMLEFT", 100, 100); slot:SetWidth(100); slot:SetHeight(100); slot:EnableMouse(true)
|
||||
slot:SetScript("OnLeave", function() table.insert(log, "leave") end)
|
||||
slot:SetScript("OnHide", function() table.insert(log, "hide") end)
|
||||
"#,
|
||||
@@ -682,7 +682,7 @@ fn hiding_an_unhovered_frame_fires_no_onleave() {
|
||||
r#"
|
||||
leaves = 0
|
||||
local a = CreateFrame("Button", "Away")
|
||||
a:SetPoint("BOTTOMLEFT", 500, 500); a:SetSize(50, 50); a:EnableMouse(true)
|
||||
a:SetPoint("BOTTOMLEFT", 500, 500); a:SetWidth(50); a:SetHeight(50); a:EnableMouse(true)
|
||||
a:SetScript("OnLeave", function() leaves = leaves + 1 end)
|
||||
"#,
|
||||
)
|
||||
@@ -706,10 +706,10 @@ fn the_repick_re_hovers_without_a_mouse_move() {
|
||||
r#"
|
||||
entered = {}
|
||||
local under = CreateFrame("Button", "Under")
|
||||
under:SetPoint("BOTTOMLEFT", 100, 100); under:SetSize(100, 100); under:EnableMouse(true)
|
||||
under:SetPoint("BOTTOMLEFT", 100, 100); under:SetWidth(100); under:SetHeight(100); under:EnableMouse(true)
|
||||
under:SetScript("OnEnter", function() table.insert(entered, "under") end)
|
||||
local over = CreateFrame("Button", "Over")
|
||||
over:SetPoint("BOTTOMLEFT", 100, 100); over:SetSize(100, 100); over:EnableMouse(true)
|
||||
over:SetPoint("BOTTOMLEFT", 100, 100); over:SetWidth(100); over:SetHeight(100); over:EnableMouse(true)
|
||||
over:SetScript("OnEnter", function() table.insert(entered, "over") end)
|
||||
-- Say "on top" with the LEVEL, not with declaration order: at equal levels the sweep takes
|
||||
-- the earlier-linked frame (`Under`), which is the tie law, not what this test is about.
|
||||
@@ -752,7 +752,7 @@ fn two_frames() -> crate::script::UiScript {
|
||||
log = {}
|
||||
local function mk(name, x)
|
||||
local f = CreateFrame("Frame", name)
|
||||
f:SetPoint("BOTTOMLEFT", x, 200); f:SetSize(200, 200); f:EnableMouse(true)
|
||||
f:SetPoint("BOTTOMLEFT", x, 200); f:SetWidth(200); f:SetHeight(200); f:EnableMouse(true)
|
||||
f:SetScript("OnMouseDown", function(self) table.insert(log, self:GetName()..":down") end)
|
||||
f:SetScript("OnMouseUp", function(self) table.insert(log, self:GetName()..":up") end)
|
||||
return f
|
||||
|
||||
@@ -184,3 +184,90 @@ fn a_raising_handler_still_consumes_and_is_recorded() {
|
||||
s.errors()
|
||||
);
|
||||
}
|
||||
|
||||
/// **A `CSimpleEditBox` in the walk is asked about FOCUS, never about a script slot** (§1's vtable
|
||||
/// table + `0x77a900`). Its ctor registers it in both key buckets, and vtable `0x81c910` replaces
|
||||
/// `+0x5c`/`+0x60` with overrides that *never chain to the base* — "there is no `call 0x76b760`
|
||||
/// anywhere in it, so the generic `OnChar` slot `+0x180` is unreachable on an editbox". An
|
||||
/// unfocused box therefore takes the `0x77a956 xor eax,eax` leg: decline, walk continues.
|
||||
///
|
||||
/// Modelling the box as a plain frame here is not a subtle divergence. Stock
|
||||
/// `SendMailNameEditBox` carries an XML `<OnChar>` (`SendMailFrame_SendeeAutocomplete`), which
|
||||
/// auto-enables it, and it registers first in the mail window — so it consumed every keystroke and
|
||||
/// the send tab's other four boxes took no input at all (decision 2145).
|
||||
#[test]
|
||||
fn an_unfocused_editbox_declines_rather_than_eating_its_neighbours_keys() {
|
||||
let mut s = script();
|
||||
s.run(
|
||||
r#"
|
||||
decoyChars = 0
|
||||
decoy = CreateFrame("EditBox", "KbDecoyBox")
|
||||
decoy:SetAutoFocus(false)
|
||||
decoy:EnableKeyboard(true)
|
||||
decoy:SetScript("OnChar", function() decoyChars = decoyChars + 1 end)
|
||||
target = CreateFrame("EditBox", "KbTargetBox")
|
||||
target:SetAutoFocus(false)
|
||||
target:SetFocus()
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(s.char_input("k"), "the focused box consumes");
|
||||
assert_eq!(
|
||||
s.eval::<String>("return KbTargetBox:GetText()").unwrap(),
|
||||
"k",
|
||||
"the keystroke reaches the box that holds the focus"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return decoyChars").unwrap(),
|
||||
0,
|
||||
"the unfocused box's own OnChar is unreachable from the walk — it declines"
|
||||
);
|
||||
|
||||
// Give the decoy the focus and its handler is reachable again — through the INSERT, which is
|
||||
// where an editbox's `OnChar` is fired from (`0x77c200`), not through the walk's base gate.
|
||||
s.run("KbDecoyBox:SetFocus()").unwrap();
|
||||
assert!(s.char_input("j"), "…and now it is the one that consumes");
|
||||
assert_eq!(s.eval::<i64>("return decoyChars").unwrap(), 1);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return KbDecoyBox:GetText()").unwrap(),
|
||||
"j"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return KbTargetBox:GetText()").unwrap(),
|
||||
"k",
|
||||
"and the box that lost focus keeps what it had"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
||||
}
|
||||
|
||||
/// The same clause on the key-down channel (`+0x60` → `0x77b160`, focus guard at `0x77b1c7`), which
|
||||
/// is the [`super::super::keyboard::frame_key_input`] walk: an unfocused box carrying only an
|
||||
/// `OnKeyUp` must not swallow a BACKSPACE the focused box is waiting for. The plain-frame
|
||||
/// asymmetry above is real *for plain frames*; on a box the base gate is not reached at all.
|
||||
#[test]
|
||||
fn an_unfocused_editbox_does_not_swallow_a_focused_boxs_editing_key() {
|
||||
let mut s = script();
|
||||
s.run(
|
||||
r#"
|
||||
decoy = CreateFrame("EditBox", "KbDecoyBox2")
|
||||
decoy:SetAutoFocus(false)
|
||||
decoy:EnableKeyboard(true)
|
||||
decoy:SetScript("OnKeyUp", function() end)
|
||||
target = CreateFrame("EditBox", "KbTargetBox2")
|
||||
target:SetAutoFocus(false)
|
||||
target:SetText("ab")
|
||||
target:SetFocus()
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
s.editbox_action(crate::script::EditAction::Delete {
|
||||
unit: crate::script::EditUnit::Char,
|
||||
back: true,
|
||||
});
|
||||
assert_eq!(
|
||||
s.eval::<String>("return KbTargetBox2:GetText()").unwrap(),
|
||||
"a",
|
||||
"the backspace reached the focused box"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ fn the_hover_re_enter_loop_neither_re_measures_nor_re_solves() {
|
||||
s.run(
|
||||
r#"
|
||||
local owner = CreateFrame("Button", "Slot")
|
||||
owner:SetPoint("TOPLEFT", 100, -100); owner:SetSize(40, 40)
|
||||
owner:SetPoint("TOPLEFT", 100, -100); owner:SetWidth(40); owner:SetHeight(40)
|
||||
tt = CreateFrame("GameTooltip", "TT")
|
||||
-- One bag-slot OnEnter: the clear (SetOwner) + the content rebuild, verbatim in shape.
|
||||
function reenter()
|
||||
|
||||
@@ -14,13 +14,13 @@ fn measured_fontstring_height_feeds_frame_anchors() {
|
||||
s.run(
|
||||
r#"
|
||||
local w = CreateFrame("Frame", "Win")
|
||||
w:SetPoint("TOPLEFT", 0, -100); w:SetSize(384, 512)
|
||||
w:SetPoint("TOPLEFT", 0, -100); w:SetWidth(384); w:SetHeight(512)
|
||||
local g = w:CreateFontString("Greeting", "ARTWORK")
|
||||
g:SetText("a long greeting that wraps")
|
||||
g:SetWidth(270)
|
||||
g:SetPoint("TOPLEFT", 33, -91)
|
||||
local row = CreateFrame("Button", "Row1")
|
||||
row:SetSize(300, 16)
|
||||
row:SetWidth(300); row:SetHeight(16)
|
||||
row:SetPoint("TOPLEFT", "Greeting", "BOTTOMLEFT", -10, -20)
|
||||
"#,
|
||||
)
|
||||
@@ -74,7 +74,7 @@ fn a_changed_text_reads_zero_until_its_own_measure_lands() {
|
||||
s.run(
|
||||
r#"
|
||||
local w = CreateFrame("Frame", "Win")
|
||||
w:SetPoint("TOPLEFT", 0, -100); w:SetSize(384, 512)
|
||||
w:SetPoint("TOPLEFT", 0, -100); w:SetWidth(384); w:SetHeight(512)
|
||||
local h = w:CreateFontString("Header", "ARTWORK")
|
||||
h:SetText("Say: ")
|
||||
h:SetPoint("LEFT", 13, 0)
|
||||
@@ -129,14 +129,14 @@ fn zero_width_fontstring_autosizes_to_its_line() {
|
||||
s.run(
|
||||
r#"
|
||||
local w = CreateFrame("Frame", "Win")
|
||||
w:SetPoint("TOPLEFT", 0, -100); w:SetSize(384, 512)
|
||||
w:SetPoint("TOPLEFT", 0, -100); w:SetWidth(384); w:SetHeight(512)
|
||||
local label = w:CreateFontString("FromLabel", "ARTWORK")
|
||||
label:SetText("From:")
|
||||
label:SetSize(0, 16)
|
||||
label:SetWidth(0); label:SetHeight(16)
|
||||
label:SetPoint("TOPRIGHT", "Win", "TOPLEFT", 114, -45)
|
||||
local value = w:CreateFontString("FromValue", "ARTWORK")
|
||||
value:SetText("Thrall")
|
||||
value:SetSize(110, 0)
|
||||
value:SetWidth(110); value:SetHeight(0)
|
||||
value:SetPoint("LEFT", "FromLabel", "RIGHT", 5, 0)
|
||||
"#,
|
||||
)
|
||||
@@ -181,7 +181,7 @@ fn frame_scale_rides_the_quads_and_the_measure_key() {
|
||||
s.run(
|
||||
r#"
|
||||
local w = CreateFrame("Frame", "Win")
|
||||
w:SetPoint("TOPLEFT", 0, -100); w:SetSize(400, 300)
|
||||
w:SetPoint("TOPLEFT", 0, -100); w:SetWidth(400); w:SetHeight(300)
|
||||
w:SetScale(0.8)
|
||||
local t = w:CreateFontString("ScaledLabel", "ARTWORK")
|
||||
t:SetText("Options")
|
||||
@@ -234,7 +234,7 @@ fn invalidate_text_measures_reopens_the_round_trip() {
|
||||
s.run(
|
||||
r#"
|
||||
local w = CreateFrame("Frame", "Win")
|
||||
w:SetPoint("TOPLEFT", 0, -100); w:SetSize(400, 300)
|
||||
w:SetPoint("TOPLEFT", 0, -100); w:SetWidth(400); w:SetHeight(300)
|
||||
local t = w:CreateFontString("Label", "ARTWORK")
|
||||
t:SetText("Keybindings")
|
||||
t:SetPoint("TOPLEFT", 10, -10)
|
||||
@@ -382,7 +382,7 @@ fn a_width_read_in_the_tick_that_set_the_text_is_not_zero() {
|
||||
s.run(
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Host")
|
||||
f:SetPoint("TOPLEFT"); f:SetSize(400, 300)
|
||||
f:SetPoint("TOPLEFT"); f:SetWidth(400); f:SetHeight(300)
|
||||
local fs = f:CreateFontString("Label", "ARTWORK")
|
||||
fs:SetPoint("TOPLEFT")
|
||||
-- the corpus idiom: set, then measure, in one statement sequence
|
||||
@@ -408,7 +408,7 @@ fn with_no_engine_installed_the_round_trip_is_still_the_only_answer() {
|
||||
s.run(
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Host")
|
||||
f:SetPoint("TOPLEFT"); f:SetSize(400, 300)
|
||||
f:SetPoint("TOPLEFT"); f:SetWidth(400); f:SetHeight(300)
|
||||
local fs = f:CreateFontString("Label", "ARTWORK")
|
||||
fs:SetPoint("TOPLEFT")
|
||||
fs:SetText("Onewarrior")
|
||||
@@ -435,7 +435,7 @@ fn resolve_closes_the_round_trip_when_an_engine_is_installed() {
|
||||
s.run(
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Host")
|
||||
f:SetPoint("TOPLEFT"); f:SetSize(400, 300)
|
||||
f:SetPoint("TOPLEFT"); f:SetWidth(400); f:SetHeight(300)
|
||||
local fs = f:CreateFontString("Label", "ARTWORK")
|
||||
fs:SetPoint("TOPLEFT")
|
||||
fs:SetWidth(35) -- a declared width ⇒ the two extents differ
|
||||
@@ -469,7 +469,7 @@ fn a_synchronous_measure_satisfies_the_batch_request_too() {
|
||||
s.run(
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Host")
|
||||
f:SetPoint("TOPLEFT"); f:SetSize(400, 300)
|
||||
f:SetPoint("TOPLEFT"); f:SetWidth(400); f:SetHeight(300)
|
||||
local fs = f:CreateFontString("Label", "ARTWORK")
|
||||
fs:SetPoint("TOPLEFT")
|
||||
fs:SetText("hello")
|
||||
@@ -505,7 +505,7 @@ fn a_settled_region_is_refound_after_each_measure_input_write() {
|
||||
s.run(
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Host")
|
||||
f:SetPoint("TOPLEFT"); f:SetSize(400, 300)
|
||||
f:SetPoint("TOPLEFT"); f:SetWidth(400); f:SetHeight(300)
|
||||
local fs = f:CreateFontString("Label", "ARTWORK")
|
||||
fs:SetPoint("TOPLEFT")
|
||||
fs:SetText("one")
|
||||
@@ -514,7 +514,8 @@ fn a_settled_region_is_refound_after_each_measure_input_write() {
|
||||
.unwrap();
|
||||
let writes: &[(&str, &str)] = &[
|
||||
("SetText", "Label:SetText('two')"),
|
||||
("SetFormattedText", "Label:SetFormattedText('n=%d', 3)"),
|
||||
// `SetFormattedText` was a second row here; it is gone (2142), and its era spelling
|
||||
// `SetText(format(...))` is the row above's path exactly.
|
||||
(
|
||||
"SetFont",
|
||||
r#"Label:SetFont("Fonts\\FRIZQT__.TTF", 14, "OUTLINE")"#,
|
||||
@@ -550,7 +551,7 @@ fn a_settled_region_is_refound_after_each_measure_input_write() {
|
||||
s.run(
|
||||
r#"
|
||||
local b = CreateFrame("Button", "Btn")
|
||||
b:SetPoint("TOPLEFT", 0, -50); b:SetSize(80, 22)
|
||||
b:SetPoint("TOPLEFT", 0, -50); b:SetWidth(80); b:SetHeight(22)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -49,6 +49,7 @@ mod tooltip_spell;
|
||||
mod tooltip_unit;
|
||||
mod toplevel;
|
||||
mod visibility;
|
||||
mod widget_surface;
|
||||
mod worldframe;
|
||||
mod worldmap;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ fn movable_panel() -> UiScript {
|
||||
starts, stops = 0, 0
|
||||
Panel = CreateFrame("Frame", "MovePanel")
|
||||
Panel:SetPoint("BOTTOMLEFT", 100, 100)
|
||||
Panel:SetSize(200, 80)
|
||||
Panel:SetWidth(200); Panel:SetHeight(80)
|
||||
Panel:EnableMouse(true)
|
||||
Panel:SetMovable(true)
|
||||
Panel:RegisterForDrag("LeftButton")
|
||||
@@ -113,7 +113,7 @@ fn start_moving_on_a_frame_that_is_not_movable_raises_and_moves_nothing() {
|
||||
r#"
|
||||
Fixed = CreateFrame("Frame", "FixedPanel")
|
||||
Fixed:SetPoint("BOTTOMLEFT", 100, 100)
|
||||
Fixed:SetSize(200, 80)
|
||||
Fixed:SetWidth(200); Fixed:SetHeight(80)
|
||||
ok, err = pcall(function() Fixed:StartMoving() end)
|
||||
"#,
|
||||
)
|
||||
@@ -153,7 +153,7 @@ fn stop_moving_or_sizing_is_harmless_with_nothing_moving_and_stops_only_its_own_
|
||||
r#"
|
||||
Other = CreateFrame("Frame", "OtherPanel")
|
||||
Other:SetPoint("BOTTOMLEFT", 400, 400)
|
||||
Other:SetSize(50, 50)
|
||||
Other:SetWidth(50); Other:SetHeight(50)
|
||||
MovePanel:StopMovingOrSizing() -- nothing is moving
|
||||
MovePanel:StopMovingOrSizing() -- twice
|
||||
OtherPanel:StopMovingOrSizing()
|
||||
@@ -191,7 +191,7 @@ fn a_frame_stretched_between_two_anchors_moves_rigidly() {
|
||||
s.run(
|
||||
r#"
|
||||
Back = CreateFrame("Frame", "StretchBack")
|
||||
Back:SetPoint("BOTTOMLEFT", 0, 0); Back:SetSize(800, 600)
|
||||
Back:SetPoint("BOTTOMLEFT", 0, 0); Back:SetWidth(800); Back:SetHeight(600)
|
||||
Stretch = CreateFrame("Frame", "StretchPanel", Back)
|
||||
Stretch:SetPoint("BOTTOMLEFT", Back, "BOTTOMLEFT", 100, 100)
|
||||
Stretch:SetPoint("TOPRIGHT", Back, "BOTTOMLEFT", 300, 200)
|
||||
@@ -229,7 +229,7 @@ fn a_scaled_frame_tracks_the_cursor_one_to_one_on_screen() {
|
||||
r#"
|
||||
Scaled = CreateFrame("Frame", "ScaledPanel")
|
||||
Scaled:SetPoint("BOTTOMLEFT", 100, 100)
|
||||
Scaled:SetSize(100, 100)
|
||||
Scaled:SetWidth(100); Scaled:SetHeight(100)
|
||||
Scaled:SetScale(2)
|
||||
Scaled:SetMovable(true)
|
||||
Scaled:StartMoving()
|
||||
@@ -266,7 +266,7 @@ fn the_three_flags_default_off_round_trip_and_user_placed_is_guarded() {
|
||||
s.run(
|
||||
r#"
|
||||
F = CreateFrame("Frame", "FlagPanel")
|
||||
F:SetPoint("BOTTOMLEFT", 10, 10); F:SetSize(50, 50)
|
||||
F:SetPoint("BOTTOMLEFT", 10, 10); F:SetWidth(50); F:SetHeight(50)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -425,7 +425,7 @@ fn a_title_region_is_a_plain_region_and_creating_it_twice_is_destructive() {
|
||||
r#"
|
||||
TFrame = CreateFrame("Frame", "TFrame")
|
||||
TFrame:SetPoint("BOTTOMLEFT", 100, 100)
|
||||
TFrame:SetSize(200, 80)
|
||||
TFrame:SetWidth(200); TFrame:SetHeight(80)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -549,7 +549,7 @@ fn a_title_region_drag_swallows_the_press_and_ends_on_release() {
|
||||
downs = 0
|
||||
TP = CreateFrame("Frame", "TP")
|
||||
TP:SetPoint("BOTTOMLEFT", 100, 100)
|
||||
TP:SetSize(200, 80)
|
||||
TP:SetWidth(200); TP:SetHeight(80)
|
||||
TP:EnableMouse(true)
|
||||
TP:SetScript("OnMouseDown", function() downs = downs + 1 end)
|
||||
TP:CreateTitleRegion():SetAllPoints(TP)
|
||||
|
||||
@@ -29,10 +29,10 @@ fn setpoint_relative_to_resolves_an_alias_global() {
|
||||
-- The reference UI's own frames.
|
||||
Bar = CreateFrame("Frame", "Bar")
|
||||
Bar:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 100, 50)
|
||||
Bar:SetSize(200, 40)
|
||||
Bar:SetWidth(200); Bar:SetHeight(40)
|
||||
for i = 1, 5 do
|
||||
local b = CreateFrame("Button", "StockButton" .. i, Bar)
|
||||
b:SetSize(30, 30)
|
||||
b:SetWidth(30); b:SetHeight(30)
|
||||
end
|
||||
|
||||
-- The addon's aliases: plain globals, no frame's name (Bartender2's Alias.lua).
|
||||
@@ -84,14 +84,14 @@ fn setpoint_relative_to_resolves_a_region_alias_global() {
|
||||
r#"
|
||||
Panel = CreateFrame("Frame", "Panel")
|
||||
Panel:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 0, 0)
|
||||
Panel:SetSize(400, 300)
|
||||
Panel:SetWidth(400); Panel:SetHeight(300)
|
||||
local tex = Panel:CreateTexture("PanelSwatch", "ARTWORK")
|
||||
tex:SetPoint("BOTTOMLEFT", "Panel", "BOTTOMLEFT", 10, 20)
|
||||
tex:SetSize(50, 50)
|
||||
tex:SetWidth(50); tex:SetHeight(50)
|
||||
Swatch = PanelSwatch -- the alias
|
||||
|
||||
Tag = CreateFrame("Frame", "Tag", Panel)
|
||||
Tag:SetSize(10, 10)
|
||||
Tag:SetWidth(10); Tag:SetHeight(10)
|
||||
Tag:SetPoint("BOTTOMLEFT", "Swatch", "BOTTOMRIGHT", 4, 0)
|
||||
"#,
|
||||
)
|
||||
@@ -117,10 +117,10 @@ fn setpoint_relative_to_ignores_a_non_widget_global() {
|
||||
r#"
|
||||
Host = CreateFrame("Frame", "Host")
|
||||
Host:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 10, 10)
|
||||
Host:SetSize(100, 100)
|
||||
Host:SetWidth(100); Host:SetHeight(100)
|
||||
NotAWidget = 5
|
||||
Child = CreateFrame("Frame", "Child", Host)
|
||||
Child:SetSize(20, 20)
|
||||
Child:SetWidth(20); Child:SetHeight(20)
|
||||
Child:SetPoint("BOTTOMLEFT", "NotAWidget", "BOTTOMRIGHT", 0, 0)
|
||||
"#,
|
||||
)
|
||||
@@ -150,17 +150,17 @@ fn setpoint_relative_to_expands_parent_against_the_first_named_ancestor() {
|
||||
r#"
|
||||
Root = CreateFrame("Frame", "Root")
|
||||
Root:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 0, 0)
|
||||
Root:SetSize(300, 200)
|
||||
Root:SetWidth(300); Root:SetHeight(200)
|
||||
|
||||
Peg = CreateFrame("Frame", "RootPeg", Root)
|
||||
Peg:SetPoint("BOTTOMLEFT", "Root", "BOTTOMLEFT", 20, 30)
|
||||
Peg:SetSize(10, 10)
|
||||
Peg:SetWidth(10); Peg:SetHeight(10)
|
||||
|
||||
-- An ANONYMOUS link in between: the walk skips it and lands on Root.
|
||||
Anon = CreateFrame("Frame", nil, Root)
|
||||
Anon:SetAllPoints(Root)
|
||||
Kid = CreateFrame("Frame", "Kid", Anon)
|
||||
Kid:SetSize(5, 5)
|
||||
Kid:SetWidth(5); Kid:SetHeight(5)
|
||||
Kid:SetPoint("BOTTOMLEFT", "$parentPeg", "BOTTOMRIGHT", 0, 0)
|
||||
"#,
|
||||
)
|
||||
@@ -227,7 +227,7 @@ fn setallpoints_resolves_an_alias_global() {
|
||||
r#"
|
||||
Plate = CreateFrame("Frame", "Plate")
|
||||
Plate:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 30, 40)
|
||||
Plate:SetSize(120, 60)
|
||||
Plate:SetWidth(120); Plate:SetHeight(60)
|
||||
PlateAlias = Plate
|
||||
Overlay = CreateFrame("Frame", "Overlay")
|
||||
Overlay:SetAllPoints("PlateAlias")
|
||||
|
||||
@@ -48,8 +48,15 @@ fn reference() -> Vec<(String, String)> {
|
||||
fn allowed_beyond_1_12() -> HashSet<&'static str> {
|
||||
[
|
||||
// ── our Lua runtime is 5.1 where 1.12's is 5.0 ────────────────────────────────────────
|
||||
// `select` is load-bearing and stays on purpose — our transcribed FrameXML uses it in 16
|
||||
// files as the 5.1 spelling of 5.0's implicit `arg` table.
|
||||
// `select`'s old reason here — "our transcribed FrameXML uses it in 16 files as the 5.1
|
||||
// spelling of 5.0's implicit `arg` table" — **was true and is not any more**: 1751's
|
||||
// stock-first migration retired those files, and `assets/ui`'s seven survivors contain
|
||||
// zero `select(` call sites (measured 2026-09-09, decision 2142). What keeps it is now a
|
||||
// different fact, and a weaker one: it is mlua's, not ours to remove without replacing the
|
||||
// 5.1 varargs it comes with. The corpus reads it as a version probe —
|
||||
// `pfUI/libs/libpredict.lua:141` and `ShaguTweaks/libs/libpredict.lua:82` both branch on
|
||||
// `elseif select and UnitCastingInfo then`, and only our NOT having `UnitCastingInfo`
|
||||
// keeps them off the TBC cast path.
|
||||
//
|
||||
// `_G` is the last one inherited rather than chosen: 1.12's base library does not export
|
||||
// it (an addon reaches the globals with `getfenv(0)`, which is what AceLibrary does), but
|
||||
@@ -64,9 +71,38 @@ fn allowed_beyond_1_12() -> HashSet<&'static str> {
|
||||
"_G",
|
||||
"select",
|
||||
// ── WoW API past 1.12 — 1188 phase 5's list, and the reason that phase exists ─────────
|
||||
// Every one of these predates 1189 and is used by our own transcribed FrameXML today.
|
||||
// Resolving each means either replacing it with its 1.12 equivalent (`UnitPower` →
|
||||
// `UnitMana`, which 1.12 has and we do not) or recording why it stays.
|
||||
// **The reason written here was "every one of these is used by our own transcribed
|
||||
// FrameXML today", and as of 2026-09-09 that is true of exactly ONE of the sixteen**
|
||||
// (`SubmitChatInput`, in the dev-only `ScriptLogFrame.xml`) — a corpus census measured it
|
||||
// and this file's own claim was checked against `assets/ui`'s seven remaining files, name
|
||||
// by name, before this comment was rewritten (decision 2142). 1751's migration retired the
|
||||
// windows that used the rest; the list outlived its justification, which is exactly how
|
||||
// 2118 found the modifier keys still answering booleans years after 0068's reason for it
|
||||
// had gone.
|
||||
//
|
||||
// **What they are NOT is load-bearing for the addon corpus, and 2143 corrects 2142 for
|
||||
// saying so.** The claim written here was that the vanilla ecosystem assumes several of
|
||||
// these exist — `pfUI/modules/loot.lua` calling `wipe` 25 times and defining it nowhere,
|
||||
// so ours "makes that module run at all". That is wrong twice over, and the error is
|
||||
// instructive: **a "1.12 addon" in that corpus is usually a MULTI-CLIENT addon**. pfUI
|
||||
// ships one codebase for three clients and gates per module —
|
||||
// `pfUI:RegisterModule("loot", "vanilla:tbc", …)`, matched by
|
||||
// `strfind(version, pfUI.expansion)` at `pfUI.lua:250` — so a TBC-era habit sits in code
|
||||
// the vanilla client reaches. The `wipe` calls are in `RequestRolls`,
|
||||
// `BuildSpecialRecipientsMenu` and `ClearRolls`: the master-looter's roll helper, not the
|
||||
// loot window, and that path throws on a real 1.12 client too. And `strsplit` was worse —
|
||||
// pfUI has its own `pfUI.api.strsplit` (`api/api.lua:16`) and ShaguTweaks its own
|
||||
// `ShaguTweaks.strsplit`; the bare calls are inside the vendored `libs/libpredict.lua`,
|
||||
// which has TBC branches of its own.
|
||||
//
|
||||
// So our superset was HIDING those latent nil-calls, never enabling anything. The one
|
||||
// real interaction left is the other direction: `AtlasLoot/Core/AtlasLoot.lua:2119`/`:2148`
|
||||
// define GLOBAL `strsplit`/`strtrim` unconditionally, with incompatible pattern-based
|
||||
// two-return semantics, and clobber ours for everything loaded after them — so ours is not
|
||||
// authoritative at runtime either.
|
||||
//
|
||||
// Resolving each means replacing it with its 1.12 equivalent (`UnitPower` → `UnitMana`,
|
||||
// which 1.12 has and we do not), deleting it, or recording why it stays.
|
||||
//
|
||||
// Three left with 1751's gossip work: `GetNumGossipQuests`, `GetGossipQuestInfo` and
|
||||
// `SelectGossipQuest` were benilla's own single-list shape over the gossip packet's quest
|
||||
@@ -332,12 +368,12 @@ fn region_set_parent_relinks_the_draw_owner_and_leaves_anchors_alone() {
|
||||
s.run(
|
||||
r#"
|
||||
A = CreateFrame("Frame", "SPOwnerA")
|
||||
A:SetPoint("BOTTOMLEFT", 0, 0) A:SetSize(100, 50)
|
||||
A:SetPoint("BOTTOMLEFT", 0, 0) A:SetWidth(100); A:SetHeight(50)
|
||||
B = CreateFrame("Frame", "SPOwnerB")
|
||||
B:SetPoint("BOTTOMLEFT", 300, 0) B:SetSize(100, 50)
|
||||
B:SetPoint("BOTTOMLEFT", 300, 0) B:SetWidth(100); B:SetHeight(50)
|
||||
Spark = A:CreateTexture("SPSpark", "ARTWORK")
|
||||
Spark:SetTexture("Interface\\SPGlow")
|
||||
Spark:SetSize(24, 24)
|
||||
Spark:SetWidth(24); Spark:SetHeight(24)
|
||||
Spark:SetPoint("TOPLEFT", 4, -4)
|
||||
"#,
|
||||
)
|
||||
@@ -1161,7 +1197,7 @@ fn every_region_map_method_is_callable_on_a_texture_and_a_fontstring() {
|
||||
s.run(
|
||||
r#"
|
||||
RMOwner = CreateFrame("Frame", "RMOwner")
|
||||
RMOwner:SetPoint("BOTTOMLEFT", 0, 0) RMOwner:SetSize(100, 50)
|
||||
RMOwner:SetPoint("BOTTOMLEFT", 0, 0) RMOwner:SetWidth(100); RMOwner:SetHeight(50)
|
||||
RMTex = RMOwner:CreateTexture("RMTex", "ARTWORK")
|
||||
RMStr = RMOwner:CreateFontString("RMStr", "ARTWORK")
|
||||
"#,
|
||||
@@ -1197,9 +1233,9 @@ fn the_region_map_readers_answer_the_way_the_edges_do() {
|
||||
s.run(
|
||||
r#"
|
||||
RRFrame = CreateFrame("Frame", "RRFrame")
|
||||
RRFrame:SetPoint("BOTTOMLEFT", 100, 200) RRFrame:SetSize(200, 100)
|
||||
RRFrame:SetPoint("BOTTOMLEFT", 100, 200) RRFrame:SetWidth(200); RRFrame:SetHeight(100)
|
||||
RRPlate = RRFrame:CreateTexture("RRPlate", "ARTWORK")
|
||||
RRPlate:SetPoint("TOPLEFT", 10, -10) RRPlate:SetSize(50, 20)
|
||||
RRPlate:SetPoint("TOPLEFT", 10, -10) RRPlate:SetWidth(50); RRPlate:SetHeight(20)
|
||||
-- the sibling-region anchor the real XML uses everywhere
|
||||
RRLabel = RRFrame:CreateFontString("RRLabel", "OVERLAY")
|
||||
RRLabel:SetPoint("LEFT", RRPlate, "RIGHT", 4, 0)
|
||||
|
||||
@@ -40,10 +40,10 @@ fn region_texture_anchored_topleft_resolves_exact_rect() {
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Owner")
|
||||
f:SetPoint("BOTTOMLEFT", 0, 0) -- owner [bottom 0, left 0, top 50, right 100]
|
||||
f:SetSize(100, 50)
|
||||
f:SetWidth(100); f:SetHeight(50)
|
||||
local t = f:CreateTexture("Tex", "ARTWORK")
|
||||
t:SetTexture("Interface\\Icon")
|
||||
t:SetSize(24, 24)
|
||||
t:SetWidth(24); t:SetHeight(24)
|
||||
t:SetPoint("TOPLEFT", 4, -4)
|
||||
"#,
|
||||
)
|
||||
@@ -69,7 +69,7 @@ fn region_fontstring_span_floors_at_one_unit_until_measured() {
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Owner")
|
||||
f:SetPoint("BOTTOMLEFT", 0, 0) -- owner [0, 0, 50, 100]
|
||||
f:SetSize(100, 50)
|
||||
f:SetWidth(100); f:SetHeight(50)
|
||||
local fs = f:CreateFontString("FS", "ARTWORK")
|
||||
fs:SetText("Name")
|
||||
fs:SetJustifyH("LEFT")
|
||||
@@ -116,7 +116,7 @@ fn a_zero_width_solid_spans_eight_units_and_its_artless_twin_gets_no_rect() {
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Owner")
|
||||
f:SetPoint("BOTTOMLEFT", 0, 0) -- owner [0, 0, 50, 100]
|
||||
f:SetSize(100, 50)
|
||||
f:SetWidth(100); f:SetHeight(50)
|
||||
Fill = f:CreateTexture("Fill", "BORDER")
|
||||
Fill:SetTexture(1, 1, 1, 1) -- the <Color> form: an 8x8 solid
|
||||
Fill:SetWidth(0); Fill:SetHeight(13)
|
||||
@@ -153,10 +153,10 @@ fn templateless_lua_region_without_anchors_never_draws() {
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Owner")
|
||||
f:SetPoint("BOTTOMLEFT", 0, 0) -- owner [0, 0, 50, 100]
|
||||
f:SetSize(100, 50)
|
||||
f:SetWidth(100); f:SetHeight(50)
|
||||
local t = f:CreateTexture("Tex", "ARTWORK")
|
||||
t:SetTexture("Interface\\Ring")
|
||||
t:SetSize(24, 24) -- no anchors, and no template ⇒ no rect, no draw
|
||||
t:SetWidth(24); t:SetHeight(24) -- no anchors, and no template ⇒ no rect, no draw
|
||||
assert(t:GetLeft() == nil, "a rect-less region reads nil edges")
|
||||
"#,
|
||||
)
|
||||
@@ -178,10 +178,10 @@ fn region_set_all_points_fills_owner() {
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Owner")
|
||||
f:SetPoint("BOTTOMLEFT", 0, 0) -- owner [0, 0, 50, 100]
|
||||
f:SetSize(100, 50)
|
||||
f:SetWidth(100); f:SetHeight(50)
|
||||
local t = f:CreateTexture("Tex", "ARTWORK")
|
||||
t:SetTexture("Interface\\Fill")
|
||||
t:SetSize(24, 24) -- size present, but setAllPoints wins
|
||||
t:SetWidth(24); t:SetHeight(24) -- size present, but setAllPoints wins
|
||||
t:SetAllPoints()
|
||||
"#,
|
||||
)
|
||||
@@ -205,14 +205,14 @@ fn region_anchors_to_sibling_region_by_name() {
|
||||
s.run(
|
||||
r#"
|
||||
local row = CreateFrame("Button", "Row")
|
||||
row:SetPoint("BOTTOMLEFT", 100, 100); row:SetSize(153, 44)
|
||||
row:SetPoint("BOTTOMLEFT", 100, 100); row:SetWidth(153); row:SetHeight(44)
|
||||
-- Plate first: its target doesn't exist yet at SetPoint time in XML order terms; the
|
||||
-- name lookup happens at SetPoint (both exist by then in real loads), the RECT ordering
|
||||
-- is the fixpoint's job.
|
||||
local plate = row:CreateTexture("RowPlate", "BACKGROUND")
|
||||
plate:SetSize(128, 78)
|
||||
plate:SetWidth(128); plate:SetHeight(78)
|
||||
local slot = row:CreateTexture("RowSlot", "BACKGROUND")
|
||||
slot:SetSize(64, 64)
|
||||
slot:SetWidth(64); slot:SetHeight(64)
|
||||
slot:SetPoint("TOPLEFT", "Row", "TOPLEFT", -13, 13)
|
||||
plate:SetPoint("LEFT", "RowSlot", "RIGHT", -9, -18)
|
||||
"#,
|
||||
@@ -266,9 +266,9 @@ fn set_portrait_texture_binds_unit_token_then_settexture_clears_it() {
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "PFrame")
|
||||
f:SetPoint("TOPLEFT", 0, 0)
|
||||
f:SetSize(100, 100)
|
||||
f:SetWidth(100); f:SetHeight(100)
|
||||
local p = f:CreateTexture("PFramePortrait", "BACKGROUND")
|
||||
p:SetSize(64, 64)
|
||||
p:SetWidth(64); p:SetHeight(64)
|
||||
p:SetPoint("TOPLEFT", 0, 0)
|
||||
SetPortraitTexture(p, "player")
|
||||
"#,
|
||||
@@ -319,7 +319,7 @@ fn benilla_set_booth_texture_binds_square() {
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "DollFrame")
|
||||
f:SetPoint("TOPLEFT", 0, 0)
|
||||
f:SetSize(200, 300)
|
||||
f:SetWidth(200); f:SetHeight(300)
|
||||
local m = f:CreateTexture("DollFrameModel", "ARTWORK")
|
||||
m:SetAllPoints()
|
||||
BenillaSetBoothTexture(m, "paperdoll")
|
||||
@@ -359,11 +359,11 @@ fn long_region_chain_resolves_and_a_frame_binds_to_its_tail() {
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Book")
|
||||
f:SetPoint("TOPLEFT", 0, 0)
|
||||
f:SetSize(384, 512)
|
||||
f:SetWidth(384); f:SetHeight(512)
|
||||
for i = 1, 12 do
|
||||
local t = f:CreateTexture("Line" .. i, "ARTWORK")
|
||||
t:SetTexture("Interface\\Line" .. i)
|
||||
t:SetSize(300, 10)
|
||||
t:SetWidth(300); t:SetHeight(10)
|
||||
if i == 1 then
|
||||
t:SetPoint("TOPLEFT", "Book", "TOPLEFT", 5, -5)
|
||||
else
|
||||
@@ -371,7 +371,7 @@ fn long_region_chain_resolves_and_a_frame_binds_to_its_tail() {
|
||||
end
|
||||
end
|
||||
local b = CreateFrame("Button", "TailButton", f)
|
||||
b:SetSize(147, 41)
|
||||
b:SetWidth(147); b:SetHeight(41)
|
||||
b:SetPoint("TOPLEFT", "Line12", "BOTTOMLEFT", 0, -6)
|
||||
"#,
|
||||
)
|
||||
@@ -414,13 +414,13 @@ fn child_frame_layers_regions_render_after_the_fixpoint() {
|
||||
r#"
|
||||
local parent = CreateFrame("Frame", "Win")
|
||||
parent:SetPoint("TOPLEFT", 10, -10)
|
||||
parent:SetSize(400, 300)
|
||||
parent:SetWidth(400); parent:SetHeight(300)
|
||||
local child = CreateFrame("Frame", "SubPanel", parent)
|
||||
child:SetPoint("TOPLEFT", "Win", "TOPLEFT", 20, -20)
|
||||
child:SetSize(200, 100)
|
||||
child:SetWidth(200); child:SetHeight(100)
|
||||
local fs = child:CreateFontString("SubText", "ARTWORK")
|
||||
fs:SetPoint("TOPLEFT", "SubPanel", "TOPLEFT", 5, -5)
|
||||
fs:SetSize(150, 12)
|
||||
fs:SetWidth(150); fs:SetHeight(12)
|
||||
fs:SetText("hello from the child")
|
||||
local tex = child:CreateTexture("SubTex", "BACKGROUND")
|
||||
tex:SetTexture("Interface\\SubFill")
|
||||
@@ -462,7 +462,7 @@ fn region_alpha_is_its_own_and_multiplies_the_owner_frames() {
|
||||
r#"
|
||||
f = CreateFrame("Frame", "AlphaOwner")
|
||||
f:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 0, 0)
|
||||
f:SetSize(10, 10)
|
||||
f:SetWidth(10); f:SetHeight(10)
|
||||
tex = f:CreateTexture("AlphaTex", "ARTWORK")
|
||||
tex:SetTexture("Interface\\Foo")
|
||||
assert(tex:GetAlpha() == 1, "an untouched region is opaque")
|
||||
@@ -895,11 +895,22 @@ fn the_two_region_leaves_answer_their_own_maps() {
|
||||
"GetTexture",
|
||||
"SetTexCoord",
|
||||
"SetBlendMode",
|
||||
"GetBlendMode",
|
||||
"SetTexCoordModifiesRect",
|
||||
"GetTexCoordModifiesRect",
|
||||
"GetVertexColor",
|
||||
] {
|
||||
assert!(has(&s, "Tex", m), "a Texture answers {m}");
|
||||
assert!(!has(&s, "Str", m), "a FontString must NOT answer {m}");
|
||||
}
|
||||
// `SetRotation` is 1.12's PLAYERMODEL verb (`0x84f1fc`/`0x505f00`), not a region one — it is in
|
||||
// neither leaf map, and ours used to put it on Texture.
|
||||
for leaf in ["Tex", "Str"] {
|
||||
assert!(
|
||||
!has(&s, leaf, "SetRotation"),
|
||||
"SetRotation is not a region method"
|
||||
);
|
||||
}
|
||||
// FontString-only.
|
||||
for m in [
|
||||
"SetText",
|
||||
@@ -1012,7 +1023,7 @@ fn the_size_getters_take_the_author_first_then_the_natural_width_and_wrapped_hei
|
||||
s.run(
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Owner")
|
||||
f:SetPoint("BOTTOMLEFT", 0, 0); f:SetSize(100, 50)
|
||||
f:SetPoint("BOTTOMLEFT", 0, 0); f:SetWidth(100); f:SetHeight(50)
|
||||
Sized = f:CreateFontString("Sized", "ARTWORK")
|
||||
Sized:SetPoint("TOPLEFT"); Sized:SetWidth(300); Sized:SetText("Name")
|
||||
Auto = f:CreateFontString("Auto", "ARTWORK")
|
||||
@@ -1054,7 +1065,7 @@ fn an_empty_string_reads_back_one_unit_and_a_pending_measure_reads_back_zero() {
|
||||
s.run(
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Owner")
|
||||
f:SetPoint("BOTTOMLEFT", 0, 0); f:SetSize(100, 50)
|
||||
f:SetPoint("BOTTOMLEFT", 0, 0); f:SetWidth(100); f:SetHeight(50)
|
||||
Pending = f:CreateFontString("Pending", "ARTWORK")
|
||||
Pending:SetPoint("TOPLEFT"); Pending:SetText("Name")
|
||||
Empty = f:CreateFontString("Empty", "ARTWORK")
|
||||
@@ -1080,7 +1091,7 @@ fn a_textures_getters_report_its_texel_span_on_an_unsized_axis() {
|
||||
s.run(
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "Owner")
|
||||
f:SetPoint("BOTTOMLEFT", 0, 0); f:SetSize(100, 50)
|
||||
f:SetPoint("BOTTOMLEFT", 0, 0); f:SetWidth(100); f:SetHeight(50)
|
||||
Art = f:CreateTexture("Art", "ARTWORK")
|
||||
Art:SetTexture("Interface\\Crest"); Art:SetPoint("TOPLEFT")
|
||||
Half = f:CreateTexture("Half", "ARTWORK")
|
||||
@@ -1214,9 +1225,9 @@ fn set_portrait_texture_folds_the_token_to_lowercase() {
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "NFrame")
|
||||
f:SetPoint("TOPLEFT", 0, 0)
|
||||
f:SetSize(100, 100)
|
||||
f:SetWidth(100); f:SetHeight(100)
|
||||
local p = f:CreateTexture("NFramePortrait", "BACKGROUND")
|
||||
p:SetSize(64, 64)
|
||||
p:SetWidth(64); p:SetHeight(64)
|
||||
p:SetPoint("TOPLEFT", 0, 0)
|
||||
SetPortraitTexture(p, "NPC")
|
||||
"#,
|
||||
@@ -1278,11 +1289,7 @@ fn an_empty_fontstring_reads_back_nil_and_an_edit_box_does_not() {
|
||||
|
||||
// Never written, and every shape of an empty write, all nil.
|
||||
assert_eq!(text_of(&s, "fresh"), None);
|
||||
for write in [
|
||||
r#"fresh:SetText("")"#,
|
||||
r#"fresh:SetText(nil)"#,
|
||||
r#"fresh:SetFormattedText("%s", "")"#,
|
||||
] {
|
||||
for write in [r#"fresh:SetText("")"#, r#"fresh:SetText(nil)"#] {
|
||||
s.run(write).unwrap();
|
||||
assert_eq!(text_of(&s, "fresh"), None, "after `{write}`");
|
||||
}
|
||||
@@ -1386,3 +1393,144 @@ fn an_unresolvable_set_texture_keeps_the_art_the_region_had() {
|
||||
s.run("t:SetTexture(nil)").unwrap();
|
||||
assert!(s.eval::<bool>("return t:GetTexture() == nil").unwrap());
|
||||
}
|
||||
|
||||
/// `Texture:GetBlendMode()` — ONE string, the reference's own spelling, round-tripping whatever
|
||||
/// `SetBlendMode` was given (`0x79a890`/`0x79a950`, table `0x87c128`, argc 1, arity 1, kinds
|
||||
/// `(string?)`).
|
||||
///
|
||||
/// The untouched default is asserted alongside the round trip because it is the load-bearing half:
|
||||
/// `ShaguTweaks/mods/dark-ui-elements.lua:169` guards a recolour with
|
||||
/// `region.GetBlendMode and region:GetBlendMode() == "ADD"`, so the answer an ordinary texture gives
|
||||
/// decides whether the reference's additive art is left alone. `"BLEND"` is the CSimpleTexture
|
||||
/// ctor's `[+0xd0] = 2` (`0x76fc64`).
|
||||
#[test]
|
||||
fn get_blend_mode_answers_one_string_and_defaults_to_the_ctors_blend() {
|
||||
let s = crate::script::UiScript::new().unwrap();
|
||||
s.run(
|
||||
r#"
|
||||
BlendOwner = CreateFrame("Frame", "BlendOwner")
|
||||
BTex = BlendOwner:CreateTexture("BTex", "ARTWORK")
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Arity 1 and kind string, measured the way the shape gate measures it.
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return select('#', BTex:GetBlendMode())")
|
||||
.unwrap(),
|
||||
1,
|
||||
"arity 1"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return type(BTex:GetBlendMode())")
|
||||
.unwrap(),
|
||||
"string",
|
||||
"kind string"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return BTex:GetBlendMode()").unwrap(),
|
||||
"BLEND",
|
||||
"an untouched texture is the ctor's mode 2"
|
||||
);
|
||||
|
||||
// Every one of the enum's five round-trips, including the three the renderer flattens.
|
||||
for mode in ["DISABLE", "ALPHAKEY", "BLEND", "ADD", "MOD"] {
|
||||
s.run(&format!(r#"BTex:SetBlendMode("{mode}")"#)).unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<String>("return BTex:GetBlendMode()").unwrap(),
|
||||
mode,
|
||||
"round trip through the setter"
|
||||
);
|
||||
}
|
||||
// Case-insensitive in, canonical out — the setter coerces, the getter answers the table's name.
|
||||
s.run(r#"BTex:SetBlendMode("add")"#).unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<String>("return BTex:GetBlendMode()").unwrap(),
|
||||
"ADD"
|
||||
);
|
||||
// A name outside the enum leaves the mode alone rather than inventing one.
|
||||
s.run(r#"BTex:SetBlendMode("NOT_A_MODE")"#).unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<String>("return BTex:GetBlendMode()").unwrap(),
|
||||
"ADD",
|
||||
"an unknown name changes nothing"
|
||||
);
|
||||
|
||||
// A FontString is not a Texture: neither half of the pair is on its map.
|
||||
assert!(s
|
||||
.eval::<bool>(
|
||||
r#"local fs = BlendOwner:CreateFontString("BStr", "ARTWORK")
|
||||
return fs.GetBlendMode == nil and fs.SetBlendMode == nil"#
|
||||
)
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
/// `Texture:GetTexCoordModifiesRect()` — the 1.12 predicate return, `1`/`nil` and never a Lua
|
||||
/// boolean (`0x79c120`, table `0x87c128`, argc 1, arity 1, kinds `(nil) | (number)`; decision 2118).
|
||||
/// `pfUI/modules/thirdparty-tbc.lua:319` calls it bare on a Texture.
|
||||
///
|
||||
/// The flag's effect on the region's rect is deliberately NOT wired (see
|
||||
/// `RegionData::tex_coord_modifies_rect`), so this asserts the state and the shape — and asserts
|
||||
/// the un-wiring too, by checking that setting the flag moves no rect: a future change that wires
|
||||
/// the geometry will fail here and have to say so.
|
||||
#[test]
|
||||
fn tex_coord_modifies_rect_is_one_slash_nil_and_moves_no_rect_yet() {
|
||||
let mut s = crate::script::UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
s.run(
|
||||
r#"
|
||||
TCMOwner = CreateFrame("Frame", "TCMOwner")
|
||||
TCMOwner:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 100, 100)
|
||||
TCMOwner:SetWidth(200); TCMOwner:SetHeight(200)
|
||||
TCMTex = TCMOwner:CreateTexture("TCMTex", "ARTWORK")
|
||||
TCMTex:SetTexture("Interface\\TcmArt")
|
||||
TCMTex:SetPoint("BOTTOMLEFT", TCMOwner, "BOTTOMLEFT", 0, 0)
|
||||
TCMTex:SetWidth(64); TCMTex:SetHeight(32)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
s.resolve();
|
||||
let before = region_tex_rect(&s, "TcmArt");
|
||||
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return select('#', TCMTex:GetTexCoordModifiesRect())")
|
||||
.unwrap(),
|
||||
1,
|
||||
"arity 1"
|
||||
);
|
||||
assert!(
|
||||
s.eval::<bool>("return TCMTex:GetTexCoordModifiesRect() == nil")
|
||||
.unwrap(),
|
||||
"unset is nil, not false"
|
||||
);
|
||||
|
||||
s.run("TCMTex:SetTexCoordModifiesRect(1)").unwrap();
|
||||
assert!(
|
||||
s.eval::<bool>("return TCMTex:GetTexCoordModifiesRect() == 1")
|
||||
.unwrap(),
|
||||
"set is the NUMBER 1, not true"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return type(TCMTex:GetTexCoordModifiesRect())")
|
||||
.unwrap(),
|
||||
"number",
|
||||
"kind number, never boolean"
|
||||
);
|
||||
|
||||
// The flag is state only: a SetTexCoord under it still leaves the rect where the anchors and
|
||||
// the size put it. When the geometry leg is built, this assertion is the one that must change.
|
||||
s.run("TCMTex:SetTexCoord(0, 0.25, 0, 0.5)").unwrap();
|
||||
s.resolve();
|
||||
assert_eq!(
|
||||
region_tex_rect(&s, "TcmArt"),
|
||||
before,
|
||||
"the flag does not (yet) let SetTexCoord re-derive the region's rect"
|
||||
);
|
||||
|
||||
s.run("TCMTex:SetTexCoordModifiesRect(nil)").unwrap();
|
||||
assert!(
|
||||
s.eval::<bool>("return TCMTex:GetTexCoordModifiesRect() == nil")
|
||||
.unwrap(),
|
||||
"cleared back to nil"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,11 @@ fn scrollframe_methods_exist_only_on_scrollframes() {
|
||||
return (type(sf.SetScrollChild) == "function") and (plain.SetScrollChild == nil)
|
||||
and (type(sf.SetVerticalScroll) == "function") and (plain.SetVerticalScroll == nil)
|
||||
and (type(sf.GetVerticalScrollRange) == "function")
|
||||
-- the horizontal trio lives in the same class table, not on every Frame
|
||||
and (type(sf.SetHorizontalScroll) == "function") and (plain.SetHorizontalScroll == nil)
|
||||
and (type(sf.GetHorizontalScroll) == "function") and (plain.GetHorizontalScroll == nil)
|
||||
and (type(sf.GetHorizontalScrollRange) == "function")
|
||||
and (plain.GetHorizontalScrollRange == nil)
|
||||
and (type(sf.Show) == "function") -- base methods still reachable through the fallback
|
||||
"#,
|
||||
)
|
||||
@@ -81,13 +86,13 @@ fn scroll_child_top_tracks_vertical_scroll_unclamped_then_restores_on_clear() {
|
||||
r#"
|
||||
local frame = CreateFrame("ScrollFrame", "SF")
|
||||
frame:SetPoint("TOPLEFT", 0, -100) -- screen top 600 -> frame top 500
|
||||
frame:SetSize(300, 200) -- frame bottom 300
|
||||
frame:SetWidth(300); frame:SetHeight(200) -- frame bottom 300
|
||||
|
||||
local child = CreateFrame("Frame", "Child")
|
||||
-- Authored anchor: relative to the screen (child has no parent) — overridden while scrolled,
|
||||
-- and must be exactly what re-applies once the scroll child is cleared.
|
||||
child:SetPoint("TOPLEFT", 20, -20)
|
||||
child:SetSize(300, 600)
|
||||
child:SetWidth(300); child:SetHeight(600)
|
||||
local marker = child:CreateTexture(nil, "ARTWORK")
|
||||
marker:SetTexture("marker:child")
|
||||
marker:SetAllPoints() -- templateless Lua regions carry no implicit anchor (decision 1310)
|
||||
@@ -170,10 +175,10 @@ fn set_vertical_scroll_fires_on_vertical_scroll_only_on_a_change() {
|
||||
Fires = {}
|
||||
local frame = CreateFrame("ScrollFrame", "SF")
|
||||
frame:SetPoint("TOPLEFT", 0, -100)
|
||||
frame:SetSize(300, 200)
|
||||
frame:SetWidth(300); frame:SetHeight(200)
|
||||
frame:SetScript("OnVerticalScroll", function() table.insert(Fires, arg1) end)
|
||||
local child = CreateFrame("Frame", "Child")
|
||||
child:SetSize(300, 600)
|
||||
child:SetWidth(300); child:SetHeight(600)
|
||||
frame:SetScrollChild(child)
|
||||
frame:SetVerticalScroll(0) -- already 0: nothing
|
||||
frame:SetVerticalScroll(40) -- a change
|
||||
@@ -199,9 +204,9 @@ fn vertical_scroll_range_is_live_and_zero_when_unresolved_or_childless_or_shorte
|
||||
r#"
|
||||
local frame = CreateFrame("ScrollFrame", "SF")
|
||||
frame:SetPoint("TOPLEFT", 0, -100)
|
||||
frame:SetSize(300, 200)
|
||||
frame:SetWidth(300); frame:SetHeight(200)
|
||||
local child = CreateFrame("Frame", "Child")
|
||||
child:SetSize(300, 600)
|
||||
child:SetWidth(300); child:SetHeight(600)
|
||||
frame:SetScrollChild(child)
|
||||
"#,
|
||||
)
|
||||
@@ -221,7 +226,7 @@ fn vertical_scroll_range_is_live_and_zero_when_unresolved_or_childless_or_shorte
|
||||
);
|
||||
|
||||
// A child shorter than the frame: 0, not negative.
|
||||
s.run("Child:SetSize(300, 150)").unwrap();
|
||||
s.run("Child:SetWidth(300); Child:SetHeight(150)").unwrap();
|
||||
s.resolve();
|
||||
assert_eq!(
|
||||
s.eval::<f32>("return SF:GetVerticalScrollRange()").unwrap(),
|
||||
@@ -251,10 +256,10 @@ fn vertical_scroll_range_is_local_units_on_a_scaled_frame() {
|
||||
r#"
|
||||
local frame = CreateFrame("ScrollFrame", "SF")
|
||||
frame:SetPoint("TOPLEFT", 0, -100)
|
||||
frame:SetSize(300, 200)
|
||||
frame:SetWidth(300); frame:SetHeight(200)
|
||||
frame:SetScale(0.5)
|
||||
local child = CreateFrame("Frame", "Child", frame)
|
||||
child:SetSize(300, 600)
|
||||
child:SetWidth(300); child:SetHeight(600)
|
||||
frame:SetScrollChild(child)
|
||||
"#,
|
||||
)
|
||||
@@ -299,9 +304,9 @@ fn vertical_scroll_fires_as_given_and_update_rect_fires_range_changed() {
|
||||
r#"
|
||||
local frame = CreateFrame("ScrollFrame", "SF")
|
||||
frame:SetPoint("TOPLEFT", 0, -100)
|
||||
frame:SetSize(300, 200)
|
||||
frame:SetWidth(300); frame:SetHeight(200)
|
||||
local child = CreateFrame("Frame", "Child")
|
||||
child:SetSize(300, 600)
|
||||
child:SetWidth(300); child:SetHeight(600)
|
||||
frame:SetScrollChild(child)
|
||||
|
||||
seen_v = nil
|
||||
@@ -345,9 +350,9 @@ fn update_scroll_child_rect_sees_a_same_tick_resize_with_no_intervening_resolve(
|
||||
r#"
|
||||
local frame = CreateFrame("ScrollFrame", "SF")
|
||||
frame:SetPoint("TOPLEFT", 0, -100)
|
||||
frame:SetSize(300, 200)
|
||||
frame:SetWidth(300); frame:SetHeight(200)
|
||||
local child = CreateFrame("Frame", "Child")
|
||||
child:SetSize(300, 200) -- starts exactly frame-height: range 0
|
||||
child:SetWidth(300); child:SetHeight(200) -- starts exactly frame-height: range 0
|
||||
frame:SetScrollChild(child)
|
||||
"#,
|
||||
)
|
||||
@@ -358,7 +363,7 @@ fn update_scroll_child_rect_sees_a_same_tick_resize_with_no_intervening_resolve(
|
||||
r#"
|
||||
seen_hi = nil
|
||||
SF:SetScript("OnScrollRangeChanged", function(self, lo, hi) seen_hi = hi end)
|
||||
Child:SetSize(300, 600) -- grows well past the frame — all in this ONE tick
|
||||
Child:SetWidth(300); Child:SetHeight(600) -- grows well past the frame — all in this ONE tick
|
||||
SF:UpdateScrollChildRect()
|
||||
"#,
|
||||
)
|
||||
@@ -389,10 +394,10 @@ fn extract_clips_the_scroll_childs_whole_subtree_and_leaves_a_sibling_unclipped(
|
||||
r#"
|
||||
local frame = CreateFrame("ScrollFrame", "SF")
|
||||
frame:SetPoint("TOPLEFT", 0, -100) -- top 500
|
||||
frame:SetSize(300, 200) -- bottom 300, right 300
|
||||
frame:SetWidth(300); frame:SetHeight(200) -- bottom 300, right 300
|
||||
|
||||
local child = CreateFrame("Frame", "Child")
|
||||
child:SetSize(300, 600)
|
||||
child:SetWidth(300); child:SetHeight(600)
|
||||
frame:SetScrollChild(child)
|
||||
local cm = child:CreateTexture(nil, "ARTWORK")
|
||||
cm:SetTexture("marker:child")
|
||||
@@ -400,14 +405,14 @@ fn extract_clips_the_scroll_childs_whole_subtree_and_leaves_a_sibling_unclipped(
|
||||
-- a grandchild FRAME (a real arena descendant of the scroll child) with its own region
|
||||
local grand = CreateFrame("Frame", "Grand", child)
|
||||
grand:SetPoint("TOPLEFT", child, "TOPLEFT", 0, 0)
|
||||
grand:SetSize(50, 50)
|
||||
grand:SetWidth(50); grand:SetHeight(50)
|
||||
local gm = grand:CreateTexture(nil, "ARTWORK")
|
||||
gm:SetTexture("marker:grand")
|
||||
|
||||
-- a sibling entirely outside the ScrollFrame
|
||||
local sib = CreateFrame("Frame", "Sib")
|
||||
sib:SetPoint("TOPLEFT", 0, 0)
|
||||
sib:SetSize(50, 50)
|
||||
sib:SetWidth(50); sib:SetHeight(50)
|
||||
local sm = sib:CreateTexture(nil, "ARTWORK")
|
||||
sm:SetTexture("marker:sib")
|
||||
"#,
|
||||
@@ -441,11 +446,11 @@ fn nested_scrollframes_intersect_their_clip_rects() {
|
||||
r#"
|
||||
local outer = CreateFrame("ScrollFrame", "Outer")
|
||||
outer:SetPoint("TOPLEFT", 0, -50) -- top 550
|
||||
outer:SetSize(400, 300) -- bottom 250, right 400
|
||||
outer:SetWidth(400); outer:SetHeight(300) -- bottom 250, right 400
|
||||
local om = outer:CreateTexture(nil, "ARTWORK"); om:SetTexture("marker:outer")
|
||||
|
||||
local c1 = CreateFrame("Frame", "C1")
|
||||
c1:SetSize(400, 1000)
|
||||
c1:SetWidth(400); c1:SetHeight(1000)
|
||||
outer:SetScrollChild(c1)
|
||||
local c1m = c1:CreateTexture(nil, "ARTWORK"); c1m:SetTexture("marker:c1")
|
||||
|
||||
@@ -454,11 +459,11 @@ fn nested_scrollframes_intersect_their_clip_rects() {
|
||||
-- Outer's right edge — an intersection distinct from either rect alone.
|
||||
local inner = CreateFrame("ScrollFrame", "Inner", c1)
|
||||
inner:SetPoint("TOPLEFT", "C1", "TOPLEFT", 350, -30)
|
||||
inner:SetSize(200, 150)
|
||||
inner:SetWidth(200); inner:SetHeight(150)
|
||||
local im = inner:CreateTexture(nil, "ARTWORK"); im:SetTexture("marker:inner")
|
||||
|
||||
local c2 = CreateFrame("Frame", "C2", inner)
|
||||
c2:SetSize(200, 500)
|
||||
c2:SetWidth(200); c2:SetHeight(500)
|
||||
inner:SetScrollChild(c2)
|
||||
local leaf = c2:CreateTexture(nil, "ARTWORK"); leaf:SetTexture("marker:leaf")
|
||||
"#,
|
||||
@@ -514,16 +519,16 @@ fn hit_test_denies_a_button_clipped_out_and_admits_it_once_scrolled_into_view()
|
||||
r#"
|
||||
local frame = CreateFrame("ScrollFrame", "SF")
|
||||
frame:SetPoint("TOPLEFT", 0, -100) -- top 500
|
||||
frame:SetSize(300, 200) -- bottom 300
|
||||
frame:SetWidth(300); frame:SetHeight(200) -- bottom 300
|
||||
frame:EnableMouse(false) -- isolate the button's own clip-gated hit (§5's subject)
|
||||
|
||||
local child = CreateFrame("Frame", "Child")
|
||||
child:SetSize(300, 600)
|
||||
child:SetWidth(300); child:SetHeight(600)
|
||||
frame:SetScrollChild(child)
|
||||
|
||||
local btn = CreateFrame("Button", "Btn", child)
|
||||
btn:SetPoint("TOPLEFT", "Child", "TOPLEFT", 0, -400)
|
||||
btn:SetSize(50, 20)
|
||||
btn:SetWidth(50); btn:SetHeight(20)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -568,9 +573,9 @@ fn update_scroll_child_rect_measures_the_childs_text_before_it_reads_the_range()
|
||||
s.run(
|
||||
r#"
|
||||
local sf = CreateFrame("ScrollFrame", "SF", UIParent)
|
||||
sf:SetPoint("TOPLEFT", 0, 0); sf:SetSize(100, 50)
|
||||
sf:SetPoint("TOPLEFT", 0, 0); sf:SetWidth(100); sf:SetHeight(50)
|
||||
local child = CreateFrame("Frame", "SFChild", sf)
|
||||
child:SetSize(100, 50); sf:SetScrollChild(child)
|
||||
child:SetWidth(100); child:SetHeight(50); sf:SetScrollChild(child)
|
||||
local fs = child:CreateFontString("SFText", "ARTWORK")
|
||||
fs:SetPoint("TOPLEFT", 0, 0); fs:SetWidth(70)
|
||||
sf:SetScript("OnScrollRangeChanged", function() RANGE = arg2 end)
|
||||
@@ -586,3 +591,172 @@ fn update_scroll_child_rect_measures_the_childs_text_before_it_reads_the_range()
|
||||
"56px of measured text under a 50px frame: the range is known at the call, not a frame later"
|
||||
);
|
||||
}
|
||||
|
||||
/// The horizontal axis is the vertical one's byte-clone (`0x786d30` against `0x786db0`), so this is
|
||||
/// the vertical trio's test on the other axis: the offset is stored VERBATIM and re-anchors the
|
||||
/// child, `OnHorizontalScroll` fires only on a real change, and the range is
|
||||
/// `max(0, subtreeWidth - frameWidth)`.
|
||||
///
|
||||
/// **The sign is the point.** `0x787100` hands both offsets to `SetPoint(TOPLEFT, self, TOPLEFT,
|
||||
/// +hScroll, +vScroll)` unnegated, and x grows right — so a positive horizontal pushes the child
|
||||
/// RIGHT where a positive vertical lifts it. `aux-addon/tabs/search/filter.lua:315-322` corroborates
|
||||
/// it from the caller's side (it bounds x into `[frameWidth - contentWidth - 10, 0]` and y into
|
||||
/// `[0, contentHeight - frameHeight]`), which is why it is asserted here rather than assumed.
|
||||
#[test]
|
||||
fn scroll_child_left_tracks_horizontal_scroll_unclamped_and_fires_on_change() {
|
||||
let mut s = script();
|
||||
s.set_screen_size(800.0, 600.0);
|
||||
|
||||
s.run(
|
||||
r#"
|
||||
local frame = CreateFrame("ScrollFrame", "HSF")
|
||||
frame:SetPoint("TOPLEFT", 0, -100) -- frame left 0, top 500
|
||||
frame:SetWidth(300); frame:SetHeight(200) -- frame right 300
|
||||
|
||||
local child = CreateFrame("Frame", "HChild")
|
||||
child:SetPoint("TOPLEFT", 20, -20)
|
||||
child:SetWidth(500); child:SetHeight(200)
|
||||
local marker = child:CreateTexture(nil, "ARTWORK")
|
||||
marker:SetTexture("marker:hchild")
|
||||
marker:SetAllPoints()
|
||||
|
||||
frame:SetScrollChild(child)
|
||||
hfired = {}
|
||||
frame:SetScript("OnHorizontalScroll", function() table.insert(hfired, arg1) end)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
s.resolve();
|
||||
|
||||
assert_eq!(
|
||||
s.eval::<f32>("return HSF:GetHorizontalScroll()").unwrap(),
|
||||
0.0,
|
||||
"a fresh ScrollFrame is at 0 on both axes"
|
||||
);
|
||||
assert_eq!(
|
||||
marker_rect(&s.extract(), "marker:hchild").map(|r| r.left),
|
||||
Some(0.0),
|
||||
"horizontal=0 -> child left = frame left"
|
||||
);
|
||||
|
||||
// Positive pushes the child RIGHT (the reference's unnegated xOff) …
|
||||
s.run("HSF:SetHorizontalScroll(40)").unwrap();
|
||||
s.resolve();
|
||||
assert_eq!(
|
||||
marker_rect(&s.extract(), "marker:hchild").map(|r| r.left),
|
||||
Some(40.0),
|
||||
"a positive horizontal offset moves the child right"
|
||||
);
|
||||
// … and it is the NEGATIVE direction that scrolls right, which is the half aux-addon relies on.
|
||||
s.run("HSF:SetHorizontalScroll(-60)").unwrap();
|
||||
s.resolve();
|
||||
assert_eq!(
|
||||
marker_rect(&s.extract(), "marker:hchild").map(|r| r.left),
|
||||
Some(-60.0),
|
||||
"a negative horizontal offset scrolls right"
|
||||
);
|
||||
|
||||
// No clamp — the range is 500-300 = 200 and 450 is stored and applied whole (decision 2017's
|
||||
// law, which is `0x786d30`'s as much as `0x786db0`'s: neither reads the range).
|
||||
assert_eq!(
|
||||
s.eval::<f32>("return HSF:GetHorizontalScrollRange()")
|
||||
.unwrap(),
|
||||
200.0,
|
||||
"range = subtree width - frame width"
|
||||
);
|
||||
s.run("HSF:SetHorizontalScroll(450)").unwrap();
|
||||
s.resolve();
|
||||
assert_eq!(
|
||||
s.eval::<f32>("return HSF:GetHorizontalScroll()").unwrap(),
|
||||
450.0,
|
||||
"stored verbatim, past the range"
|
||||
);
|
||||
|
||||
// Arity/kind, the way the shape gate measures them.
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return select('#', HSF:GetHorizontalScroll())")
|
||||
.unwrap(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return select('#', HSF:GetHorizontalScrollRange())")
|
||||
.unwrap(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return type(HSF:GetHorizontalScroll())")
|
||||
.unwrap(),
|
||||
"number"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return type(HSF:GetHorizontalScrollRange())")
|
||||
.unwrap(),
|
||||
"number"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return select('#', HSF:SetHorizontalScroll(450))")
|
||||
.unwrap(),
|
||||
0,
|
||||
"the setter answers nothing"
|
||||
);
|
||||
|
||||
// The change-gate: four setter calls moved the value (40, -60, 450, and 450 again is not one).
|
||||
let fired: Vec<f32> = (1..=s.eval::<i64>("return table.getn(hfired)").unwrap())
|
||||
.map(|i| s.eval::<f32>(&format!("return hfired[{i}]")).unwrap())
|
||||
.collect();
|
||||
assert_eq!(
|
||||
fired,
|
||||
vec![40.0, -60.0, 450.0],
|
||||
"OnHorizontalScroll fires once per ACTUAL change, with the new offset"
|
||||
);
|
||||
|
||||
// The two axes are independent state, not one offset behind two names.
|
||||
s.run("HSF:SetVerticalScroll(25)").unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<f32>("return HSF:GetHorizontalScroll()").unwrap(),
|
||||
450.0
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<f32>("return HSF:GetVerticalScroll()").unwrap(),
|
||||
25.0
|
||||
);
|
||||
}
|
||||
|
||||
/// `UpdateScrollChildRect` notifies `OnScrollRangeChanged(self, xRange, yRange)` — **horizontal
|
||||
/// first**. Byte-derived, not guessed: the vertical result leaves `0x786e30` on top of the x87
|
||||
/// stack, is `fstp`'d first and so pushed deepest, landing as `arg2` (wow-re
|
||||
/// `scrollframe-offset-and-range-law.md` §3.6 — two of that round's own workers published it the
|
||||
/// other way round, which is why it is asserted here). `arg1` was a hardcoded `0.0` for as long as
|
||||
/// there was no horizontal range to put in it.
|
||||
#[test]
|
||||
fn update_scroll_child_rect_reports_both_ranges_horizontal_first() {
|
||||
let mut s = script();
|
||||
s.set_screen_size(800.0, 600.0);
|
||||
s.run(
|
||||
r#"
|
||||
local frame = CreateFrame("ScrollFrame", "BSF")
|
||||
frame:SetPoint("TOPLEFT", 0, -100)
|
||||
frame:SetWidth(300); frame:SetHeight(200)
|
||||
local child = CreateFrame("Frame", "BChild")
|
||||
child:SetPoint("TOPLEFT", 0, 0)
|
||||
child:SetWidth(500); child:SetHeight(600)
|
||||
frame:SetScrollChild(child)
|
||||
seen = nil
|
||||
frame:SetScript("OnScrollRangeChanged", function() seen = { arg1, arg2 } end)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
s.resolve();
|
||||
s.run("BSF:UpdateScrollChildRect()").unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<f32>("return seen[1]").unwrap(),
|
||||
200.0,
|
||||
"arg1 is the HORIZONTAL range (500 - 300)"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<f32>("return seen[2]").unwrap(),
|
||||
400.0,
|
||||
"arg2 is the VERTICAL range (600 - 200)"
|
||||
);
|
||||
assert!(s.take_errors().is_empty());
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ fn page(s: &UiScript) {
|
||||
f:SetTextColor(0.18, 0.12, 0.06)
|
||||
Page = CreateFrame("SimpleHTML", "Page")
|
||||
Page:SetPoint("TOPLEFT", 0, 0)
|
||||
Page:SetSize(270, 304)
|
||||
Page:SetWidth(270); Page:SetHeight(304)
|
||||
Page:SetFontObject("P", ItemTextFontNormal)
|
||||
"#,
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@ fn a_resize_fires_on_size_changed_and_a_move_does_not() {
|
||||
fires, w, h = 0, nil, nil
|
||||
Panel = CreateFrame("Frame", "SizedPanel")
|
||||
Panel:SetPoint("BOTTOMLEFT", 100, 100)
|
||||
Panel:SetSize(200, 50)
|
||||
Panel:SetWidth(200); Panel:SetHeight(50)
|
||||
Panel:SetScript("OnSizeChanged", function(self, aw, ah)
|
||||
fires = fires + 1 w = aw h = ah
|
||||
end)
|
||||
@@ -70,7 +70,7 @@ fn an_anchor_driven_resize_fires_it_too() {
|
||||
fires, w = 0, nil
|
||||
Parent = CreateFrame("Frame", "SizeParent")
|
||||
Parent:SetPoint("BOTTOMLEFT", 0, 0)
|
||||
Parent:SetSize(300, 200)
|
||||
Parent:SetWidth(300); Parent:SetHeight(200)
|
||||
Child = CreateFrame("Frame", "SizeChild", Parent)
|
||||
Child:SetPoint("BOTTOMLEFT", Parent, "BOTTOMLEFT", 0, 0)
|
||||
Child:SetPoint("TOPRIGHT", Parent, "TOPRIGHT", 0, 0)
|
||||
@@ -107,7 +107,7 @@ fn a_handler_that_resizes_its_own_frame_settles_instead_of_spinning() {
|
||||
fires = 0
|
||||
Sq = CreateFrame("Frame", "SquarePanel")
|
||||
Sq:SetPoint("BOTTOMLEFT", 0, 0)
|
||||
Sq:SetSize(200, 50)
|
||||
Sq:SetWidth(200); Sq:SetHeight(50)
|
||||
-- The idiom: "keep me square". It writes back into the very input that fired it.
|
||||
Sq:SetScript("OnSizeChanged", function(self, aw, ah)
|
||||
fires = fires + 1
|
||||
@@ -148,7 +148,7 @@ fn set_script_on_size_changed_is_accepted_because_the_resolve_pass_fires_it() {
|
||||
ran = false
|
||||
Accepted = CreateFrame("Frame", "SizeAccepted")
|
||||
Accepted:SetPoint("BOTTOMLEFT", 0, 0)
|
||||
Accepted:SetSize(10, 10)
|
||||
Accepted:SetWidth(10); Accepted:SetHeight(10)
|
||||
Accepted:SetScript("OnSizeChanged", function() ran = true end)
|
||||
"#,
|
||||
)
|
||||
@@ -180,7 +180,7 @@ fn the_on_size_changed_watch_list_is_exactly_the_frames_carrying_the_script() {
|
||||
fires = 0
|
||||
Watched = CreateFrame("Frame", "WatchedPanel")
|
||||
Watched:SetPoint("BOTTOMLEFT", 0, 0)
|
||||
Watched:SetSize(10, 10)
|
||||
Watched:SetWidth(10); Watched:SetHeight(10)
|
||||
Other = CreateFrame("Frame", "UnwatchedPanel")
|
||||
Other:SetScript("OnShow", function() end) -- another kind never enrols
|
||||
local bump = function() fires = fires + 1 end
|
||||
@@ -199,7 +199,8 @@ fn the_on_size_changed_watch_list_is_exactly_the_frames_carrying_the_script() {
|
||||
assert_eq!(watched(&s), 1, "one frame carries the script");
|
||||
|
||||
s.resolve();
|
||||
s.run("Watched:SetSize(40, 40)").unwrap();
|
||||
s.run("Watched:SetWidth(40); Watched:SetHeight(40)")
|
||||
.unwrap();
|
||||
s.resolve();
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return fires").unwrap(),
|
||||
|
||||
@@ -27,10 +27,10 @@ fn slider_thumb_draws_at_value_fraction_along_the_track() {
|
||||
local sl = CreateFrame("Slider", "SlRender")
|
||||
-- A vertical scrollbar: 16 wide, 100 tall, bottom-left at (100, 100) -> track y in [100, 200].
|
||||
sl:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 100, 100)
|
||||
sl:SetSize(16, 100)
|
||||
sl:SetWidth(16); sl:SetHeight(100)
|
||||
sl:SetThumbTexture("Interface\\Buttons\\UI-ScrollBar-Knob")
|
||||
local t = sl:GetThumbTexture()
|
||||
t:SetSize(16, 16)
|
||||
t:SetWidth(16); t:SetHeight(16)
|
||||
sl:SetMinMaxValues(0, 100)
|
||||
sl:SetValue(0)
|
||||
"#,
|
||||
@@ -73,9 +73,9 @@ fn slider_thumb_drag_maps_cursor_to_value() {
|
||||
r#"
|
||||
bar = CreateFrame("Slider", "SlDrag")
|
||||
bar:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 100, 100)
|
||||
bar:SetSize(16, 100)
|
||||
bar:SetWidth(16); bar:SetHeight(100)
|
||||
bar:SetThumbTexture("Interface\\Buttons\\UI-ScrollBar-Knob")
|
||||
bar:GetThumbTexture():SetSize(16, 16)
|
||||
local thumb = bar:GetThumbTexture(); thumb:SetWidth(16); thumb:SetHeight(16)
|
||||
bar:SetMinMaxValues(0, 100)
|
||||
bar:SetValue(0)
|
||||
"#,
|
||||
@@ -104,16 +104,16 @@ fn slider_thumb_drag_maps_cursor_to_value() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slider_track_press_seats_the_thumb_and_a_disabled_slider_ignores_it() {
|
||||
fn slider_track_press_seats_the_thumb_and_a_mouseless_slider_ignores_it() {
|
||||
let mut s = script();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
s.run(
|
||||
r#"
|
||||
bar = CreateFrame("Slider", "SlTrack")
|
||||
bar:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 100, 100)
|
||||
bar:SetSize(16, 100)
|
||||
bar:SetWidth(16); bar:SetHeight(100)
|
||||
bar:SetThumbTexture("Interface\\Buttons\\UI-ScrollBar-Knob")
|
||||
bar:GetThumbTexture():SetSize(16, 16)
|
||||
local thumb = bar:GetThumbTexture(); thumb:SetWidth(16); thumb:SetHeight(16)
|
||||
bar:SetMinMaxValues(0, 100)
|
||||
bar:SetValue(0)
|
||||
fired = {}
|
||||
@@ -142,18 +142,25 @@ fn slider_track_press_seats_the_thumb_and_a_disabled_slider_ignores_it() {
|
||||
assert_eq!(v, 100.0, "the gesture drags on without re-grabbing");
|
||||
s.mouse_button(108.0, 108.0, "LeftButton", false);
|
||||
|
||||
// A disabled slider ignores a press anywhere — thumb and track alike.
|
||||
s.run(r#"SlTrack:SetValue(0); SlTrack:Disable(); fired = {}"#)
|
||||
// A slider the mouse cannot reach ignores a press anywhere — thumb and track alike. This was
|
||||
// `SlTrack:Disable()` until the Button-only `Enable`/`Disable`/`IsEnabled` trio came off the
|
||||
// Slider table (1.12 registers them on `0x879d00` alone, and the Slider's LoadXML has no
|
||||
// `enabled` attribute either). `EnableMouse(false)` is how the reference keeps a slider off a
|
||||
// press, and it gates upstream in the hit test rather than inside `begin_drag`.
|
||||
s.run(r#"SlTrack:SetValue(0); SlTrack:EnableMouse(false); fired = {}"#)
|
||||
.unwrap();
|
||||
for y in [192.0, 150.0] {
|
||||
s.mouse_button(108.0, y, "LeftButton", true);
|
||||
s.mouse_move(108.0, 130.0);
|
||||
let v: f32 = s.eval("return SlTrack:GetValue()").unwrap();
|
||||
assert_eq!(v, 0.0, "disabled slider does not move (press at y={y})");
|
||||
assert_eq!(
|
||||
v, 0.0,
|
||||
"mouse-disabled slider does not move (press at y={y})"
|
||||
);
|
||||
s.mouse_button(108.0, 130.0, "LeftButton", false);
|
||||
}
|
||||
let n: usize = s.eval("return table.getn(fired)").unwrap();
|
||||
assert_eq!(n, 0, "disabled: no OnValueChanged at all");
|
||||
assert_eq!(n, 0, "mouse disabled: no OnValueChanged at all");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -235,17 +242,30 @@ fn slider_setvalue_fires_onvaluechanged_only_on_change() {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// `Enable`/`Disable`/`IsEnabled` are a **Button** trio in 1.12 (table `0x879d00`), and a Slider
|
||||
/// must not answer them: a duck-typing addon that branches on `if widget.IsEnabled then` reads a
|
||||
/// superset as the wrong class (1189/1250 §5). This asserts the removal *and* its control — the
|
||||
/// same three names still present on a Button, so a regression that emptied the Button table would
|
||||
/// fail here rather than pass by accident.
|
||||
#[test]
|
||||
fn slider_enable_disable_roundtrips() {
|
||||
fn a_slider_does_not_answer_the_buttons_enable_trio() {
|
||||
let s = script();
|
||||
s.run(
|
||||
r#"
|
||||
local sl = CreateFrame("Slider", "SlEnable")
|
||||
assert(sl:IsEnabled(), "enabled by ctor")
|
||||
sl:Disable()
|
||||
assert(not sl:IsEnabled(), "disabled")
|
||||
sl:Enable()
|
||||
assert(sl:IsEnabled(), "re-enabled")
|
||||
for _, name in ipairs({ "Enable", "Disable", "IsEnabled" }) do
|
||||
assert(sl[name] == nil, "Slider must not answer " .. name)
|
||||
end
|
||||
local b = CreateFrame("Button", "SlEnableControl")
|
||||
for _, name in ipairs({ "Enable", "Disable", "IsEnabled" }) do
|
||||
assert(type(b[name]) == "function", "Button still answers " .. name)
|
||||
end
|
||||
-- The Button's own predicate is the NUMBER 1 / the NUMBER 0, never a Lua boolean — its
|
||||
-- false leg is 0 rather than nil, settled per body at `0x7800b0`'s `setne`+`fild`
|
||||
-- (wow-re `button-enabled-state.md`; decision 2118's `binding_abi::flag` doc).
|
||||
assert(b:IsEnabled() == 1, "1, not true")
|
||||
b:Disable()
|
||||
assert(b:IsEnabled() == 0, "0, not false and not nil")
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -279,9 +299,9 @@ fn slider_scrollbar_wiring_does_not_recurse() {
|
||||
r#"
|
||||
local sf = CreateFrame("ScrollFrame", "SlSF")
|
||||
sf:SetPoint("TOPLEFT", nil, "TOPLEFT", 0, 0)
|
||||
sf:SetSize(100, 100)
|
||||
sf:SetWidth(100); sf:SetHeight(100)
|
||||
local child = CreateFrame("Frame", "SlSFChild", sf)
|
||||
child:SetSize(100, 300) -- 200px taller than the frame -> scroll range 200
|
||||
child:SetWidth(100); child:SetHeight(300) -- 200px taller than the frame -> scroll range 200
|
||||
sf:SetScrollChild(child)
|
||||
|
||||
bar = CreateFrame("Slider", "SlSFBar")
|
||||
@@ -399,7 +419,7 @@ fn a_thumb_with_no_authored_size_takes_its_arts_texel_span_and_still_drags() {
|
||||
-- Slider and not to the mouse-enabled host sitting under it.
|
||||
host = CreateFrame("Frame", "DdHost", nil)
|
||||
host:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 100, 100)
|
||||
host:SetSize(80, 170)
|
||||
host:SetWidth(80); host:SetHeight(170)
|
||||
host:SetFrameStrata("FULLSCREEN_DIALOG")
|
||||
host:EnableMouse(true)
|
||||
bar = CreateFrame("Slider", "DdSlider", host)
|
||||
|
||||
@@ -72,7 +72,7 @@ fn statusbar_bar_region_scales_by_fraction_on_extract() {
|
||||
r#"
|
||||
local b = CreateFrame("StatusBar", "SbFill")
|
||||
b:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 100, 100)
|
||||
b:SetSize(200, 20)
|
||||
b:SetWidth(200); b:SetHeight(20)
|
||||
b:SetStatusBarTexture(0.2, 0.7, 0.2)
|
||||
b:SetMinMaxValues(0, 100)
|
||||
b:SetValue(25)
|
||||
@@ -137,7 +137,7 @@ fn statusbar_bar_region_crops_its_texture_rather_than_stretching_it() {
|
||||
r#"
|
||||
local b = CreateFrame("StatusBar", "SbCrop")
|
||||
b:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 0, 0)
|
||||
b:SetSize(200, 20)
|
||||
b:SetWidth(200); b:SetHeight(20)
|
||||
b:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
|
||||
b:SetMinMaxValues(0, 100)
|
||||
b:SetValue(25)
|
||||
|
||||
@@ -147,7 +147,7 @@ fn set_talent_renders_the_interleaved_tooltip() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "TB1"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "TB1"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:SetTalent(1, 1)
|
||||
@@ -183,7 +183,7 @@ fn set_talent_locked_reqs_and_the_ask_once_miss() {
|
||||
// No spell view pushed for Ignite (11119): the render falls back, the ask is recorded.
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "TB2"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "TB2"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT2")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:SetTalent(1, 2)
|
||||
|
||||
@@ -31,7 +31,7 @@ fn line_stack_autosize_and_right_flush() {
|
||||
s.run(
|
||||
r#"
|
||||
local owner = CreateFrame("Button", "Slot")
|
||||
owner:SetPoint("TOPLEFT", 100, -100); owner:SetSize(40, 40)
|
||||
owner:SetPoint("TOPLEFT", 100, -100); owner:SetWidth(40); owner:SetHeight(40)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(owner, "ANCHOR_RIGHT")
|
||||
tt:AddLine("Tough Jerky", 1, 1, 1)
|
||||
@@ -101,7 +101,7 @@ fn empty_line_is_a_one_unit_row_and_the_chain_stays_inside_the_plate() {
|
||||
s.run(
|
||||
r#"
|
||||
local owner = CreateFrame("Button", "Slot2")
|
||||
owner:SetPoint("TOPLEFT", 100, -100); owner:SetSize(40, 40)
|
||||
owner:SetPoint("TOPLEFT", 100, -100); owner:SetWidth(40); owner:SetHeight(40)
|
||||
local tt = CreateFrame("GameTooltip", "TTE")
|
||||
tt:SetOwner(owner, "ANCHOR_RIGHT")
|
||||
tt:AddLine("Marshal McBride", 0.25, 0.75, 0.25)
|
||||
@@ -154,15 +154,15 @@ fn owner_clear_and_hide_lifecycle() {
|
||||
s.run(
|
||||
r#"
|
||||
cleared = 0
|
||||
local a = CreateFrame("Button", "A"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local b = CreateFrame("Button", "B"); b:SetPoint("CENTER", 50, 0); b:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "A"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local b = CreateFrame("Button", "B"); b:SetPoint("CENTER", 50, 0); b:SetWidth(10); b:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT2")
|
||||
tt:SetScript("OnTooltipCleared", function() cleared = cleared + 1 end)
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:AddLine("first hover")
|
||||
tt:Show()
|
||||
assert(tt:IsOwned(a) and not tt:IsOwned(b), "owned by a")
|
||||
assert(tt:GetOwner() == a, "GetOwner returns the owner wrapper")
|
||||
assert(tt:BenillaGetTooltipOwner() == a, "the owner wrapper reads back")
|
||||
tt:SetOwner(b, "ANCHOR_LEFT")
|
||||
assert(cleared >= 1, "SetOwner cleared the old content")
|
||||
assert(tt:NumLines() == 0, "content cleared on re-own")
|
||||
@@ -190,7 +190,7 @@ fn settext_shows_and_both_addline_shapes() {
|
||||
s.set_screen_size(800.0, 600.0);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "A3"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "A3"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT3")
|
||||
tt:Hide() -- the XML instance ships hidden="true"; CreateFrame defaults shown
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
@@ -248,9 +248,9 @@ fn unmeasured_lines_hold_declared_size() {
|
||||
s.set_screen_size(800.0, 600.0);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "A6"); a:SetPoint("TOPLEFT", 100, -100); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "A6"); a:SetPoint("TOPLEFT", 100, -100); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT6")
|
||||
tt:SetSize(120, 32)
|
||||
tt:SetWidth(120); tt:SetHeight(32)
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:AddLine("Tough Jerky")
|
||||
tt:AddDoubleLine("One-Hand", "Sword")
|
||||
@@ -291,7 +291,7 @@ fn minimum_width_floors_autosize() {
|
||||
s.set_screen_size(800.0, 600.0);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "A4"); a:SetPoint("TOPLEFT", 100, -100); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "A4"); a:SetPoint("TOPLEFT", 100, -100); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT4")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:AddLine("tiny")
|
||||
@@ -316,7 +316,7 @@ fn fadeout_ramps_then_hides() {
|
||||
s.run(
|
||||
r#"
|
||||
cleared = 0
|
||||
local a = CreateFrame("Button", "A5"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "A5"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT5")
|
||||
tt:SetScript("OnTooltipCleared", function() cleared = cleared + 1 end)
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
@@ -387,7 +387,7 @@ fn owner_anchored_tooltip_clamps_to_screen() {
|
||||
s.run(
|
||||
r#"
|
||||
local owner = CreateFrame("Button", "ZoneTextBtn")
|
||||
owner:SetPoint("TOPRIGHT", -100, 0); owner:SetSize(90, 14)
|
||||
owner:SetPoint("TOPRIGHT", -100, 0); owner:SetWidth(90); owner:SetHeight(14)
|
||||
local tt = CreateFrame("GameTooltip", "TTC")
|
||||
assert(tt:IsClampedToScreen(), "a GameTooltip clamps by construction")
|
||||
assert(not owner:IsClampedToScreen(), "a plain frame does not")
|
||||
@@ -539,7 +539,7 @@ fn an_emptied_pooled_line_drops_its_stale_box_and_the_plate_still_contains_the_c
|
||||
s.run(
|
||||
r#"
|
||||
local owner = CreateFrame("Button", "SlotR")
|
||||
owner:SetPoint("TOPLEFT", 100, -100); owner:SetSize(40, 40)
|
||||
owner:SetPoint("TOPLEFT", 100, -100); owner:SetWidth(40); owner:SetHeight(40)
|
||||
local tt = CreateFrame("GameTooltip", "TTR")
|
||||
tt:SetOwner(owner, "ANCHOR_RIGHT")
|
||||
tt:AddLine("Marshal McBride")
|
||||
@@ -589,3 +589,92 @@ fn an_emptied_pooled_line_drops_its_stale_box_and_the_plate_still_contains_the_c
|
||||
.unwrap();
|
||||
assert!(s.take_errors().is_empty());
|
||||
}
|
||||
|
||||
/// `GameTooltip:GetAnchorType()` — ONE string, the reference's own spelling, round-tripping
|
||||
/// whatever `SetOwner` was given (`0x5313e0`, table `0x854198`, argc 1, arity 1, kinds `(string)`,
|
||||
/// reading `[+0x318]` back through the name table `0x531530`).
|
||||
///
|
||||
/// The round trip is the whole point rather than a nicety: `_Nameplates/_Nameplates.lua:479` is
|
||||
/// `if GameTooltip:GetAnchorType() ~= Anchor then GameTooltip:SetOwner(Column, Anchor) end`, so an
|
||||
/// answer that never equals what the setter was handed would re-own the plate every OnUpdate; and
|
||||
/// `pfUI/modules/tooltip.lua:97` compares against the literal `"ANCHOR_NONE"`.
|
||||
#[test]
|
||||
fn tooltip_anchor_type_round_trips_every_reachable_mode() {
|
||||
let s = script();
|
||||
s.run(
|
||||
r#"
|
||||
AnchorTip = CreateFrame("GameTooltip", "AnchorTip")
|
||||
AnchorOwner = CreateFrame("Frame", "AnchorOwner")
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Arity 1, kind string, and NO nil leg — the shapes row is `(string)` with no alternative, so
|
||||
// a plate nobody has owned still answers a string.
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return select('#', AnchorTip:GetAnchorType())")
|
||||
.unwrap(),
|
||||
1,
|
||||
"arity 1"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return type(AnchorTip:GetAnchorType())")
|
||||
.unwrap(),
|
||||
"string",
|
||||
"kind string, never nil"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return AnchorTip:GetAnchorType()")
|
||||
.unwrap(),
|
||||
"ANCHOR_NONE",
|
||||
"a plate nothing has owned is anchored to nothing"
|
||||
);
|
||||
|
||||
// The eight modes SetOwner accepts, each answered back verbatim — ANCHOR_PRESERVE included,
|
||||
// which the reference stores like any other rather than resolving back to what it preserved.
|
||||
for mode in [
|
||||
"ANCHOR_RIGHT",
|
||||
"ANCHOR_LEFT",
|
||||
"ANCHOR_TOPRIGHT",
|
||||
"ANCHOR_TOPLEFT",
|
||||
"ANCHOR_BOTTOMRIGHT",
|
||||
"ANCHOR_BOTTOMLEFT",
|
||||
"ANCHOR_NONE",
|
||||
"ANCHOR_PRESERVE",
|
||||
] {
|
||||
s.run(&format!(r#"AnchorTip:SetOwner(AnchorOwner, "{mode}")"#))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<String>("return AnchorTip:GetAnchorType()")
|
||||
.unwrap(),
|
||||
mode,
|
||||
"round trip through SetOwner"
|
||||
);
|
||||
}
|
||||
|
||||
// Lower case in, the reference's canonical spelling out — SetOwner already folds the token.
|
||||
s.run(r#"AnchorTip:SetOwner(AnchorOwner, "anchor_left")"#)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<String>("return AnchorTip:GetAnchorType()")
|
||||
.unwrap(),
|
||||
"ANCHOR_LEFT"
|
||||
);
|
||||
|
||||
// An anchor SetOwner cannot honour is recorded as the ANCHOR_RIGHT it is actually PLACED by,
|
||||
// never as the string it was handed — the getter reports the plate, not the request.
|
||||
// `ANCHOR_CURSOR` is the live case (nine corpus files ask for it; mode 6 follows the cursor per
|
||||
// frame and this engine has no such driver), and SetOwner warns rather than pretending.
|
||||
s.run(r#"AnchorTip:SetOwner(AnchorOwner, "ANCHOR_CURSOR")"#)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<String>("return AnchorTip:GetAnchorType()")
|
||||
.unwrap(),
|
||||
"ANCHOR_RIGHT"
|
||||
);
|
||||
assert!(
|
||||
s.warnings().iter().any(|w| w.contains("ANCHOR_CURSOR")),
|
||||
"the unhonoured anchor is warned, not silently taken: {:?}",
|
||||
s.warnings()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ fn shift_compare_fires_seats_and_renders_the_compare_shape() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
-- CreateFrame'd frames start SHOWN; the shipped XML instances are hidden="true".
|
||||
CreateFrame("GameTooltip", "GameTooltip"):Hide()
|
||||
CreateFrame("GameTooltip", "ShoppingTooltip1"):Hide()
|
||||
@@ -101,7 +101,7 @@ fn shift_compare_fires_seats_and_renders_the_compare_shape() {
|
||||
compare_calls = {}
|
||||
for slot = 11, 12 do
|
||||
local f = CreateFrame("Button", "Doll" .. slot)
|
||||
f:SetPoint("CENTER", 100, 0); f:SetSize(8, 8)
|
||||
f:SetPoint("CENTER", 100, 0); f:SetWidth(8); f:SetHeight(8)
|
||||
f.invSlotId = slot
|
||||
f:RegisterEvent("SHOW_COMPARE_TOOLTIP")
|
||||
f:SetScript("OnEvent", function()
|
||||
@@ -213,7 +213,7 @@ fn set_inventory_item_renders_full_outside_compare() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot9"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot9"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
assert(tt:SetInventoryItem("player", 16) == 1, "occupied slot answers 1")
|
||||
@@ -266,7 +266,7 @@ fn set_hyperlink_renders_items_and_ignores_other_links() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot10"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot10"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:SetHyperlink("|cff1eff00|Hitem:7002:0:0:0|h[New Loop]|h|r")
|
||||
|
||||
@@ -23,11 +23,11 @@ fn item_line_law_and_red_requirements() {
|
||||
});
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:SetItemById(871)
|
||||
assert(tt:IsShown(), "SetItemById shows")
|
||||
tt:BenillaSetItemById(871)
|
||||
assert(tt:IsShown(), "BenillaSetItemById shows")
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -95,10 +95,10 @@ fn red_lines_track_player_state() {
|
||||
});
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot2"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot2"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:SetItemById(871)
|
||||
tt:BenillaSetItemById(871)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -122,7 +122,7 @@ fn red_lines_track_player_state() {
|
||||
/// The §5-verified families folded back 2026-07-10 (tooltip-content-law.md): SIGNABLE green,
|
||||
/// UNIQUE before STARTS_QUEST, LOCKED red, six-equal resistances collapse to the ALL line (and
|
||||
/// Holy never prints singly), a known taught spell reds "Already known", the description gold —
|
||||
/// and NO openable line: `SetItemById` is a template source, and the whole openable/readable/
|
||||
/// and NO openable line: `BenillaSetItemById` is a template source, and the whole openable/readable/
|
||||
/// creator tail rides the ref's item-OBJECT gate (`0x52e1c7`/`0x52e2e0` — byte-read 2026-07-20;
|
||||
/// the instance-tail law itself is in [`instance_tail_creator_and_readable`]).
|
||||
#[test]
|
||||
@@ -154,10 +154,10 @@ fn verified_families_signable_locked_resists_known() {
|
||||
});
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot5"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot5"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:SetItemById(5518)
|
||||
tt:BenillaSetItemById(5518)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -208,7 +208,7 @@ fn verified_families_signable_locked_resists_known() {
|
||||
s.run(
|
||||
r#"
|
||||
TT:SetOwner(Slot5, "ANCHOR_RIGHT")
|
||||
TT:SetItemById(5519)
|
||||
TT:BenillaSetItemById(5519)
|
||||
assert(TT:NumLines() == 1, "a lone Holy resist prints no line, got " .. TT:NumLines())
|
||||
"#,
|
||||
)
|
||||
@@ -243,10 +243,10 @@ fn proficiency_and_reputation_reds() {
|
||||
s.set_player_req_state(req.clone());
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot9"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot9"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:SetItemById(871)
|
||||
tt:BenillaSetItemById(871)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -281,7 +281,7 @@ fn proficiency_and_reputation_reds() {
|
||||
req2.proficiency.insert(2, 1 << 0);
|
||||
req2.rep_ranks.insert(72, 4);
|
||||
s.set_player_req_state(req2.clone());
|
||||
s.run(r#"TT:SetOwner(Slot9, "ANCHOR_RIGHT"); TT:SetItemById(871)"#)
|
||||
s.run(r#"TT:SetOwner(Slot9, "ANCHOR_RIGHT"); TT:BenillaSetItemById(871)"#)
|
||||
.unwrap();
|
||||
let lines = lines_of(&mut s);
|
||||
assert_eq!(
|
||||
@@ -300,7 +300,7 @@ fn proficiency_and_reputation_reds() {
|
||||
let mut alt = item;
|
||||
alt.proficiency_alt = Some(0);
|
||||
s.set_item_template(871, alt);
|
||||
s.run(r#"TT:SetOwner(Slot9, "ANCHOR_RIGHT"); TT:SetItemById(871)"#)
|
||||
s.run(r#"TT:SetOwner(Slot9, "ANCHOR_RIGHT"); TT:BenillaSetItemById(871)"#)
|
||||
.unwrap();
|
||||
let lines = lines_of(&mut s);
|
||||
assert_eq!(
|
||||
@@ -328,7 +328,7 @@ fn proficiency_and_reputation_reds() {
|
||||
let mut req3 = req2.clone();
|
||||
req3.proficiency.insert(2, 1 << 15);
|
||||
s.set_player_req_state(req3.clone());
|
||||
s.run(r#"TT:SetOwner(Slot9, "ANCHOR_RIGHT"); TT:SetItemById(872)"#)
|
||||
s.run(r#"TT:SetOwner(Slot9, "ANCHOR_RIGHT"); TT:BenillaSetItemById(872)"#)
|
||||
.unwrap();
|
||||
let lines = lines_of(&mut s);
|
||||
// `INVTYPE_WEAPONOFFHAND`, NOT `INVTYPE_SHIELD` — one enUS sentence, two keys, and only the
|
||||
@@ -345,7 +345,7 @@ fn proficiency_and_reputation_reds() {
|
||||
);
|
||||
req3.can_dual_wield = true;
|
||||
s.set_player_req_state(req3);
|
||||
s.run(r#"TT:SetOwner(Slot9, "ANCHOR_RIGHT"); TT:SetItemById(872)"#)
|
||||
s.run(r#"TT:SetOwner(Slot9, "ANCHOR_RIGHT"); TT:BenillaSetItemById(872)"#)
|
||||
.unwrap();
|
||||
let lines = lines_of(&mut s);
|
||||
assert_eq!(
|
||||
@@ -365,7 +365,7 @@ fn proficiency_and_reputation_reds() {
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
s.run(r#"TT:SetOwner(Slot9, "ANCHOR_RIGHT"); TT:SetItemById(118)"#)
|
||||
s.run(r#"TT:SetOwner(Slot9, "ANCHOR_RIGHT"); TT:BenillaSetItemById(118)"#)
|
||||
.unwrap();
|
||||
let lines = lines_of(&mut s);
|
||||
assert_eq!(
|
||||
@@ -387,7 +387,7 @@ fn proficiency_and_reputation_reds() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
TT:SetOwner(Slot9, "ANCHOR_RIGHT"); TT:SetItemById(889)
|
||||
TT:SetOwner(Slot9, "ANCHOR_RIGHT"); TT:BenillaSetItemById(889)
|
||||
assert(TTTextLeft2:GetText() == "[INVTYPE_FINGER]")
|
||||
assert(TTTextRight2:GetText() == nil or TTTextRight2:GetText() == "",
|
||||
"a ring never prints its Miscellaneous type")
|
||||
@@ -416,13 +416,13 @@ fn required_level_one_is_hidden() {
|
||||
}
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "SlotR"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "SlotR"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT"); tt:SetItemById(11)
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT"); tt:BenillaSetItemById(11)
|
||||
assert(tt:NumLines() == 1, "req 0: name only, got " .. tt:NumLines())
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT"); tt:SetItemById(12)
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT"); tt:BenillaSetItemById(12)
|
||||
assert(tt:NumLines() == 1, "req 1 hides like req 0, got " .. tt:NumLines())
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT"); tt:SetItemById(13)
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT"); tt:BenillaSetItemById(13)
|
||||
assert(tt:NumLines() == 2, "req 2 prints, got " .. tt:NumLines())
|
||||
assert(TTTextLeft2:GetText() == "[MIN_LEVEL 2]")
|
||||
"#,
|
||||
@@ -515,7 +515,7 @@ fn charter_lines_sit_between_the_name_and_the_signable_line() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "SlotC"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "SlotC"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT"); tt:SetBagItem(0, 1)
|
||||
"#,
|
||||
@@ -651,7 +651,7 @@ fn instance_tail_creator_and_readable() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "SlotL"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "SlotL"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT"); tt:SetBagItem(0, 1)
|
||||
"#,
|
||||
@@ -770,7 +770,7 @@ fn a_runtime_bound_instance_overrides_the_bind_line_to_soulbound() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(Slot, "ANCHOR_RIGHT")
|
||||
tt:SetBagItem(0, 1)
|
||||
@@ -804,7 +804,7 @@ fn a_runtime_bound_instance_overrides_the_bind_line_to_soulbound() {
|
||||
assert_eq!(bind_line(&mut s).0, "[ITEM_BIND_ON_EQUIP]");
|
||||
|
||||
// Control 2: a TEMPLATE hover carries no instance at all, so nothing can override.
|
||||
s.run(r#"TT:SetItemById(871)"#).unwrap();
|
||||
s.run(r#"TT:BenillaSetItemById(871)"#).unwrap();
|
||||
assert_eq!(bind_line(&mut s).0, "[ITEM_BIND_ON_EQUIP]");
|
||||
|
||||
// Control 3: the override's other arm is ITEM_BIND_QUEST — the same text 4|5 already print.
|
||||
|
||||
@@ -55,7 +55,7 @@ fn bag_item_money_law_and_fallback() {
|
||||
s.run(
|
||||
r#"
|
||||
money_fired = nil
|
||||
local a = CreateFrame("Button", "Slot3"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot3"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetScript("OnTooltipAddMoney", function(self) money_fired = arg1 end)
|
||||
-- No merchant: no money handler fires.
|
||||
@@ -129,10 +129,10 @@ fn wrap_lines_measure_wrapped_in_one_pass_and_survive_the_reenter_loop() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot4"); a:SetPoint("TOPLEFT", 10, -10); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot4"); a:SetPoint("TOPLEFT", 10, -10); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:SetItemById(7)
|
||||
tt:BenillaSetItemById(7)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -174,7 +174,7 @@ fn wrap_lines_measure_wrapped_in_one_pass_and_survive_the_reenter_loop() {
|
||||
s.run(
|
||||
r#"
|
||||
TT:SetOwner(Slot4, "ANCHOR_RIGHT")
|
||||
TT:SetItemById(7)
|
||||
TT:BenillaSetItemById(7)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -219,10 +219,10 @@ fn item_render_hides_the_unit_health_bar() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot6"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot6"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "GameTooltip"); tt:Hide()
|
||||
local bar = CreateFrame("StatusBar", "GameTooltipStatusBar", tt)
|
||||
bar:SetPoint("TOPLEFT", tt, "BOTTOMLEFT", 2, -1); bar:SetSize(100, 8)
|
||||
bar:SetPoint("TOPLEFT", tt, "BOTTOMLEFT", 2, -1); bar:SetWidth(100); bar:SetHeight(8)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -232,7 +232,7 @@ fn item_render_hides_the_unit_health_bar() {
|
||||
s.run(
|
||||
r#"
|
||||
GameTooltip:SetOwner(Slot6, "ANCHOR_RIGHT")
|
||||
GameTooltip:SetItemById(9)
|
||||
GameTooltip:BenillaSetItemById(9)
|
||||
assert(not GameTooltipStatusBar:IsShown(), "item content hides the unit bar")
|
||||
assert(GameTooltip:IsShown(), "the item tooltip itself shows")
|
||||
"#,
|
||||
@@ -278,7 +278,7 @@ fn real_instance_hover_renders_live_durability() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(Slot, "ANCHOR_RIGHT")
|
||||
tt:SetBagItem(0, 2)
|
||||
@@ -289,7 +289,7 @@ fn real_instance_hover_renders_live_durability() {
|
||||
end
|
||||
assert(found == "[DURABILITY 30/40]", "live pair on a bag hover, got " .. tostring(found))
|
||||
-- The template/link hover of the SAME item keeps the authored full pair.
|
||||
tt:SetItemById(2264)
|
||||
tt:BenillaSetItemById(2264)
|
||||
found = nil
|
||||
for i = 1, tt:NumLines() do
|
||||
local t = getglobal("TTTextLeft" .. i):GetText()
|
||||
@@ -339,7 +339,7 @@ fn broken_instance_hover_renders_zero_durability() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(Slot, "ANCHOR_RIGHT")
|
||||
tt:SetBagItem(0, 1)
|
||||
@@ -415,7 +415,7 @@ fn an_enchanted_instance_renders_its_enchant_line_before_durability() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(Slot, "ANCHOR_RIGHT")
|
||||
tt:SetBagItem(0, 1)
|
||||
@@ -442,7 +442,7 @@ fn an_enchanted_instance_renders_its_enchant_line_before_durability() {
|
||||
);
|
||||
|
||||
// The control: the same item as a TEMPLATE hover has no instance, so no enchant line.
|
||||
s.run(r#"TT:SetItemById(22816)"#).unwrap();
|
||||
s.run(r#"TT:BenillaSetItemById(22816)"#).unwrap();
|
||||
let lines = super::lines_of(&mut s);
|
||||
assert!(
|
||||
!lines.iter().any(|(t, _)| t.starts_with("Agility")),
|
||||
@@ -514,7 +514,7 @@ fn enchant_line_colour_is_per_slot_and_sign() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(Slot, "ANCHOR_RIGHT")
|
||||
tt:SetBagItem(0, 1)
|
||||
@@ -597,7 +597,7 @@ fn temporary_enchant_line_carries_its_countdown_and_charges() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(Slot, "ANCHOR_RIGHT")
|
||||
tt:SetBagItem(0, 1)
|
||||
@@ -623,7 +623,7 @@ fn temporary_enchant_line_carries_its_countdown_and_charges() {
|
||||
/// `ITEM_RANDOM_ENCHANT` arm is unreachable (§E1's fork). The row's name is the suffix-joined one
|
||||
/// (`0x5d8b00`, the same string `GetLootSlotInfo` returns), so the plate reads "… of the Monkey".
|
||||
///
|
||||
/// This is the reported bug's exact shape: through the template path (`SetItemById`) the same
|
||||
/// This is the reported bug's exact shape: through the template path (`BenillaSetItemById`) the same
|
||||
/// hover printed the ITEM_RANDOM_ENCHANT placeholder until the item reached a bag.
|
||||
#[test]
|
||||
fn a_looted_roll_shows_its_lines_and_never_the_placeholder() {
|
||||
@@ -680,7 +680,7 @@ fn a_looted_roll_shows_its_lines_and_never_the_placeholder() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(Slot, "ANCHOR_RIGHT")
|
||||
tt:SetLootItem(1)
|
||||
@@ -738,7 +738,7 @@ fn a_looted_item_with_no_roll_shows_neither_line_nor_placeholder() {
|
||||
}));
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(Slot, "ANCHOR_RIGHT")
|
||||
tt:SetLootItem(1)
|
||||
@@ -790,7 +790,7 @@ fn a_linked_roll_shows_its_lines_and_never_the_placeholder() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(Slot, "ANCHOR_RIGHT")
|
||||
tt:SetHyperlink("|cff1eff00|Hitem:8888:0:584:0|h[Bloodrazor of the Monkey]|h|r")
|
||||
@@ -835,10 +835,10 @@ fn random_property_template_hover_shows_the_placeholder() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(Slot, "ANCHOR_RIGHT")
|
||||
tt:SetItemById(8888)
|
||||
tt:BenillaSetItemById(8888)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -60,10 +60,10 @@ fn item_set_block_counts_and_colors() {
|
||||
s.set_item_set(161, set_view.clone());
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Slot10"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Slot10"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:SetItemById(6303)
|
||||
tt:BenillaSetItemById(6303)
|
||||
-- The spacer is the reference's own literal `0x854b2c` — a space and a newline, which is
|
||||
-- ONE row carrying one space, not the empty string (an empty AddLine adds no line at all
|
||||
-- in the reference: `0x530270` bails before its `inc [esi+0x31c]`).
|
||||
@@ -129,7 +129,7 @@ fn item_set_block_counts_and_colors() {
|
||||
s.set_item_set(161, gated);
|
||||
s.run(
|
||||
r#"
|
||||
TT:SetOwner(Slot10, "ANCHOR_RIGHT"); TT:SetItemById(6303)
|
||||
TT:SetOwner(Slot10, "ANCHOR_RIGHT"); TT:BenillaSetItemById(6303)
|
||||
assert(TTTextLeft5:GetText() == "[MIN_SKILL Leatherworking 250]",
|
||||
"the skill line sits between header and members")
|
||||
assert(TTTextLeft6:GetText() == " Defias Mark")
|
||||
@@ -150,7 +150,7 @@ fn item_set_block_counts_and_colors() {
|
||||
let mut req = PlayerReqState::default();
|
||||
req.skills.insert(165, 300);
|
||||
s.set_player_req_state(req);
|
||||
s.run(r#"TT:SetOwner(Slot10, "ANCHOR_RIGHT"); TT:SetItemById(6303)"#)
|
||||
s.run(r#"TT:SetOwner(Slot10, "ANCHOR_RIGHT"); TT:BenillaSetItemById(6303)"#)
|
||||
.unwrap();
|
||||
let lines = lines_of(&mut s);
|
||||
assert_eq!(
|
||||
@@ -171,7 +171,7 @@ fn item_set_block_counts_and_colors() {
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
s.run(r#"TT:SetOwner(Slot10, "ANCHOR_RIGHT"); TT:SetItemById(9999)"#)
|
||||
s.run(r#"TT:SetOwner(Slot10, "ANCHOR_RIGHT"); TT:BenillaSetItemById(9999)"#)
|
||||
.unwrap();
|
||||
assert_eq!(s.take_item_set_asks(), vec![1], "the set ask recorded");
|
||||
assert!(s.take_errors().is_empty());
|
||||
@@ -206,7 +206,7 @@ fn quest_item_hovers_render_the_rows_item_by_id() {
|
||||
}));
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Q1"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Q1"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT"); tt:SetQuestItem("choice", 1)
|
||||
"#,
|
||||
|
||||
@@ -36,7 +36,7 @@ fn spellbook_hover_renders_the_verified_shape() {
|
||||
});
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "SB1"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "SB1"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:SetSpell(1, "spell")
|
||||
@@ -102,7 +102,7 @@ fn a_pet_book_hover_reads_the_pets_book_not_the_players() {
|
||||
});
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "SB1"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "SB1"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:SetSpell(1, "pet")
|
||||
@@ -152,7 +152,7 @@ fn tracking_hover_renders_gold_name_over_white_description() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local f = CreateFrame("Frame", "TRK"); f:SetPoint("CENTER", 0, 0); f:SetSize(10, 10)
|
||||
local f = CreateFrame("Frame", "TRK"); f:SetPoint("CENTER", 0, 0); f:SetWidth(10); f:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(f, "ANCHOR_BOTTOMLEFT")
|
||||
tt:SetTrackingSpell()
|
||||
@@ -206,7 +206,7 @@ fn passive_omits_the_casttime_line() {
|
||||
});
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "SB2"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "SB2"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:SetSpell(1, "spell")
|
||||
@@ -260,7 +260,7 @@ fn player_buff_hover_is_the_aura_variant() {
|
||||
-- the engine reads the string table at all, not what the strings say.
|
||||
SPELL_TIME_REMAINING_MIN = "<%d m>"
|
||||
SPELL_TIME_REMAINING_MIN_P1 = "<%d mm>"
|
||||
local a = CreateFrame("Button", "BF1"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "BF1"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
-- 0, not 1: SetPlayerBuff takes a 1.12 CACHE POSITION (see `script::aura`'s header).
|
||||
@@ -329,7 +329,7 @@ fn player_buff_hover_names_the_dispel_class_in_gold() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "BF1"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "BF1"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:SetPlayerBuff(0)
|
||||
@@ -380,7 +380,7 @@ fn requirement_and_reagent_lines_render_in_law_order() {
|
||||
});
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "SB2"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "SB2"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:SetSpell(1, "spell")
|
||||
@@ -458,7 +458,7 @@ fn unit_buff_and_debuff_hover_render_the_aura_variant_without_remaining() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "TF1"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "TF1"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_BOTTOMRIGHT", 15, -25)
|
||||
tt:SetUnitDebuff("target", 1)
|
||||
@@ -535,7 +535,7 @@ fn player_buff_hover_indexes_the_cache_position_not_a_filtered_ordinal() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "BF1"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "BF1"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
local function at(i, extra)
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
@@ -587,7 +587,7 @@ fn action_hover_delegates_by_kind() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "AB1"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "AB1"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:SetAction(24)
|
||||
@@ -638,7 +638,7 @@ fn action_hover_on_a_macro_slot_shows_its_name_in_white() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "AB1"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "AB1"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:SetAction(24)
|
||||
@@ -739,7 +739,7 @@ fn set_trainer_service_selects_the_builder_and_never_renders_its_own_line() {
|
||||
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "AB1"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "AB1"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
CreateFrame("GameTooltip", "TT")
|
||||
|
||||
-- Row 1 is the "Arms" header: a no-op, not a shifted hit onto a neighbouring service.
|
||||
@@ -855,7 +855,7 @@ fn set_craft_spell_selects_the_builder_like_the_trainer_hover_does() {
|
||||
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "AB2"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "AB2"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
CreateFrame("GameTooltip", "TT")
|
||||
TT:SetOwner(a, "ANCHOR_RIGHT")
|
||||
TT:SetCraftSpell(1)
|
||||
@@ -914,7 +914,7 @@ fn the_remaining_line_is_gated_on_until_cancelled_not_on_the_duration() {
|
||||
SPELL_TIME_REMAINING_SEC = "<%d s>"
|
||||
SPELL_TIME_REMAINING_SEC_P1 = "<%d ss>"
|
||||
BENILLA_ANCHOR = CreateFrame("Button", "BF2")
|
||||
BENILLA_ANCHOR:SetPoint("CENTER", 0, 0); BENILLA_ANCHOR:SetSize(10, 10)
|
||||
BENILLA_ANCHOR:SetPoint("CENTER", 0, 0); BENILLA_ANCHOR:SetWidth(10); BENILLA_ANCHOR:SetHeight(10)
|
||||
BENILLA_TIP = CreateFrame("GameTooltip", "TT2")
|
||||
"#,
|
||||
)
|
||||
@@ -1003,7 +1003,7 @@ fn quest_reward_spell_getters_and_hovers() {
|
||||
s.set_spell_tooltip(133, fireball());
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "Q1"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "Q1"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
"#,
|
||||
)
|
||||
|
||||
@@ -61,7 +61,7 @@ fn creature_line_law() {
|
||||
});
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "UF1"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "UF1"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
assert(tt:SetUnit("target") == 1, "SetUnit returns 1 on a live unit")
|
||||
@@ -130,7 +130,7 @@ fn a_pending_name_titles_unknownobject_and_the_answer_replaces_it() {
|
||||
s.run(
|
||||
r#"
|
||||
UNKNOWNOBJECT = "Unknown" -- GlobalStrings.lua:4444, enUS
|
||||
local a = CreateFrame("Button", "UF1"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "UF1"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
assert(tt:SetUnit("target") == 1, "a resolved unit shows, name or no name")
|
||||
@@ -241,7 +241,7 @@ fn faction_line_and_civilian_gate() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "UF9"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "UF9"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:SetUnit("target")
|
||||
@@ -329,7 +329,7 @@ fn level_line_variants() {
|
||||
);
|
||||
s.run(
|
||||
r#"
|
||||
local a = CreateFrame("Button", "UF2"); a:SetPoint("CENTER", 0, 0); a:SetSize(10, 10)
|
||||
local a = CreateFrame("Button", "UF2"); a:SetPoint("CENTER", 0, 0); a:SetWidth(10); a:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TT")
|
||||
tt:SetOwner(a, "ANCHOR_RIGHT")
|
||||
tt:SetUnit("target")
|
||||
@@ -450,7 +450,7 @@ fn world_hover_drive_and_health_watcher() {
|
||||
getglobal("GameTooltipTextLeft1"):SetTextColor(1, 0, 0)
|
||||
end)
|
||||
local bar = CreateFrame("StatusBar", "GameTooltipStatusBar", tt)
|
||||
bar:SetPoint("TOPLEFT", tt, "BOTTOMLEFT", 2, -1); bar:SetSize(100, 8)
|
||||
bar:SetPoint("TOPLEFT", tt, "BOTTOMLEFT", 2, -1); bar:SetWidth(100); bar:SetHeight(8)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -512,7 +512,7 @@ fn minimap_blip_tooltip_shows_and_fades() {
|
||||
s.run(
|
||||
r#"
|
||||
local up = CreateFrame("Frame", "UIParent")
|
||||
up:SetPoint("BOTTOMLEFT"); up:SetSize(800, 600)
|
||||
up:SetPoint("BOTTOMLEFT"); up:SetWidth(800); up:SetHeight(600)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -42,13 +42,13 @@ fn board_and_dialog() -> UiScript {
|
||||
r#"
|
||||
Board = CreateFrame("Frame", "Board")
|
||||
Board:SetPoint("BOTTOMLEFT", 100, 100)
|
||||
Board:SetSize(300, 300)
|
||||
Board:SetWidth(300); Board:SetHeight(300)
|
||||
Board:SetFrameLevel(5)
|
||||
Board:CreateTexture(nil, "ARTWORK"):SetTexture("Board.blp")
|
||||
|
||||
Dialog = CreateFrame("Frame", "Dialog")
|
||||
Dialog:SetPoint("BOTTOMLEFT", 200, 200) -- overlaps Board's top-right quadrant
|
||||
Dialog:SetSize(300, 300)
|
||||
Dialog:SetWidth(300); Dialog:SetHeight(300)
|
||||
Dialog:CreateTexture(nil, "ARTWORK"):SetTexture("Dialog.blp")
|
||||
Dialog:Hide()
|
||||
"#,
|
||||
@@ -200,7 +200,7 @@ fn the_trigger_is_the_effective_visibility_transition_not_the_show_call() {
|
||||
r#"
|
||||
Holder = CreateFrame("Frame", "Holder")
|
||||
Holder:SetPoint("BOTTOMLEFT", 200, 200)
|
||||
Holder:SetSize(300, 300)
|
||||
Holder:SetWidth(300); Holder:SetHeight(300)
|
||||
Holder:Hide()
|
||||
Dialog:SetParent(Holder)
|
||||
Dialog:SetToplevel(true)
|
||||
@@ -239,7 +239,7 @@ fn a_raise_on_a_frame_that_overlaps_nothing_is_a_total_no_op() {
|
||||
r#"
|
||||
Dialog:ClearAllPoints()
|
||||
Dialog:SetPoint("BOTTOMLEFT", 600, 450) -- clear of Board's (100,100)-(400,400)
|
||||
Dialog:SetSize(100, 100)
|
||||
Dialog:SetWidth(100); Dialog:SetHeight(100)
|
||||
Dialog:SetToplevel(true)
|
||||
Dialog:Show()
|
||||
"#,
|
||||
@@ -311,20 +311,20 @@ fn a_raise_can_never_lift_a_frame_out_of_its_stratum() {
|
||||
r#"
|
||||
Med = CreateFrame("Frame", "Med")
|
||||
Med:SetPoint("BOTTOMLEFT", 100, 100)
|
||||
Med:SetSize(300, 300)
|
||||
Med:SetWidth(300); Med:SetHeight(300)
|
||||
Med:CreateTexture(nil, "ARTWORK"):SetTexture("Med.blp")
|
||||
|
||||
LowOther = CreateFrame("Frame", "LowOther")
|
||||
LowOther:SetFrameStrata("LOW")
|
||||
LowOther:SetPoint("BOTTOMLEFT", 100, 100)
|
||||
LowOther:SetSize(300, 300)
|
||||
LowOther:SetWidth(300); LowOther:SetHeight(300)
|
||||
LowOther:SetFrameLevel(4)
|
||||
LowOther:CreateTexture(nil, "ARTWORK"):SetTexture("LowOther.blp")
|
||||
|
||||
LowTop = CreateFrame("Frame", "LowTop")
|
||||
LowTop:SetFrameStrata("LOW")
|
||||
LowTop:SetPoint("BOTTOMLEFT", 150, 150)
|
||||
LowTop:SetSize(300, 300)
|
||||
LowTop:SetWidth(300); LowTop:SetHeight(300)
|
||||
LowTop:SetToplevel(true)
|
||||
LowTop:CreateTexture(nil, "ARTWORK"):SetTexture("LowTop.blp")
|
||||
LowTop:Hide()
|
||||
@@ -408,11 +408,11 @@ fn compaction_bounds_the_raise_across_repeated_shows() {
|
||||
r#"
|
||||
A = CreateFrame("Frame", "A")
|
||||
A:SetPoint("BOTTOMLEFT", 100, 100)
|
||||
A:SetSize(300, 300)
|
||||
A:SetWidth(300); A:SetHeight(300)
|
||||
A:SetToplevel(true)
|
||||
B = CreateFrame("Frame", "B")
|
||||
B:SetPoint("BOTTOMLEFT", 200, 200)
|
||||
B:SetSize(300, 300)
|
||||
B:SetWidth(300); B:SetHeight(300)
|
||||
B:SetToplevel(true)
|
||||
"#,
|
||||
)
|
||||
@@ -451,7 +451,7 @@ fn lua_raise_acts_on_the_nearest_toplevel_ancestor_and_is_silent_without_one() {
|
||||
Kid = CreateFrame("Frame", "Kid", Dialog)
|
||||
Loose = CreateFrame("Frame", "Loose")
|
||||
Loose:SetPoint("BOTTOMLEFT", 200, 200)
|
||||
Loose:SetSize(300, 300)
|
||||
Loose:SetWidth(300); Loose:SetHeight(300)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -575,7 +575,7 @@ fn pressing_a_child_of_a_toplevel_window_raises_the_window() {
|
||||
Dialog:Show()
|
||||
Knob = CreateFrame("Frame", "Knob", Dialog)
|
||||
Knob:SetPoint("CENTER", Dialog, "CENTER")
|
||||
Knob:SetSize(40, 40)
|
||||
Knob:SetWidth(40); Knob:SetHeight(40)
|
||||
Knob:EnableMouse(true)
|
||||
Board:SetFrameLevel(9)
|
||||
Dialog:SetFrameLevel(0)
|
||||
@@ -643,12 +643,12 @@ fn a_chorded_press_raises_the_held_frame_not_the_one_under_the_cursor() {
|
||||
-- gate is occlusion, so a frame overlapping nothing would decline whatever we press.
|
||||
Other = CreateFrame("Frame", "Other")
|
||||
Other:SetPoint("BOTTOMLEFT", 500, 100)
|
||||
Other:SetSize(200, 200)
|
||||
Other:SetWidth(200); Other:SetHeight(200)
|
||||
Other:EnableMouse(true)
|
||||
Other:SetToplevel(true)
|
||||
Tile = CreateFrame("Frame", "Tile")
|
||||
Tile:SetPoint("BOTTOMLEFT", 660, 260) -- clips Other's far corner only
|
||||
Tile:SetSize(60, 60)
|
||||
Tile:SetWidth(60); Tile:SetHeight(60)
|
||||
Tile:SetFrameLevel(9)
|
||||
|
||||
Board:SetFrameLevel(9)
|
||||
@@ -725,7 +725,7 @@ fn a_raise_with_a_hidden_child_keeps_the_windows_own_siblings_level() {
|
||||
Other:SetFrameStrata("DIALOG")
|
||||
Other:SetFrameLevel(1)
|
||||
Other:SetPoint("BOTTOMLEFT", 50, 50)
|
||||
Other:SetSize(400, 400)
|
||||
Other:SetWidth(400); Other:SetHeight(400)
|
||||
Other:EnableMouse(true)
|
||||
Other:SetToplevel(true)
|
||||
OtherKid = CreateFrame("Frame", "OtherKid", Other) -- level 2
|
||||
@@ -734,13 +734,13 @@ fn a_raise_with_a_hidden_child_keeps_the_windows_own_siblings_level() {
|
||||
Win:SetFrameStrata("DIALOG")
|
||||
Win:SetFrameLevel(1)
|
||||
Win:SetPoint("BOTTOMLEFT", 200, 100)
|
||||
Win:SetSize(400, 400)
|
||||
Win:SetWidth(400); Win:SetHeight(400)
|
||||
Win:EnableMouse(true)
|
||||
Win:SetToplevel(true)
|
||||
|
||||
Close = CreateFrame("Button", "Close", Win) -- level 2, visible with Win
|
||||
Close:SetPoint("BOTTOMRIGHT", Win, "BOTTOMRIGHT", -15, 15)
|
||||
Close:SetSize(100, 21)
|
||||
Close:SetWidth(100); Close:SetHeight(21)
|
||||
Close:EnableMouse(true)
|
||||
Close:SetScript("OnEnter", function(self) hovered = self:GetName() end)
|
||||
Close:SetScript("OnClick", function(self) clicked = self:GetName() end)
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
//! **The widget-method surface gate** — the sibling of [`super::reference_surface`], for the API
|
||||
//! an addon reaches through a *widget* rather than through `_G` (decision 2142).
|
||||
//!
|
||||
//! `our_globals_stay_inside_the_1_12_surface` has guarded the global namespace since 1189, and the
|
||||
//! shape gate (1842/1843/2118) guards the arity and return kinds of the methods we *have*. Between
|
||||
//! them sat the question neither asks: **is the set of method NAMES on each widget class the set
|
||||
//! 1.12 registers?** A method the reference has and we do not is invisible to the shape gate,
|
||||
//! which skips what does not exist; a method we have and the reference does not is invisible to
|
||||
//! everything, and is the superset `api-coverage.sh`'s header calls "not free" — an addon that
|
||||
//! feature-detects (`if region.GetBlendMode then`) takes a path we cannot honour, and the failure
|
||||
//! surfaces far from the cause.
|
||||
//!
|
||||
//! A corpus census over 110 top-20 and 219 vanilla addons found both directions live at once
|
||||
//! (2142): `ShaguTweaks` probes `GetBlendMode`, which we were missing, and silently recolours
|
||||
//! textures the reference leaves alone; `pfUI` and `_Nameplates` call `GameTooltip:GetAnchorType`
|
||||
//! bare and raise. The demand is in the corpus, which is not in this repo — so this gate does not
|
||||
//! measure demand. It measures the SURFACE, and every entry in the two declared lists below is
|
||||
//! where the reading of the demand got written down.
|
||||
//!
|
||||
//! ## The reference side is a CHAIN, not 23 flat surfaces
|
||||
//!
|
||||
//! The reference's 23 widget registrar tables are mutually **disjoint** — a class's real surface is
|
||||
//! its own table plus the tables it misses into. Comparing a live `Frame` against `0x878ec0` alone
|
||||
//! reports 87 of its 88 methods as beyond-1.12, which is how you get a gate nobody believes.
|
||||
//! [`WIDGET_CHAINS`] is that inheritance, and its two non-obvious rows are byte-settled, not
|
||||
//! guessed: `0x87c9b8` is the base `Region` map (its 19 names are exactly [`super::super`]'s
|
||||
//! `REGION_MAP_METHODS`), and `0x847ce4` is `LootButton`, whose whole surface of its own is
|
||||
//! `SetSlot` (decision 1799, read off the registrar's own `mov edx,1`).
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use crate::script::{widget_method_census, UiScript};
|
||||
|
||||
/// `(class, its own registrar table, the tables it inherits)`.
|
||||
///
|
||||
/// `WorldFrame` is a `Frame` to Lua (1983/1984) and `TitleRegion` a bare `Region`, so neither has a
|
||||
/// table of its own among the 23. `TaxiRouteFrame` is absent for the same reason (1828).
|
||||
const WIDGET_CHAINS: &[(&str, &str, &[&str])] = &[
|
||||
("Frame", FRAME, &[REGION]),
|
||||
("WorldFrame", FRAME, &[REGION]),
|
||||
("Button", BUTTON, FRAME_CHAIN),
|
||||
("LootButton", "0x847ce4", &[BUTTON, FRAME, REGION]),
|
||||
("CheckButton", "0x87bf74", &[BUTTON, FRAME, REGION]),
|
||||
("EditBox", "0x87bb68", FRAME_CHAIN),
|
||||
("StatusBar", "0x87b010", FRAME_CHAIN),
|
||||
("Slider", "0x87b260", FRAME_CHAIN),
|
||||
("ScrollFrame", "0x87b3c0", FRAME_CHAIN),
|
||||
("Model", MODEL, FRAME_CHAIN),
|
||||
("PlayerModel", PLAYER_MODEL, &[MODEL, FRAME, REGION]),
|
||||
(
|
||||
"DressUpModel",
|
||||
"0x84f190",
|
||||
&[PLAYER_MODEL, MODEL, FRAME, REGION],
|
||||
),
|
||||
(
|
||||
"TabardModel",
|
||||
"0x84ee40",
|
||||
&[PLAYER_MODEL, MODEL, FRAME, REGION],
|
||||
),
|
||||
("MessageFrame", "0x87b960", FRAME_CHAIN),
|
||||
("ScrollingMessageFrame", "0x87b5c0", FRAME_CHAIN),
|
||||
("ColorSelect", "0x87abb0", FRAME_CHAIN),
|
||||
("SimpleHTML", "0x87ba80", FRAME_CHAIN),
|
||||
("MovieFrame", "0x87ab4c", FRAME_CHAIN),
|
||||
("GameTooltip", "0x854198", FRAME_CHAIN),
|
||||
("Minimap", "0x84c538", FRAME_CHAIN),
|
||||
("Texture", "0x87c128", &[REGION]),
|
||||
("FontString", "0x87c1d8", &[REGION]),
|
||||
// The font OBJECT (`CreateFont`) carries its own `GetName`/`GetObjectType`/`IsObjectType` and
|
||||
// inherits nothing.
|
||||
("Font", "0x87c7c8", &[]),
|
||||
("TitleRegion", REGION, &[]),
|
||||
];
|
||||
const REGION: &str = "0x87c9b8";
|
||||
const FRAME: &str = "0x878ec0";
|
||||
const BUTTON: &str = "0x879d00";
|
||||
const MODEL: &str = "0x878948";
|
||||
const PLAYER_MODEL: &str = "0x84f1fc";
|
||||
const FRAME_CHAIN: &[&str] = &[FRAME, REGION];
|
||||
|
||||
/// Methods the reference registers that this client does not answer — **a list that may only
|
||||
/// shrink**, one row per name, naming every class it is missing on and why it has not been built.
|
||||
///
|
||||
/// The corpus demand behind each is 2142's census. A name with real, live callers does not belong
|
||||
/// here: it belongs implemented.
|
||||
const NOT_YET_ANSWERED: &[(&str, &str, &str)] = &[
|
||||
// ── The MovieFrame trio: zero addon demand, and the stock GLUE screen calls all three ──────
|
||||
// `_extracted_gluexml/MovieFrame.lua:23/30/35/47/71`. The glue screens stay ours permanently
|
||||
// (0068 §8), so the intro-movie screen is owed these however little the ecosystem wants them —
|
||||
// which makes this the one group here that is a real backlog item rather than a name nobody
|
||||
// has ever typed. benilla has no movie playback at all, so it is a feature, not a verb.
|
||||
(
|
||||
"StartMovie",
|
||||
"MovieFrame",
|
||||
"the intro-movie screen's own verb; benilla plays no movie yet",
|
||||
),
|
||||
("StopMovie", "MovieFrame", "the same, on the skip path"),
|
||||
(
|
||||
"EnableSubtitles",
|
||||
"MovieFrame",
|
||||
"the same — `MovieFrame.lua:35` passes `GetMovieSubtitles()`",
|
||||
),
|
||||
// ── The Minimap's four content setters ────────────────────────────────────────────────────
|
||||
// benilla draws the minimap's blips, arrow and mask from the app side (`minimap/blips`), never
|
||||
// through Lua; the stock UI does not call these either. Wiring them means routing the art
|
||||
// choice back through the VM, which nothing has asked for.
|
||||
(
|
||||
"SetArrowModel",
|
||||
"Minimap",
|
||||
"app-side art; no caller in the stock UI or either corpus",
|
||||
),
|
||||
("SetBlipTexture", "Minimap", "the same"),
|
||||
("SetIconTexture", "Minimap", "the same"),
|
||||
("SetPlayerModel", "Minimap", "the same"),
|
||||
// ── The Button font-object and text-colour family ─────────────────────────────────────────
|
||||
// Seven getters and one setter over a button's disabled/highlight/pushed font state. benilla
|
||||
// keeps that state (`button.rs`'s `Slot` triple) but publishes none of it back. **Zero call
|
||||
// sites in 329 addon folders and zero in the stock UI** — the whole family is completeness.
|
||||
(
|
||||
"GetDisabledFontObject",
|
||||
"Button CheckButton LootButton",
|
||||
"the button font-state family; no caller anywhere measured",
|
||||
),
|
||||
(
|
||||
"GetDisabledTextColor",
|
||||
"Button CheckButton LootButton",
|
||||
"the same family",
|
||||
),
|
||||
(
|
||||
"GetHighlightFontObject",
|
||||
"Button CheckButton LootButton",
|
||||
"the same family",
|
||||
),
|
||||
(
|
||||
"GetHighlightTextColor",
|
||||
"Button CheckButton LootButton",
|
||||
"the same family",
|
||||
),
|
||||
(
|
||||
"GetTextFontObject",
|
||||
"Button CheckButton LootButton",
|
||||
"the same family",
|
||||
),
|
||||
(
|
||||
"GetPushedTextOffset",
|
||||
"Button CheckButton LootButton",
|
||||
"the same family",
|
||||
),
|
||||
(
|
||||
"SetPushedTextOffset",
|
||||
"Button CheckButton LootButton",
|
||||
"the same family",
|
||||
),
|
||||
// ── Line spacing, on the five classes that carry text ─────────────────────────────────────
|
||||
// A real layout property benilla's text layout does not model — a setter here is not a store,
|
||||
// it is a change to how `ui_text` lays out wrapped lines. No caller anywhere measured.
|
||||
(
|
||||
"GetSpacing",
|
||||
"EditBox Font FontString MessageFrame ScrollingMessageFrame",
|
||||
"line spacing is not modelled by `ui_text`'s layout; no caller anywhere measured",
|
||||
),
|
||||
(
|
||||
"SetSpacing",
|
||||
"EditBox Font FontString MessageFrame ScrollingMessageFrame",
|
||||
"the same — the setter is a layout change, not a stored value",
|
||||
),
|
||||
// ── EditBox mode read-backs ───────────────────────────────────────────────────────────────
|
||||
// The four modes are all SET here (`editbox`'s `SetAutoFocus`/`SetMultiLine`/`SetNumeric`/
|
||||
// `SetPassword`) and none is readable. Cheap, and nothing has ever asked.
|
||||
(
|
||||
"IsAutoFocus",
|
||||
"EditBox",
|
||||
"the mode is set and not readable back; no caller anywhere measured",
|
||||
),
|
||||
("IsMultiLine", "EditBox", "the same"),
|
||||
("IsNumeric", "EditBox", "the same"),
|
||||
("IsPassword", "EditBox", "the same"),
|
||||
// ── ScrollingMessageFrame's line-position surface ─────────────────────────────────────────
|
||||
// The scroll POSITION verbs (`ScrollUp`/`ScrollDown`/`ScrollToTop`…) are all here; these four
|
||||
// report or set where that position sits, which our message buffer tracks internally and does
|
||||
// not publish. No caller anywhere measured.
|
||||
(
|
||||
"GetCurrentLine",
|
||||
"ScrollingMessageFrame",
|
||||
"the buffer's position is internal; no caller measured",
|
||||
),
|
||||
("GetCurrentScroll", "ScrollingMessageFrame", "the same"),
|
||||
("GetNumLinesDisplayed", "ScrollingMessageFrame", "the same"),
|
||||
("SetScrollFromBottom", "ScrollingMessageFrame", "the same"),
|
||||
// ── The colour picker's HSV pair ──────────────────────────────────────────────────────────
|
||||
// `ColorSelect` publishes the RGB pair; the HSV twin would need the conversion the reference
|
||||
// does in C. The stock `ColorPickerFrame` uses RGB only, and so does every corpus caller.
|
||||
(
|
||||
"GetColorHSV",
|
||||
"ColorSelect",
|
||||
"the RGB pair is published; nothing measured asks for HSV",
|
||||
),
|
||||
("SetColorHSV", "ColorSelect", "the same"),
|
||||
// ── One texture read-back ─────────────────────────────────────────────────────────────────
|
||||
(
|
||||
"IsDesaturated",
|
||||
"Texture",
|
||||
"`SetDesaturated` is here and not readable back; no caller measured",
|
||||
),
|
||||
];
|
||||
|
||||
/// Methods this client answers that 1.12 does not register on that class — **each a deliberate,
|
||||
/// reasoned exception, never a quiet superset**. Same contract as
|
||||
/// [`super::reference_surface::allowed_beyond_1_12`]: a name here must say why it stays.
|
||||
const ALLOWED_BEYOND: &[(&str, &str, &str)] = &[];
|
||||
|
||||
/// The reference's widget rows, `table_va -> {name}`.
|
||||
fn reference_tables() -> BTreeMap<String, BTreeSet<String>> {
|
||||
let tsv = concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../../reference/1.12-shapes.tsv"
|
||||
);
|
||||
let text = std::fs::read_to_string(tsv).expect("reference/1.12-shapes.tsv");
|
||||
let mut out: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
|
||||
for line in text.lines() {
|
||||
if line.starts_with('#') || line.starts_with("name\t") {
|
||||
continue;
|
||||
}
|
||||
let f: Vec<&str> = line.split('\t').collect();
|
||||
if f.len() < 5 || f[4] != "widget" {
|
||||
continue;
|
||||
}
|
||||
out.entry(f[3].to_string())
|
||||
.or_default()
|
||||
.insert(f[0].to_string());
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// `class -> the effective reference surface` (own table + everything it inherits).
|
||||
fn reference_surface_by_class() -> BTreeMap<&'static str, BTreeSet<String>> {
|
||||
let tables = reference_tables();
|
||||
assert!(
|
||||
tables.len() == 23,
|
||||
"the shapes table gave {} widget registrar tables, not the reference's 23 — it changed \
|
||||
shape, and every count below is against the wrong denominator",
|
||||
tables.len()
|
||||
);
|
||||
let mut out = BTreeMap::new();
|
||||
for (class, own, bases) in WIDGET_CHAINS {
|
||||
let mut set = tables.get(*own).cloned().unwrap_or_default();
|
||||
for base in *bases {
|
||||
set.extend(tables.get(*base).cloned().unwrap_or_default());
|
||||
}
|
||||
assert!(!set.is_empty(), "{class}: empty reference surface");
|
||||
out.insert(*class, set);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// `class -> what this VM answers`, asked of a live instance of each.
|
||||
fn ours_by_class() -> BTreeMap<String, BTreeSet<String>> {
|
||||
let script = UiScript::new().expect("VM");
|
||||
let rows = widget_method_census(&script).expect("census");
|
||||
let mut out: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
|
||||
for (class, method) in rows {
|
||||
assert_ne!(
|
||||
method, "!NOT-INSTANTIABLE",
|
||||
"{class} could not be instantiated — the census cannot speak for a class it never made"
|
||||
);
|
||||
out.entry(class).or_default().insert(method);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// A declared list, `name -> (classes, reason)`.
|
||||
fn declared(list: &[(&str, &str, &str)]) -> BTreeMap<String, BTreeSet<String>> {
|
||||
list.iter()
|
||||
.map(|(name, classes, _)| {
|
||||
(
|
||||
(*name).to_string(),
|
||||
classes.split_whitespace().map(str::to_string).collect(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Render a `class -> names` map as the declared-list rows it would need, so a failure hands you
|
||||
/// the entry to paste rather than a set difference to transcribe.
|
||||
fn as_rows(diff: &BTreeMap<String, BTreeSet<String>>) -> String {
|
||||
let mut by_name: BTreeMap<&String, Vec<&String>> = BTreeMap::new();
|
||||
for (class, names) in diff {
|
||||
for n in names {
|
||||
by_name.entry(n).or_default().push(class);
|
||||
}
|
||||
}
|
||||
by_name
|
||||
.iter()
|
||||
.map(|(name, classes)| {
|
||||
format!(
|
||||
" (\"{name}\", \"{}\", \"WHY\"),",
|
||||
classes
|
||||
.iter()
|
||||
.map(|c| c.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
/// Every widget method the 1.12 client registers on a class is answered by ours, or listed.
|
||||
#[test]
|
||||
fn every_1_12_widget_method_is_answered() {
|
||||
let reference = reference_surface_by_class();
|
||||
let ours = ours_by_class();
|
||||
let listed = declared(NOT_YET_ANSWERED);
|
||||
|
||||
let mut missing: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
|
||||
let mut total = 0usize;
|
||||
for (class, want) in &reference {
|
||||
let have = ours.get(*class).cloned().unwrap_or_default();
|
||||
total += want.len();
|
||||
let gap: BTreeSet<String> = want
|
||||
.iter()
|
||||
.filter(|n| !have.contains(*n))
|
||||
.filter(|n| !listed.get(*n).is_some_and(|c| c.contains(*class)))
|
||||
.cloned()
|
||||
.collect();
|
||||
if !gap.is_empty() {
|
||||
missing.insert((*class).to_string(), gap);
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
total > 2_000,
|
||||
"only {total} class/method pairs on the reference side — the chain stopped resolving"
|
||||
);
|
||||
assert!(
|
||||
missing.is_empty(),
|
||||
"the 1.12 client registers these on a class benilla does not answer them on, and they are \
|
||||
not listed:\n{}\n\nImplement it, or add the row to NOT_YET_ANSWERED with the reason it \
|
||||
has not been built.",
|
||||
as_rows(&missing)
|
||||
);
|
||||
|
||||
// A listed name that came back fails, so the list cannot outlive its fix.
|
||||
let mut healed: Vec<String> = Vec::new();
|
||||
for (name, classes, _) in NOT_YET_ANSWERED {
|
||||
for class in classes.split_whitespace() {
|
||||
if ours.get(class).is_some_and(|h| h.contains(*name)) {
|
||||
healed.push(format!("{class}:{name}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
healed.is_empty(),
|
||||
"these are answered now — take them out of NOT_YET_ANSWERED so the list keeps meaning what \
|
||||
it says: {healed:?}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Every widget method benilla answers is one 1.12 registers on that class, or a listed exception.
|
||||
#[test]
|
||||
fn our_widget_methods_stay_inside_the_1_12_surface() {
|
||||
let reference = reference_surface_by_class();
|
||||
let ours = ours_by_class();
|
||||
let listed = declared(ALLOWED_BEYOND);
|
||||
|
||||
let mut beyond: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
|
||||
for (class, have) in &ours {
|
||||
let Some(want) = reference.get(class.as_str()) else {
|
||||
panic!("{class} is instantiable here but has no row in WIDGET_CHAINS");
|
||||
};
|
||||
let extra: BTreeSet<String> = have
|
||||
.iter()
|
||||
.filter(|n| !want.contains(*n))
|
||||
// Our host bridge and the VM's own internals declare themselves ours by prefix, the
|
||||
// same discipline `reference_surface` uses for globals.
|
||||
.filter(|n| !n.starts_with("Benilla") && !n.starts_with("__benilla_"))
|
||||
.filter(|n| !listed.get(*n).is_some_and(|c| c.contains(class)))
|
||||
.cloned()
|
||||
.collect();
|
||||
if !extra.is_empty() {
|
||||
beyond.insert(class.clone(), extra);
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
beyond.is_empty(),
|
||||
"benilla answers these on a class the 1.12 client does not register them on:\n{}\n\n\
|
||||
1.12 is the target (decision 1188). Either remove it, give it its 1.12 spelling, or add \
|
||||
the row to ALLOWED_BEYOND WITH the reason it has to stay — an unexplained superset is \
|
||||
what 1189 had to roll back, and an addon that feature-detects one takes a path we cannot \
|
||||
honour.",
|
||||
as_rows(&beyond)
|
||||
);
|
||||
|
||||
let mut gone: Vec<String> = Vec::new();
|
||||
for (name, classes, _) in ALLOWED_BEYOND {
|
||||
for class in classes.split_whitespace() {
|
||||
if !ours.get(class).is_some_and(|h| h.contains(*name)) {
|
||||
gone.push(format!("{class}:{name}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
gone.is_empty(),
|
||||
"these are no longer exposed — take them out of ALLOWED_BEYOND: {gone:?}"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user