Files
ShaguTweaks-ClassicAPI/tests/test_control_timers_native.lua

188 lines
10 KiB
Lua

local root=(arg[1] or '.')..'/'
local function Check(native)
local env=setmetatable({},{__index=getfenv(0)});env._G=env
local modules,frames,timers,slots,effects={},{},{},{},{}
local now,keys,totemReads,effectReads,schoolReads=10,false,0,0,0
local methods={}
local function object(kind,name,parent)
local f=setmetatable({kind=kind,parent=parent,scripts={},events={},shown=true,writes=0},
{__index=function(_,key)return methods[key] or (string.find(key,'^[A-Z]') and function()end or nil)end})
table.insert(frames,f);if name then env[name]=f end;return f
end
function methods:RegisterEvent(name)self.events[name]=true end
function methods:UnregisterAllEvents()self.events={}end
function methods:SetScript(k,v)self.scripts[k]=v end
function methods:Show()self.shown=true end
function methods:Hide()self.shown=false end
function methods:IsShown()return self.shown end
function methods:CreateTexture()return object('Texture',nil,self)end
function methods:CreateFontString()return object('FontString',nil,self)end
function methods:SetTexture(path)self.path=path;self.writes=self.writes+1 end
function methods:SetText(text)self.text=text;self.writes=self.writes+1 end
function methods:SetTextColor(...)self.textColor=arg end
function methods:SetStatusBarColor(...)self.color=arg end
function methods:SetVertexColor(...)self.color=arg end
function methods:SetWidth(n)self.width=n end
function methods:SetStatusBarTexture(p)self.path=p end
function methods:SetMinMaxValues()self.timer=nil end
function methods:SetValue(v)self.value=v end
function methods:SetTimerDuration(d,_,direction)self.timer,self.direction=d,direction end
env.CreateFrame=object;env.UIParent=object('Frame')
env.SlashCmdList={};env.ShaguTweaks_config={}
env.GetTime=function()return now end
env.UnitClass=function()return 'Shaman','SHAMAN' end
env.GameTooltip={Hide=function()end,SetOwner=function()end,SetTotem=function()end}
env.Enum={StatusBarInterpolation={Immediate=0},StatusBarTimerDirection={RemainingTime=1},NumericRuleFormatRounding={Down=2}}
env.C_Timer={NewTimer=function(delay,fn)
local t={at=now+delay,callback=fn};function t:Cancel()self.cancelled=true end
table.insert(timers,t);return t
end}
env.C_DurationUtil={CreateDuration=function()
local d={finish=0,total=0}
function d:SetTimeFromEnd(finish,total)self.finish,self.total=finish,total end
function d:GetRemainingDuration()return math.max(0,self.finish-now)end
return d
end,CreateDurationTextBinding=function()
local b={}
function b:SetFontString(text)self.text=text end
function b:SetDuration(d)self.duration=d end
function b:SetFormatter(f)self.formatter=f end
function b:SetExpiredText(t)self.expired=t end
function b:SetUpdateInterval(t)self.interval=t end
function b:Enable()self.enabled=true end
function b:Disable()self.enabled=false end
return b
end}
env.C_StringUtil={CreateNumericRuleFormatter=function()return {SetBreakpoints=function(self,rules)self.rules=rules end}end}
env.GetTotemInfo=function(slot)
local data=slots[slot]
if data then return true,'Totem',data.start,data.total,'icon' end
return false,'',0,0,''
end
env.GetTotemTimeLeft=function(slot)totemReads=totemReads+1;local s=slots[slot];return s and math.max(0,s.start+s.total-now)or 0 end
env.GetTotemDuration=function(slot)
local d=env.C_DurationUtil.CreateDuration();local s=slots[slot]
if s then d:SetTimeFromEnd(s.start+s.total,s.total)end
return d
end
env.C_LossOfControl={GetActiveLossOfControlDataCount=function()effectReads=effectReads+1;return table.getn(effects)end,
GetActiveLossOfControlData=function(i)
local e=effects[i]
return {locType=e.kind,timeRemaining=e.finish and math.max(0,e.finish-now),duration=e.total,
displayText=e.label or e.kind,lockoutSchool=e.mask or 0,iconTexture='effect'}
end,GetSchoolLockout=function()
schoolReads=schoolReads+1;local mask,left=0,0
for _,e in ipairs(effects)do if e.kind=='SCHOOL_INTERRUPT' and e.finish>now then mask=e.mask;left=math.max(left,e.finish-now)end end
return mask,mask~=0 and left or nil
end}
env.C_Spell={GetSchoolString=function()return 'Nature' end}
env.ShaguTweaks={T=setmetatable({},{__index=function(_,k)return k end}),
API={durationtimers=native,AreMoveModifiersDown=function()return keys end},GetGlobalEnv=function()return env end}
function env.ShaguTweaks:register(m)modules[m.title]=m;return m end
local function load(path)local f=assert(loadfile(root..path));setfenv(f,env);f()end
local function emit(name)
env.event=name
for _,f in ipairs(frames)do if f.events[name]then env.this=f;f.scripts.OnEvent()end end
env.this=nil
end
local function advance(delta)
local finish=now+delta
for iteration=1,1000 do
local nextTimer
for _,t in ipairs(timers)do
if not t.cancelled and not t.fired and t.at<=finish and (not nextTimer or t.at<nextTimer.at)then nextTimer=t end
end
if not nextTimer then now=finish;return end
now=nextTimer.at;nextTimer.fired=true;nextTimer.callback()
end
error('timer loop')
end
slots[1]={start=9,total=30};slots[3]={start=9,total=60}
load('mods/totem-timers.lua');modules['Totem Timers']:enable()
local totem=env.ShaguTweaksTotem1;local holder=env.ShaguTweaksTotemTimers
local function formatted(formatter,n)
local rule
for _,candidate in ipairs(formatter.rules)do if n>=candidate.threshold then rule=candidate end end
local function rounded(v,step,mode)
if not step then return v end
return (mode==2 and math.floor(v/step) or math.floor(v/step+.5))*step
end
n=rounded(n,rule.step,rule.rounding)
if rule.components then
local values={}
for _,part in ipairs(rule.components)do
local v=n;if part.div then v=v/part.div end;if part.mod then v=math.mod(v,part.mod)end
table.insert(values,rounded(v,part.step,part.rounding))
end
return string.format(rule.format,unpack(values))
end
return string.format(rule.format,n)
end
if native then
assert(not holder.scripts.OnUpdate and totem.binding.enabled and totem.bar.timer.total==30 and totem.bar.direction==1)
assert(totem.binding.formatter==env.ShaguTweaksTotem3.binding.formatter)
assert(totem.binding.formatter.rules[4].format=='|cffffffff%d:%02d')
assert(formatted(totem.binding.formatter,5)=='|cffff33335')
assert(formatted(totem.binding.formatter,5.001)=='|cffffe6335')
assert(formatted(totem.binding.formatter,10)=='|cffffe63310')
assert(formatted(totem.binding.formatter,10.001)=='|cffffffff10')
assert(formatted(totem.binding.formatter,61.9)=='|cffffffff1:01')
local original=totem.expiry;slots[1]={start=10,total=60};emit('PLAYER_TOTEM_UPDATE')
assert(original.cancelled and totem.bar.timer.finish==70)
local oldTimer=totem.expiry;keys=true;emit('MODIFIER_STATE_CHANGED')
assert(oldTimer.cancelled and not totem.binding.enabled and totem.bar.value==1)
keys=false;emit('MODIFIER_STATE_CHANGED');assert(totem.binding.enabled)
local before=totemReads;advance(61);assert(totemReads==before and not holder.shown)
assert(not totem.binding.enabled and not totem.shown)
slots[2]={start=now,total=30};emit('PLAYER_TOTEM_UPDATE')
local earth=env.ShaguTweaksTotem2;local pending=earth.expiry
modules['Totem Timers']:disable();assert(pending.cancelled and not earth.binding.enabled)
modules['Totem Timers']:enable();assert(earth.binding.enabled)
else
assert(holder.scripts.OnUpdate and not totem.binding)
assert(totem.time.text=='29')
now=36;env.arg1=.1;holder.scripts.OnUpdate();assert(totem.time.text=='3' and totem.time.textColor[2]==.2)
end
modules['Totem Timers']:disable()
effects={{kind='ROOT',finish=now+20,total=20},{kind='STUN',finish=now+4,total=4}}
load('mods/loss-of-control.lua');modules['Loss of Control Alert']:enable()
local alert=env.ShaguTweaksLossOfControl
assert(alert.title.text=='Stunned')
if native then
assert(not alert.scripts.OnUpdate and alert.bar.timer.total==4)
local before=effectReads;local writes=alert.icon.writes
advance(2);assert(effectReads==before and alert.icon.writes==writes,'native animation must not rescan/repaint')
advance(2.1);assert(alert.title.text=='Rooted' and alert.shown)
effects={};emit('LOSS_OF_CONTROL_UPDATE');assert(not alert.shown)
effects={{kind='STUN'}};emit('LOSS_OF_CONTROL_UPDATE')
assert(alert.shown and alert.time.text=='' and not alert.bar.shown)
keys=true;emit('MODIFIER_STATE_CHANGED');assert(alert.time.text=='8.0' and alert.bar.value==1)
keys=false;emit('MODIFIER_STATE_CHANGED');assert(alert.time.text=='' and alert.shown)
effects={{kind='SCHOOL_INTERRUPT',mask=8,finish=now+6,total=6}}
emit('LOSS_OF_CONTROL_UPDATE');local listReads=effectReads;advance(.4)
assert(effectReads==listReads and schoolReads>0,'steady lockouts use scalar queries only')
local oldEnd=alert.bar.timer.finish
effects[1].finish=now+10;effects[1].total=10
advance(.3);assert(alert.bar.timer.finish>oldEnd and effectReads>listReads)
local scalarQuery=env.C_LossOfControl.GetSchoolLockout
env.C_LossOfControl.GetSchoolLockout=nil
listReads=effectReads;advance(.3);assert(effectReads>listReads,'older scalar-less API must still detect extensions')
env.C_LossOfControl.GetSchoolLockout=scalarQuery
advance(6);assert(alert.shown,'old expiry must not hide the extended lockout')
local beforeDisable=effectReads
modules['Loss of Control Alert']:disable();advance(20)
assert(not alert.shown and effectReads==beforeDisable)
modules['Loss of Control Alert']:enable();assert(not alert.shown)
-- A higher-priority newly applied effect replaces the lockout immediately.
effects={{kind='ROOT',finish=now+10,total=10}};emit('LOSS_OF_CONTROL_UPDATE')
table.insert(effects,{kind='FEAR',finish=now+3,total=3});emit('LOSS_OF_CONTROL_ADDED')
assert(alert.title.text=='Feared');advance(3.1);assert(alert.title.text=='Rooted')
else
assert(alert.scripts.OnUpdate and alert.time.text=='4.0')
env.arg1=.05;alert.scripts.OnUpdate();assert(alert.title.text=='Stunned')
end
modules['Loss of Control Alert']:disable()
end
Check(true);Check(false)
print('Control timers: native totem fill/text/expiry, replacements, preview/disable cleanup, loss-of-control priority/untimed effects, school extension scalar checks and older API fallback passed')