Skip to content
Draft
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
99 changes: 99 additions & 0 deletions spec/System/TestSkills_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,106 @@ describe("TestSkills", function()
assert.are.equals(minionId, build.controls.mainSkillMinion.list[1].minionId)
end)

it("does not crash when minion activeSkillList is missing", function()
local srcInstance = { statSet = { }, skillPart = { }, nameSpec = "Minion: Test" }
local activeEffect = {
srcInstance = srcInstance,
grantedEffect = {
id = "TestMinionSkill",
name = "Minion: Test",
statSets = { { label = "Default" } },
},
statSet = { skillFlags = { } },
}
local activeSkill = {
activeEffect = activeEffect,
skillData = { },
minion = {
-- activeSkillList is absent, reproducing the crash fix in #2243
}
}
build.skillsTab.socketGroupList[1] = {
displaySkillList = { activeSkill },
mainActiveSkill = 1,
}

assert.has_no.errors(function()
build:RefreshSkillSelectControls(build.controls, 1, "")
end)
end)

it("does not crash when minion activeSkillList is an empty table", function()
local srcInstance = { statSet = { }, skillPart = { }, nameSpec = "Minion: Test" }
local activeEffect = {
srcInstance = srcInstance,
grantedEffect = {
id = "TestMinionSkill",
name = "Minion: Test",
statSets = { { label = "Default" } },
},
statSet = { skillFlags = { } },
}
local activeSkill = {
activeEffect = activeEffect,
skillData = { },
minion = {
activeSkillList = { } -- empty list, guard must check [1] as well
}
}
build.skillsTab.socketGroupList[1] = {
displaySkillList = { activeSkill },
mainActiveSkill = 1,
}

assert.has_no.errors(function()
build:RefreshSkillSelectControls(build.controls, 1, "")
end)

-- Minion skill dropdown should remain hidden when there are no skills
assert.is_false(build.controls.mainSkillMinionSkill.shown)
end)

