Add /tfb test mode for previewing target buffs and debuffs

- /tfb test [buffs debuffs] and an options-panel button fill the target frame
  with fake auras (default 32 buffs, 16 debuffs) to check layout, stacks,
  borders, timers and sweeps. Targets the player if nothing is targeted.
- Fake auras restart when they expire so timers keep running.
- Works on the native path too (icons, stacks, borders only).
- Bump to 0.9.4; tests now 21 scenarios.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019W3rwutyoLNHxXPycYWSMc
This commit is contained in:
2026-11-01 00:22:03 +00:00
co-authored by Claude Sonnet 5.5
parent 09784ec5b2
commit cbf459104b
7 changed files with 153 additions and 11 deletions
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## 0.9.4
- Add `/tfb test [buffs debuffs]` and an options-panel button: fake auras on the
current target for checking layout, timers, stacks, borders and sweeps.
- Tests: 21 scenarios.
## 0.9.3
- Warn once in chat when ShaguTweaks Debuff Timer is drawing on target debuffs.
+7
View File
@@ -38,10 +38,17 @@ aura order and lists your own auras first.
| `/tfb sweeps [on\|off]` | Reverse cooldown sweeps | off |
| `/tfb size 6-20` | Countdown font size | 9 |
| `/tfb warning 0-30` | Seconds left at which text turns red | 5 |
| `/tfb test [buffs debuffs]` | Fake auras on your target for checking layout (default 32 and 16; 0-32 each). Run again or `/tfb test off` to stop. | off |
| `/tfb reset` | Restore defaults | |
Settings are saved and kept even while ClassicAPI is absent.
**Test mode** fills the target frame with fake buffs and debuffs so you can check
layout, borders, stack counts and, with ClassicAPI, timers, sweeps and the
red warning. It targets you if you have no target, restarts expiring fake auras
so timers keep running, and is not saved; `/reload` turns it off. Debuffs past 16
spill into the buff row, as real ones do. The options panel has a matching button.
## Compatibility with ShaguTweaks
Checked against the upstream [ShaguTweaks](https://github.com/shagu/ShaguTweaks) source.
+18 -1
View File
@@ -72,6 +72,14 @@ local function CreateOptions()
end);
sliders[i] = slider;
end
local testButton = CreateFrame("Button", "TargetFrameBuffTestButton", panel, "UIPanelButtonTemplate");
testButton:SetWidth(180);
testButton:SetHeight(22);
testButton:SetPoint("TOPLEFT", panel, "TOPLEFT", 22, -330);
testButton:SetText("Toggle test auras");
testButton:SetScript("OnClick", function()
TargetFrameBuff_SetTest(not TargetFrameBuff_IsTest());
end);
local saved = {};
local function Sync()
for i = 1, 3 do checks[i]:SetChecked(TargetFrameBuffDB[checks[i].key]); end
@@ -115,6 +123,15 @@ SlashCmdList["TARGETFRAMEBUFF"] = function(message)
Print("Choose a number from "..min.." to "..max.."."); return;
end
TargetFrameBuffDB[command == "size" and "timerSize" or "warning"] = math.floor(number + 0.5);
elseif (command == "test") then
local _, _, buffs, debuffs = string.find(value, "^(%d+)%s*(%d*)$");
local on;
if (value == "off") then on = false;
elseif (value == "" ) then on = not TargetFrameBuff_IsTest();
elseif (value == "on" or buffs) then on = true;
else Print("Use /tfb test [on|off] or /tfb test <buffs> <debuffs>."); return; end
local _, nb, nd = TargetFrameBuff_SetTest(on, tonumber(buffs), tonumber(debuffs));
Print("test auras: "..(on and ("on ("..nb.." buffs, "..nd.." debuffs). Needs a target; /tfb test turns it off.") or "off"));
elseif (command == "reset") then
TargetFrameBuffDB = nil;
Validate();
@@ -122,7 +139,7 @@ SlashCmdList["TARGETFRAMEBUFF"] = function(message)
CreateOptions();
InterfaceOptionsFrame_OpenToCategory(panel);
else
Print("/tfb timers|highlight|sweeps [on|off], /tfb size 6-20, /tfb warning 0-30, /tfb reset");
Print("/tfb timers|highlight|sweeps [on|off], /tfb size 6-20, /tfb warning 0-30, /tfb test [buffs debuffs], /tfb reset");
return;
end
RefreshAuras();
+91 -6
View File
@@ -26,6 +26,42 @@ local lHelpfulSlots = {};
local lHarmfulSlots = {};
local lRemovableColor = {r = 1, g = 0.82, b = 0};
-- Test mode (/tfb test): fake auras on whatever you are targeting, for checking
-- layout, timers, stacks and borders. Lasts until turned off or /reload.
local lTestMode = false;
local lTestBuffs = 32;
local lTestDebuffs = 16;
local lTestStart = 0;
local lTestElapsed = 0;
local TEST_ICONS = {
"Interface\\Icons\\Spell_Holy_WordFortitude", "Interface\\Icons\\Spell_Nature_Regeneration",
"Interface\\Icons\\Spell_Shadow_ShadowWordPain", "Interface\\Icons\\Spell_Fire_FlameBolt",
"Interface\\Icons\\Spell_Frost_FrostArmor02", "Interface\\Icons\\Spell_Nature_Slow",
"Interface\\Icons\\Ability_Rogue_Rupture", "Interface\\Icons\\Spell_Holy_PowerWordShield"
};
local TEST_TYPES = {"Magic", "Curse", "Disease", "Poison"};
-- Mixed lengths so seconds, minutes, hours and the red warning all appear.
local TEST_DURATIONS = {10, 18, 45, 90, 300, 900, 1800, 3600, 7200};
-- Lua 5.0 has no % operator.
local function TargetFrameBuff_Mod(a, b)
return a - b * math.floor(a / b);
end
-- Same values as UnitAura: name, icon, count, dispelType, duration, expiration,
-- source, removable. Each aura restarts when its time runs out.
local function TargetFrameBuff_TestAura(pass, index)
local duration = TEST_DURATIONS[TargetFrameBuff_Mod(index * 2, table.getn(TEST_DURATIONS)) + 1];
local expiration = lTestStart + duration * (math.floor((GetTime() - lTestStart) / duration) + 1);
local count = 1;
if (TargetFrameBuff_Mod(index, 4) == 0) then count = TargetFrameBuff_Mod(index, 7) + 2; end
local dispelType = nil;
if (pass == 2) then dispelType = TEST_TYPES[TargetFrameBuff_Mod(index, 5) + 1]; end
local icon = TEST_ICONS[TargetFrameBuff_Mod(index, table.getn(TEST_ICONS)) + 1];
return "Test aura "..index, icon, count, dispelType, duration, expiration, "player",
(pass == 1 and TargetFrameBuff_Mod(index, 5) == 0);
end
local function TargetFrameBuff_Setting(key)
if (TargetFrameBuffDB and TargetFrameBuffDB[key] ~= nil) then
return TargetFrameBuffDB[key];
@@ -49,7 +85,11 @@ local function TargetFrameBuff_AuraOnEnter()
TargetFrameBuff_UpdateClassicAPI();
if (not this.auraIndex) then GameTooltip:Hide(); return; end
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT", 15, -25);
GameTooltip:SetUnitAura("target", this.auraIndex, this.auraFilter);
if (lTestMode) then
GameTooltip:SetText("Test "..(this.auraFilter == "HARMFUL" and "debuff " or "buff ")..this.auraIndex);
else
GameTooltip:SetUnitAura("target", this.auraIndex, this.auraFilter);
end
GameTooltip:Show();
end
@@ -205,7 +245,12 @@ end
function TargetFrameBuff_UpdateNative()
local button, buff, i;
for i=1, TARGETFRAMEBUFF_MAX_TARGET_BUFFS do
buff = UnitBuff("target", i);
if (lTestMode) then
buff = nil;
if (i <= lTestBuffs) then buff = TEST_ICONS[TargetFrameBuff_Mod(i, table.getn(TEST_ICONS)) + 1]; end
else
buff = UnitBuff("target", i);
end
button = lBuffButtons[i];
if (buff) then
getglobal("TargetFrameBuff"..i.."Icon"):SetTexture(buff);
@@ -218,7 +263,16 @@ function TargetFrameBuff_UpdateNative()
local debuff, debuffApplications, debuffType;
for i=1, TARGETFRAMEBUFF_MAX_TARGET_DEBUFFS do
debuff, debuffApplications, debuffType = UnitDebuff("target", i);
if (lTestMode) then
debuff, debuffApplications, debuffType = nil, nil, nil;
if (i <= lTestDebuffs) then
debuff = TEST_ICONS[TargetFrameBuff_Mod(i, table.getn(TEST_ICONS)) + 1];
debuffApplications = TargetFrameBuff_Mod(i, 4) == 0 and 3 or 1;
debuffType = TEST_TYPES[TargetFrameBuff_Mod(i, 5) + 1];
end
else
debuff, debuffApplications, debuffType = UnitDebuff("target", i);
end
button = lDebuffButtons[i];
if (debuff) then
TargetFrameBuff_ColorBorder(i, debuffType);
@@ -320,7 +374,7 @@ local function TargetFrameBuff_ShowAura(button, filter, index, icon, count, disp
button.timerText = nil;
button.timer:Hide();
end
if (GameTooltip:IsOwned(button)) then
if (GameTooltip:IsOwned(button) and not lTestMode) then
GameTooltip:SetUnitAura("target", index, filter);
end
end
@@ -342,13 +396,17 @@ function TargetFrameBuff_UpdateClassicAPI()
else
filter, slots = "HARMFUL", lHarmfulSlots;
end
if (batched) then
if (lTestMode) then
n = (pass == 1) and lTestBuffs or lTestDebuffs;
elseif (batched) then
token, n = C_UnitAuras.GetAuraSlots("target", filter, nil, nil, slots);
else
n = TARGETFRAMEBUFF_MAX_TARGET_BUFFS + TARGETFRAMEBUFF_MAX_TARGET_DEBUFFS;
end
for index = 1, n do
if (batched) then
if (lTestMode) then
name, icon, count, dispelType, duration, expiration, source, removable = TargetFrameBuff_TestAura(pass, index);
elseif (batched) then
name, icon, count, dispelType, duration, expiration, source, removable = C_UnitAuras.UnitAuraBySlot("target", slots[index]);
else
name, icon, count, dispelType, duration, expiration, source, removable = C_UnitAuras.UnitAura("target", index, filter);
@@ -455,6 +513,13 @@ end
-- Driven by TargetFrameBuffFrame, which is parented to TargetFrame, so this
-- only runs while you have a target.
function TargetFrameBuff_OnUpdate(elapsed)
if (lTestMode) then
lTestElapsed = lTestElapsed + (elapsed or 0);
if (lTestElapsed >= 1) then
lTestElapsed = 0;
TargetFrameBuff_Update();
end
end
if (not lBuffButtons[1] or not lBuffButtons[1].timer or not TargetFrameBuff_HasClassicAPI()) then
return;
end
@@ -500,3 +565,23 @@ function TargetFrameBuff_Restore()
end
end
end
-- Turn test mode on or off. buffs (0-32) and debuffs (0-32) are optional; debuffs
-- past 16 spill into the buff row, as real ones do with ClassicAPI.
function TargetFrameBuff_SetTest(enabled, buffs, debuffs)
lTestMode = enabled and true or false;
if (buffs) then lTestBuffs = math.max(0, math.min(TARGETFRAMEBUFF_MAX_TARGET_BUFFS, math.floor(buffs))); end
if (debuffs) then lTestDebuffs = math.max(0, math.min(TARGETFRAMEBUFF_MAX_TARGET_BUFFS, math.floor(debuffs))); end
lTestStart = GetTime();
lTestElapsed = 0;
if (lTestMode and UnitExists and TargetUnit and not UnitExists("target")) then
TargetUnit("player");
end
TargetFrameBuff_Update();
return lTestMode, lTestBuffs, lTestDebuffs;
end
function TargetFrameBuff_IsTest()
return lTestMode;
end
+1 -1
View File
@@ -1,5 +1,5 @@
## Interface: 1700
## Title: TargetFrameBuff v0.9.3
## Title: TargetFrameBuff v0.9.4
## Notes: Shows up to 32 Buffs & 16 Debuffs on a Target, with countdowns when ClassicAPI is installed
## Author: lua@lumpn.de (modified)
## SavedVariables: TargetFrameBuffDB
+3 -3
View File
@@ -1,8 +1,8 @@
# TargetFrameBuff v0.9.3 validation
# TargetFrameBuff v0.9.4 validation
19 simulated Lua 5.1 regression scenarios passed, plus XML parsing. These cover
21 simulated Lua 5.1 regression scenarios passed, plus XML parsing. These cover
old/native clients, aura reordering, overflow, settings, sweep lifecycle, delta
refreshes and full updates, the Debuff Timer overlap warning, target/pet/spellbook changes, reused delta payloads,
refreshes and full updates, the Debuff Timer overlap warning, test mode, target/pet/spellbook changes, reused delta payloads,
no polling in delta mode, and native-number/flash suppression.
ClassicAPI 1.17.10+ is detected through GetAuraDataByAuraInstanceID, introduced with
+27
View File
@@ -226,6 +226,33 @@ TargetFrameDebuff1.cd={readable={}}
Tick(); Tick()
for _,m in ipairs(messages) do assert(not string.find(m,"Debuff Timer",1,true)) end
''')
run("test mode fills all 32 buffs and 16 debuffs, then restores real auras", 'InstallAPI(true); auras.HELPFUL={Aura("Real")}', '''
Tick(); assert(TargetFrameBuff1Icon.texture=="Real" and not TargetFrameBuff2:IsShown())
SlashCmdList.TARGETFRAMEBUFF("test")
assert(TargetFrameBuff_IsTest())
for i=1,32 do assert(_G["TargetFrameBuff"..i]:IsShown() and _G["TargetFrameBuff"..i].timer.text) end
for i=1,16 do assert(_G["TargetFrameDebuff"..i]:IsShown() and _G["TargetFrameDebuff"..i].timer.text) end
assert(TargetFrameBuff5.auraBorder:IsShown() and not TargetFrameBuff6.auraBorder:IsShown())
assert(TargetFrameBuff4.auraCount.text==6)
this=TargetFrameBuff3; this.scripts.OnEnter()
assert(GameTooltip.tooltip==nil)
now=now+500; Tick(); TargetFrameBuff_OnUpdate(1)
for i=1,32 do assert(_G["TargetFrameBuff"..i]:IsShown()) end
SlashCmdList.TARGETFRAMEBUFF("test")
assert(not TargetFrameBuff_IsTest())
assert(TargetFrameBuff1Icon.texture=="Real" and not TargetFrameBuff2:IsShown())
''')
run("test mode custom counts, clamping, native path and bad input", 'nativeBuffs={}; nativeDebuffs={}', '''
SlashCmdList.TARGETFRAMEBUFF("test 3 5")
assert(TargetFrameBuff3:IsShown() and not TargetFrameBuff4:IsShown())
assert(TargetFrameDebuff5:IsShown() and not TargetFrameDebuff6:IsShown())
SlashCmdList.TARGETFRAMEBUFF("test 40 99")
assert(TargetFrameBuff32:IsShown() and TargetFrameDebuff16:IsShown())
SlashCmdList.TARGETFRAMEBUFF("test banana")
assert(TargetFrameBuff_IsTest())
SlashCmdList.TARGETFRAMEBUFF("test off")
assert(not TargetFrameBuff_IsTest() and not TargetFrameBuff1:IsShown() and not TargetFrameDebuff1:IsShown())
''')
ET.parse(addon/"TargetFrameBuff.xml")
print("PASS XML parsing")
print(f"{passed} Lua regression scenarios passed; XML parsed.")