Files
benilla-octo/crates/benilla-ui/src/script/tests/statusbar.rs
T
SamandClaude Fable 5 2ad0043523 benilla: initial public release
A from-scratch World of Warcraft 1.12.1 client in Rust + Bevy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:27:01 +07:00

199 lines
6.2 KiB
Rust

//! StatusBar (per-kind behavior; RF-28-grounded).
use super::common::script;
use crate::script::*;
#[test]
fn statusbar_methods_exist_only_on_statusbars() {
let s = script();
let ok: bool = s
.eval(
r#"
local bar = CreateFrame("StatusBar", "SbDuck")
local plain = CreateFrame("Frame")
-- Duck-typing: addons branch on `if frame.SetValue then` — a plain frame must say nil.
return (type(bar.SetValue) == "function") and (plain.SetValue == nil)
and (type(bar.SetMinMaxValues) == "function") and (plain.SetMinMaxValues == nil)
and (type(bar.Show) == "function") -- base methods still reachable through the fallback
"#,
)
.unwrap();
assert!(ok);
}
#[test]
fn statusbar_value_clamps_and_minmax_swaps() {
let s = script();
s.run(
r#"
local b = CreateFrame("StatusBar", "SbClamp")
b:SetMinMaxValues(0, 100)
b:SetValue(250)
assert(b:GetValue() == 100, "clamped to max")
b:SetValue(-5)
assert(b:GetValue() == 0, "clamped to min")
-- A reversed pair is swapped (RF-28's LoadXML behavior, one rule for both paths).
b:SetMinMaxValues(80, 20)
local mn, mx = b:GetMinMaxValues()
assert(mn == 20 and mx == 80, "reversed pair swapped")
assert(b:GetValue() == 20, "held value re-clamped into the new range")
"#,
)
.unwrap();
}
#[test]
fn statusbar_setvalue_fires_onvaluechanged() {
let s = script();
s.run(
r#"
local b = CreateFrame("StatusBar", "SbEvt")
b:SetMinMaxValues(0, 10)
seen = {}
b:SetScript("OnValueChanged", function(self, value)
table.insert(seen, value)
assert(self == b and arg1 == value, "RF-0025 conventions carry the value")
end)
b:SetValue(4)
b:SetValue(4) -- no change, no fire
b:SetMinMaxValues(0, 3) -- re-clamp 4 -> 3: a value change, fires
assert(table.getn(seen) == 2 and seen[1] == 4 and seen[2] == 3,
"fired once per actual change: " .. table.getn(seen))
"#,
)
.unwrap();
}
#[test]
fn statusbar_bar_region_scales_by_fraction_on_extract() {
let mut s = script();
s.set_screen_size(1024.0, 768.0);
s.run(
r#"
local b = CreateFrame("StatusBar", "SbFill")
b:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 100, 100)
b:SetSize(200, 20)
b:SetStatusBarTexture(0.2, 0.7, 0.2)
b:SetMinMaxValues(0, 100)
b:SetValue(25)
"#,
)
.unwrap();
s.resolve();
let quads = s.extract();
// The bar-fill texture: a solid-color region whose rect is 25% of the frame width.
let bar = quads
.iter()
.find(|q| {
matches!(
&q.content,
QuadContent::Texture {
path: None,
color: Some(_),
..
}
)
})
.expect("bar fill quad");
let r = bar.rect.expect("bar rect resolved");
assert_eq!(
(r.left, r.right, r.bottom, r.top),
(100.0, 150.0, 100.0, 120.0),
"horizontal fill: left-pinned, 25% of 200px"
);
// Vertical: fills bottom-up.
s.run(r#"SbFill:SetOrientation("VERTICAL")"#).unwrap();
let quads = s.extract();
let bar = quads
.iter()
.find(|q| {
matches!(
&q.content,
QuadContent::Texture {
path: None,
color: Some(_),
..
}
)
})
.expect("bar fill quad");
let r = bar.rect.expect("bar rect resolved");
assert_eq!(
(r.left, r.right, r.bottom, r.top),
(100.0, 300.0, 100.0, 105.0),
"vertical fill: bottom-pinned, 25% of 20px"
);
}
/// The fill CROPs its art, never squeezes it (wow-re `nameplate-vkey.md`, VERIFIED): `SetValue`
/// rewrites the region's 4-corner UV block with `u1 = GetValue()` *and* shrinks the quad. Squeezing
/// instead would run a bar texture's whole horizontal ramp inside every partial fill.
#[test]
fn statusbar_bar_region_crops_its_texture_rather_than_stretching_it() {
let mut s = script();
s.set_screen_size(1024.0, 768.0);
s.run(
r#"
local b = CreateFrame("StatusBar", "SbCrop")
b:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 0, 0)
b:SetSize(200, 20)
b:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
b:SetMinMaxValues(0, 100)
b:SetValue(25)
"#,
)
.unwrap();
let uv = |s: &UiScript| -> [f32; 4] {
s.extract()
.iter()
.find_map(|q| match &q.content {
QuadContent::Texture {
path: Some(_),
tex_coords,
..
} => tex_coords.map(|tc| tc.edges()),
_ => None,
})
.expect("the bar fill always carries its crop")
};
s.resolve();
assert_eq!(
uv(&s),
[0.0, 0.25, 0.0, 1.0],
"horizontal: u cropped to the fill fraction, v whole"
);
// Vertical fills bottom-up, so the art's BOTTOM edge is the one pinned.
s.run(r#"SbCrop:SetOrientation("VERTICAL")"#).unwrap();
assert_eq!(
uv(&s),
[0.0, 1.0, 0.75, 1.0],
"vertical: v cropped up from the bottom, u whole"
);
// An empty bar samples nothing; a full one samples everything.
s.run("SbCrop:SetValue(0)").unwrap();
assert_eq!(uv(&s), [0.0, 1.0, 1.0, 1.0], "empty");
s.run("SbCrop:SetValue(100)").unwrap();
assert_eq!(uv(&s), [0.0, 1.0, 0.0, 1.0], "full");
}
#[test]
fn statusbar_get_texture_returns_the_bar_region() {
let s = script();
s.run(
r#"
local b = CreateFrame("StatusBar", "SbTex")
assert(b:GetStatusBarTexture() == nil, "no bar before one is set")
b:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
local t = b:GetStatusBarTexture()
assert(t ~= nil and t == b:GetStatusBarTexture(), "stable region wrapper")
b:SetStatusBarColor(1, 0, 0)
local r, g, bb, a = b:GetStatusBarColor()
assert(r == 1 and g == 0 and bb == 0 and a == 1)
"#,
)
.unwrap();
}