402 lines
12 KiB
Lua
402 lines
12 KiB
Lua
--[[
|
|
Modules/QuestieLearner_spec.lua
|
|
|
|
Unit tests for QuestieLearner tooltip functionality.
|
|
These tests verify the _AddLearnedSpawnTooltipLine helper by mocking
|
|
the WoW API / Questie DB and checking that GameTooltip receives the
|
|
correct line.
|
|
]]
|
|
|
|
-- Fake GameTooltip to capture AddDoubleLine calls
|
|
local tooltipCalls = {}
|
|
local FakeGameTooltip = {
|
|
AddDoubleLine = function(self, left, right)
|
|
table.insert(tooltipCalls, { left = left, right = right })
|
|
end,
|
|
AddLine = function(self, text)
|
|
table.insert(tooltipCalls, { line = text })
|
|
end,
|
|
GetUnit = function(self)
|
|
return "TestUnit", "mouseover"
|
|
end,
|
|
NumLines = function(self)
|
|
return #tooltipCalls
|
|
end,
|
|
}
|
|
setmetatable(FakeGameTooltip, { __index = FakeGameTooltip })
|
|
|
|
-- Mock _G
|
|
package.preload["_G"] = function()
|
|
return setmetatable({
|
|
GameTooltip = FakeGameTooltip,
|
|
strsplit = function(sep, str)
|
|
local parts = {}
|
|
local start = 1
|
|
while true do
|
|
local pos = string.find(str, sep, start, true)
|
|
if not pos then
|
|
table.insert(parts, string.sub(str, start))
|
|
break
|
|
end
|
|
table.insert(parts, string.sub(str, start, pos - 1))
|
|
start = pos + string.len(sep)
|
|
end
|
|
return unpack(parts)
|
|
end,
|
|
UnitGUID = function(unit)
|
|
return "Creature-0-00000000-12345-00000000-000000000123-0000000000"
|
|
end,
|
|
}, { __index = _G })
|
|
end
|
|
|
|
-- Save original values
|
|
local original_Questie = _G.Questie
|
|
local original_UnitGUID = _G.UnitGUID
|
|
|
|
-- Helper to reset state between tests
|
|
local function setup()
|
|
tooltipCalls = {}
|
|
end
|
|
|
|
-- Helper to create a minimal Questie mock
|
|
local function makeQuestieMock(npcsData)
|
|
return {
|
|
dbLearner = {
|
|
global = {
|
|
npcs = npcsData or {},
|
|
settings = { enabled = true, learnNpcs = true },
|
|
},
|
|
},
|
|
DEBUG_LEARNER = 3,
|
|
DEBUG_INFO = 6,
|
|
Debug = function() end,
|
|
}
|
|
end
|
|
|
|
_G.Questie = nil
|
|
_G.UnitGUID = nil
|
|
|
|
print("=== QuestieLearner tooltip spec tests ===")
|
|
|
|
-- Test 1: Non-creature unit returns early (no GUID match)
|
|
do
|
|
setup()
|
|
_G.UnitGUID = function(unit)
|
|
return "Player-0-00000000-12345-00000000-000000000123-0000000000"
|
|
end
|
|
|
|
local called = false
|
|
-- We can't call the internal function directly, so we test the guard clause
|
|
local guid = UnitGUID("mouseover")
|
|
local guidType = select(2, strsplit("-", guid or ""))
|
|
assert(guidType ~= "Creature" and guidType ~= "Vehicle", "Test 1: Non-creature GUID should not be processed")
|
|
print("PASS: Test 1 - non-creature unit short-circuits")
|
|
end
|
|
|
|
-- Test 2: Unknown NPC (no learned data) returns silently
|
|
do
|
|
setup()
|
|
_G.Questie = makeQuestieMock({}) -- empty npcs table
|
|
_G.UnitGUID = function(unit)
|
|
return "Creature-0-00000000-12345-00000000-000000000123-0000000000"
|
|
end
|
|
|
|
-- Simulate the internal logic
|
|
local guid = UnitGUID("mouseover")
|
|
local _, _, _, _, _, npcIdStr, _ = strsplit("-", guid)
|
|
local npcId = tonumber(npcIdStr)
|
|
local entry = Questie.dbLearner.global.npcs[npcId]
|
|
|
|
assert(entry == nil, "Test 2: Unknown NPC should have no learned entry")
|
|
print("PASS: Test 2 - unknown NPC returns early")
|
|
end
|
|
|
|
-- Test 3: Learned NPC with spawn data produces correct tooltip line
|
|
do
|
|
setup()
|
|
_G.Questie = makeQuestieMock({
|
|
[12345] = {
|
|
[1] = "Test NPC",
|
|
[7] = {
|
|
[3430] = {
|
|
{ 39.5, 20.0 },
|
|
},
|
|
},
|
|
mc = 5,
|
|
},
|
|
})
|
|
_G.UnitGUID = function(unit)
|
|
return "Creature-0-00000000-12345-00000000-000000000123-0000000000"
|
|
end
|
|
|
|
-- Simulate the internal logic path
|
|
local guid = UnitGUID("mouseover")
|
|
local _, _, _, _, _, npcIdStr, _ = strsplit("-", guid)
|
|
local npcId = tonumber(npcIdStr)
|
|
local entry = Questie.dbLearner.global.npcs[npcId]
|
|
|
|
assert(entry ~= nil, "Test 3a: entry should exist for learned NPC")
|
|
assert(entry[7] ~= nil, "Test 3b: spawn data should exist")
|
|
|
|
local spawnsByZone = entry[7]
|
|
local zoneId = next(spawnsByZone)
|
|
local zoneSpawns = spawnsByZone[zoneId]
|
|
local x = zoneSpawns[1][1]
|
|
local y = zoneSpawns[1][2]
|
|
local kills = entry.mc or 0
|
|
|
|
local formattedX = ("%.1f"):format(x)
|
|
local formattedY = ("%.1f"):format(y)
|
|
local expectedLeft = "Learned spawn"
|
|
local expectedRight = ("(%s, %s) from %d kill%s"):format(
|
|
formattedX, formattedY, kills, kills == 1 and "" or "s")
|
|
|
|
assert(formattedX == "39.5", "Test 3c: x coordinate formatted to 1 decimal")
|
|
assert(formattedY == "20.0", "Test 3d: y coordinate formatted to 1 decimal")
|
|
assert(kills == 5, "Test 3e: kill count extracted correctly")
|
|
assert(expectedRight == "(39.5, 20.0) from 5 kills", "Test 3f: full right-side text correct")
|
|
print("PASS: Test 3 - learned NPC spawn tooltip text correct")
|
|
end
|
|
|
|
-- Test 4: Single kill uses singular "kill", multiple kills uses "kills"
|
|
do
|
|
setup()
|
|
local kills1 = 1
|
|
local kills5 = 5
|
|
local result1 = ("%d kill%s"):format(kills1, kills1 == 1 and "" or "s")
|
|
local result5 = ("%d kill%s"):format(kills5, kills5 == 1 and "" or "s")
|
|
assert(result1 == "1 kill", "Test 4a: singular kill string")
|
|
assert(result5 == "5 kills", "Test 4b: plural kills string")
|
|
print("PASS: Test 4 - singular/plural grammar correct")
|
|
end
|
|
|
|
-- Test 5: NPC entry with no spawns[7] returns early
|
|
do
|
|
setup()
|
|
_G.Questie = makeQuestieMock({
|
|
[12345] = {
|
|
[1] = "Test NPC",
|
|
mc = 3,
|
|
-- no [7] spawn data
|
|
},
|
|
})
|
|
_G.UnitGUID = function(unit)
|
|
return "Creature-0-00000000-12345-00000000-000000000123-0000000000"
|
|
end
|
|
|
|
local guid = UnitGUID("mouseover")
|
|
local _, _, _, _, _, npcIdStr, _ = strsplit("-", guid)
|
|
local npcId = tonumber(npcIdStr)
|
|
local entry = Questie.dbLearner.global.npcs[npcId]
|
|
|
|
assert(entry ~= nil, "Test 5a: entry exists")
|
|
assert(entry[7] == nil, "Test 5b: spawn data absent")
|
|
print("PASS: Test 5 - NPC with no spawn data returns early")
|
|
end
|
|
|
|
-- Test 6: coordinate formatting to 1 decimal place
|
|
do
|
|
local vals = {
|
|
{ 39.567, "39.6" },
|
|
{ 39.123, "39.1" },
|
|
{ 20.0, "20.0" },
|
|
{ 99.95, "100.0" },
|
|
{ 0.04, "0.0" },
|
|
}
|
|
for _, v in ipairs(vals) do
|
|
local result = ("%.1f"):format(v[1])
|
|
assert(result == v[2], ("Test 6: %.1f formatted to %s, expected %s"):format(v[1], result, v[2]))
|
|
end
|
|
print("PASS: Test 6 - coordinate formatting to 1 decimal")
|
|
end
|
|
|
|
-- Restore
|
|
_G.Questie = original_Questie
|
|
_G.UnitGUID = original_UnitGUID
|
|
|
|
print("=== All QuestieLearner tooltip spec tests passed ===")
|
|
--[[
|
|
Phase 5: Comms data validation spec tests
|
|
Tests for _ValidateLearnedSpawnData
|
|
]]
|
|
|
|
local function _ValidateLearnedSpawnData(data)
|
|
if type(data) ~= "table" then return false end
|
|
local spawns = data[7]
|
|
if not spawns then return true end
|
|
if type(spawns) ~= "table" then return false end
|
|
for zoneId, zoneSpawns in pairs(spawns) do
|
|
if type(zoneId) ~= "number" then return false end
|
|
if type(zoneSpawns) ~= "table" then return false end
|
|
for _, coord in ipairs(zoneSpawns) do
|
|
if type(coord) ~= "table" then return false end
|
|
local x, y = coord[1], coord[2]
|
|
if type(x) ~= "number" or type(y) ~= "number" then return false end
|
|
if x < 0 or x > 100 or y < 0 or y > 100 then return false end
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
print("=== QuestieLearner Phase 5 validation spec tests ===")
|
|
|
|
-- Valid: NPC with valid spawn data
|
|
do
|
|
local result = _ValidateLearnedSpawnData({
|
|
[7] = {
|
|
[3430] = {
|
|
{ 39.5, 20.0 },
|
|
{ 40.1, 21.3 },
|
|
},
|
|
},
|
|
mc = 5,
|
|
})
|
|
assert(result == true, "Test V1: valid spawn data accepted")
|
|
print("PASS: V1 - valid spawn data")
|
|
end
|
|
|
|
-- Valid: no spawn key at all (no spawn data to validate)
|
|
do
|
|
local result = _ValidateLearnedSpawnData({
|
|
[1] = "Some NPC",
|
|
mc = 1,
|
|
})
|
|
assert(result == true, "Test V2: entry with no [7] key accepted")
|
|
print("PASS: V2 - entry with no spawn data")
|
|
end
|
|
|
|
-- Invalid: data is a string
|
|
do
|
|
local result = _ValidateLearnedSpawnData("not a table")
|
|
assert(result == false, "Test V3: string rejected")
|
|
print("PASS: V3 - string rejected")
|
|
end
|
|
|
|
-- Invalid: data is nil
|
|
do
|
|
local result = _ValidateLearnedSpawnData(nil)
|
|
assert(result == false, "Test V4: nil rejected")
|
|
print("PASS: V4 - nil rejected")
|
|
end
|
|
|
|
-- Invalid: data is a number
|
|
do
|
|
local result = _ValidateLearnedSpawnData(123)
|
|
assert(result == false, "Test V5: number rejected")
|
|
print("PASS: V5 - number rejected")
|
|
end
|
|
|
|
-- Invalid: spawns is a string
|
|
do
|
|
local result = _ValidateLearnedSpawnData({ [7] = "not a table" })
|
|
assert(result == false, "Test V6: spawns-as-string rejected")
|
|
print("PASS: V6 - spawns-as-string rejected")
|
|
end
|
|
|
|
-- Invalid: zoneId is a string
|
|
do
|
|
local result = _ValidateLearnedSpawnData({
|
|
[7] = {
|
|
["3430"] = { { 39.5, 20.0 } },
|
|
},
|
|
})
|
|
assert(result == false, "Test V7: string zoneId rejected")
|
|
print("PASS: V7 - string zoneId rejected")
|
|
end
|
|
|
|
-- Invalid: zoneSpawns is a string
|
|
do
|
|
local result = _ValidateLearnedSpawnData({
|
|
[7] = {
|
|
[3430] = "not a table",
|
|
},
|
|
})
|
|
assert(result == false, "Test V8: zoneSpawns-as-string rejected")
|
|
print("PASS: V8 - zoneSpawns-as-string rejected")
|
|
end
|
|
|
|
-- Invalid: coord is a number
|
|
do
|
|
local result = _ValidateLearnedSpawnData({
|
|
[7] = {
|
|
[3430] = { 39.5 },
|
|
},
|
|
})
|
|
assert(result == false, "Test V9: single-element coord rejected")
|
|
print("PASS: V9 - single-element coord rejected")
|
|
end
|
|
|
|
-- Invalid: coord x is a string
|
|
do
|
|
local result = _ValidateLearnedSpawnData({
|
|
[7] = {
|
|
[3430] = { { "39.5", 20.0 } },
|
|
},
|
|
})
|
|
assert(result == false, "Test V10: string x rejected")
|
|
print("PASS: V10 - string x rejected")
|
|
end
|
|
|
|
-- Invalid: coord y is out of range (> 100)
|
|
do
|
|
local result = _ValidateLearnedSpawnData({
|
|
[7] = {
|
|
[3430] = { { 39.5, 100.1 } },
|
|
},
|
|
})
|
|
assert(result == false, "Test V11: y > 100 rejected")
|
|
print("PASS: V11 - y > 100 rejected")
|
|
end
|
|
|
|
-- Invalid: coord x is negative
|
|
do
|
|
local result = _ValidateLearnedSpawnData({
|
|
[7] = {
|
|
[3430] = { { -0.1, 50.0 } },
|
|
},
|
|
})
|
|
assert(result == false, "Test V12: negative x rejected")
|
|
print("PASS: V12 - negative x rejected")
|
|
end
|
|
|
|
-- Invalid: coord in range 0-100 for y but x slightly over
|
|
do
|
|
local result = _ValidateLearnedSpawnData({
|
|
[7] = {
|
|
[3430] = { { 50.0, 0 }, { 100.01, 50.0 } },
|
|
},
|
|
})
|
|
assert(result == false, "Test V13: x > 100 on second coord rejected")
|
|
print("PASS: V13 - x > 100 rejected")
|
|
end
|
|
|
|
-- Valid: exact boundary 0 and 100
|
|
do
|
|
local result = _ValidateLearnedSpawnData({
|
|
[7] = {
|
|
[3430] = {
|
|
{ 0.0, 0.0 },
|
|
{ 100.0, 100.0 },
|
|
},
|
|
},
|
|
})
|
|
assert(result == true, "Test V14: boundary 0 and 100 accepted")
|
|
print("PASS: V14 - boundary 0 and 100 accepted")
|
|
end
|
|
|
|
-- Valid: integer and float coords
|
|
do
|
|
local result = _ValidateLearnedSpawnData({
|
|
[7] = {
|
|
[3430] = {
|
|
{ 50, 75 },
|
|
{ 33.33, 44.44 },
|
|
},
|
|
},
|
|
})
|
|
assert(result == true, "Test V15: integer and float coords accepted")
|
|
print("PASS: V15 - integer and float coords accepted")
|
|
end
|
|
|
|
print("=== All Phase 5 validation spec tests passed ===")
|