From dc30858504c215b0f6ac9a01b61a14371558a62d Mon Sep 17 00:00:00 2001 From: Xurkon Date: Sun, 7 Jun 2026 08:50:20 -0500 Subject: [PATCH] chore: untrack Tests/ and stray Modules spec file (dev-only) Tests are for local dev only. .gitignore already excludes Tests/ and Modules/*_spec.lua from new adds, but the files were still tracked from prior commits. Untrack them with --cached so the working copies stay on disk for development but no longer ship in the live repo. --- Modules/QuestieLearner_spec.lua | 401 --------- Tests/AuditFindings_spec.lua | 386 --------- Tests/QuestieAvailableQuests_spec.lua | 16 - Tests/QuestieDebugThrottle_spec.lua | 86 -- Tests/QuestieError_policy_spec.lua | 65 -- Tests/QuestieItemNameSafety_spec.lua | 33 - Tests/QuestieLearnerDataSourceMode_spec.lua | 869 ------------------- Tests/QuestieLearnerReset_spec.lua | 67 -- Tests/QuestieLearner_performance_spec.lua | 870 -------------------- Tests/QuestieQuestTooltipFallback_spec.lua | 23 - Tests/QuestieTooltip_precedence_spec.lua | 66 -- 11 files changed, 2882 deletions(-) delete mode 100644 Modules/QuestieLearner_spec.lua delete mode 100644 Tests/AuditFindings_spec.lua delete mode 100644 Tests/QuestieAvailableQuests_spec.lua delete mode 100644 Tests/QuestieDebugThrottle_spec.lua delete mode 100644 Tests/QuestieError_policy_spec.lua delete mode 100644 Tests/QuestieItemNameSafety_spec.lua delete mode 100644 Tests/QuestieLearnerDataSourceMode_spec.lua delete mode 100644 Tests/QuestieLearnerReset_spec.lua delete mode 100644 Tests/QuestieLearner_performance_spec.lua delete mode 100644 Tests/QuestieQuestTooltipFallback_spec.lua delete mode 100644 Tests/QuestieTooltip_precedence_spec.lua diff --git a/Modules/QuestieLearner_spec.lua b/Modules/QuestieLearner_spec.lua deleted file mode 100644 index c935e82..0000000 --- a/Modules/QuestieLearner_spec.lua +++ /dev/null @@ -1,401 +0,0 @@ ---[[ - 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 ===") diff --git a/Tests/AuditFindings_spec.lua b/Tests/AuditFindings_spec.lua deleted file mode 100644 index 8e37760..0000000 --- a/Tests/AuditFindings_spec.lua +++ /dev/null @@ -1,386 +0,0 @@ --- Audit verification suite for workflow/performance-audit-2026-06-03-FULL.md (Pass 8). --- --- These are STATIC source assertions: each test reads the actual file and checks --- that the audit's claim still matches the source. They run in desktop Lua (no --- WoW mock needed) and are re-runnable. --- --- Tests in "false positives" and "structural facts" should ALWAYS pass. --- Tests in "confirmed bugs"/"confirmed perf" are a SNAPSHOT at HEAD 581634d: --- they pass while the finding is unfixed. When you land a fix, invert or remove --- the matching assertion (each is tagged with its Pass-8 ID, e.g. [B1]). - -local function read(path) - local f = assert(io.open(path, "r"), "cannot open " .. path) - local c = f:read("*a") - f:close() - return c -end - --- plain (non-pattern) substring search -local function has(content, needle) - return string.find(content, needle, 1, true) ~= nil -end - --- count plain (non-pattern) occurrences -local function count(content, needle) - local n, pos = 0, 1 - while true do - local s, e = string.find(content, needle, pos, true) - if not s then break end - n = n + 1 - pos = e + 1 - end - return n -end - -local function startsWithBOM(path) - local f = assert(io.open(path, "rb"), "cannot open " .. path) - local head = f:read(3) - f:close() - return head == "\239\187\191" -end - -describe("Audit Pass 8.1 - Lua 5.0 incompatibility surface", function() - it("[8.1] raw # length operator is used in TOC-loaded files (5.0 parse error)", function() - -- The dominant 5.0 blocker the pass-6/7 scan missed entirely. - assert.is_true(has(read("Localization/l10n.lua"), "#args")) - assert.is_true(has(read("Modules/Map/QuestieMap.lua"), "#mapDrawQueue")) - end) - - it("[8.1] QuestieLib.tpack uses the ... expression (real 5.0 parse error)", function() - assert.is_true(has(read("Modules/Libs/QuestieLib.lua"), 'n = select("#", ...), ...')) - end) - - it("[8.1] % is NOT used as a modulo operator (codebase uses math.mod)", function() - -- math.mod shim exists; raw % only appears in format strings. - assert.is_true(has(read("Modules/Libs/QuestieLoader.lua"), "math.mod")) - end) - - it("[8.1] no goto / bit32 / string.pack / utf8 in core runtime", function() - local db = read("Database/QuestieDB.lua") - assert.is_false(has(db, "goto ")) - assert.is_false(has(db, "bit32.")) - assert.is_false(has(db, "string.pack")) - end) -end) - -describe("Audit Pass 8.2 - corrected FALSE POSITIVES (must always pass)", function() - it("[FP2] UnitFactionGroup('Player') capital-P is an established working pattern", function() - -- Used across Corrections DB files that gate real entries; proves tokens - -- are case-insensitive, so QuestieMenu.lua:114 is NOT a bug. - assert.is_true(has(read("Database/Corrections/classicItemFixes.lua"), - 'UnitFactionGroup("Player")')) - assert.is_true(has(read("Modules/QuestieMenu/QuestieMenu.lua"), - 'UnitFactionGroup("Player")')) - end) - - it("[FP1] Ascension_IsScalingEnabled is declared no-arg but called with questId (harmless lint)", function() - local lib = read("Modules/Libs/QuestieLib.lua") - assert.is_true(has(lib, "local function Ascension_IsScalingEnabled()")) - assert.is_true(has(lib, "Ascension_IsScalingEnabled(questId)")) - -- Lua discards extra args; this changes no behavior. Lint only. - end) - - it("[FP3] the select() shim exists, proving Lua 5.0 lacks select (pass-3 was wrong)", function() - local loader = read("Modules/Libs/QuestieLoader.lua") - assert.is_true(has(loader, "if not select then")) - assert.is_true(has(loader, "select = function(index, ...)")) - end) - - it("[FP5] TaskQueue is wired (NOT dead code)", function() - assert.is_true(has(read("Questie-X.toc"), "TaskQueue.lua")) - assert.is_nil(io.open("Questie-X-Turtle.toc", "r")) - local qq = read("Modules/Quest/QuestieQuest.lua") - assert.is_true(has(qq, 'ImportModule("TaskQueue")')) - assert.is_true(has(qq, "TaskQueue:Queue(")) - end) -end) - -describe("Audit Pass 8.3 - confirmed BUGS (snapshot at HEAD; invert on fix)", function() - it("[B1] IsComplete calls GetQuest(questId) twice in one expression", function() - assert.is_true(has(read("Database/QuestieDB.lua"), - "QuestieDB.GetQuest(questId) and QuestieDB.GetQuest(questId).ObjectiveData")) - end) - - it("[B2] QuestieOptionsTracker calls :Cancel() on the number fadeTickerValue", function() - local content = read("Modules/Options/TrackerTab/QuestieOptionsTracker.lua") - assert.is_true(count(content, "fadeTickerValue:Cancel()") >= 3) - end) - - it("[B3] alreadySentBandaid is declared once and never wiped (unbounded)", function() - local content = read("Modules/QuestieAnnounce.lua") - assert.is_true(has(content, "local alreadySentBandaid = {}")) - assert.is_false(has(content, "wipe(alreadySentBandaid)")) - assert.equals(1, count(content, "alreadySentBandaid = {}")) - end) - - it("[B4] factionReactions reads UnitFactionGroup at module load time", function() - assert.is_true(has(read("Database/QuestieDB.lua"), - 'local playerFaction = UnitFactionGroup("player")')) - end) - - it("[B5] Questie-X.toc lists QuestieSlash.lua twice", function() - assert.equals(2, count(read("Questie-X.toc"), "QuestieSlash.lua")) - end) - - it("[B6] correction files begin with a UTF-8 BOM", function() - assert.is_true(startsWithBOM("Database/Corrections/tbcQuestFixes.lua")) - assert.is_true(startsWithBOM("Database/Corrections/wotlkItemFixes.lua")) - assert.is_true(startsWithBOM("Database/Corrections/wotlkQuestFixes.lua")) - end) - - it("[B7] _Qframe.BaseOnUpdate is referenced but never defined (dead glow ticker)", function() - local frame = read("Modules/FramePool/QuestieFrame.lua") - local pool = read("Modules/FramePool/QuestieFramePool.lua") - assert.is_true(has(frame, "_Qframe.BaseOnUpdate")) -- assigned from - assert.is_true(has(pool, "returnFrame.BaseOnUpdate")) -- gated on - -- never defined anywhere: - assert.is_false(has(frame, "function _Qframe.BaseOnUpdate")) - assert.is_false(has(frame, "function _Qframe:BaseOnUpdate")) - assert.is_false(has(frame, "_Qframe.BaseOnUpdate = function")) - end) -end) - -describe("Audit Pass 8.4 - confirmed PERFORMANCE findings (snapshot)", function() - it("[P1] QuestieComms no longer re-serializes the accumulating list inside the loop", function() - local comms = read("Modules/Network/QuestieComms.lua") - assert.is_false(has(comms, "string.len(QuestieSerializer:Serialize(rawQuestList)) > 200")) - assert.is_true(has(comms, "GetSerializedPacketSize(quest)")) - end) - - it("[P3] QuestieComms no longer front-removes its broadcast queues", function() - local comms = read("Modules/Network/QuestieComms.lua") - assert.is_false(has(comms, "tremove(blocks, 1)")) - assert.is_false(has(comms, "tremove(_QuestieComms._nextBroadcastData, 1)")) - assert.is_false(has(comms, "tremove(_QuestieComms._nextBroadcastDataV2, 1)")) - assert.is_true(has(comms, "local function QueuePop(queue, queueState)")) - end) - - it("[P4] QuestieLib.tunpack is recursive (slow vararg unpack)", function() - local lib = read("Modules/Libs/QuestieLib.lua") - assert.is_true(has(lib, "local function recursion(i)")) - assert.is_true(has(lib, "return tbl[i], recursion(i + 1)")) - assert.is_false(has(lib, "return unpack(tbl, 1, tbl.n)")) -- the proposed fix - end) - - it("[P6] O(n) front-insert tinsert(t, 1, x) is used in the tooltip path", function() - assert.is_true(has(read("Modules/Tooltips/Tooltip.lua"), - "tinsert(tempObjectives, 1, objectiveInfo.text)")) - end) -end) - -describe("Audit Pass 9 - file-by-file findings (snapshot at HEAD)", function() - it("[N1] bit library is used UNGUARDED in TOC files (1.12 runtime risk)", function() - assert.is_true(has(read("Questie.lua"), "local band = bit.band")) - assert.is_true(has(read("Database/QuestieDB.lua"), "local bitband = bit.band")) - -- no bit shim in the compat layer: - assert.is_false(has(read("Modules/QuestieCompat.lua"), "bit =")) - -- ...while the vendored XXH lib DOES guard it (the contrast): - assert.is_true(has(read("Libs/XXH_Lua_Lib/XXH_Lua_Lib.lua"), "bit and bit.band")) - end) - - it("[N2] strsplit is used in runtime but only shimmed in a test mock", function() - assert.is_true(has(read("Modules/Network/QuestieComms.lua"), "strsplit")) - assert.is_false(has(read("Modules/QuestieCompat.lua"), "strsplit")) - end) - - it("[N3] Options files use { ... } where {} was intended (5.0 parse error)", function() - assert.is_true(has(read("Modules/Options/QuestieOptions.lua"), "tabs = { ... }")) - assert.is_true(has(read("Modules/Options/ArrowTab/QuestieOptionsArrow.lua"), "= { ... }")) - end) - - it("[N4] QuestieCommsData indexes GetNPC/GetObject result with no nil-check", function() - local d = read("Modules/Network/QuestieCommsData.lua") - assert.is_true(has(d, "QuestieDB:GetNPC(objective.id).name")) - assert.is_true(has(d, "QuestieDB:GetObject(objective.id).name")) - -- the item branch right below DOES guard, proving the inconsistency: - assert.is_true(has(d, "if(dbItem and dbItem.name and (not dbItem.Hidden)) then")) - end) - - it("[N5] explicit unpack replaced select(8, ...) in live quest/instance lookups", function() - local player = read("Modules/QuestiePlayer.lua") - local handler = read("Modules/Quest/QuestEventHandler.lua") - local tracker = read("Modules/Tracker/QuestieTracker.lua") - - assert.is_true(has(player, "local _, _, _, _, _, _, _, instanceMapID = GetInstanceInfo()")) - assert.is_false(has(player, "select(8, GetInstanceInfo())")) - - assert.is_true(has(handler, "local _, _, _, _, _, _, _, questLogQuestId = GetQuestLogTitle(questLogIndex)")) - assert.is_false(has(handler, "select(8, GetQuestLogTitle(questLogIndex))")) - - assert.is_true(has(tracker, "local _, _, _, _, _, _, _, questId = GetQuestLogTitle(questIndex)")) - assert.is_true(has(tracker, "local _, _, _, _, _, _, _, questId = GetQuestLogTitle(index)")) - assert.is_false(has(tracker, "select(8, GetQuestLogTitle(questIndex))")) - assert.is_false(has(tracker, "select(8, GetQuestLogTitle(index))")) - end) - - it("[9.1->11.1] CORRECTED: % modulo IS used (the 'avoided' claim was wrong)", function() - -- Pass 8-10 wrongly said % modulo = 0. Real modulo operators exist in - -- Turtle-TOC files and are 5.0 parse errors that math.mod cannot rescue. - assert.is_true(has(read("Modules/QuestieStream.lua"), "mod(val, 256)")) - assert.is_true(has(read("Modules/QuestiePlayer.lua"), "% playerRaceFlagX2")) - end) -end) - -describe("Audit Pass 11 - gap-fill findings (snapshot at HEAD)", function() - it("[11.1] % modulo operator is present in multiple Turtle-TOC files", function() - assert.is_true(has(read("Database/QuestieDB.lua"), " % ")) - assert.is_true(has(read("Modules/QuestieLearner.lua"), " % ")) - end) - - it("[G1] QuestieNameplate:UpdateNameplate re-splits the GUID and early-returns in-loop", function() - local np = read("Modules/QuestieNameplate.lua") - -- re-derives npcId from the guid on every update (cacheable): - assert.is_true(has(np, 'strsplit("-", guid)')) - -- the early return aborts the whole loop on a missing unit: - assert.is_true(has(np, "if (not unitName) or (not npcId) then\n return")) - end) - - it("[G2] QuestieValidateGameCache has the unreachable isQuestLogGood guard", function() - local v = read("Modules/QuestieValidateGameCache.lua") - assert.is_true(has(v, "local isQuestLogGood = true")) - assert.is_false(has(v, "isQuestLogGood = false")) -- never set false => guard is dead - end) - - it("[G3] QuestieCompat shims neither bit nor strsplit (N1/N2 gaps stand)", function() - local c = read("Modules/QuestieCompat.lua") - assert.is_false(has(c, "strsplit =")) - assert.is_false(has(c, "bit =")) - assert.is_true(has(c, "QuestieCompat.C_Timer")) -- but C_Timer IS polyfilled - end) -end) - -describe("Audit Pass 10 - additional performance findings (snapshot)", function() - it("[PP1] a batch Query(id, keys) API exists that IsDoable does not use", function() - local compiler = read("Database/compiler.lua") - assert.is_true(has(compiler, "handle.Query = function(id, keys)")) -- batch reader exists - local db = read("Database/QuestieDB.lua") - -- IsDoable issues many single-key reads instead of one batch read: - assert.is_true(count(db, "QueryQuestSingle(questId,") >= 8) - end) - - it("[PP2] hot non-fragile files repeat Questie.db.profile chains", function() - assert.is_true(count(read("Modules/Tooltips/MapIconTooltip.lua"), "Questie.db.profile.") >= 8) - assert.is_true(count(read("Modules/Map/QuestieMap.lua"), "Questie.db.profile.") >= 8) - end) - - it("[PP3] arrow target sort no longer allocates an inline comparator per refresh", function() - assert.is_true(has(read("Modules/Arrow/QuestieArrow.lua"), - "local function _SortTargetByDistance(a, b)")) - assert.is_true(has(read("Modules/Arrow/QuestieArrow.lua"), - "table.sort(sortedTargets, _SortTargetByDistance)")) - assert.is_true(count(read("Modules/Network/QuestieComms.lua"), "table.sort(") >= 3) - end) - - it("[PP4] arrow avoids re-setting unchanged distance text every throttled tick", function() - assert.is_true(has(read("Modules/Arrow/QuestieArrow.lua"), - "objectiveFrame._lastDistanceText ~= distanceText")) - end) - - it("[PP7] arrow performance throttles are profile-backed and exposed in Arrow options", function() - local arrow = read("Modules/Arrow/QuestieArrow.lua") - local arrowOptions = read("Modules/Options/ArrowTab/QuestieOptionsArrow.lua") - local advancedOptions = read("Modules/Options/AdvancedTab/QuestieOptionsAdvanced.lua") - local defaults = read("Modules/Options/QuestieOptionsDefaults.lua") - - assert.is_true(has(arrow, 'return _GetProfileNumber("arrowUpdateThrottle"')) - assert.is_true(has(arrow, 'return _GetProfileNumber("arrowRecalcInterval"')) - assert.is_true(has(arrow, 'return _GetProfileNumber("arrowTrackerRefreshThrottle"')) - assert.is_false(has(arrowOptions, "arrowPerformanceHeader")) - assert.is_false(has(arrowOptions, "arrowUpdateThrottle")) - assert.is_true(has(advancedOptions, "arrowPerformanceHeader")) - assert.is_true(has(advancedOptions, "arrowUpdateThrottle")) - assert.is_true(has(advancedOptions, "arrowRecalcInterval")) - assert.is_true(has(advancedOptions, "arrowTrackerRefreshThrottle")) - assert.is_true(has(defaults, "arrowUpdateThrottle = 0.05")) - assert.is_true(has(defaults, "arrowRecalcInterval = 1.0")) - assert.is_true(has(defaults, "arrowTrackerRefreshThrottle = 0.5")) - end) - - it("[L9] learner performance presets keep comms UI and broadcast gate synchronized", function() - local advancedOptions = read("Modules/Options/AdvancedTab/QuestieOptionsAdvanced.lua") - assert.is_true(has(advancedOptions, 'settings.learnerCommsIntensity = "fast"')) - assert.is_true(has(advancedOptions, 'settings.learnerCommsIntensity = "normal"')) - assert.is_true(has(advancedOptions, 'settings.learnerCommsIntensity = "low"')) - assert.is_true(has(advancedOptions, "Questie.db.profile.learnerBroadcast = true")) - end) - - it("[C1] QuestieComms full quest-list throttles are profile-backed", function() - local comms = read("Modules/Network/QuestieComms.lua") - local defaults = read("Modules/Options/QuestieOptionsDefaults.lua") - local broadcastQuestUpdatePos = comms:find("function _QuestieComms:BroadcastQuestUpdate", 1, true) - local isQuestieCommsEnabledPos = comms:find("local IsQuestieCommsEnabled", 1, true) - - assert.is_true(has(comms, 'GetProfileNumber("questieCommsQuestListPacketSize"')) - assert.is_true(has(comms, 'GetProfileNumber("questieCommsQuestListInitialJitter"')) - assert.is_true(has(comms, 'GetProfileNumber("questieCommsQuestListBlockInterval"')) - assert.is_true(has(comms, "IsQuestieCommsEnabled = function()")) - assert.is_true(isQuestieCommsEnabledPos ~= nil) - assert.is_true(broadcastQuestUpdatePos ~= nil) - assert.is_true(isQuestieCommsEnabledPos < broadcastQuestUpdatePos) - assert.is_true(has(comms, "GetQuestListPacketSizeLimit()")) - assert.is_true(has(comms, "GetQuestListInitialJitter()")) - assert.is_true(has(comms, "GetQuestListBlockInterval()")) - assert.is_true(has(defaults, "questieCommsEnabled = true")) - assert.is_true(has(defaults, "questieCommsQuestListPacketSize = 200")) - assert.is_true(has(defaults, "questieCommsQuestListInitialJitter = 3")) - assert.is_true(has(defaults, "questieCommsQuestListBlockInterval = 3")) - end) - - it("[C2] QuestieComms performance controls are exposed in Advanced options", function() - local advancedOptions = read("Modules/Options/AdvancedTab/QuestieOptionsAdvanced.lua") - - assert.is_true(has(advancedOptions, "questieCommsPerformanceHeader")) - assert.is_true(has(advancedOptions, "questieCommsEnabled")) - assert.is_true(has(advancedOptions, "questieCommsQuestListPacketSize")) - assert.is_true(has(advancedOptions, "questieCommsQuestListInitialJitter")) - assert.is_true(has(advancedOptions, "questieCommsQuestListBlockInterval")) - assert.is_true(has(advancedOptions, "Questie.db.profile.questieCommsEnabled == false")) - end) - - it("[L50] loader, serializer, and stream are wired for Lua 5.0 compatibility", function() - local loader = read("Modules/Libs/QuestieLoader.lua") - local serializer = read("Modules/Libs/QuestieSerializer.lua") - local stream = read("Modules/QuestieStream.lua") - - assert.is_true(has(loader, "bitlib.band = bitlib.band or band32")) - assert.is_true(has(loader, "strsplit = function(separator, text, max)")) - - assert.is_true(has(serializer, "local mod = math.mod")) - assert.is_true(has(serializer, "mod(expo, 0x2)")) - assert.is_true(has(serializer, "mod(b1, 0x80)")) - assert.is_false(has(serializer, "expo % 0x2")) - - assert.is_true(has(stream, "local mod = math.mod")) - assert.is_true(has(stream, "table.getn(self._bin)")) - assert.is_true(has(stream, "mod(val1, 256)")) - assert.is_true(has(stream, "mod(val2, 256)")) - assert.is_false(has(stream, "val1 % 256")) - assert.is_false(has(stream, "val2 % 256")) - end) - - it("[L10] UNIT_DIED dedupe cannot suppress a later PARTY_KILL learner update", function() - local learner = read("Modules/QuestieLearner.lua") - - assert.is_true(has(learner, 'local lastEventType = type(last) == "table" and last.eventType or nil')) - assert.is_true(has(learner, 'if eventType ~= "PARTY_KILL" or lastEventType == "PARTY_KILL" then')) - assert.is_true(has(learner, '_Learner.killDebounce[dstGUID] = { ts = now, eventType = eventType }')) - end) - - it("[L11] object capture traces and gameobject learning are wired into the learner", function() - local learner = read("Modules/QuestieLearner.lua") - - assert.is_true(has(learner, 'local function TraceLearnerEntity(source, guid, unitType, entityId, name)')) - assert.is_true(has(learner, 'TraceLearnerEntity("mouseover", guid, unitType, entityId, name)')) - assert.is_true(has(learner, 'TraceLearnerEntity("target", guid, unitType, entityId, name)')) - assert.is_true(has(learner, 'TraceLearnerEntity("loot_target", targetGuid, targetType, targetId, UnitName("target"))')) - assert.is_true(has(learner, 'GetLootSourceInfo then')) - assert.is_true(has(learner, 'TraceLearnerEntity("loot_source", sourceGuid, nil, sourceQty, lootName)')) - assert.is_true(has(learner, 'function QuestieLearner:OnGameObjectUsed(objectId)')) - assert.is_true(has(learner, 'frame:RegisterEvent("GAMEOBJECT_USED")')) - assert.is_true(has(learner, 'self:OnGameObjectUsed(arg1)')) - assert.is_true(has(learner, 'TraceLearnerEntity("gossip", npcGuid, unitType, id, name)')) - assert.is_true(has(learner, 'self:LearnObject(entityId, name)')) - assert.is_true(has(learner, 'if unitType == "GameObject" then')) - end) -end) diff --git a/Tests/QuestieAvailableQuests_spec.lua b/Tests/QuestieAvailableQuests_spec.lua deleted file mode 100644 index d99eee3..0000000 --- a/Tests/QuestieAvailableQuests_spec.lua +++ /dev/null @@ -1,16 +0,0 @@ -local function read(path) - local f = assert(io.open(path, "r"), "cannot open " .. path) - local c = f:read("*a") - f:close() - return c -end - -describe("Questie available quests guard", function() - it("skips unavailable quests before touching tagInfoWasCached", function() - local content = read("Modules/Quest/AvailableQuests.lua") - assert.is_true(content:find("if not quest then", 1, true) ~= nil) - assert.is_true(content:find("Skipping unavailable quest during draw", 1, true) ~= nil) - assert.is_true(content:find("unavailableQuestLogged[questId]", 1, true) ~= nil) - assert.is_true(content:find("quest.tagInfoWasCached = true", 1, true) ~= nil) - end) -end) diff --git a/Tests/QuestieDebugThrottle_spec.lua b/Tests/QuestieDebugThrottle_spec.lua deleted file mode 100644 index 033945e..0000000 --- a/Tests/QuestieDebugThrottle_spec.lua +++ /dev/null @@ -1,86 +0,0 @@ -describe("Questie debug throttle", function() - local originalPrint - local originalGetTime - local originalImport - - before_each(function() - dofile("Tests/wow_api_mock.lua") - Questie.Print = nil - _G.IsLoggedIn = function() - return false - end - _G.C_Timer = _G.C_Timer or {} - _G.C_Timer.After = function() - end - originalImport = QuestieLoader.ImportModule - QuestieLoader.ImportModule = function(self, name) - if name == "QuestieLib" then - return { AddonPath = "" } - elseif name == "QuestieValidateGameCache" then - return { StartCheck = function() end } - end - return originalImport(self, name) - end - dofile("Questie.lua") - - originalPrint = _G.print - originalGetTime = _G.GetTime - - _G._questie_debug_lines = {} - _G.print = function(...) - local parts = {} - for i = 1, select("#", ...) do - parts[#parts + 1] = tostring(select(i, ...)) - end - table.insert(_G._questie_debug_lines, table.concat(parts, " ")) - end - end) - - after_each(function() - _G.print = originalPrint - _G.GetTime = originalGetTime - QuestieLoader.ImportModule = originalImport - _G._questie_debug_lines = nil - end) - - it("suppresses rapid debug lines until the throttle window reopens", function() - local now = 0 - _G.GetTime = function() - return now - end - - Questie.db.profile.debugEnabled = true - Questie.db.profile.debugEnabledPrint = true - Questie.db.profile.debugLevel = Questie.DEBUG_INFO - Questie.db.profile.debugMessageThrottle = 0.5 - - Questie:Debug(Questie.DEBUG_INFO, "alpha") - now = 0.1 - Questie:Debug(Questie.DEBUG_INFO, "beta") - now = 0.6 - Questie:Debug(Questie.DEBUG_INFO, "gamma") - - assert.equals(2, table.getn(_G._questie_debug_lines)) - assert.is_true(string.find(_G._questie_debug_lines[1], "alpha", 1, true) ~= nil) - assert.is_true(string.find(_G._questie_debug_lines[2], "gamma", 1, true) ~= nil) - end) - - it("throttles critical messages too because fatal output is separate", function() - local now = 0 - _G.GetTime = function() - return now - end - - Questie.db.profile.debugEnabled = true - Questie.db.profile.debugEnabledPrint = true - Questie.db.profile.debugLevel = Questie.DEBUG_INFO + Questie.DEBUG_CRITICAL - Questie.db.profile.debugMessageThrottle = 0.5 - - Questie:Debug(Questie.DEBUG_INFO, "alpha") - now = 0.1 - Questie:Debug(Questie.DEBUG_CRITICAL, "boom") - - assert.equals(1, table.getn(_G._questie_debug_lines)) - assert.is_true(string.find(_G._questie_debug_lines[1], "alpha", 1, true) ~= nil) - end) -end) diff --git a/Tests/QuestieError_policy_spec.lua b/Tests/QuestieError_policy_spec.lua deleted file mode 100644 index 13bd340..0000000 --- a/Tests/QuestieError_policy_spec.lua +++ /dev/null @@ -1,65 +0,0 @@ -local function read_file(path) - local handle = assert(io.open(path, "r")) - local content = handle:read("*a") - handle:close() - return content -end - -local function find_lines(content, needle) - local lines = {} - for line in content:gmatch("[^\r\n]+") do - if line:find(needle, 1, true) then - lines[#lines + 1] = line - end - end - return lines -end - -local function count_occurrences(content, needle) - local count = 0 - local start = 1 - while true do - local first, last = content:find(needle, start, true) - if not first then - break - end - count = count + 1 - start = last + 1 - end - return count -end - -describe("Questie error policy", function() - it("routes Questie:Error through debug critical and keeps a fatal printer", function() - local questie = read_file("Questie.lua") - - assert.is.truthy(questie:find("function Questie:Error(...)", 1, true)) - assert.is.truthy(questie:find("Questie:Debug(Questie.DEBUG_CRITICAL", 1, true)) - assert.is.truthy(questie:find("function Questie:Fatal(...)", 1, true)) - assert.is.truthy(questie:find("[FATAL]", 1, true)) - end) - - it("routes missing quest spam through debug critical", function() - local quest = read_file("Modules/Quest/QuestieQuest.lua") - local comms = read_file("Modules/Network/QuestieComms.lua") - assert.are.equal(2, count_occurrences(quest, "Questie:Debug(Questie.DEBUG_CRITICAL, l10n(")) - assert.are.equal(2, count_occurrences(comms, "Questie:Debug(Questie.DEBUG_CRITICAL, l10n(")) - end) - - it("keeps startup-breaking conditions on the fatal path", function() - local versionCheck = read_file("Modules/VersionCheck.lua") - local init = read_file("Modules/QuestieInit.lua") - local eventHandler = read_file("Modules/QuestieEventHandler.lua") - local versionLines = find_lines(versionCheck, "ERROR inside NewAddon") - local initLines = find_lines(init, "Module not loaded correctly") - local eventLines = find_lines(eventHandler, "Config DB from saved variables") - - assert.are.equal(1, #versionLines) - assert.are.equal(1, #initLines) - assert.are.equal(1, #eventLines) - - assert.is.truthy(versionLines[1]:find("Questie:Fatal", 1, true)) - assert.is.truthy(initLines[1]:find("Questie:Fatal", 1, true)) - assert.is.truthy(eventLines[1]:find("Questie:Fatal", 1, true)) - end) -end) diff --git a/Tests/QuestieItemNameSafety_spec.lua b/Tests/QuestieItemNameSafety_spec.lua deleted file mode 100644 index 300e0ac..0000000 --- a/Tests/QuestieItemNameSafety_spec.lua +++ /dev/null @@ -1,33 +0,0 @@ -describe("Questie item name safety", function() - local function read(path) - local f = assert(io.open(path, "r"), "cannot open " .. path) - local c = f:read("*a") - f:close() - return c - end - - before_each(function() - dofile("Tests/wow_api_mock.lua") - GetItemInfo = function() - error("GetItemInfo should not be called for invalid item ids") - end - dofile("Database/QuestieDB.lua") - end) - - it("returns a placeholder instead of calling GetItemInfo for invalid item ids", function() - local item = Item:CreateFromItemID(nil) - assert.equals("item:nil", item:GetItemName()) - end) - - it("also handles non-numeric item ids safely", function() - local item = Item:CreateFromItemID("bad-id") - assert.equals("item:bad-id", item:GetItemName()) - end) - - it("skips malformed item objectives without an item id", function() - local lib = read("Modules/Libs/QuestieLib.lua") - assert.is_true(string.find(lib, "local itemId = objectiveDB.Id", 1, true) ~= nil) - assert.is_true(string.find(lib, "if not itemId then", 1, true) ~= nil) - assert.is_true(string.find(lib, "QuestieDB.itemDataOverrides[itemId]", 1, true) ~= nil) - end) -end) diff --git a/Tests/QuestieLearnerDataSourceMode_spec.lua b/Tests/QuestieLearnerDataSourceMode_spec.lua deleted file mode 100644 index 0276981..0000000 --- a/Tests/QuestieLearnerDataSourceMode_spec.lua +++ /dev/null @@ -1,869 +0,0 @@ -local function read(path) - local f = assert(io.open(path, "r"), "cannot open " .. path) - local c = f:read("*a") - f:close() - return c -end - -local function has(content, needle) - return string.find(content, needle, 1, true) ~= nil -end - -describe("QuestieLearner data source mode", function() - it("adds a mode selector and explicit fallback options in the database tab", function() - local dbOptions = read("Modules/Options/DatabaseTab/QuestieOptionsDatabase.lua") - local advanced = read("Modules/Options/AdvancedTab/QuestieOptionsAdvanced.lua") - assert.is_true(has(dbOptions, "Data Source Mode")) - assert.is_true(has(dbOptions, "auto = l10n(\"Auto (current behavior)\")")) - assert.is_true(has(dbOptions, "learner = l10n(\"Learner Only\")")) - assert.is_true(has(dbOptions, "static = l10n(\"Static Only\")")) - assert.is_true(has(dbOptions, "none = l10n(\"Neither (base DB only)\")")) - assert.is_true(has(dbOptions, "local function GetLearnerSelectedMode()")) - assert.is_true(has(dbOptions, "get = function() return GetLearnerSelectedMode() end")) - assert.is_true(has(dbOptions, "Runtime mode:")) - assert.is_true(has(advanced, "local function RefreshLearnerRuntime()")) - assert.is_true(has(advanced, "RefreshLearnerRuntime()")) - end) - - it("defaults the learner mode to auto and exposes the live refresh hook", function() - local learner = read("Modules/QuestieLearner.lua") - local defaults = read("Modules/Options/QuestieOptionsDefaults.lua") - assert.is_true(has(defaults, "dataSourceMode = \"auto\"")) - assert.is_true(has(learner, "function QuestieLearner:GetDataSourceMode()")) - assert.is_true(has(learner, "function QuestieLearner:IsLearnerLiveEnabled()")) - assert.is_true(has(learner, "function QuestieLearner:ApplyDataSourceMode()")) - assert.is_true(has(learner, "function QuestieLearner:RefreshLiveState()")) - end) - - it("gates static suppression and tooltip fallback on the selected mode", function() - local quest = read("Modules/Quest/QuestieQuest.lua") - local priv = read("Modules/Quest/QuestieQuestPrivates.lua") - local tip = read("Modules/Tooltips/Tooltip.lua") - assert.is_true(has(quest, "dataSourceMode == \"auto\" or dataSourceMode == \"learner\"")) - assert.is_true(has(priv, "dataSourceMode == \"none\"")) - assert.is_true(has(tip, "mode ~= \"static\" and mode ~= \"none\"")) - end) - - it("exposes learner tooltip controls and tooltip resize support", function() - local general = read("Modules/Options/GeneralTab/QuestieOptionsGeneral.lua") - local defaults = read("Modules/Options/QuestieOptionsDefaults.lua") - local advanced = read("Modules/Options/AdvancedTab/QuestieOptionsAdvanced.lua") - local tip = read("Modules/Tooltips/Tooltip.lua") - local handler = read("Modules/Tooltips/TooltipHandler.lua") - local learner = read("Modules/QuestieLearner.lua") - assert.is_true(has(defaults, "learnerTooltips = true")) - assert.is_true(has(defaults, "learnerTooltipShowSpawn = true")) - assert.is_true(has(defaults, "learnerTooltipShowConfidence = true")) - assert.is_true(has(defaults, "learnerTooltipShowTotalSpawns = true")) - assert.is_true(has(defaults, "learnerTooltipAutoResize = true")) - assert.is_true(has(defaults, "learnerTooltipUseSecondary = false")) - assert.is_true(has(general, "Learner Tooltips")) - assert.is_true(has(general, "Enable learner tooltips")) - assert.is_true(has(general, "Show total spawns learned")) - assert.is_true(has(general, "Use secondary learner tooltip")) - assert.is_true(has(tip, "function QuestieTooltips:ResizeTooltip(tooltip)")) - assert.is_true(has(handler, "QuestieTooltips:ResizeTooltip(GameTooltip)")) - assert.is_true(has(handler, "QuestieTooltips:ResizeTooltip(self)")) - assert.is_true(has(learner, "learnerTooltipShowSpawn")) - assert.is_true(has(learner, "_CountLearnedNpcSpawns(entry)")) - assert.is_true(has(learner, "_ShowLearnerTooltipFrame(GameTooltip, rendered)")) - assert.is_true(has(advanced, "QuestieOptionsUtils:Delay(0.05, QuestieQuest.SmoothReset")) - assert.is_true(has(advanced, "QuestieOptionsUtils:Delay(0.05, QuestieOptions.ClusterRedraw")) - end) - - it("allows learner mode to draw pins from a single learned spawn when needed", function() - local priv = read("Modules/Quest/QuestieQuestPrivates.lua") - assert.is_true(has(priv, "local staticHasSpawns = spawns and next(spawns) ~= nil")) - assert.is_true(has(priv, "local canUseLearnerSpawns = dataSourceMode == \"learner\"")) - assert.is_true(has(priv, "or not staticHasSpawns")) - end) - - it("keeps learner-only pin builders off the static DB lookup path", function() - local priv = read("Modules/Quest/QuestieQuestPrivates.lua") - assert.is_true(has(priv, "local npcData = QuestieDB:GetNPC(npcId)")) - assert.is_true(has(priv, "local name = npcData and npcData.name or nil")) - assert.is_true(has(priv, "local spawns = npcData and npcData.spawns or {}")) - assert.is_true(has(priv, "local rank = npcData and npcData.rank")) - assert.is_true(has(priv, "local objectData = QuestieDB:GetObject(objectId)")) - assert.is_true(has(priv, "local name = objectData and objectData.name or nil")) - assert.is_true(has(priv, "local spawns = objectData and objectData.spawns or {}")) - end) - - it("maps object objectives from learner object captures before refresh", function() - local learner = read("Modules/QuestieLearner.lua") - assert.is_true(has(learner, "_Learner.recentObjects = _Learner.recentObjects or {}")) - assert.is_true(has(learner, "function QuestieLearner:LearnQuestObjectiveObject(questId, objectId, objText, objectiveIndex)")) - assert.is_true(has(learner, "objType == \"object\"")) - assert.is_true(has(learner, "self:LearnQuestObjectiveObject(questId, objectId, objText, j)")) - assert.is_true(has(learner, "_Learner.recentObjects[objectId]")) - end) -end) - -describe("QuestieLearner missing base DB fallback", function() - local QuestieLearner - - before_each(function() - dofile("Tests/wow_api_mock.lua") - Questie.dbLearner.global.settings.enabled = false - Questie.dbLearner.global.settings.dataSourceMode = "static" - QuestieDB.baseDatabaseMissing = true - QuestieDB.IsBaseDatabaseMissing = function() - return true - end - QuestieLearner = dofile("Modules/QuestieLearner.lua") - end) - - it("forces learner mode and live recording when the base DB is missing", function() - assert.equals("learner", QuestieLearner:GetDataSourceMode()) - assert.is_true(QuestieLearner:IsEnabled()) - end) -end) - -describe("QuestieLearner learner mode activation", function() - local QuestieLearner - - before_each(function() - dofile("Tests/wow_api_mock.lua") - Questie.dbLearner.global.settings.enabled = false - Questie.dbLearner.global.settings.dataSourceMode = "learner" - QuestieDB.baseDatabaseMissing = false - QuestieDB.IsBaseDatabaseMissing = function() - return false - end - QuestieLearner = dofile("Modules/QuestieLearner.lua") - end) - - it("re-enables learner recording when learner mode is applied", function() - QuestieLearner:ApplyDataSourceMode() - assert.is_true(Questie.dbLearner.global.settings.enabled) - assert.is_true(QuestieLearner:IsEnabled()) - end) - - it("refreshes live learner caches and quest pins when runtime settings change", function() - local clearCount = 0 - local resetCount = 0 - - QuestieDB.ClearModeCaches = function() - clearCount = clearCount + 1 - end - QuestieQuest.SmoothReset = function() - resetCount = resetCount + 1 - end - - QuestieLearner:RefreshLiveState() - - assert.is_true(clearCount >= 1) - assert.is_true(resetCount >= 1) - end) -end) - -describe("QuestieDB learner mode merges learner overrides", function() - before_each(function() - dofile("Tests/wow_api_mock.lua") - dofile("Database/QuestieDB.lua") - dofile("Database/npcDB.lua") - Questie.dbLearner.global.settings.enabled = true - Questie.dbLearner.global.settings.dataSourceMode = "learner" - Questie.dbLearner.global.npcs = { - [7001] = { - [1] = "Learner NPC", - }, - } - QuestieDB.npcDataOverrides = { - [7001] = { - [1] = "Learner NPC", - [7] = { - [44] = { - { 12.5, 34.5 }, - }, - }, - }, - } - QuestieDB.private = QuestieDB.private or {} - QuestieDB.private.npcCache = {} - end) - - it("prefers the learner override spawn table when raw learner data is incomplete", function() - local npc = QuestieDB:GetNPC(7001) - assert.is_table(npc) - assert.is_table(npc.spawns) - assert.is_table(npc.spawns[44]) - assert.equals(1, table.getn(npc.spawns[44])) - assert.equals(12.5, npc.spawns[44][1][1]) - assert.equals(34.5, npc.spawns[44][1][2]) - end) -end) - -describe("QuestieDB quest build guards missing objective tables", function() - local originalImport - local originalQueryItemSingle - - before_each(function() - dofile("Tests/wow_api_mock.lua") - - local questieLib = { - GetTbcLevel = function() - return 1, 1 - end, - } - local questieCorrections = { - hiddenQuests = {}, - killCreditObjectiveFirst = {}, - } - - originalImport = QuestieLoader.ImportModule - QuestieLoader.ImportModule = function(self, name) - if name == "QuestieLib" then - return questieLib - end - if name == "QuestieCorrections" then - return questieCorrections - end - return originalImport(self, name) - end - - dofile("Database/QuestieDB.lua") - dofile("Database/questDB.lua") - - Questie.dbLearner.global.settings.enabled = true - Questie.dbLearner.global.settings.dataSourceMode = "learner" - QuestieDB.private.questCache = {} - QuestieDB.questDataOverrides = {} - - originalQueryItemSingle = QuestieDB.QueryItemSingle - QuestieDB.QueryItemSingle = function(itemId, field) - if field == "name" then - return "Test Item " .. tostring(itemId) - end - return originalQueryItemSingle(itemId, field) - end - end) - - after_each(function() - if originalQueryItemSingle then - QuestieDB.QueryItemSingle = originalQueryItemSingle - end - if originalImport then - QuestieLoader.ImportModule = originalImport - end - end) - - it("does not crash when required source items exist but objectives are missing", function() - local questId = 900001 - QuestieDB.questDataOverrides[questId] = { - [1] = "Source Item Quest", - [21] = { - [1] = 20482, - }, - } - - local ok, quest = pcall(function() - return QuestieDB:GetQuest(questId) - end) - - assert.is_true(ok) - assert.is_table(quest) - assert.is_table(quest.SpecialObjectives) - assert.is_table(quest.SpecialObjectives[20482]) - assert.equals("Test Item 20482", quest.SpecialObjectives[20482].Description) - end) -end) - -describe("QuestieDB missing quest logs are deduplicated", function() - local originalImport - local originalDebug - local originalQueryQuest - - before_each(function() - dofile("Tests/wow_api_mock.lua") - - local questieLib = { - GetTbcLevel = function() - return 1, 1 - end, - } - local questieCorrections = { - hiddenQuests = {}, - killCreditObjectiveFirst = {}, - } - - originalImport = QuestieLoader.ImportModule - QuestieLoader.ImportModule = function(self, name) - if name == "QuestieLib" then - return questieLib - end - if name == "QuestieCorrections" then - return questieCorrections - end - return originalImport(self, name) - end - - dofile("Database/QuestieDB.lua") - dofile("Database/questDB.lua") - - QuestieDB.private.questCache = {} - QuestieDB.private.missingQuestLog = {} - - originalDebug = Questie.Debug - Questie.Debug = function(self, level, ...) - _G._questie_debug_calls = (_G._questie_debug_calls or 0) + 1 - end - - originalQueryQuest = QuestieDB.QueryQuest - QuestieDB.QueryQuest = function() - return nil - end - end) - - after_each(function() - if originalQueryQuest then - QuestieDB.QueryQuest = originalQueryQuest - end - if originalDebug then - Questie.Debug = originalDebug - end - if originalImport then - QuestieLoader.ImportModule = originalImport - end - _G._questie_debug_calls = nil - end) - - it("logs a missing quest only once per quest id", function() - local questId = 900002 - - assert.is_nil(QuestieDB:GetQuest(questId)) - assert.is_nil(QuestieDB:GetQuest(questId)) - assert.equals(1, _G._questie_debug_calls) - end) -end) - -describe("QuestieLearner quest accept resolution", function() - local QuestieLearner - local originalGetNumQuestLogEntries - local originalGetQuestLogSelection - local originalGetQuestLogTitle - local originalGetQuestIDFromLogIndex - local originalGetQuestLogIndexByID - local originalLearnQuest - local originalUnitGUID - - before_each(function() - dofile("Tests/wow_api_mock.lua") - Questie.dbLearner.global.settings.enabled = true - Questie.dbLearner.global.settings.dataSourceMode = "learner" - - originalGetNumQuestLogEntries = _G.GetNumQuestLogEntries - originalGetQuestLogSelection = QuestieCompat.GetQuestLogSelection - originalGetQuestLogTitle = QuestieCompat.GetQuestLogTitle - originalGetQuestIDFromLogIndex = QuestieCompat.GetQuestIDFromLogIndex - originalGetQuestLogIndexByID = QuestieCompat.GetQuestLogIndexByID - originalUnitGUID = _G.UnitGUID - - _G.GetNumQuestLogEntries = function() - return 1 - end - _G.UnitGUID = function() - return nil - end - - QuestieCompat.GetQuestLogSelection = function() - return 1 - end - - QuestieCompat.GetQuestLogTitle = function(index) - if index == 1 then - return "Real Quest", 10, nil, false, nil, nil, nil, 4321 - end - return nil - end - - QuestieCompat.GetQuestIDFromLogIndex = function(index) - if index == 1 then - return 4321 - end - return nil - end - - QuestieCompat.GetQuestLogIndexByID = function(questId) - if questId == 4321 then - return 1 - end - return nil - end - - QuestieLearner = dofile("Modules/QuestieLearner.lua") - originalLearnQuest = QuestieLearner.LearnQuest - end) - - after_each(function() - QuestieLearner.LearnQuest = originalLearnQuest - _G.GetNumQuestLogEntries = originalGetNumQuestLogEntries - _G.UnitGUID = originalUnitGUID - QuestieCompat.GetQuestLogSelection = originalGetQuestLogSelection - QuestieCompat.GetQuestLogTitle = originalGetQuestLogTitle - QuestieCompat.GetQuestIDFromLogIndex = originalGetQuestIDFromLogIndex - QuestieCompat.GetQuestLogIndexByID = originalGetQuestLogIndexByID - end) - - it("uses a real quest log entry instead of raw accepted event args", function() - local capturedQuestId = nil - QuestieLearner.LearnQuest = function(self, questId, data) - capturedQuestId = questId - end - - QuestieLearner:OnQuestAccepted(615514513, nil) - - assert.equals(4321, capturedQuestId) - end) - - it("refuses impossible quest ids when they do not resolve to the quest log", function() - local capturedQuestId = nil - QuestieCompat.GetQuestLogSelection = function() - return nil - end - QuestieCompat.GetQuestLogTitle = function() - return nil - end - QuestieCompat.GetQuestIDFromLogIndex = function() - return nil - end - QuestieCompat.GetQuestLogIndexByID = function() - return nil - end - QuestieLearner.LearnQuest = function(self, questId, data) - capturedQuestId = questId - end - - QuestieLearner:OnQuestAccepted(615514513, nil) - - assert.is_nil(capturedQuestId) - end) -end) - -describe("QuestieLearner quest turn-in resolution", function() - local QuestieLearner - local originalLearnQuest - local originalGetQuestID - local originalGetRewardText - local originalUnitGUID - - before_each(function() - dofile("Tests/wow_api_mock.lua") - - originalGetQuestID = _G.GetQuestID - originalGetRewardText = _G.GetRewardText - originalUnitGUID = _G.UnitGUID - - Questie.dbLearner.global.settings.enabled = true - Questie.dbLearner.global.settings.dataSourceMode = "learner" - - _G.GetQuestID = function() - return 4321 - end - _G.GetRewardText = function() - return "Reward text" - end - _G.UnitGUID = function() - return nil - end - - QuestieLearner = dofile("Modules/QuestieLearner.lua") - originalLearnQuest = QuestieLearner.LearnQuest - end) - - after_each(function() - QuestieLearner.LearnQuest = originalLearnQuest - _G.GetQuestID = originalGetQuestID - _G.GetRewardText = originalGetRewardText - _G.UnitGUID = originalUnitGUID - end) - - it("rejects malformed turn-in quest ids unless they resolve to a recent completion", function() - local capturedQuestId = nil - QuestieLearner.LearnQuest = function(self, questId, data) - capturedQuestId = questId - end - - QuestieLearner:OnQuestTurnedIn(545915281, nil, nil) - - assert.is_nil(capturedQuestId) - end) - - it("uses the recent quest-complete cache when the raw turn-in quest id is malformed", function() - local capturedQuestId = nil - QuestieLearner.LearnQuest = function(self, questId, data) - capturedQuestId = questId - end - - QuestieLearner:OnQuestComplete() - QuestieLearner:OnQuestTurnedIn(545915281, nil, nil) - - assert.equals(4321, capturedQuestId) - end) -end) - -describe("QuestieLearner GUID and loot learning", function() - local QuestieLearner - local originalGetNPC - local originalGetItemInfo - local originalLearnItem - local originalLearnItemDrop - local originalGetLootSourceInfo - local originalGetNumLootItems - local originalGetLootSlotInfo - local originalGetLootSlotLink - - before_each(function() - dofile("Tests/wow_api_mock.lua") - Questie.dbLearner.global.settings.enabled = true - Questie.dbLearner.global.settings.dataSourceMode = "learner" - Questie.dbLearner.global.settings.learnItems = true - Questie.dbLearner.global.settings.learnNpcs = true - - QuestieDB.npcData = { - [15297] = { [1] = "Arcanist Helion" }, - } - originalGetNPC = QuestieDB.GetNPC - QuestieDB.GetNPC = function(self, id) - if id == 168 then - return { name = "Something Else" } - end - return originalGetNPC and originalGetNPC(self, id) or nil - end - - originalGetItemInfo = _G.GetItemInfo - _G.GetItemInfo = function(link) - if link == "item:20470" then - return "Quest Token", nil, nil, 1, 1, 3, 1, nil, nil, nil, nil, 1, 0 - end - return nil - end - originalGetLootSourceInfo = _G.GetLootSourceInfo - _G.GetLootSourceInfo = function() - return "Creature-0-0-0-0-15297-0000000000", 1 - end - originalGetNumLootItems = _G.GetNumLootItems - _G.GetNumLootItems = function() - return 1 - end - originalGetLootSlotInfo = _G.GetLootSlotInfo - _G.GetLootSlotInfo = function() - return nil, "Quest Token", nil, nil, 1 - end - originalGetLootSlotLink = _G.GetLootSlotLink - _G.GetLootSlotLink = function() - return "item:20470" - end - dofile("Modules/QuestieLearner.lua") - QuestieLearner = _G.QuestieLearner - originalLearnItem = QuestieLearner.LearnItem - originalLearnItemDrop = QuestieLearner.LearnItemDrop - end) - - after_each(function() - QuestieDB.GetNPC = originalGetNPC - _G.GetItemInfo = originalGetItemInfo - _G.GetLootSourceInfo = originalGetLootSourceInfo - _G.GetNumLootItems = originalGetNumLootItems - _G.GetLootSlotInfo = originalGetLootSlotInfo - _G.GetLootSlotLink = originalGetLootSlotLink - if QuestieLearner then - QuestieLearner.LearnItem = originalLearnItem - QuestieLearner.LearnItemDrop = originalLearnItemDrop - end - end) - - it("prefers the exact NPC name over a mismatched GUID entry id", function() - local resolvedId, unitType = QuestieLearner:ResolveNpcIdFromGuidAndName("Creature-0-0-0-0-168-0000000000", "Arcanist Helion") - assert.equals(15297, resolvedId) - assert.equals("Creature", unitType) - end) - - it("ignores non-quest loot items and only learns quest-item drops", function() - local learnedItemId = nil - local dropItemId = nil - local dropNpcId = nil - - QuestieLearner.LearnItem = function(self, itemId, name, itemLevel, requiredLevel, itemClassId, itemSubClassId) - if itemClassId == 12 then - learnedItemId = itemId - return true - end - return false - end - QuestieLearner.LearnItemDrop = function(self, itemId, npcId) - dropItemId = itemId - dropNpcId = npcId - end - - local originalGetItemInfo = _G.GetItemInfo - _G.GetItemInfo = function(link) - if link == "item:20470" then - return "Quest Token", nil, nil, 1, 1, nil, nil, nil, nil, nil, nil, 1, 0 - end - return originalGetItemInfo(link) - end - - QuestieLearner:OnLootOpened() - - assert.is_nil(learnedItemId) - assert.is_nil(dropItemId) - assert.is_nil(dropNpcId) - - _G.GetItemInfo = originalGetItemInfo - end) -end) - -describe("QuestieDB learner source fallback", function() - before_each(function() - dofile("Tests/wow_api_mock.lua") - dofile("Database/QuestieDB.lua") - dofile("Database/npcDB.lua") - dofile("Database/objectDB.lua") - dofile("Database/questDB.lua") - dofile("Database/itemDB.lua") - - Questie.dbLearner.global.settings.enabled = true - Questie.dbLearner.global.settings.dataSourceMode = "learner" - Questie.dbLearner.global.npcs = { - [9001] = { - [1] = "Learner Whelp", - [7] = { - [44] = { - { 12.3, 45.6 }, - }, - }, - [8] = { - [44] = { - { 13.3, 46.6 }, - }, - }, - [9] = 44, - }, - } - Questie.dbLearner.global.objects = { - [9002] = { - [1] = "Learner Cache", - [4] = { - [44] = { - { 11.1, 22.2 }, - }, - }, - [5] = 44, - }, - } - - QuestieDB.QueryNPC = function() return nil end - QuestieDB.QueryObject = function() return nil end - QuestieDB.QueryQuest = function() return nil end - QuestieDB.QueryItem = function() return nil end - QuestieDB.private.npcCache = {} - QuestieDB.private.objectCache = {} - QuestieDB.private.questCache = {} - QuestieDB.private.itemCache = {} - end) - - it("returns learner NPC data when static queries are unavailable", function() - local npc = QuestieDB:GetNPC(9001) - assert.is_table(npc) - assert.equals("Learner Whelp", npc.name) - assert.is_table(npc.spawns) - assert.is_table(npc.waypoints) - assert.equals(44, npc.zoneID) - end) - - it("turns learner kill evidence into NPC spawn coordinates immediately in learner mode", function() - Questie.dbLearner.global.npcs[9003] = { - [1] = "Learner Kill", - [8] = { - [101] = { - zoneId = 44, - x = 18.5, - y = 27.25, - }, - }, - } - - local npc = QuestieDB:GetNPC(9003) - assert.is_table(npc) - assert.is_table(npc.spawns) - assert.is_table(npc.spawns[44]) - assert.equals(1, #npc.spawns[44]) - assert.equals(18.5, npc.spawns[44][1][1]) - assert.equals(27.25, npc.spawns[44][1][2]) - end) - - it("collapses many nearby kills (distinct GUIDs) into one spawn pin", function() - -- Five kills of respawns at the same spot: distinct GUID keys, slightly - -- drifting player coords. This must render as ONE pin, not five. - Questie.dbLearner.global.npcs[9005] = { - [1] = "Respawning Boar", - [8] = { - [201] = { zoneId = 44, x = 50.0, y = 50.0 }, - [202] = { zoneId = 44, x = 50.4, y = 50.3 }, - [203] = { zoneId = 44, x = 49.7, y = 50.6 }, - [204] = { zoneId = 44, x = 50.9, y = 49.8 }, - [205] = { zoneId = 44, x = 50.2, y = 50.1 }, - }, - } - - local npc = QuestieDB:GetNPC(9005) - assert.is_table(npc) - assert.is_table(npc.spawns[44]) - assert.equals(1, #npc.spawns[44]) - end) - - it("keeps genuinely separate spawn locations as distinct pins", function() - Questie.dbLearner.global.npcs[9006] = { - [1] = "Field Boars", - [8] = { - [301] = { zoneId = 44, x = 20.0, y = 20.0 }, - [302] = { zoneId = 44, x = 20.3, y = 20.2 }, -- same spot as 301 - [303] = { zoneId = 44, x = 70.0, y = 65.0 }, -- far corner - }, - } - - local npc = QuestieDB:GetNPC(9006) - assert.is_table(npc) - assert.is_table(npc.spawns[44]) - assert.equals(2, #npc.spawns[44]) - end) - - it("honors the spawn dedup radius knob (0 disables proximity merge)", function() - Questie.dbLearner.global.settings.spawnDedupRadius = 0 - Questie.dbLearner.global.npcs[9007] = { - [1] = "Drifting Kills", - [8] = { - [401] = { zoneId = 44, x = 50.0, y = 50.0 }, - [402] = { zoneId = 44, x = 50.4, y = 50.3 }, - [403] = { zoneId = 44, x = 49.7, y = 50.6 }, - [404] = { zoneId = 44, x = 50.9, y = 49.8 }, - [405] = { zoneId = 44, x = 50.2, y = 50.1 }, - }, - } - - local npc = QuestieDB:GetNPC(9007) - assert.is_table(npc) - assert.is_table(npc.spawns[44]) - -- With merging off, each distinct kill coordinate stays its own pin. - assert.equals(5, #npc.spawns[44]) - end) - - it("widens merging when the dedup radius is increased", function() - Questie.dbLearner.global.settings.spawnDedupRadius = 12 - Questie.dbLearner.global.npcs[9008] = { - [1] = "Loose Cluster", - [8] = { - [501] = { zoneId = 44, x = 40.0, y = 40.0 }, - [502] = { zoneId = 44, x = 48.0, y = 46.0 }, -- ~10 away: merges at radius 12 - }, - } - - local npc = QuestieDB:GetNPC(9008) - assert.is_table(npc) - assert.is_table(npc.spawns[44]) - assert.equals(1, #npc.spawns[44]) - end) - - it("returns learner object data when static queries are unavailable", function() - local obj = QuestieDB:GetObject(9002) - assert.is_table(obj) - assert.equals("Learner Cache", obj.name) - assert.is_table(obj.spawns) - assert.equals(44, obj.zoneID) - end) -end) - -describe("QuestieDB partial base DB missing", function() - before_each(function() - dofile("Tests/wow_api_mock.lua") - dofile("Database/QuestieDB.lua") - end) - - it("does not report the base DB missing when only one store failed", function() - QuestieDB.baseDatabaseMissing = true - QuestieDB.baseDatabaseMissingKeys = { itemData = true } - assert.is_false(QuestieDB:IsBaseDatabaseMissing()) - assert.is_true(QuestieDB:IsStoreMissing("itemData")) - assert.is_false(QuestieDB:IsStoreMissing("npcData")) - end) - - it("reports the base DB missing only when every core store failed", function() - QuestieDB.baseDatabaseMissing = true - QuestieDB.baseDatabaseMissingKeys = { - npcData = true, objectData = true, questData = true, itemData = true, - } - assert.is_true(QuestieDB:IsBaseDatabaseMissing()) - end) - - it("honors the static selection for a present store even when another is missing", function() - QuestieDB.baseDatabaseMissing = true - QuestieDB.baseDatabaseMissingKeys = { itemData = true } - Questie.dbLearner.global.settings.dataSourceMode = "static" - Questie.dbLearner.global.npcs = { - [9100] = { [1] = "Should Not Win", [7] = { [44] = { { 1, 2 } } }, [9] = 44 }, - } - QuestieDB.private.npcCache = {} - local queried = false - QuestieDB.QueryNPC = function() queried = true; return nil end - - pcall(function() QuestieDB:GetNPC(9100) end) - assert.is_true(queried) - end) -end) - -describe("QuestieDB mode cohesion", function() - before_each(function() - dofile("Tests/wow_api_mock.lua") - dofile("Database/QuestieDB.lua") - dofile("Database/npcDB.lua") - Questie.dbLearner.global.settings.enabled = true - Questie.dbLearner.global.npcs = { - [9200] = { [1] = "Learner Only NPC", [7] = { [44] = { { 5, 6 } } }, [9] = 44 }, - } - QuestieDB.QueryNPC = function() return nil end - QuestieDB.baseDatabaseMissing = false - QuestieDB.baseDatabaseMissingKeys = {} - QuestieDB.private.npcCache = {} - end) - - it("does NOT leak learner data into static mode when the store is present", function() - Questie.dbLearner.global.settings.dataSourceMode = "static" - QuestieDB.private.npcCache = {} - assert.is_nil(QuestieDB:GetNPC(9200)) - end) - - it("does NOT leak learner data into none mode when the store is present", function() - Questie.dbLearner.global.settings.dataSourceMode = "none" - QuestieDB.private.npcCache = {} - assert.is_nil(QuestieDB:GetNPC(9200)) - end) - - it("DOES overlay learner data in auto mode when the static DB lacks it", function() - Questie.dbLearner.global.settings.dataSourceMode = "auto" - QuestieDB.private.npcCache = {} - local npc = QuestieDB:GetNPC(9200) - assert.is_table(npc) - assert.equals("Learner Only NPC", npc.name) - end) - - it("clears every cache including the zone cache on mode switch", function() - QuestieDB.private.questCache[1] = {} - QuestieDB.private.zoneCache[1] = {} - QuestieDB:ClearModeCaches() - assert.is_nil(QuestieDB.private.questCache[1]) - assert.is_nil(QuestieDB.private.zoneCache[1]) - end) -end) - -describe("QuestieLearner mode switch redraw wiring", function() - it("drives a full real-time refresh via SmoothReset, not the mis-named event handler", function() - local function read(path) - local f = assert(io.open(path, "r")); local c = f:read("*a"); f:close(); return c - end - local dbOptions = read("Modules/Options/DatabaseTab/QuestieOptionsDatabase.lua") - assert.is_true(string.find(dbOptions, "QuestieQuest:SmoothReset()", 1, true) ~= nil) - -- The old import name resolved to nil and silently skipped the redraw. - assert.is_nil(string.find(dbOptions, "ImportModule(\"QuestieEventHandler\")", 1, true)) - end) -end) diff --git a/Tests/QuestieLearnerReset_spec.lua b/Tests/QuestieLearnerReset_spec.lua deleted file mode 100644 index 557c73c..0000000 --- a/Tests/QuestieLearnerReset_spec.lua +++ /dev/null @@ -1,67 +0,0 @@ -local function countKeys(tbl) - local n = 0 - for _ in pairs(tbl or {}) do - n = n + 1 - end - return n -end - -describe("QuestieLearner reset all learned data", function() - local QuestieLearner - - before_each(function() - dofile("Tests/wow_api_mock.lua") - - Questie.dbLearner.global = { - settings = { - enabled = false, - prioritizeMyData = true, - dataSourceMode = "auto", - learnNpcs = true, - learnQuests = true, - learnItems = true, - learnObjects = true, - }, - npcs = { - [1001] = { [1] = "Boar", mc = 2 }, - }, - quests = { - [2001] = { [1] = "Quest", mc = 1 }, - }, - items = { - [3001] = { [1] = "Item", mc = 1 }, - }, - objects = { - [4001] = { [1] = "Object", mc = 1 }, - }, - Ascension = { - npcs = { [9001] = { [1] = "Bucket NPC" } }, - quests = { [9002] = { [1] = "Bucket Quest" } }, - items = { [9003] = { [1] = "Bucket Item" } }, - objects = { [9004] = { [1] = "Bucket Object" } }, - }, - } - - dofile("Modules/QuestieLearner.lua") - QuestieLearner = _G.QuestieLearner - end) - - it("clears all learned data buckets while preserving settings", function() - QuestieLearner:ClearAllData() - - assert.is_table(Questie.dbLearner.global.settings) - assert.equals("auto", Questie.dbLearner.global.settings.dataSourceMode) - assert.is_true(Questie.dbLearner.global.settings.learnObjects) - - local npcCount, questCount, itemCount, objectCount = QuestieLearner:GetStats() - assert.equals(0, npcCount) - assert.equals(0, questCount) - assert.equals(0, itemCount) - assert.equals(0, objectCount) - assert.equals(0, countKeys(Questie.dbLearner.global.npcs)) - assert.equals(0, countKeys(Questie.dbLearner.global.quests)) - assert.equals(0, countKeys(Questie.dbLearner.global.items)) - assert.equals(0, countKeys(Questie.dbLearner.global.objects)) - assert.is_nil(Questie.dbLearner.global.Ascension) - end) -end) diff --git a/Tests/QuestieLearner_performance_spec.lua b/Tests/QuestieLearner_performance_spec.lua deleted file mode 100644 index 4e3835d..0000000 --- a/Tests/QuestieLearner_performance_spec.lua +++ /dev/null @@ -1,870 +0,0 @@ -describe("QuestieLearner kill-path batching", function() - local queuedTimers - local broadcasts - local QuestieLearner - local simulatedTime - - local function read(path) - local f = assert(io.open(path, "r")) - local content = f:read("*a") - f:close() - return content - end - - local function has(text, needle) - return text and text:find(needle, 1, true) ~= nil - end - - local function drainQueuedTimers() - while next(queuedTimers) do - local currentQueue = queuedTimers - queuedTimers = {} - for i = 1, table.getn(currentQueue) do - local fn = currentQueue[i] - if fn then - simulatedTime = simulatedTime + 1 - fn() - end - end - end - end - - before_each(function() - dofile("Tests/wow_api_mock.lua") - - queuedTimers = {} - broadcasts = {} - simulatedTime = 1000 - _G.GetTime = function() - return simulatedTime - end - QuestieCompat.C_Timer.After = function(_, fn) - queuedTimers[table.getn(queuedTimers) + 1] = fn - end - - local originalImportModule = QuestieLoader.ImportModule - QuestieLoader.ImportModule = function(self, name) - if name == "QuestieLearnerComms" then - return { - BroadcastLearnedData = function(_, op, typ, id, data) - broadcasts[table.getn(broadcasts) + 1] = { - op = op, - typ = typ, - id = id, - data = data, - } - end, - } - end - return originalImportModule(self, name) - end - - Questie.dbLearner.global.settings.enabled = true - Questie.dbLearner.global.settings.learnNpcs = true - Questie.dbLearner.global.settings.learnQuests = true - Questie.dbLearner.global.settings.learnItems = true - Questie.dbLearner.global.settings.learnObjects = true - Questie.dbLearner.global.settings.minConfidencePins = 1 - Questie.dbLearner.global.npcs = {} - Questie.dbLearner.global.quests = {} - Questie.dbLearner.global.items = {} - Questie.dbLearner.global.objects = {} - QuestieDB.npcDataOverrides = {} - QuestieDB.questDataOverrides = {} - QuestieDB.itemDataOverrides = {} - QuestieDB.objectDataOverrides = {} - QuestieDB.private = { - npcCache = { - [1001] = { name = "Cached Boar" }, - }, - itemCache = {}, - } - QuestiePlayer.currentQuestlog = {} - - QuestieLearner = dofile("Modules/QuestieLearner.lua") - end) - - it("coalesces repeated NPC live updates instead of invalidating DB cache per kill", function() - QuestieLearner:LearnNPC(1001, "Laggy Boar", nil, nil, nil, nil, 41.25, 52.5, 44) - QuestieLearner:LearnNPC(1001, "Laggy Boar", nil, nil, nil, nil, 41.35, 52.6, 44) - - assert.is_nil(QuestieDB.npcDataOverrides[1001]) - assert.is_table(QuestieDB.private.npcCache[1001]) - assert.equals(2, table.getn(queuedTimers)) - - drainQueuedTimers() - - assert.is_table(QuestieDB.npcDataOverrides[1001]) - assert.is_nil(QuestieDB.private.npcCache[1001]) - assert.is_table(QuestieDB.npcDataOverrides[1001][7]) - - local flushedSpawnCount = table.getn(QuestieDB.npcDataOverrides[1001][7][44]) - - QuestieLearner:LearnNPC(1001, "Laggy Boar", nil, nil, nil, nil, 80.0, 80.0, 44) - - assert.equals(flushedSpawnCount, table.getn(QuestieDB.npcDataOverrides[1001][7][44])) - assert.is_true(table.getn(queuedTimers) >= 2) - end) - - it("heals repeated same-bucket npc coordinates without duplicating the learned spawn", function() - QuestieLearner:LearnNPC(2001, "Healing Boar", nil, nil, nil, nil, 40.10, 40.10, 44) - QuestieLearner:LearnNPC(2001, "Healing Boar", nil, nil, nil, nil, 40.30, 40.30, 44) - - local spawns = Questie.dbLearner.global.npcs[2001][7][44] - assert.is_table(spawns) - assert.equals(1, table.getn(spawns)) - assert.equals(2, spawns[1][3]) - assert.is_true(math.abs(spawns[1][1] - 40.20) < 0.001) - assert.is_true(math.abs(spawns[1][2] - 40.20) < 0.001) - end) - - it("keeps explicit learned npc spawns available for learner arrows", function() - Questie.dbLearner.global.settings.dataSourceMode = "learner" - QuestieLearner:LearnNPC(3001, "Arrow Boar", nil, nil, nil, nil, 22.10, 33.20, 44) - QuestieLearner:InjectLearnedData() - - assert.equals("explicit", Questie.dbLearner.global.npcs[3001].spawnSource) - assert.is_table(Questie.dbLearner.global.npcs[3001][7]) - assert.is_table(Questie.dbLearner.global.npcs[3001][7][44]) - assert.equals(1, table.getn(Questie.dbLearner.global.npcs[3001][7][44])) - assert.equals(22.10, Questie.dbLearner.global.npcs[3001][7][44][1][1]) - assert.equals(33.20, Questie.dbLearner.global.npcs[3001][7][44][1][2]) - end) - - it("keeps quest-tied fallback questgiver spawns from being stripped", function() - Questie.dbLearner.global.settings.dataSourceMode = "learner" - QuestieLearner:LearnNPC(4001, "Quest Giver", nil, nil, nil, nil, 10.0, 20.0, 44) - QuestieLearner:LearnQuestGiver(6004, 4001, 1, true) - - assert.equals("explicit", Questie.dbLearner.global.npcs[4001].spawnSource) - assert.is_table(Questie.dbLearner.global.npcs[4001][7]) - assert.is_table(Questie.dbLearner.global.npcs[4001][7][44]) - assert.equals(1, table.getn(Questie.dbLearner.global.npcs[4001][7][44])) - end) - - it("keeps quest-related protected npc spawns available for turn-in arrows", function() - Questie.dbLearner.global.settings.dataSourceMode = "auto" - QuestieDB.ascensionOverrideKeys = QuestieDB.ascensionOverrideKeys or {} - QuestieDB.ascensionOverrideKeys.NPC = QuestieDB.ascensionOverrideKeys.NPC or {} - QuestieDB.ascensionOverrideKeys.NPC[5001] = { [7] = true } - - QuestieLearner:LearnQuestGiver(6007, 5001, 1, false) - QuestieLearner:LearnNPC(5001, "Protected Turn-In", nil, nil, nil, nil, 15.5, 25.5, 44) - drainQueuedTimers() - - assert.is_table(QuestieDB.npcDataOverrides[5001]) - assert.is_table(QuestieDB.npcDataOverrides[5001][7]) - assert.is_table(QuestieDB.npcDataOverrides[5001][7][44]) - assert.equals(1, table.getn(QuestieDB.npcDataOverrides[5001][7][44])) - end) - - it("ignores non-quest loot items so they do not pollute learner state", function() - Questie.dbLearner.global.settings.dataSourceMode = "learner" - QuestieDB.private.itemCache = {} - - local learned = QuestieLearner:LearnItem(2301, "Quest Shard", 1, 1, 1, 0) - - assert.is_false(learned) - assert.is_nil(Questie.dbLearner.global.items[2301]) - assert.is_nil(QuestieDB.private.itemCache[2301]) - end) - - it("keeps quest item drop sources when the item is a quest item", function() - Questie.dbLearner.global.settings.dataSourceMode = "learner" - local learned = QuestieLearner:LearnItem(2302, "Quest Shard", 1, 1, 12, 0) - assert.is_true(learned) - assert.is_true(Questie.dbLearner.global.items[2302].questRelevant) - - QuestieLearner:LearnItemDrop(2302, 7301) - - assert.is_table(Questie.dbLearner.global.items[2302][2]) - assert.equals(7301, Questie.dbLearner.global.items[2302][2][1]) - end) - - it("ignores non-quest objects until they are explicitly quest-related", function() - Questie.dbLearner.global.settings.dataSourceMode = "learner" - - local learned = QuestieLearner:LearnObject(3301, "Random Object", 11.1, 22.2, 44) - - assert.is_false(learned) - assert.is_nil(Questie.dbLearner.global.objects[3301]) - end) - - it("stores quest-related object coordinates when promoted", function() - Questie.dbLearner.global.settings.dataSourceMode = "learner" - Questie.dbLearner.global.quests[6005] = { - [10] = { - [2] = { - { 3302, "Quest Object" }, - }, - }, - } - - local learned = QuestieLearner:LearnObject(3302, "Quest Object", 11.1, 22.2, 44, true) - - assert.is_true(learned) - assert.is_table(Questie.dbLearner.global.objects[3302]) - assert.is_true(Questie.dbLearner.global.objects[3302].questRelevant) - assert.is_table(Questie.dbLearner.global.objects[3302][4]) - assert.is_table(Questie.dbLearner.global.objects[3302][4][44]) - assert.equals(1, table.getn(Questie.dbLearner.global.objects[3302][4][44])) - assert.equals(11.1, Questie.dbLearner.global.objects[3302][4][44][1][1]) - assert.equals(22.2, Questie.dbLearner.global.objects[3302][4][44][1][2]) - end) - - it("keeps quest-related protected object spawns available for turn-in arrows", function() - Questie.dbLearner.global.settings.dataSourceMode = "auto" - QuestieDB.ascensionOverrideKeys = QuestieDB.ascensionOverrideKeys or {} - QuestieDB.ascensionOverrideKeys.OBJECT = QuestieDB.ascensionOverrideKeys.OBJECT or {} - QuestieDB.ascensionOverrideKeys.OBJECT[5002] = { [4] = true } - - QuestieLearner:LearnQuestGiver(6008, 5002, 2, false) - local learned = QuestieLearner:LearnObject(5002, "Protected Object", 21.5, 31.5, 44, true) - - assert.is_true(learned) - assert.is_table(QuestieDB.objectDataOverrides[5002]) - assert.is_table(QuestieDB.objectDataOverrides[5002][4]) - assert.is_table(QuestieDB.objectDataOverrides[5002][4][44]) - assert.equals(1, table.getn(QuestieDB.objectDataOverrides[5002][4][44])) - end) - - it("rejects non-quest item network merges without quest references", function() - Questie.dbLearner.global.settings.dataSourceMode = "learner" - local changed = QuestieLearner:_ApplyIncomingNetworkMerge("ITEM", 2401, { [1] = "Arcane Sliver" }, "NEW") - - assert.is_false(changed) - assert.is_nil(Questie.dbLearner.global.items[2401]) - end) - - it("rejects non-quest object network merges without quest references", function() - Questie.dbLearner.global.settings.dataSourceMode = "learner" - local changed = QuestieLearner:_ApplyIncomingNetworkMerge("OBJECT", 3401, { [1] = "Random Object", [4] = { [44] = { { 11.1, 22.2 } } } }, "NEW") - - assert.is_false(changed) - assert.is_nil(Questie.dbLearner.global.objects[3401]) - end) - - it("clears cached quest data when learner adds questgiver links", function() - Questie.dbLearner.global.settings.dataSourceMode = "learner" - QuestieDB.private.questCache = { - [6004] = { cached = true }, - } - - QuestieLearner:LearnQuestGiver(6004, 4001, 1, true) - - assert.is_table(Questie.dbLearner.global.quests[6004]) - assert.is_nil(QuestieDB.private.questCache[6004]) - end) - - it("force-flushes active quest pins within the NPC live-update flush (no second debounce)", function() - local updateCount = 0 - local originalUpdateQuest = QuestieQuest.UpdateQuest - QuestieQuest.UpdateQuest = function(_, questId) - updateCount = updateCount + 1 - end - - QuestiePlayer.currentQuestlog = { [5500] = true } - local originalGetQuest = QuestieDB.GetQuest - QuestieDB.GetQuest = function(questId) - if questId == 5500 then - return { Objectives = { [1] = { Id = 7500, spawnList = { [7500] = {} } } } } - end - return nil - end - - QuestieLearner:LearnNPC(7500, "Quest Boar", nil, nil, nil, nil, 41.0, 52.0, 44) - - -- Drain exactly ONE timer round (the NPC live-update flush). The pin refresh - -- must happen inside that same flush, not in a later pinRefreshDelay cycle. - local firstRound = queuedTimers - queuedTimers = {} - for i = 1, table.getn(firstRound) do - simulatedTime = simulatedTime + 1 - firstRound[i]() - end - - assert.is_true(updateCount >= 1) - - -- And draining the rest must terminate (no infinite self-re-arming timer). - drainQueuedTimers() - - QuestieDB.GetQuest = originalGetQuest - QuestieQuest.UpdateQuest = originalUpdateQuest - end) - - it("coalesces repeated quest-pin refreshes into one flush", function() - local updateCount = 0 - local originalUpdateQuest = QuestieQuest.UpdateQuest - QuestieQuest.UpdateQuest = function(_, questId) - updateCount = updateCount + 1 - end - - QuestiePlayer.currentQuestlog = { [5001] = true } - Questie.dbLearner.global.quests = {} - QuestieDB.questDataOverrides = {} - - QuestieLearner:LearnQuestObjectiveNPC(5001, 7001, "Pin Boar slain", 1) - QuestieLearner:LearnQuestObjectiveNPC(5001, 7001, "Pin Boar slain", 1) - - assert.is_true(table.getn(queuedTimers) >= 1) - - for i = 1, table.getn(queuedTimers) do - queuedTimers[i]() - end - - assert.is_table(Questie.dbLearner.global.quests[5001]) - assert.is_table(QuestieDB.questDataOverrides[5001]) - - QuestieQuest.UpdateQuest = originalUpdateQuest - end) - - it("suppresses an already queued pin refresh after switching to manual mode", function() - local updateCount = 0 - local originalUpdateQuest = QuestieQuest.UpdateQuest - QuestieQuest.UpdateQuest = function(_, questId) - updateCount = updateCount + 1 - end - - QuestiePlayer.currentQuestlog = { [5003] = true } - Questie.dbLearner.global.quests = {} - QuestieDB.questDataOverrides = {} - - QuestieLearner:LearnQuestObjectiveNPC(5003, 7003, "Manual Boar slain", 1) - - assert.is_true(table.getn(queuedTimers) >= 1) - - Questie.dbLearner.global.settings.pinRefreshMode = "manual" - drainQueuedTimers() - - assert.equals(0, updateCount) - - QuestieQuest.UpdateQuest = originalUpdateQuest - end) - - it("keeps bystander UNIT_DIED eligible for learning in the source path", function() - local learner = read("Modules/QuestieLearner.lua") - - assert.is_true(has(learner, "Unconditionally map the spawn position for Ascension DB building")) - assert.is_true(has(learner, "self:LearnNPC(npcId, name, nil, nil, nil, nil, px, py, zoneId)")) - assert.is_false(has(learner, 'if eventType ~= "PARTY_KILL" and not credited then')) - end) - - it("still learns and batches local PARTY_KILL combat-log events", function() - QuestieLearner:OnCombatLogEvent( - 1234, - "PARTY_KILL", - UnitGUID("player"), - UnitName("player"), - nil, - "Creature-0-0-0-0-7003-0000000001", - "Tagged Boar", - nil - ) - - assert.is_table(Questie.dbLearner.global.npcs[7003]) - assert.equals("Tagged Boar", Questie.dbLearner.global.npcs[7003][1]) - assert.equals(2, table.getn(queuedTimers)) - end) - - it("only announces combat-log learning once per unique NPC id", function() - local debugMessages = {} - local originalDebug = Questie.Debug - Questie.Debug = function(self, level, ...) - local parts = { ... } - for i = 1, table.getn(parts) do - if parts[i] == "[QuestieLearner] Combat-log learned NPC:" then - debugMessages[table.getn(debugMessages) + 1] = level - break - end - end - end - - QuestieLearner:OnCombatLogEvent( - 1234, - "PARTY_KILL", - UnitGUID("player"), - UnitName("player"), - nil, - "Creature-0-0-0-0-7010-0000000001", - "Unique Boar", - nil - ) - - QuestieLearner:OnCombatLogEvent( - 1235, - "PARTY_KILL", - UnitGUID("player"), - UnitName("player"), - nil, - "Creature-0-0-0-0-7010-0000000002", - "Unique Boar", - nil - ) - - assert.is_table(Questie.dbLearner.global.npcs[7010]) - assert.equals(1, table.getn(debugMessages)) - - Questie.Debug = originalDebug - end) - - it("still learns credited UNIT_DIED combat-log events when PARTY_KILL is absent", function() - QuestieLearner:OnCombatLogEvent( - 1234, - "UNIT_DIED", - UnitGUID("player"), - UnitName("player"), - nil, - "Creature-0-0-0-0-7004-0000000001", - "Credited Boar", - nil - ) - - assert.is_table(Questie.dbLearner.global.npcs[7004]) - assert.equals("Credited Boar", Questie.dbLearner.global.npcs[7004][1]) - assert.equals(2, table.getn(queuedTimers)) - end) - - it("cross-links quest giver NPCs and objects learned after the quest without duplicate pin flushes", function() - local updateCount = 0 - local originalUpdateQuest = QuestieQuest.UpdateQuest - QuestieQuest.UpdateQuest = function(_, questId) - updateCount = updateCount + 1 - end - Questie.db.profile.learnerBroadcast = false - QuestiePlayer.currentQuestlog = { [6001] = true } - - QuestieLearner:LearnQuest(6001, { - [1] = "Cross Link Givers", - [2] = { [1] = { 7101 }, [2] = { 8101 } }, - [3] = { [1] = { 7102 }, [2] = { 8102 } }, - }) - - queuedTimers = {} - - QuestieLearner:LearnNPC(7101, "Starter Guard", nil, nil, nil, nil, 10, 20, 44) - QuestieLearner:LearnNPC(7102, "Finisher Guard", nil, nil, nil, nil, 30, 40, 44) - QuestieLearner:LearnObject(8101, "Starter Chest") - QuestieLearner:LearnObject(8102, "Finisher Chest") - - assert.same({ 6001 }, Questie.dbLearner.global.npcs[7101][10]) - assert.same({ 6001 }, Questie.dbLearner.global.npcs[7102][11]) - assert.same({ 6001 }, Questie.dbLearner.global.objects[8101][2]) - assert.same({ 6001 }, Questie.dbLearner.global.objects[8102][3]) - assert.same({ 6001 }, QuestieDB.npcDataOverrides[7101][10]) - assert.same({ 6001 }, QuestieDB.npcDataOverrides[7102][11]) - assert.same({ 6001 }, QuestieDB.objectDataOverrides[8101][2]) - assert.same({ 6001 }, QuestieDB.objectDataOverrides[8102][3]) - - drainQueuedTimers() - - assert.is_table(Questie.dbLearner.global.quests[6001]) - assert.is_table(QuestieDB.questDataOverrides[6001]) - - QuestieQuest.UpdateQuest = originalUpdateQuest - end) - - it("cross-links item objective drop NPCs into quest creature objectives", function() - Questie.db.profile.learnerBroadcast = false - QuestiePlayer.currentQuestlog = { [6002] = true } - - QuestieLearner:LearnQuest(6002, { - [1] = "Cross Link Drops", - [10] = { - [3] = { - { 2201, 0, 1, "Collect one tusk" }, - }, - }, - }) - QuestieLearner:LearnItem(2201, "Quest Tusk", 1, 1, 12, 0) - QuestieLearner:LearnItemDrop(2201, 7201) - - assert.equals(7201, Questie.dbLearner.global.quests[6002][10][1][1][1]) - assert.equals(7201, QuestieDB.questDataOverrides[6002][10][1][1][1]) - - QuestieLearner:LearnItemDrop(2201, 7201) - - assert.equals(1, table.getn(Questie.dbLearner.global.quests[6002][10][1])) - assert.equals(1, table.getn(QuestieDB.questDataOverrides[6002][10][1])) - end) - - it("defers learner objective tooltip registration to protected AscensionDB quest data", function() - Questie.db.profile.learnerBroadcast = false - QuestiePlayer.currentQuestlog = {} - QuestieDB.ascensionOverrideKeys = { - QUEST = { - [8334] = { - [10] = true, - }, - }, - } - _G._lastRegisteredTooltip = nil - - QuestieLearner:LearnQuestObjectiveNPC(8334, 15271, "Tender slain", 1) - - assert.is_table(Questie.dbLearner.global.quests[8334]) - assert.is_nil(_G._lastRegisteredTooltip) - end) - - it("coalesces repeated learner broadcasts into one latest update", function() - QuestieLearner:LearnItem(2001, "Quest Tusk", 1, 1, 12, 0) - QuestieLearner:LearnItem(2001, "Quest Tusk", 1, 1, 12, 0) - - assert.equals(0, table.getn(broadcasts)) - assert.equals(1, table.getn(queuedTimers)) - - drainQueuedTimers() - - assert.equals(1, table.getn(broadcasts)) - assert.equals("NEW", broadcasts[1].op) - assert.equals("ITEM", broadcasts[1].typ) - assert.equals(2001, broadcasts[1].id) - end) - - it("coalesces repeated inbound network merges into one inject pass", function() - local injectCount = 0 - local originalInject = QuestieLearner.InjectLearnedData - QuestieLearner.InjectLearnedData = function(self) - injectCount = injectCount + 1 - QuestieLearner.data = Questie.dbLearner.global - end - - QuestieLearner:HandleNetworkData("NPC", 3001, { [1] = "Net Boar", [7] = { [44] = { { 11, 22 } } } }, "NEW") - QuestieLearner:HandleNetworkData("NPC", 3001, { [1] = "Net Boar", [7] = { [44] = { { 11, 22 }, { 33, 44 } } } }, "UPDATE") - - assert.equals(0, injectCount) - assert.equals(1, table.getn(queuedTimers)) - - drainQueuedTimers() - - assert.equals(1, injectCount) - assert.equals("Net Boar", Questie.dbLearner.global.npcs[3001][1]) - assert.equals(2, table.getn(Questie.dbLearner.global.npcs[3001][7][44])) - - QuestieLearner.InjectLearnedData = originalInject - end) - - it("forwards OnEvent payload arguments to learner handlers", function() - local originalCreateFrame = _G.CreateFrame - local originalStrsplit = _G.strsplit - local createdFrame = nil - local captured = { - questTurnedIn = nil, - questAccepted = nil, - combatLog = nil, - itemInfoReceived = nil, - questTrackingCleared = nil, - } - - _G.CreateFrame = function() - createdFrame = { - RegisterEvent = function() end, - SetScript = function(self, scriptName, fn) - if scriptName == "OnEvent" then - self._onEvent = fn - end - end, - } - return createdFrame - end - _G.strsplit = function(sep, str) - local parts = {} - local pattern = string.format("([^%s]+)", sep) - for part in string.gmatch(str, pattern) do - parts[#parts + 1] = part - end - return unpack(parts) - end - - QuestieLearner = dofile("Modules/QuestieLearner.lua") - local originalOnQuestTurnedIn = QuestieLearner.OnQuestTurnedIn - local originalOnQuestAccepted = QuestieLearner.OnQuestAccepted - local originalOnCombatLogEvent = QuestieLearner.OnCombatLogEvent - local originalOnGetItemInfoReceived = QuestieLearner.OnGetItemInfoReceived - local originalClearQuestObjectiveTracking = QuestieLearner.ClearQuestObjectiveTracking - - QuestieLearner.OnQuestTurnedIn = function(self, questId, npcId, questFlags) - captured.questTurnedIn = { questId, npcId, questFlags } - end - QuestieLearner.OnQuestAccepted = function(self, questId, questGiver) - captured.questAccepted = { questId, questGiver } - end - QuestieLearner.OnCombatLogEvent = function(self, timestamp, eventType, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, spellId, spellName) - captured.combatLog = { timestamp, eventType, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, spellId, spellName } - end - QuestieLearner.OnGetItemInfoReceived = function(self, itemId) - captured.itemInfoReceived = { itemId } - end - QuestieLearner.ClearQuestObjectiveTracking = function(self, questId) - captured.questTrackingCleared = { questId } - end - - QuestieLearner:RegisterEvents() - - local frame = createdFrame - frame._onEvent(frame, "QUEST_TURNED_IN", 101, 202, 303) - frame._onEvent(frame, "QUEST_ACCEPTED", 404, 505) - frame._onEvent(frame, "COMBAT_LOG_EVENT_UNFILTERED", 1, "SPELL_DAMAGE", "src-guid", "Src", 2, "dst-guid", "Dst", 4, 777, "Fireball") - frame._onEvent(frame, "GET_ITEM_INFO_RECEIVED", 888) - frame._onEvent(frame, "QUEST_REMOVED", 999) - - assert.same({ 101, 202, 303 }, captured.questTurnedIn) - assert.same({ 404, 505 }, captured.questAccepted) - assert.same({ 1, "SPELL_DAMAGE", "src-guid", "Src", 2, "dst-guid", "Dst", 4, 777, "Fireball" }, captured.combatLog) - assert.same({ 888 }, captured.itemInfoReceived) - assert.same({ 999 }, captured.questTrackingCleared) - - QuestieLearner.OnQuestTurnedIn = originalOnQuestTurnedIn - QuestieLearner.OnQuestAccepted = originalOnQuestAccepted - QuestieLearner.OnCombatLogEvent = originalOnCombatLogEvent - QuestieLearner.OnGetItemInfoReceived = originalOnGetItemInfoReceived - QuestieLearner.ClearQuestObjectiveTracking = originalClearQuestObjectiveTracking - _G.CreateFrame = originalCreateFrame - _G.strsplit = originalStrsplit - end) -end) - -describe("QuestieLearnerComms queue draining", function() - local sentMessages - local processedMessages - local QuestieLearnerComms - local currentTime - - before_each(function() - dofile("Tests/wow_api_mock.lua") - - sentMessages = {} - processedMessages = {} - currentTime = 1000 - - local originalImportModule = QuestieLoader.ImportModule - QuestieLoader.ImportModule = function(self, name) - if name == "QuestieLearner" then - return _G.QuestieLearner - end - return originalImportModule(self, name) - end - QuestieLoader.CreateModule = function(self, name) - _G[name] = { private = {} } - return _G[name] - end - - _G.LibStub = function(name, silent) - if name == "AceComm-3.0" then - return { RegisterComm = function() end } - elseif name == "LibDeflate" then - return { - CompressDeflate = function(_, s) return s end, - EncodeForPrint = function(_, s) return s end, - DecodeForPrint = function(_, s) return s end, - DecompressDeflate = function(_, s) return s end, - } - elseif name == "AceSerializer-3.0" then - return { - Serialize = function(_, payload) - return payload.op .. ":" .. tostring(payload.id) - end, - Deserialize = function(_, serialized) - local op, id = string.match(serialized, "([^:]+):(.+)") - return true, { - _ver = 2, - op = op, - typ = "NPC", - id = tonumber(id) or id, - d = { [1] = "net" }, - } - end, - } - elseif name == "XXH_Lua_Lib" then - return nil - elseif name == "HereBeDragonsQuestie-2.0" then - return {} - end - return {} - end - - _G.QuestieLearner = { - HandleNetworkData = function(_, typ, id, d, op) - processedMessages[table.getn(processedMessages) + 1] = { - typ = typ, - id = id, - op = op, - } - end, - } - - _G.GetChannelName = function() return 1 end - _G.JoinPermanentChannel = function() end - _G.ChatFrame_RemoveChannel = function() end - _G.DEFAULT_CHAT_FRAME = { GetID = function() return 1 end } - _G.SendChatMessage = function(msg, mode, nilarg, channel) - sentMessages[table.getn(sentMessages) + 1] = msg - end - _G.InCombatLockdown = function() return false end - _G.random = function() return 0 end - _G.GetTime = function() - return currentTime - end - QuestieCompat.C_Timer.NewTicker = function(delay, fn) - return { Cancel = function() end } - end - - dofile("Modules/Network/QuestieLearnerComms.lua") - QuestieLearnerComms = _G.QuestieLearnerComms - QuestieLearnerComms:Initialize() - end) - - it("drains outgoing and incoming queues in FIFO order without front-removal", function() - QuestieLearnerComms:BroadcastLearnedData("NEW", "NPC", 101, { foo = "a" }) - QuestieLearnerComms:BroadcastLearnedData("UPDATE", "NPC", 102, { foo = "b" }) - - QuestieLearnerComms:OnCommReceived("QuestieLearner", "NEW:201", "CHANNEL", "Alice") - QuestieLearnerComms:OnCommReceived("QuestieLearner", "UPDATE:202", "CHANNEL", "Bob") - - QuestieLearnerComms.private:ProcessQueues() - currentTime = currentTime + 4 - QuestieLearnerComms.private:ProcessQueues() - currentTime = currentTime + 4 - QuestieLearnerComms.private:ProcessQueues() - - assert.equals(2, table.getn(sentMessages)) - assert.equals("NEW:101", sentMessages[1]) - assert.equals("UPDATE:102", sentMessages[2]) - assert.equals(2, table.getn(processedMessages)) - assert.equals(201, processedMessages[1].id) - assert.equals(202, processedMessages[2].id) - end) -end) - -describe("QuestieComms packet sizing", function() - local serializeCount - local sendCount - local QuestieComms - - before_each(function() - dofile("Tests/wow_api_mock.lua") - - serializeCount = 0 - sendCount = 0 - - local originalCreateModule = QuestieLoader.CreateModule - local originalImportModule = QuestieLoader.ImportModule - QuestieLoader.CreateModule = function(self, name) - _G[name] = { private = {} } - return _G[name] - end - QuestieLoader.ImportModule = function(self, name) - if name == "QuestieSerializer" then - return { - Serialize = function(_, payload) - serializeCount = serializeCount + 1 - return "packet:" .. tostring(payload.id or payload[1] or "x") - end, - Deserialize = function() return true, {} end, - } - end - return originalImportModule(self, name) - end - - _G.LibStub = function(name, silent) - if name == "AceComm-3.0" then - return { RegisterComm = function() end } - elseif name == "LibDeflate" then - return { - CompressDeflate = function(_, s) return s end, - EncodeForPrint = function(_, s) return s end, - DecodeForPrint = function(_, s) return s end, - DecompressDeflate = function(_, s) return s end, - } - elseif name == "AceSerializer-3.0" then - return { - Serialize = function(_, payload) return "packet:" .. tostring(payload.id or payload[1] or "x") end, - Deserialize = function() return true, {} end, - } - elseif name == "XXH_Lua_Lib" then - return nil - elseif name == "HereBeDragonsQuestie-2.0" then - return {} - end - return {} - end - - _G.QuestiePlayer.GetGroupType = function() return "party" end - _G.QuestiePlayer.numberOfGroupMembers = 5 - _G.QuestieDB.QuestPointers = { - [101] = true, - [102] = true, - [103] = true, - } - _G.QuestieDB.QueryQuestSingle = function() - return 0 - end - _G.QuestieDB.GetQuest = function(questId) - return { - Objectives = { - [1] = { Id = questId * 10 }, - }, - } - end - _G.QuestLogCache.questLog_DO_NOT_MODIFY = { - [101] = { questTag = "Normal" }, - [102] = { questTag = "Normal" }, - [103] = { questTag = "Normal" }, - } - _G.QuestLogCache.GetQuestObjectives = function() - return { - { type = "monster", numFulfilled = 0, numRequired = 1 }, - } - end - _G.ZoneDB.GetUiMapIdByAreaId = function() - return 1 - end - _G.HBD = { - GetZoneDistance = function() return 1 end, - GetPlayerZone = function() return 1 end, - } - _G.GetChannelName = function() return 1 end - _G.SendChatMessage = function() end - _G.Questie.SendCommMessage = function() - sendCount = sendCount + 1 - end - _G.UnitInBattleground = function() return false end - _G.random = function() return 0 end - _G.GetTime = function() return 0 end - _G.tinsert = table.insert - QuestieCompat.C_Timer.After = function(_, fn) end - QuestieCompat.C_Timer.NewTicker = function(_, fn) - return { Cancel = function() end } - end - - QuestieComms = dofile("Modules/Network/QuestieComms.lua") - QuestieComms.private.CreatePacket = function() - return { - data = {}, - write = function() end, - } - end - - serializeCount = 0 - end) - - it("serializes each quest once while packing broadcast blocks", function() - QuestieComms.private:BroadcastQuestLog("QC_ID_BROADCAST_FULL_QUESTLIST", "WHISPER", "Tester") - assert.equals(3, serializeCount) - - serializeCount = 0 - QuestieComms.private:BroadcastQuestLogV2("QC_ID_BROADCAST_FULL_QUESTLIST", "WHISPER", "Tester") - assert.equals(3, serializeCount) - end) - - it("suppresses outgoing QuestieComms immediately when disabled", function() - Questie.db.profile.questieCommsEnabled = false - - local packet = QuestieComms.private:CreatePacket(QuestieComms.private.QC_ID_BROADCAST_QUEST_REMOVE) - packet.data.writeMode = QuestieComms.private.QC_WRITE_ALLGROUP - packet.data.priority = "NORMAL" - packet.data.id = 101 - packet:write() - - assert.equals(0, sendCount) - end) -end) diff --git a/Tests/QuestieQuestTooltipFallback_spec.lua b/Tests/QuestieQuestTooltipFallback_spec.lua deleted file mode 100644 index 13901ec..0000000 --- a/Tests/QuestieQuestTooltipFallback_spec.lua +++ /dev/null @@ -1,23 +0,0 @@ -describe("QuestieQuest tooltip fallback", function() - local function read(path) - local f = assert(io.open(path, "r"), "cannot open " .. path) - local c = f:read("*a") - f:close() - return c - end - - local function has(content, needle) - return string.find(content, needle, 1, true) ~= nil - end - - it("registers direct objective tooltips when special objectives have ids but no spawnList", function() - local questieQuest = read("Modules/Quest/QuestieQuest.lua") - - assert.is_true(has(questieQuest, 'tooltipKey = "m_" .. objective.Id')) - assert.is_true(has(questieQuest, 'tooltipKey = "o_" .. objective.Id')) - assert.is_true(has(questieQuest, 'tooltipKey = "i_" .. objective.Id')) - assert.is_true(has(questieQuest, 'elseif objective.Type == "killcredit" then')) - assert.is_true(has(questieQuest, 'QuestieTooltips:RegisterObjectiveTooltip(questId, "m_" .. id, objective)')) - assert.is_true(has(questieQuest, "objective.registeredItemTooltips = true")) - end) -end) diff --git a/Tests/QuestieTooltip_precedence_spec.lua b/Tests/QuestieTooltip_precedence_spec.lua deleted file mode 100644 index f442cdb..0000000 --- a/Tests/QuestieTooltip_precedence_spec.lua +++ /dev/null @@ -1,66 +0,0 @@ -describe("Questie tooltip precedence", function() - local function contains(lines, needle) - for _, line in ipairs(lines or {}) do - if string.find(line, needle, 1, true) then - return true - end - end - return false - end - - before_each(function() - dofile("Tests/wow_api_mock.lua") - - local originalImportModule = QuestieLoader.ImportModule - QuestieLoader.ImportModule = function(self, name) - if name == "QuestieLib" then - return { - GetColoredQuestName = function(_, questId) - return "Quest " .. tostring(questId) - end, - Colorize = function(_, text) - return text - end, - } - end - return originalImportModule(self, name) - end - - Questie.db.profile.showQuestsInNpcTooltip = true - Questie.db.profile.enableTooltipsQuestLevel = false - Questie.db.profile.enableTooltipsNPCID = false - QuestiePlayer.numberOfGroupMembers = 0 - QuestieCompat.IsInGroup = function() return false end - QuestieCompat.UnitInParty = function() return false end - - QuestieDB.GetQuest = function(_, questId) - if questId ~= 8334 then - return nil - end - - return { - ObjectiveData = { - [1] = { Type = "monster", Id = 15271, Text = "Tender slain" }, - [2] = { Type = "monster", Id = 15294, Text = "Feral Tender slain" }, - }, - } - end - - dofile("Modules/Tooltips/Tooltip.lua") - QuestieTooltips.lookupByKey["m_15271"] = { - ["8334 Tender 15271"] = { - questId = 8334, - name = "Tender", - starterId = 15271, - }, - } - end) - - it("adds AscensionDB objective text under quest titles when no objective tooltip is registered", function() - local lines = QuestieTooltips:GetTooltip("m_15271") - - assert.is_true(contains(lines, "Quest 8334")) - assert.is_true(contains(lines, "Tender slain")) - assert.is_true(contains(lines, "Feral Tender slain")) - end) -end)