387 lines
20 KiB
Lua
387 lines
20 KiB
Lua
-- 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)
|