Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Thank you for your interest in contributing to **PhDamage** - a floating combat
## Prerequisites

- World of Warcraft client (Retail, TBC Anniversary, MoP Classic, Cata, or Classic)
- [Lua 5.1](https://www.lua.org/) (for linting and testing)
- [Lua 5.1](https://www.lua.org/) or [Lua 5.4](https://www.lua.org/) (for linting and testing)
- [Luacheck](https://github.com/mpeterv/luacheck) (for static analysis)
- [Busted](https://olivinelabs.com/busted/) (for unit tests)
- [Git](https://git-scm.com/)
Expand Down Expand Up @@ -116,11 +116,18 @@ luacheck path/to/File.lua # single file for fast feedback
### Automated Tests
PhDamage uses [busted](https://olivinelabs.com/busted/) for unit testing. Tests live in the `tests/` directory.

CI still runs tests with Lua 5.1. Local runs may use Lua 5.1 or Lua 5.4.

Use the repo-local launcher instead of calling `busted` directly. Invoke it with whichever interpreter you want to verify, such as `lua`, `lua5.1`, or `lua5.4`. The launcher asks LuaRocks where `busted.runner` is installed and prepends that rock tree to `package.path` and `package.cpath` for the active interpreter.

```bash
busted --verbose # run all tests
busted tests/some_spec.lua # run a single test file
lua tests/RunBusted.lua --verbose # run all tests
lua tests/RunBusted.lua tests/some_spec.lua # run a single test file
lua5.4 tests/RunBusted.lua --verbose # explicitly test under Lua 5.4
```

If `busted --verbose` fails with `module 'busted.runner' not found`, your `busted` wrapper and your active interpreter are using different module paths. The launcher above avoids that mismatch without hard-coded machine-specific paths.

### Manual Testing
1. Load the addon in the target game version
2. Attack a target dummy to verify floating combat text
Expand Down
3 changes: 2 additions & 1 deletion Data/SpellData_Warlock.lua
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,8 @@ SpellData[1949] = {
-------------------------------------------------------------------------------

-- Life Tap — instant, Shadow (converts health to mana)
-- Coefficient: ~0.8 (gains spell power scaling)
-- Current addon model uses a 0.8 coefficient for the spell line, including Rank 6 at level 70.
-- Revisit only if stronger in-game evidence shows this model is wrong.
SpellData[1454] = {
name = "Life Tap",
school = SCHOOL_SHADOW,
Expand Down
98 changes: 98 additions & 0 deletions tests/RunBusted.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
-------------------------------------------------------------------------------
-- RunBusted.lua
-- Repo-local Busted launcher that bootstraps LuaRocks paths for the active Lua
--
-- Supported versions: Retail, MoP Classic, TBC Anniversary, Cata, Classic
-------------------------------------------------------------------------------

local io = io
local package = package
local table = table

local ACTIVE_LUA_VERSION = (_VERSION and _VERSION:match("(%d+%.%d+)"))
or error("Could not detect active Lua version", 0)

local function Trim(value)
return (value:gsub("^%s+", ""):gsub("%s+$", ""))
end

local function ReadFirstLine(command)
local handle = io.popen(command)
if not handle then
return nil, "Failed to start command: " .. command
end

local output = handle:read("*a") or ""
local ok, _, code = handle:close()
local firstLine = Trim((output:match("([^\r\n]+)") or ""))

if firstLine == "" then
if ok or code == 0 then
return nil, "Command returned no output: " .. command
end

return nil, "Command failed: " .. command
end

return firstLine
end

local function PrependPath(currentValue, entries)
return table.concat(entries, ";") .. ";" .. currentValue
end

local function BootstrapLuaRocksPaths()
local runnerPath, runnerErr = ReadFirstLine(
"luarocks --lua-version " .. ACTIVE_LUA_VERSION .. " which busted.runner"
)
if not runnerPath then
return nil, runnerErr
end

runnerPath = runnerPath:gsub("\\", "/")

local rocksRoot, luaVersion = runnerPath:match("^(.*)/share/lua/(%d+%.%d+)/busted/runner%.lua$")
if not rocksRoot then
return nil, "Could not derive LuaRocks tree from: " .. runnerPath
end

if luaVersion ~= ACTIVE_LUA_VERSION then
return nil, "LuaRocks resolved busted for Lua " .. luaVersion
.. " but active lua is " .. ACTIVE_LUA_VERSION
end

package.path = PrependPath(package.path, {
rocksRoot .. "/share/lua/" .. luaVersion .. "/?.lua",
rocksRoot .. "/share/lua/" .. luaVersion .. "/?/init.lua",
})

package.cpath = PrependPath(package.cpath, {
rocksRoot .. "/lib/lua/" .. luaVersion .. "/?.dll",
rocksRoot .. "/lib/lua/" .. luaVersion .. "/?.so",
})

return true
end

local function RunBusted()
local ok, runner = pcall(require, "busted.runner")
if ok then
return runner({ standalone = false })
end

local originalError = runner
local _, bootstrapErr = BootstrapLuaRocksPaths()
if bootstrapErr then
error("Failed to load busted.runner. Original error: " .. tostring(originalError)
.. " | Bootstrap error: " .. tostring(bootstrapErr), 0)
end

ok, runner = pcall(require, "busted.runner")
if not ok then
error("Failed to load busted.runner after LuaRocks bootstrap: " .. tostring(runner), 0)
end

return runner({ standalone = false })
end

RunBusted()
13 changes: 13 additions & 0 deletions tests/test_spellcalc.lua
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,19 @@ describe("SpellCalc", function()

assert.is_near(0.8, result.coefficient, 0.0001)
end)

it("should preserve the current modeled 0.8 scaling for Rank 6", function()
local spellData = ns.SpellData[1454]
local rankData = spellData.ranks[6]
local result = SpellCalc.ComputeBase(spellData, rankData, playerState)

local expectedSpBonus = 0.8 * 1000 -- 800
local expectedManaGain = 420 + expectedSpBonus -- 1220

assert.is_near(expectedSpBonus, result.spellPowerBonus, 0.01)
assert.is_near(expectedManaGain, result.manaGain, 0.01)
assert.is_near(0.8, result.coefficient, 0.0001)
end)
end)

it("should return nil for an unknown spell type", function()
Expand Down
Loading