From 19c2340cf77c7e05feef0cac4470d98e3e23a6a1 Mon Sep 17 00:00:00 2001 From: Argi <15852038+argium@users.noreply.github.com> Date: Sat, 23 May 2026 19:29:31 +1000 Subject: [PATCH 1/2] Round calculated colours to protect against floating point precision errors. Moved IsDeathKnight into ClassUtil. Removed unused ColorUtil.AreEqual --- ClassUtil.lua | 7 ++++ ColorUtil.lua | 14 -------- Constants.lua | 47 +++++++++++--------------- ECM.lua | 6 ---- EnhancedCooldownManager.code-workspace | 3 +- Modules/RuneBar.lua | 4 +-- Runtime.lua | 2 +- Tests/ClassUtil_spec.lua | 18 ++++++++++ Tests/ColorUtil_spec.lua | 19 +++++------ Tests/FrameUtil_spec.lua | 10 ------ Tests/Modules/RuneBar_spec.lua | 8 +++-- Tests/TestHelpers.lua | 11 +++--- UI/ResourceBarOptions.lua | 2 +- UI/RuneBarOptions.lua | 4 +-- 14 files changed, 73 insertions(+), 82 deletions(-) diff --git a/ClassUtil.lua b/ClassUtil.lua index 6cffd5b4..5f34410b 100644 --- a/ClassUtil.lua +++ b/ClassUtil.lua @@ -64,6 +64,12 @@ function ClassUtil.GetPlayerResourceType() return ClassUtil.GetResourceType(class, GetSpecialization(), GetShapeshiftForm()) end +--- Returns whether the player is a Death Knight. +function ClassUtil.IsDeathKnight() + local _, class = UnitClass("player") + return class == "DEATHKNIGHT" +end + --- Gets the max Maelstrom value that can diff based on talents local function getMaelstromWeaponMax() if C_SpellBook.IsSpellKnown(C.RESOURCEBAR_RAGING_MAELSTROM_SPELLID) then @@ -135,6 +141,7 @@ function ClassUtil.GetCurrentMaxResourceValues(resourceType) if resourceType then local max = UnitPowerMax("player", resourceType) local current = UnitPower("player", resourceType) + ---@type number|nil local safeMax = max if issecretvalue(max) then safeMax = nil diff --git a/ColorUtil.lua b/ColorUtil.lua index df07b357..eb715f0f 100644 --- a/ColorUtil.lua +++ b/ColorUtil.lua @@ -7,20 +7,6 @@ local _, ns = ... local ColorUtil = {} ns.ColorUtil = ColorUtil ---- Compares two ECM_Color tables for equality. ----@param c1 ECM_Color|nil ----@param c2 ECM_Color|nil ----@return boolean -function ColorUtil.AreEqual(c1, c2) - if c1 == c2 then - return true - end - if not c1 or not c2 then - return false - end - return c1.r == c2.r and c1.g == c2.g and c1.b == c2.b and c1.a == c2.a -end - local function clamp(v, minV, maxV) return math.max(minV, math.min(maxV, v)) end diff --git a/Constants.lua b/Constants.lua index 913be60e..f9af1da0 100644 --- a/Constants.lua +++ b/Constants.lua @@ -177,19 +177,19 @@ local constants = { --- Predefined icon stacks resolved at runtime by stackKey. --- Each entry defines an icon kind and its candidate sources. -local BUILTIN_STACKS = { +constants.BUILTIN_STACKS = { trinket1 = { kind = "equipSlot", slotId = 13, label = "Trinket 1" }, trinket2 = { kind = "equipSlot", slotId = 14, label = "Trinket 2" }, } --- Default display order for builtin stack keys (matches default viewers.utility order). -local BUILTIN_STACK_ORDER = { "trinket1", "trinket2" } +constants.BUILTIN_STACK_ORDER = { "trinket1", "trinket2" } -local DRACTHYR_WING_BUFFET_IDS = { 357214, 368970 } -- Base and enhanced evoker variants. +local dracthyrWingBuffetIds = { 357214, 368970 } -- Base and enhanced evoker variants. --- Racial ability lookup keyed by UnitRace("player") raceFileName. --- One primary active racial per race. -local RACIAL_ABILITIES = { +constants.RACIAL_ABILITIES = { Human = { spellId = 59752 }, -- Every Man for Himself Orc = { spellId = 33697 }, -- Blood Fury Dwarf = { spellId = 20594 }, -- Stoneform @@ -213,25 +213,25 @@ local RACIAL_ABILITIES = { Vulpera = { spellId = 312411 }, -- Bag of Tricks MagharOrc = { spellId = 274738 }, -- Ancestral Call Mechagnome = { spellId = 312924 }, -- Hyper Organic Light Originator - Dracthyr = { spellIds = DRACTHYR_WING_BUFFET_IDS }, -- Wing Buffet + Dracthyr = { spellIds = dracthyrWingBuffetIds }, -- Wing Buffet EarthenDwarf = { spellId = 436717 }, -- Azerite Surge } --- Some racial abilities have different spell IDs. For example, Dracthyr evokers --- have a more potent wing buffet compared to other classes. -local RACIAL_SPELL_ALIASES = { - [357214] = DRACTHYR_WING_BUFFET_IDS, - [368970] = DRACTHYR_WING_BUFFET_IDS, +constants.RACIAL_SPELL_ALIASES = { + [357214] = dracthyrWingBuffetIds, + [368970] = dracthyrWingBuffetIds, } -local BLIZZARD_FRAMES = { +constants.BLIZZARD_FRAMES = { "EssentialCooldownViewer", "UtilityCooldownViewer", "BuffIconCooldownViewer", "BuffBarCooldownViewer", } -local CLASS_COLORS = { +constants.CLASS_COLORS = { DEATHKNIGHT = "C41F3B", DEMONHUNTER = "A330C9", DRUID = "FF7D0A", @@ -249,19 +249,19 @@ local CLASS_COLORS = { -- Resource types that support a separate color when at maximum value. -- Code-level gate; user toggle is stored in the profile (maxColorsEnabled). -local resourceBarMaxColorTypes = { +constants.RESOURCEBAR_MAX_COLOR_TYPES = { [constants.RESOURCEBAR_TYPE_ICICLES] = true, [constants.RESOURCEBAR_TYPE_DEVOURER_META] = true, [constants.RESOURCEBAR_TYPE_DEVOURER_NORMAL] = true, } -local resourceBarCastableMaxColorSpells = { +constants.RESOURCEBAR_CASTABLE_MAX_COLOR_SPELLS = { [constants.RESOURCEBAR_TYPE_DEVOURER_META] = constants.SPELLID_COLLAPSING_STAR, [constants.RESOURCEBAR_TYPE_DEVOURER_NORMAL] = constants.SPELLID_VOID_META, } ---- Authoritative mapping from module name to its profile config key. -local moduleConfigKeys = { +--- Maps modules to their respective profile config key. +constants.MODULE_CONFIG_KEYS = { [constants.POWERBAR] = "powerBar", [constants.RESOURCEBAR] = "resourceBar", [constants.RUNEBAR] = "runeBar", @@ -273,18 +273,20 @@ local moduleConfigKeys = { --- Returns the profile config key for a module name. --- Uses the authoritative lookup; falls back to lowercasing the first character. function constants.ConfigKeyForModule(name) - return moduleConfigKeys[name] or (name:sub(1, 1):lower() .. name:sub(2)) + return constants.MODULE_CONFIG_KEYS[name] or (name:sub(1, 1):lower() .. name:sub(2)) end -local chainOrder = { +-- Defines the ordering of modules for chained anchoring. +constants.CHAIN_ORDER = { constants.POWERBAR, constants.RESOURCEBAR, constants.RUNEBAR, constants.BUFFBARS, constants.EXTERNALBARS, } -constants.CHAIN_ORDER = chainOrder -constants.MODULE_ORDER = { + +-- Controls the order that modules are loaded in. +constants.MODULE_LOAD_ORDER = { constants.POWERBAR, constants.RESOURCEBAR, constants.RUNEBAR, @@ -292,14 +294,5 @@ constants.MODULE_ORDER = { constants.EXTERNALBARS, constants.EXTRAICONS, } -constants.MODULE_CONFIG_KEYS = moduleConfigKeys -constants.BLIZZARD_FRAMES = BLIZZARD_FRAMES -constants.BUILTIN_STACKS = BUILTIN_STACKS -constants.BUILTIN_STACK_ORDER = BUILTIN_STACK_ORDER -constants.RACIAL_ABILITIES = RACIAL_ABILITIES -constants.RACIAL_SPELL_ALIASES = RACIAL_SPELL_ALIASES -constants.RESOURCEBAR_CASTABLE_MAX_COLOR_SPELLS = resourceBarCastableMaxColorSpells -constants.CLASS_COLORS = CLASS_COLORS -constants.RESOURCEBAR_MAX_COLOR_TYPES = resourceBarMaxColorTypes ns.Constants = constants diff --git a/ECM.lua b/ECM.lua index 762c898a..92c1d474 100644 --- a/ECM.lua +++ b/ECM.lua @@ -51,12 +51,6 @@ function ns.AreWarningsEnabled() return not gc or gc.warnings ~= false end ---- Returns whether the player is a Death Knight. -function ns.IsDeathKnight() - local _, class = UnitClass("player") - return class == "DEATHKNIGHT" -end - local function getAddonVersion() return C_AddOns.GetAddOnMetadata(ADDON_NAME, C.ADDON_METADATA_VERSION_KEY) end diff --git a/EnhancedCooldownManager.code-workspace b/EnhancedCooldownManager.code-workspace index 239ff677..7a11d98a 100644 --- a/EnhancedCooldownManager.code-workspace +++ b/EnhancedCooldownManager.code-workspace @@ -97,7 +97,8 @@ "SettingsPanel", "SettingsSliderControlMixin", "CreateSettingsListSectionHeaderInitializer", - "Round" + "Round", + "CLASS_COLORS" ], "Lua.diagnostics.disable": [ "assign-type-mismatch" diff --git a/Modules/RuneBar.lua b/Modules/RuneBar.lua index 1d5cea0c..3e9504b4 100644 --- a/Modules/RuneBar.lua +++ b/Modules/RuneBar.lua @@ -293,7 +293,7 @@ function RuneBar:CreateFrame() end function RuneBar:ShouldShow() - return ns.IsDeathKnight() and ns.BarMixin.FrameProto.ShouldShow(self) + return ns.ClassUtil.IsDeathKnight() and ns.BarMixin.FrameProto.ShouldShow(self) end function RuneBar:Refresh(why, force) @@ -359,7 +359,7 @@ function RuneBar:OnInitialize() end function RuneBar:OnEnable() - if not ns.IsDeathKnight() then + if not ns.ClassUtil.IsDeathKnight() then return end diff --git a/Runtime.lua b/Runtime.lua index 4bea7baa..7ebbd891 100644 --- a/Runtime.lua +++ b/Runtime.lua @@ -694,7 +694,7 @@ end function Runtime.Enable(addon) local profile = addon.db and addon.db.profile - for _, moduleName in ipairs(C.MODULE_ORDER) do + for _, moduleName in ipairs(C.MODULE_LOAD_ORDER) do local configKey = C.MODULE_CONFIG_KEYS[moduleName] local moduleConfig = profile and profile[configKey] local shouldEnable = (not moduleConfig) or (moduleConfig.enabled ~= false) diff --git a/Tests/ClassUtil_spec.lua b/Tests/ClassUtil_spec.lua index 2c6699cd..b9d04fda 100644 --- a/Tests/ClassUtil_spec.lua +++ b/Tests/ClassUtil_spec.lua @@ -71,6 +71,24 @@ describe("ClassUtil", function() end end) + describe("IsDeathKnight", function() + it("returns true when the player's class token is DEATHKNIGHT", function() + _G.UnitClass = function() + return "Death Knight", "DEATHKNIGHT", 6 + end + + assert.is_true(ns.ClassUtil.IsDeathKnight()) + end) + + it("returns false when the player's class token is not DEATHKNIGHT", function() + _G.UnitClass = function() + return "Warrior", "WARRIOR", 1 + end + + assert.is_false(ns.ClassUtil.IsDeathKnight()) + end) + end) + describe("GetResourceType", function() local function setAvailablePowerType(powerType) UnitStub.Reset() diff --git a/Tests/ColorUtil_spec.lua b/Tests/ColorUtil_spec.lua index 061aed23..bd84c7df 100644 --- a/Tests/ColorUtil_spec.lua +++ b/Tests/ColorUtil_spec.lua @@ -19,21 +19,20 @@ describe("ColorUtil", function() end) before_each(function() - ns = {} + ns = { + Round = function(value) + if value == nil then return 0 end + return math.floor((value * 100) + 0.5) / 100 + end, + } + ns.NumericEquals = function(a, b) + return ns.Round(a) == ns.Round(b) + end TestHelpers.LoadChunk("ColorUtil.lua", "Unable to load ColorUtil.lua")(nil, ns) ColorUtil = assert(ns.ColorUtil, "ColorUtil did not initialize") end) - it("AreEqual handles identical, nil, and distinct colors", function() - local color = { r = 1, g = 0.5, b = 0.25, a = 1 } - - assert.is_true(ColorUtil.AreEqual(color, color)) - assert.is_true(ColorUtil.AreEqual(nil, nil)) - assert.is_false(ColorUtil.AreEqual(color, nil)) - assert.is_false(ColorUtil.AreEqual(color, { r = 1, g = 0.5, b = 0.25, a = 0.5 })) - end) - it("ColorToHex converts normalized RGB values to lowercase hex", function() local hex = ColorUtil.ColorToHex({ r = 1, g = 0.5, b = 0, a = 1 }) assert.are.equal("ff8000", hex) diff --git a/Tests/FrameUtil_spec.lua b/Tests/FrameUtil_spec.lua index c5b60683..1fc606be 100644 --- a/Tests/FrameUtil_spec.lua +++ b/Tests/FrameUtil_spec.lua @@ -45,16 +45,6 @@ describe("FrameUtil", function() secretValues = {} ns = {} - ns.ColorUtil = {} - ns.ColorUtil.AreEqual = function(a, b) - if a == nil and b == nil then - return true - end - if a == nil or b == nil then - return false - end - return a.r == b.r and a.g == b.g and a.b == b.b and a.a == b.a - end ns.DebugAssert = function(condition, message) if not condition then error(message or "ECM.DebugAssert failed") diff --git a/Tests/Modules/RuneBar_spec.lua b/Tests/Modules/RuneBar_spec.lua index 0b2ef1a6..e1263db9 100644 --- a/Tests/Modules/RuneBar_spec.lua +++ b/Tests/Modules/RuneBar_spec.lua @@ -68,9 +68,11 @@ describe("RuneBar real source", function() target.EnsureFrame = target.EnsureFrame or function() end end, }, - IsDeathKnight = function() - return isDeathKnight - end, + ClassUtil = { + IsDeathKnight = function() + return isDeathKnight + end, + }, Runtime = { RegisterFrame = function() registerFrameCalls = registerFrameCalls + 1 diff --git a/Tests/TestHelpers.lua b/Tests/TestHelpers.lua index 785c9312..5affe935 100644 --- a/Tests/TestHelpers.lua +++ b/Tests/TestHelpers.lua @@ -1851,11 +1851,12 @@ function TestHelpers.SetupOptionsEnv(profile, defaults) ns.GetGlobalConfig = function() return mod.db.profile and mod.db.profile.global end - ns.IsDeathKnight = function() - local _, classToken = UnitClass("player") - return classToken == "DEATHKNIGHT" - end - ns.ClassUtil = {} + ns.ClassUtil = { + IsDeathKnight = function() + local _, classToken = UnitClass("player") + return classToken == "DEATHKNIGHT" + end, + } TestHelpers.LoadChunk("UI/OptionUtil.lua", "Unable to load UI/OptionUtil.lua")(nil, ns) TestHelpers.LoadChunk("UI/ExtraIconsShared.lua", "Unable to load UI/ExtraIconsShared.lua")(nil, ns) diff --git a/UI/ResourceBarOptions.lua b/UI/ResourceBarOptions.lua index 9ca546c3..a0d4ae2f 100644 --- a/UI/ResourceBarOptions.lua +++ b/UI/ResourceBarOptions.lua @@ -130,7 +130,7 @@ rows[#rows + 1] = { ResourceBarOptions.key = "resourceBar" ResourceBarOptions.name = L["RESOURCE_BAR"] -ResourceBarOptions.disabled = ns.IsDeathKnight +ResourceBarOptions.disabled = ns.ClassUtil.IsDeathKnight ResourceBarOptions.pages = { { key = "main", diff --git a/UI/RuneBarOptions.lua b/UI/RuneBarOptions.lua index 768beaa0..4703d2b0 100644 --- a/UI/RuneBarOptions.lua +++ b/UI/RuneBarOptions.lua @@ -30,7 +30,7 @@ for _, row in ipairs(ns.OptionUtil.CreateBarRows(isDisabled, { showText = false, rows[#rows + 1] = row end -if not ns.IsDeathKnight() then +if not ns.ClassUtil.IsDeathKnight() then table.insert(rows, 1, { type = "subheader", name = L["DK_ONLY_WARNING"], @@ -79,7 +79,7 @@ rows[#rows + 1] = { RuneBarOptions.key = "runeBar" RuneBarOptions.name = L["RUNE_BAR"] RuneBarOptions.disabled = function() - return not ns.IsDeathKnight() + return not ns.ClassUtil.IsDeathKnight() end RuneBarOptions.pages = { { From e3e8a6860a7271e53ec748f6bc227802c597231b Mon Sep 17 00:00:00 2001 From: Argi <15852038+argium@users.noreply.github.com> Date: Sun, 24 May 2026 13:01:21 +1000 Subject: [PATCH 2/2] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- Tests/ColorUtil_spec.lua | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Tests/ColorUtil_spec.lua b/Tests/ColorUtil_spec.lua index bd84c7df..aa8447f6 100644 --- a/Tests/ColorUtil_spec.lua +++ b/Tests/ColorUtil_spec.lua @@ -19,15 +19,7 @@ describe("ColorUtil", function() end) before_each(function() - ns = { - Round = function(value) - if value == nil then return 0 end - return math.floor((value * 100) + 0.5) / 100 - end, - } - ns.NumericEquals = function(a, b) - return ns.Round(a) == ns.Round(b) - end + ns = {} TestHelpers.LoadChunk("ColorUtil.lua", "Unable to load ColorUtil.lua")(nil, ns) ColorUtil = assert(ns.ColorUtil, "ColorUtil did not initialize")