You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

481 lines
14 KiB

local Util = require("Util");
local TestUtil = require("TestUtil");
-- Test doubles and tests.
g_test_settings = {
wbt_print = false; -- If WBT:Print(...) should be print.
}
--------------------------------------------------------------------------------
-- Blizzard-like event framework
--------------------------------------------------------------------------------
local EventManager = {};
EventManager.frames = {};
function EventManager:Reset()
self.frames = {};
end
function EventManager:FireEvent(event, ...)
for _, frame in pairs(self.frames) do
frame:HandleEvent(event, ...);
end
end
function EventManager:RegisterFrame(f)
if f.name then
self.frames[f.name] = f;
else
table.insert(self.frames, f);
end
end
local Frame = {};
function Frame:New(name)
local f = {};
f.name = name; -- Can be nil.
f.events = {};
f.event_handler = nil;
setmetatable(f, self);
self.__index = self; -- XXX: Should be f.__index = self ?
return f;
end
function CreateFrame(_, name)
local f = Frame:New(name);
EventManager:RegisterFrame(f);
return f;
end
function Frame:RegisterEvent(event)
self.events[event] = true;
end
function Frame:UnregisterEvent(event)
self.events[event] = nil;
end
function Frame:SetScript(triggerKind, eventHandler)
self.trigger_kind = triggerKind;
self.event_handler = eventHandler;
end
function Frame:HandleEvent(event, ...)
if self.trigger_kind ~= "OnEvent" then
-- print("Fake Frame: Only 'OnEvent' supported. My trigger kind: ", self.trigger_kind);
return;
end
if self.events[event] then
-- print(self.name)
self:event_handler(event, ...);
return;
end
end
--------------------------------------------------------------------------------
-- Ace, LibStub and other 3rd party:
--------------------------------------------------------------------------------
local AceAddon = {};
function AceAddon:New()
local o = {};
setmetatable(o, self);
self.__index = self;
return o;
end
function AceAddon:RegisterChatCommand(...)
return;
end
function AceAddon:Print(text)
if g_test_settings.wbt_print then
print("WBT:", text);
end
end
function LibStub(addonName)
local ls = {};
function ls:NewAddon(addonName, aceModule)
return AceAddon:New();
end
function ls:New(dbName, defaultDb)
return defaultDb;
end
function ls:AddToBlizOptions(...)
return;
end
function ls:RegisterOptionsTable(addonName, optionsTable, ...)
return;
end
function ls:Embed(comClass)
function RegisterComm(...)
return;
end
comClass.RegisterComm = RegisterComm;
end
return ls;
end
--------------------------------------------------------------------------------
-- WBT test doubles
--------------------------------------------------------------------------------
local GUI = {};
function GUI:New()
local o = {};
setmetatable(o, self);
self.__index = self;
return o;
end
function GUI.SetupAceGUI() return; end
function GUI.Init() return; end
function GUI:Update() return; end
function GUI:UpdateWindowTitle() return; end
function GUI:Rebuild() return; end
--------------------------------------------------------------------------------
-- WoW API
--------------------------------------------------------------------------------
-- Fake "world" object. Set fields as necessary before testing.
local g_game = {
servertime = 1677434396,
world = {
shard_id = 44
},
player = {
-- Static
name = "Chainorth",
realmname = "Stormreaver",
connected_realms = {"Stormreaver", "Vashj"},
-- Dynamic
map_id = 507, -- Isle of Giants
coords = { -- Oondasta spawn point.
x = 0.50,
y = 0.56
},
}
}
function GetUnitName(unit)
if unit == "player" then
return "Playerone"
end
return "Notplayer";
end
function UnitExists(unit)
return unit == "mouseover";
end
function UnitGUID(unit)
if unit == "mouseover" then
return "Creature-field2-field3-field4-" .. tostring(g_game.world.shard_id)
end
error("NYI")
end
function GetRealmName()
return g_game.player.realm_name;
end
function GetAutoCompleteRealms()
return g_game.player.connected_realms;
end
-- XXX: Not correct, but doesn't matter right now.
function GetNormalizedRealmName()
return g_game.player.realm_name;
end
function GetServerTime()
return g_game.servertime;
end
local Coord = {};
function Coord:GetXY()
return g_game.player.coords.x,
g_game.player.coords.y;
end
C_Map = {};
function C_Map.GetPlayerMapPosition(mapId, unitname)
return Coord;
end
function C_Map.GetBestMapForUnit(_)
return g_game.player.map_id;
end
C_Timer = {};
function C_Timer.After(_, fcn)
fcn();
end
function PlaySoundFile(_, _) return; end
function FlashClientIcon() return; end
function RequestRaidInfo() return; end
-- Change this fields before firing an event that uses combat event info.
local CombatEventInfo = {
eventType = "",
dest_unit_guid = "";
}
function CombatLogGetCurrentEventInfo()
local _ = nil;
return _, CombatEventInfo.eventType, _, _, _, _, _, CombatEventInfo.dest_unit_guid, _;
end
--------------------------------------------------------------------------------
-- WoW lua API
--------------------------------------------------------------------------------
function strsplit(sep, str)
local t={}
for match in string.gmatch(str, "([^"..sep.."]+)") do
table.insert(t, match)
end
return table.unpack(t)
end
--------------------------------------------------------------------------------
-- Blizzard-like addon loader for WBT
--------------------------------------------------------------------------------
-- Returns an instance of WBT, and tries to perform loading in the same way
-- as Blizzard (probably) does it. (An option would be to change how module
-- imports are done, and instead import with 'require' instead of file varargs.)
local function LoadWBT()
local addonName = "addonname_unused";
local addonTable = {};
-- Load in same order as in .toc file:
assert(loadfile("Util.lua")) (addonName, addonTable);
assert(loadfile("Sound.lua")) (addonName, addonTable);
assert(loadfile("BossData.lua")) (addonName, addonTable);
assert(loadfile("Com.lua")) (addonName, addonTable);
assert(loadfile("NameplateTracker.lua"))(addonName, addonTable);
addonTable.GUI = GUI; -- GUI is a fake defined in this file.
assert(loadfile("Options.lua")) (addonName, addonTable);
assert(loadfile("KillInfo.lua")) (addonName, addonTable);
local WBT = assert(loadfile("WorldBossTimers.lua"))(addonName, addonTable);
return WBT;
end
--------------------------------------------------------------------------------
-- Tests
--------------------------------------------------------------------------------
local WBT; -- Convenient to put it in this scope, in order to write helper fcns.
function TestStrSplit()
local a, b, c = strsplit("-", "a-b1-c22");
assert(a == "a", a);
assert(b == "b1", b);
assert(c == "c22", c);
end
local function BuildGUID(boss_name)
local npc_id = tostring(WBT.BossData.Get(boss_name).ids[1]);
local shard_id = tostring(g_game.world.shard_id);
return "Creature-2-3-4-"..shard_id.."-"..npc_id;
end
local function FireDetectShard()
EventManager:FireEvent("UPDATE_MOUSEOVER_UNIT", "mouseover");
end
local function FireRestartShardDetection()
EventManager:FireEvent("ZONE_CHANGED_NEW_AREA");
end
local function FireLocalBossDeath()
CombatEventInfo.eventType = "UNIT_DIED";
CombatEventInfo.dest_unit_guid = BuildGUID(WBT.BossesInCurrentZone()[1].name);
EventManager:FireEvent("COMBAT_LOG_EVENT_UNFILTERED");
end
local function TestSharingWithoutShardId()
g_test_settings.wbt_print = false;
local bossname = "Oondasta";
EventManager:Reset();
WBT = LoadWBT();
WBT.AceAddon:OnEnable();
-- Detect current shard:
local test_shard_id = 44;
g_game.world.shard_id = test_shard_id;
FireDetectShard();
-- Get shared a timer without shard_id
local event = "CHAT_MSG_SAY";
local msg = TestUtil.CreateShareMsg(bossname, g_game.servertime, 9, nil);
local sender = "Shareson";
EventManager:FireEvent(event, msg, sender);
local ki = WBT.GetPrimaryKillInfo();
assert(ki:HasUnknownShard(), ki.shard_id)
-- Get new KillInfo with shard ID. The new KillInfo should now be prioritized.
-- Regardless of if the player's current shard id is known or not.
local event = "CHAT_MSG_SAY";
local msg = TestUtil.CreateShareMsg(bossname, g_game.servertime, 8, test_shard_id);
local sender = "Sharesontwo";
EventManager:FireEvent(event, msg, sender);
local ki = WBT.GetPrimaryKillInfo();
assert(ki.shard_id == 44, ki.shard_id)
end
local function TestShare(bossname, expectSuccess)
EventManager:Reset();
WBT = LoadWBT();
WBT.AceAddon:OnEnable();
-- Detect current shard:
g_game.world.shard_id = 44;
FireDetectShard();
-- Assert no timer:
assert(Util.TableIsEmpty(WBT.db.global.kill_infos), "Incorrect setup.");
-- Share the timer:
local event = "CHAT_MSG_SAY";
local msg = TestUtil.CreateShareMsg(bossname, g_game.servertime, 9, g_game.world.shard_id);
local sender = "Shareson";
EventManager:FireEvent(event, msg, sender);
-- Assert parsed:
local nTimers = Util.TableLength(WBT.db.global.kill_infos);
local nTimersExp = expectSuccess and 1 or 0;
assert(nTimers == nTimersExp, "Incorrect number of timers: " .. nTimers);
end
local function TestSavedShard()
EventManager:Reset();
WBT = LoadWBT();
local KillInfo = WBT.KillInfo;
WBT.AceAddon:OnEnable();
-- Initially saved shard should be unknown:
assert(WBT.GetCurrentShardID() == KillInfo.UNKNOWN_SHARD);
assert(WBT.GetSavedShardID(WBT.GetCurrentMapId()) == KillInfo.UNKNOWN_SHARD);
-- Detect initial shard:
g_game.world.shard_id = 44;
FireDetectShard();
assert(WBT.GetCurrentShardID() == g_game.world.shard_id);
assert(WBT.GetSavedShardID(WBT.GetCurrentMapId()) == g_game.world.shard_id);
-- Reset current shard:
FireRestartShardDetection();
assert(WBT.GetCurrentShardID() == KillInfo.UNKNOWN_SHARD);
assert(WBT.GetSavedShardID(WBT.GetCurrentMapId()) == g_game.world.shard_id);
-- Find same shard again:
FireDetectShard();
assert(WBT.GetCurrentShardID() == g_game.world.shard_id);
assert(WBT.GetSavedShardID(WBT.GetCurrentMapId()) == g_game.world.shard_id);
-- Reset again:
FireRestartShardDetection();
assert(WBT.GetCurrentShardID() == KillInfo.UNKNOWN_SHARD);
assert(WBT.GetSavedShardID(WBT.GetCurrentMapId()) == g_game.world.shard_id);
-- Find new shard:
g_game.world.shard_id = 55;
FireDetectShard();
assert(WBT.GetCurrentShardID() == g_game.world.shard_id);
assert(WBT.GetSavedShardID(WBT.GetCurrentMapId()) == g_game.world.shard_id);
-- Reset once again:
FireRestartShardDetection();
assert(WBT.GetCurrentShardID() == KillInfo.UNKNOWN_SHARD);
assert(WBT.GetSavedShardID(WBT.GetCurrentMapId()) == g_game.world.shard_id);
end
local function TestSavedShardKillInfo()
EventManager:Reset();
WBT = LoadWBT();
local Options = WBT.Options;
WBT.AceAddon:OnEnable();
local ki;
-- Detect shard and kill boss:
g_game.world.shard_id = 44;
FireDetectShard();
FireLocalBossDeath();
Options.assume_realm_keeps_shard.set(true);
ki = WBT.GetPrimaryKillInfo();
assert(ki);
Options.assume_realm_keeps_shard.set(false);
ki = WBT.GetPrimaryKillInfo();
assert(ki);
-- Reset current shard:
FireRestartShardDetection();
Options.assume_realm_keeps_shard.set(true);
ki = WBT.GetPrimaryKillInfo();
assert(ki);
Options.assume_realm_keeps_shard.set(false);
ki = WBT.GetPrimaryKillInfo();
assert(ki == nil); -- Now nil
-- Find same shard again:
FireDetectShard();
Options.assume_realm_keeps_shard.set(true);
ki = WBT.GetPrimaryKillInfo();
assert(ki);
Options.assume_realm_keeps_shard.set(false);
ki = WBT.GetPrimaryKillInfo();
assert(ki); -- Found again
-- Find new shard:
g_game.world.shard_id = 55;
FireRestartShardDetection();
FireDetectShard();
Options.assume_realm_keeps_shard.set(true);
ki = WBT.GetPrimaryKillInfo();
assert(ki == nil); -- Now also nil!
Options.assume_realm_keeps_shard.set(false);
ki = WBT.GetPrimaryKillInfo();
assert(ki == nil); -- Nil again
-- Reset the new shard:
FireRestartShardDetection();
Options.assume_realm_keeps_shard.set(true);
ki = WBT.GetPrimaryKillInfo();
assert(ki == nil); -- Still nil
Options.assume_realm_keeps_shard.set(false);
ki = WBT.GetPrimaryKillInfo();
assert(ki == nil); -- Still nil
-- Find old shard again:
g_game.world.shard_id = 44;
FireRestartShardDetection();
FireDetectShard();
Options.assume_realm_keeps_shard.set(true);
ki = WBT.GetPrimaryKillInfo();
assert(ki); -- Found again
Options.assume_realm_keeps_shard.set(false);
ki = WBT.GetPrimaryKillInfo();
assert(ki); -- Found again
end
local function main()
TestStrSplit();
TestSharingWithoutShardId();
TestShare("Oondasta", true);
TestShare("Sha of Anger", true);
TestShare("A. Harvester", true);
TestShare("NotBoss", false);
TestShare("Sha of Rage", false);
TestSavedShard();
TestSavedShardKillInfo();
end
main()