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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions spec/System/TestGemSocketQuality_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ describe("TestGemSocketQuality", function()
end
end

it("requires a physical socket for Dialla's 'always matches' mod", function()
equipBody("R", "Gems Socketed always have the Quality bonus from Socket Colour\n")
build.skillsTab:PasteSocketGroup("Slot: Body Armour\nFireball 20/0 1\nFireball 20/0 1\n")
runCallback("OnFrame")

local group = groupForSlot("Body Armour")
assert.is_true(group.gemList[1].matchesSocket)
assert.is_false(group.gemList[2].matchesSocket)
group.mainActiveSkill = 2
build.buildFlag = true
runCallback("OnFrame")
assert.are.equals(0, build.calcsTab.mainOutput.GemQuality)
end)

it("grants +10% quality to a gem in a matching colour socket", function()
equipBody("B-B-B")
build.skillsTab:PasteSocketGroup("Slot: Body Armour\nFireball 20/0 1\n")
Expand Down
84 changes: 84 additions & 0 deletions spec/System/TestImport_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,90 @@ describe("TestImport", function()
newBuild()
end)

describe("account-name overwrite import", function()
local downloadPage, requests, imports, tab
local realm = { hostName = "https://example.invalid/", realmCode = "pc" }

before_each(function()
tab = build.importTab
tab.controls.siteAccountName:SetText("First#0001")
tab.controls.siteCharSelect.list = { { char = { name = "FirstCharacter", league = "Standard" } } }
tab.controls.siteCharSelect.selIndex = 1
tab.lastLeague = "Standard"
requests, imports = { }, { }
downloadPage = launch.DownloadPage
launch.DownloadPage = function(_, url, callback)
table.insert(requests, { url = url, callback = callback })
end
tab.ImportItemsAndSkills = function(_, character)
table.insert(imports, { "items", character.name })
end
tab.ImportPassiveTreeAndJewels = function(_, character)
table.insert(imports, { "tree", character.name })
end
end)

after_each(function()
launch.DownloadPage = downloadPage
end)

it("keeps both requests tied to the original account and character", function()
tab:DownloadItems(realm, true)
tab.controls.siteAccountName:SetText("Second#0002")
tab.controls.siteCharSelect.list = { { char = { name = "SecondCharacter", league = "Standard" } } }
requests[1].callback({ body = '{"items":[]}' })
assert.matches("accountName=First%%230001&character=FirstCharacter&realm=pc", requests[2].url)
assert.same({ }, imports)
requests[2].callback({ body = '{"items":[]}' })
assert.same({ { "items", "FirstCharacter" }, { "tree", "FirstCharacter" } }, imports)
end)

for _, phase in ipairs({ "items", "tree" }) do
it("ignores a cancelled " .. phase .. " response after reopening import", function()
tab:DownloadItems(realm, true)
if phase == "tree" then
requests[1].callback({ body = '{"items":[]}' })
end
local pending = requests[#requests]
tab.controls.siteCharClose.onClick()
tab.charImportMode = "SELECTCHAR"
tab.charImportStatus = "New character selection"
pending.callback({ body = '{"items":[]}' })
assert.same({ }, imports)
assert.equal("New character selection", tab.charImportStatus)
assert.equal(pending, requests[#requests])
end)
end

it("ignores an older request without interrupting a newer import", function()
tab:DownloadItems(realm, true)
tab:DownloadItems(realm, true)
requests[1].callback({ body = '{"items":[]}' })
assert.equal(2, #requests)
assert.equal("IMPORTING", tab.charImportMode)
requests[2].callback({ body = '{"items":[]}' })
requests[3].callback({ body = '{"items":[]}' })
assert.same({ { "items", "FirstCharacter" }, { "tree", "FirstCharacter" } }, imports)
end)

it("leaves the build untouched if the second download fails", function()
tab:DownloadItems(realm, true)
requests[1].callback({ body = '{"items":[]}' })
requests[2].callback(nil, "Download failed")
assert.same({ }, imports)
assert.equal("SELECTCHAR", tab.charImportMode)
assert.matches("Download failed", tab.charImportStatus)
end)

it("ignores responses after switching to another build", function()
tab:DownloadItems(realm, true)
newBuild()
requests[1].callback({ body = '{"items":[]}' })
assert.same({ }, imports)
assert.equal(1, #requests)
end)
end)

it("imports with correct tree", function()
build.importTab:ImportPassiveTreeAndJewels(sampleData, true)
runCallback("OnFrame")
Expand Down
46 changes: 46 additions & 0 deletions spec/System/TestItemListControl_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ describe("ItemListControl", function()
PopulateSlots = function() end,
AddUndoState = function() end,
}
for id, item in pairs(itemsTab.items) do
item.name = item.type .. " " .. id
item.GetPrimarySlot = function(self) return self.type end
end
local control = new("ItemListControl"):ItemListControl(nil, { 0, 0, 360, 308 }, itemsTab, true)
return control, itemsTab, treeTab
end
Expand All @@ -66,6 +70,48 @@ describe("ItemListControl", function()
GetCursorPos = originalGetCursorPos
end)

it("sorts and filters without overwriting custom order or using sorted drop positions", function()
local control, itemsTab = newItemListControl()
itemsTab.items[1].name = "Z Armour"
control.controls.sortMode:SelByValue("Sort by Name")
control:UpdateList()
assert.are.same({ 2, 3, 4, 1 }, control.list)
assert.is_false(control.isMutable)
control.controls.search.buf = "armour"
control:UpdateList()
assert.are.same({ 2, 1 }, control.list)
assert.are.same({ 1, 2, 3, 4 }, itemsTab.itemOrderList)

itemsTab.AddItem = function(self, item, _, index)
assert.is_nil(index)
item.id = 5
self.items[item.id] = item
table.insert(self.itemOrderList, item.id)
end
itemsTab.AddForbiddenJewelCounterpart = function() end
control.selDragIndex = 2
control:ReceiveDrag("Item", { raw = "Rarity: Normal\nPlate Vest" })
control.controls.search.buf = ""
control.controls.sortMode:SelByValue("Custom Order")
control:UpdateList()
assert.are.same({ 1, 2, 3, 4, 5 }, control.list)
assert.equal(itemsTab.itemOrderList, control.list)
end)

it("skips non-item group headers during selection and keyboard navigation", function()
local control = newItemListControl()
control.controls.sortMode:SelByValue("Sort by Loadout")
control:UpdateList()
assert.is_false(control:SelectIndex(1))
assert.is_nil(control.selValue)
control:OnKeyDown("HOME")
assert.equal(1, control.selValue)
control:OnKeyDown("DOWN")
assert.equal(3, control.selValue)
control:OnKeyDown("DOWN")
assert.equal(2, control.selValue)
end)

it("only shows items from the active item set and passive tree", function()
local control = newItemListControl()
control:UpdateLoadoutList()
Expand Down
127 changes: 127 additions & 0 deletions spec/System/TestItemMods_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,133 @@ describe("TetsItemMods", function()
-- newBuild() takes care of resetting everything in setup()
end)

it("keeps catalyst edits separate from ordinary quality through serialization", function()
build.itemsTab:CreateDisplayItemFromRaw([[Rarity: Rare
Test Ring
Amethyst Ring
Crafted: true
Prefix: None
Prefix: None
Prefix: None
Suffix: None
Suffix: None
Suffix: None
Quality: 12
Implicits: 0]])
local item = build.itemsTab.displayItem
local controls = build.itemsTab.controls
controls.displayItemCatalyst:SetSel(2)
controls.displayItemQualityEdit:SetText("17", true)
controls.displayItemCatalyst:SetSel(3)
local restored = new("Item"):Item(item:BuildRaw())
assert.are.equal(2, restored.catalyst)
assert.are.equal(17, restored.catalystQuality)
assert.are.equal(12, restored.quality)
controls.displayItemCatalyst:SetSel(1)
restored = new("Item"):Item(item:BuildRaw())
assert.is_nil(restored.catalystQuality)
assert.are.equal(12, restored.quality)
controls.displayItemQualityEdit:SetText("17", true)
controls.displayItemCatalyst:SetSel(2)
assert.are.equal(17, item.catalystQuality)
end)

it("reapplies bulk socket edits after individual overrides and serializes both", function()
build.itemsTab:CreateDisplayItemFromRaw("Rarity: Normal\nPlate Vest\nSockets: R-G B")
local controls = build.itemsTab.controls
local item = build.itemsTab.displayItem
controls.displayItemSetColors:SetSel(2)
controls.displayItemSocket2:SetSel(2)
local restored = new("Item"):Item(item:BuildRaw())
assert.are.equal("W", restored.sockets[1].color)
assert.are.equal("G", restored.sockets[2].color)
assert.are.equal(restored.sockets[1].group, restored.sockets[2].group)
assert.are_not.equal(restored.sockets[2].group, restored.sockets[3].group)
controls.displayItemSetColors:SetSel(2)
controls.displayItemSetLinks:SetSel(4)
controls.displayItemLink2.changeFunc(false)
assert.are_not.equal(item.sockets[2].group, item.sockets[3].group)
controls.displayItemSetLinks:SetSel(4)
restored = new("Item"):Item(item:BuildRaw())
assert.are.equal(3, #restored.sockets)
for _, socket in ipairs(restored.sockets) do
assert.are.equal("W", socket.color)
assert.are.equal(restored.sockets[1].group, socket.group)
end
end)

describe("Crucible modifier source", function()
local popupCount

before_each(function()
popupCount = #main.popups
end)

after_each(function()
while #main.popups > popupCount do
main:ClosePopup()
end
end)

local function selectSource(controls, sourceId)
for index, source in ipairs(controls.source.list) do
if source.sourceId == sourceId then
controls.source:SetSel(index)
return
end
end
error("Missing modifier source: " .. sourceId)
end

it("switches sources and round-trips legacy nodes without replacing explicit modifiers", function()
local itemsTab = build.itemsTab
itemsTab:CreateDisplayItemFromRaw("Rarity: Rare\nTest Axe\nRusted Hatchet\nImplicits: 0\n+10 to Strength")
itemsTab:AddCustomModifierToDisplayItem()
local popup = main.popups[1]
local controls = popup.controls
local originalSource = controls.source:GetSelValue().sourceId
selectSource(controls, "CRUCIBLE")
local node = controls.modSelectNode1
local selectedMod
for index, entry in ipairs(node.list) do
if entry ~= "None" and #entry.mod == 2 then
node:SetSel(index)
selectedMod = entry
break
end
end
assert.is_not_nil(selectedMod)
selectSource(controls, originalSource)
selectSource(controls, "CRUCIBLE")
assert.are.equals(selectedMod, node:GetSelValue())
controls.save:Click()
assert.are.equals("+10 to Strength", itemsTab.displayItem.explicitModLines[1].line)
assert.are.equals(2, #itemsTab.displayItem.crucibleModLines)

itemsTab:CreateDisplayItemFromRaw(itemsTab.displayItem:BuildRaw())
itemsTab:AddCustomModifierToDisplayItem()
controls = main.popups[1].controls
selectSource(controls, "CRUCIBLE")
assert.are.equals(selectedMod.defaultOrder, controls.modSelectNode1:GetSelValue().defaultOrder)
controls.modSelectNode1:SetSel(1)
selectSource(controls, "CUSTOM")
controls.custom:SetText("+20 to maximum Life")
controls.save:Click()
assert.are.equals(2, #itemsTab.displayItem.crucibleModLines)
assert.are.equals("+20 to maximum Life", itemsTab.displayItem.explicitModLines[2].line)

itemsTab:AddCustomModifierToDisplayItem()
controls = main.popups[1].controls
selectSource(controls, "CRUCIBLE")
for i = 1, 5 do
controls["modSelectNode" .. i]:SetSel(1)
end
controls.save:Click()
assert.are.equals(0, #itemsTab.displayItem.crucibleModLines)
assert.are.equals(2, #itemsTab.displayItem.explicitModLines)
end)
end)

it("shows versioned reusable variant groups", function()
build.itemsTab:CreateDisplayItemFromRaw([[
Rarity: Unique
Expand Down
Loading
Loading