it("populates minion skill list and UI controls after full OnFrame cycle", function()
-- End-to-end test: exercises the complete pipeline including
-- calcs.buildOutput, calcs.perform, createMinionSkills, and
-- RefreshSkillSelectControls in a single OnFrame frame.
build.skillsTab:PasteSocketGroup("Skeletal Sniper 20/0 1")

assert.has_no.errors(function()
runCallback("OnFrame")
end)

-- Verify the calculation engine populated the minion correctly
local minion = build.calcsTab.mainEnv.minion
assert.is_not_nil(minion, "minion should be created by the calc engine")
assert.is_not_nil(minion.activeSkillList, "activeSkillList should be populated by createMinionSkills")
assert.is_true(#minion.activeSkillList > 0, "minion should have at least one skill")
assert.is_not_nil(minion.mainSkill, "mainSkill should be selected from activeSkillList")

-- Verify the UI controls were populated by RefreshSkillSelectControls
assert.is_true(build.controls.mainSkillMinion.shown, "minion dropdown should be visible")
assert.is_true(build.controls.mainSkillMinionSkill.shown, "minion skill dropdown should be visible")
assert.is_true(#build.controls.mainSkillMinionSkill.list > 0, "minion skill dropdown should have entries")
end)

it("does not crash rendering socket tooltip when minion skill selection is missing", function()
build.skillsTab:PasteSocketGroup("Skeletal Sniper 20/0 1")
runCallback("OnFrame")

local socketGroup = build.skillsTab.socketGroupList[1]
socketGroup.displaySkillList[1].minion.mainSkill = nil

local tooltip = {
AddLine = function() end,
AddSeparator = function() end,
}
assert.has_no.errors(function()
build.skillsTab:AddSocketGroupTooltip(tooltip, socketGroup)
end)
end)

it("applies minion skill stat set selections to the selected minion skill only", function()

build.skillsTab:PasteSocketGroup("Skeletal Sniper 20/0 1")
runCallback("OnFrame")

Expand Down
3 changes: 1 addition & 2 deletions src/Classes/SkillsTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1329,7 +1329,7 @@ function SkillsTabClass:AddSocketGroupTooltip(tooltip, socketGroup)
gemShown[skillEffect.srcInstance] = true
end
end
if activeSkill.minion then
if activeSkill.minion and activeSkill.minion.mainSkill then
tooltip:AddSeparator(10)
tooltip:AddLine(16, "^7Active Skill #" .. index .. "'s Main Minion Skill:")
local activeEffect = activeSkill.minion.mainSkill.effectList[1]
Expand Down Expand Up @@ -1557,4 +1557,3 @@ function SkillsTabClass:UpdateGlobalGemCountAssignments()
end
GlobalGemAssignments["GemGroupCount"] = countSocketGroups
end

28 changes: 15 additions & 13 deletions src/Modules/Build.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2041,20 +2041,22 @@ function buildMode:RefreshSkillSelectControls(controls, mainGroup, suffix)
controls.mainSkillMinion.shown = true
wipeTable(controls.mainSkillMinionSkill.list)
if activeSkill.minion then
for _, minionSkill in ipairs(activeSkill.minion.activeSkillList) do
t_insert(controls.mainSkillMinionSkill.list, minionSkill.activeEffect.grantedEffect.name)
end
controls.mainSkillMinionSkill.selIndex = activeEffect.srcInstance["skillMinionSkill"..suffix] or 1
controls.mainSkillMinionSkill.shown = true
controls.mainSkillMinionSkill.enabled = #controls.mainSkillMinionSkill.list > 1
wipeTable(controls.mainSkillMinionSkillStatSet.list)
for _, statSet in ipairs(activeSkill.minion.activeSkillList[controls.mainSkillMinionSkill.selIndex].activeEffect.grantedEffect.statSets) do
t_insert(controls.mainSkillMinionSkillStatSet.list, {label = statSet.label, grantedEffectId = activeEffect.grantedEffect.id})
if activeSkill.minion.activeSkillList and activeSkill.minion.activeSkillList[1] then
for _, minionSkill in ipairs(activeSkill.minion.activeSkillList) do
t_insert(controls.mainSkillMinionSkill.list, minionSkill.activeEffect.grantedEffect.name)
end
controls.mainSkillMinionSkill.selIndex = activeEffect.srcInstance["skillMinionSkill"..suffix] or 1
controls.mainSkillMinionSkill.shown = true
controls.mainSkillMinionSkill.enabled = #controls.mainSkillMinionSkill.list > 1
wipeTable(controls.mainSkillMinionSkillStatSet.list)
for _, statSet in ipairs(activeSkill.minion.activeSkillList[controls.mainSkillMinionSkill.selIndex].activeEffect.grantedEffect.statSets) do
t_insert(controls.mainSkillMinionSkillStatSet.list, {label = statSet.label, grantedEffectId = activeEffect.grantedEffect.id})
end
local minionStatSetIndexLookup = activeEffect.srcInstance["skillMinionSkillStatSetIndexLookup"..suffix]
controls.mainSkillMinionSkillStatSet.selIndex = minionStatSetIndexLookup and minionStatSetIndexLookup[activeEffect.grantedEffect.id] and minionStatSetIndexLookup[activeEffect.grantedEffect.id][controls.mainSkillMinionSkill.selIndex] or 1
controls.mainSkillMinionSkillStatSet.shown = true
controls.mainSkillMinionSkillStatSet.enabled = #controls.mainSkillMinionSkillStatSet.list > 1
end
local minionStatSetIndexLookup = activeEffect.srcInstance["skillMinionSkillStatSetIndexLookup"..suffix]
controls.mainSkillMinionSkillStatSet.selIndex = minionStatSetIndexLookup and minionStatSetIndexLookup[activeEffect.grantedEffect.id] and minionStatSetIndexLookup[activeEffect.grantedEffect.id][controls.mainSkillMinionSkill.selIndex] or 1
controls.mainSkillMinionSkillStatSet.shown = true
controls.mainSkillMinionSkillStatSet.enabled = #controls.mainSkillMinionSkillStatSet.list > 1
else
t_insert(controls.mainSkillMinion.list, "<No spectres in build>")
end
Expand Down
4 changes: 2 additions & 2 deletions src/Modules/Calcs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ function calcs.buildOutput(build, mode)
for _, skillEffect in ipairs(activeSkill.effectList) do
env.skillsUsed[skillEffect.grantedEffect.name] = true
end
if activeSkill.minion then
if activeSkill.minion and activeSkill.minion.activeSkillList then
for _, activeSkill in ipairs(activeSkill.minion.activeSkillList) do
env.skillsUsed[activeSkill.activeEffect.grantedEffect.id] = true
end
Expand Down Expand Up @@ -575,7 +575,7 @@ function calcs.buildOutput(build, mode)
addTo(env.tagTypesUsed, tag.type, mod)
end
end
if activeSkill.minion then
if activeSkill.minion and activeSkill.minion.activeSkillList then
for _, activeSkill in pairs(activeSkill.minion.activeSkillList) do
for _, mod in ipairs(activeSkill.baseSkillModList) do
addModTags(env.minion, mod)
Expand Down