Files
fs25-adsmartpickup/tests/test_adFlowGuide.lua
T
masterdraco 1739b01d92 feat: flow-guide tildeler ledige vogne til solo-traktorer
Solo-traktorer (ingen vogn bagpå) er valgbare når en frakoblet vogn med
Connect-markør kan bære flowets varer. Guiden foreslår en vogn (navnematch,
så Disponibel) med vognvalg pr. traktor; flowet husker parret ved gem, så
vognens parkering ikke skal omdøbes.
2026-09-29 20:48:04 +02:00

299 lines
19 KiB
Lua

-- Kør: luajit tests/test_adFlowGuide.lua (fra repo-roden)
dofile("FS25_ADSmartPickup/adGuide.lua")
dofile("FS25_ADSmartPickup/adRuns.lua")
dofile("FS25_ADSmartPickup/adFlowView.lua")
dofile("FS25_ADSmartPickup/adFlowGuide.lua")
local FG, G = ADFlowGuide, ADGuide
local failures = 0
local function check(name, actual, expected)
if actual == expected then print("OK " .. name) else
failures = failures + 1
print(string.format("FAIL %s: forventede %s, fik %s", name, tostring(expected), tostring(actual)))
end
end
local function newRun(kind, changes)
local store, run = ADRuns.createRun(ADRuns.newStore(), "Mælk ud", kind)
if changes ~= nil then store = ADRuns.updateRun(store, run.id, changes) end
return ADRuns.getRun(store, run.id)
end
local function ctx(extra)
local context = {
buildings = {
{id = "b1", name = "Kostald", label = "Kostald M", hasMarker = true, text = "Kostald — M 5 m"},
{id = "b2", name = "Silo", label = "Silo M", hasMarker = true, text = "Silo — M 3 m"},
{id = "b3", name = "Lade", hasMarker = false, text = "Lade — INGEN MARKØR"},
},
fillTypes = {{name = "HAY", title = "Hø"}, {name = "STRAW", title = "Halm"}},
products = {{name = "MILK", title = "Mælk"}, {name = "EGG", title = "Æg"}},
storeBuildings = {{id = "s1", name = "Silo Nord", text = "Silo Nord — M 5 m"}, {id = "s2", name = "Palleskur", text = "Palleskur — M 2 m"}},
waitPools = {{group = "Wait A", count = 2}},
vehicles = {
{id = "t1", label = "Traktor 1", text = "Traktor 1 — 1000 l — OK"},
{id = "t2", text = "Traktor 2", otherRunId = 9, otherRunName = "Andet"},
{id = "t3", text = "Traktor 3", blocked = true},
{id = "t4", text = "Traktor 4"},
},
}
for key, value in pairs(extra or {}) do context[key] = value end
return context
end
local function steps(guide) return table.concat(G.visibleSteps(guide), ",") end
local function findRow(rows, id, item)
for _, row in ipairs(rows) do
if row.id == id and (item == nil or row.item == item) then return row end
end
return nil
end
local function countType(rows, rowType)
local n = 0
for _, row in ipairs(rows) do if row.type == rowType then n = n + 1 end end
return n
end
local function textKeys(rows)
local keys = {}
for _, row in ipairs(rows) do if row.type == "text" and row.label.key ~= nil then table.insert(keys, row.label.key) end end
return table.concat(keys, ",")
end
local function at(guide, step) return G.jump(guide, step, "close") end
-- ---- trin ----
local supply = FG.open(newRun("supply"), ctx())
check("FL1 forsyning", steps(supply), "direction,buildings,goods,rules,wait,tractors,summary")
local out = FG.open(newRun("outbound"), ctx())
check("FL1 udkørsel uden lager-vare", steps(out), "direction,buildings,goods,rules,wait,tractors,summary")
local outStore = FG.open(newRun("outbound", {products = {{fillType = "MILK", policy = "sellElseStore"}}}), ctx())
check("FL1 udkørsel med lager", steps(outStore), "direction,buildings,goods,store,rules,wait,tractors,summary")
local new = FG.openNew("Flow 3", ctx())
check("FL1 nyt flow har navn", steps(new), "name,direction,buildings,goods,rules,wait,tractors,summary")
check("FL1 nyt flow starter på navn", new.step, "name")
check("FL1 nyt flow er forsyning", new.draft.kind, "supply")
check("FL1 nyt flow navn", new.draft.name, "Flow 3")
check("FL1 eksisterende starter på retning", supply.step, "direction")
-- ---- titler følger retningen ----
check("FL2 til", FG.stepKey("buildings", supply), "spu_guide_flow_step_to")
check("FL2 fra", FG.stepKey("buildings", out), "spu_guide_flow_step_from")
check("FL2 andre", FG.stepKey("rules", out), "spu_guide_flow_step_rules")
check("FL2 hint", FG.hintKey("goods", out), "spu_guide_flow_hint_goods_out")
check("FL2 hint forsyning", FG.hintKey("goods", supply), "spu_guide_flow_hint_goods_in")
-- ---- choose er immutabel ----
local toOut = FG.choose(supply, "kind", "outbound")
check("FL3 retning skiftet", toOut.draft.kind, "outbound")
check("FL3 gammel urørt", supply.draft.kind, "supply")
local withTarget = FG.choose(supply, "buildings", {item = "b1", on = true})
check("FL3 forsyning: mål", table.concat(withTarget.draft.targetBuildingIds, ","), "b1")
check("FL3 forsyning: kilder urørt", #withTarget.draft.sourceBuildingIds, 0)
local withSource = FG.choose(out, "buildings", {item = "b2", on = true})
check("FL3 udkørsel: kilde", table.concat(withSource.draft.sourceBuildingIds, ","), "b2")
local policy = FG.choose(out, "policy", {item = "MILK", value = "store"})
check("FL3 politik", policy.draft.products[1].policy, "store")
check("FL3 politik gør lager-trin synligt", FG.isVisible("store", policy), true)
local off = FG.choose(policy, "policy", {item = "MILK", value = "off"})
check("FL3 politik fra fjerner", #off.draft.products, 0)
local replaced = FG.choose(policy, "policy", {item = "MILK", value = "sell"})
check("FL3 politik erstattes", #replaced.draft.products, 1)
check("FL3 politik ny", replaced.draft.products[1].policy, "sell")
check("FL3 navn", FG.choose(new, "name", "Æg til salg").draft.name, "Æg til salg")
check("FL3 tomt navn ignoreres", FG.choose(new, "name", " ").draft.name, "Flow 3")
-- ---- rækker ----
local dirRows = FG.rows(supply)
check("FL4 retning valg", findRow(dirRows, "kind").type, "choice")
check("FL4 retning værdi", findRow(dirRows, "kind").value, "supply")
local buildingRows = FG.rows(at(supply, "buildings"))
check("FL4 bygninger med markør", countType(buildingRows, "check"), 2)
check("FL4 vis alle", findRow(buildingRows, "showAllBuildings").type, "toggle")
local allRows = FG.rows(at(FG.choose(supply, "showAllBuildings", true), "buildings"))
check("FL4 vis alle: tre", countType(allRows, "check"), 3)
local noHidden = FG.rows(at(FG.open(newRun("supply"), ctx({buildings = {{id = "b1", name = "Kostald", hasMarker = true, text = "K"}}})), "buildings"))
check("FL4 ingen vis-alle uden skjulte", findRow(noHidden, "showAllBuildings"), nil)
local chosenFirst = FG.rows(at(FG.choose(supply, "buildings", {item = "b2", on = true}), "buildings"))
local firstCheck
for _, row in ipairs(chosenFirst) do if row.type == "check" and firstCheck == nil then firstCheck = row end end
check("FL4 fast rækkefølge (navn)", firstCheck.item, "b1")
check("FL4 valgt markeret", findRow(chosenFirst, "buildings", "b2").value, true)
check("FL4 ingen bygninger", textKeys(FG.rows(at(FG.open(newRun("supply"), ctx({buildings = {}})), "buildings"))), "spu_noBuildings")
local goodsAuto = FG.rows(at(supply, "goods"))
check("FL5 forsyning auto", findRow(goodsAuto, "ingredientMode").value, "auto")
check("FL5 forsyning auto: ingen liste", countType(goodsAuto, "check"), 0)
local goodsManual = FG.rows(at(FG.choose(supply, "ingredientMode", "manual"), "goods"))
check("FL5 forsyning vælg: varer", countType(goodsManual, "check"), 2)
check("FL5 forsyning vælg uden varer", textKeys(FG.rows(at(FG.choose(FG.open(newRun("supply"), ctx({fillTypes = {}})), "ingredientMode", "manual"), "goods"))), "spu_guide_flow_noGoodsTo")
local goodsOut = FG.rows(at(policy, "goods"))
check("FL5 udkørsel: politik pr. vare", countType(goodsOut, "choice"), 2)
check("FL5 udkørsel: mælk lager", findRow(goodsOut, "policy", "MILK").value, "store")
check("FL5 udkørsel: æg fra", findRow(goodsOut, "policy", "EGG").value, "off")
check("FL5 udkørsel: politikker", #findRow(goodsOut, "policy", "EGG").options, 4)
check("FL5 udkørsel uden varer", textKeys(FG.rows(at(FG.open(newRun("outbound"), ctx({products = {}})), "goods"))), "spu_guide_flow_noProducts")
local storeRows = FG.rows(at(outStore, "store"))
check("FL6 lager auto", findRow(storeRows, "storeAuto").value, true)
check("FL6 lager auto: ingen liste", countType(storeRows, "check"), 0)
local storeManual = FG.rows(at(FG.choose(outStore, "storeAuto", false), "store"))
check("FL6 lager vælg selv", countType(storeManual, "check"), 2)
local chosenStore = FG.open(newRun("outbound", {products = {{fillType = "MILK", policy = "store"}}, storeBuildingIds = {"s2"}}), ctx())
check("FL6 valgt lager -> vælg selv", chosenStore.draft.storeAuto, false)
local rulesOut = FG.rows(at(out, "rules"))
check("FL7 udkørsel: sælg fra", findRow(rulesOut, "sellAtPercent").value, 90)
check("FL7 udkørsel: sælg-trin", #findRow(rulesOut, "sellAtPercent").options, 11)
check("FL7 udkørsel: lager over", findRow(rulesOut, "storeAbovePercent").value, 80)
check("FL7 udkørsel: samlerunde", findRow(rulesOut, "collectRound").type, "toggle")
check("FL7 udkørsel: ingen ture", findRow(rulesOut, "loops"), nil)
local rulesIn = FG.rows(at(supply, "rules"))
check("FL7 forsyning: ture", findRow(rulesIn, "loops").value, 0)
check("FL7 forsyning: ture-valg", #findRow(rulesIn, "loops").options, ADRuns.MAX_LOOPS + 1)
check("FL7 forsyning: 0 = uendeligt", findRow(rulesIn, "loops").options[1].label.key, "spu_loops_infinite")
check("FL7 forsyning: retur", findRow(rulesIn, "returnBelowPercent").value, 50)
check("FL7 forsyning: ingen salg", findRow(rulesIn, "sellAtPercent"), nil)
local waitRows = FG.rows(at(supply, "wait"))
check("FL8 vent: alle først", findRow(waitRows, "waitPoolGroup").options[1].value, "")
check("FL8 vent: puljer", #findRow(waitRows, "waitPoolGroup").options, 2)
local assigned = FG.open(newRun("supply", {}), ctx())
assigned = FG.choose(assigned, "vehicleIds", {item = "t4", on = true})
local tractorRows = FG.rows(at(assigned, "tractors"))
check("FL9 traktorer: kun egne + ledige", countType(tractorRows, "check"), 2)
check("FL9 egne først", (function() for _, r in ipairs(tractorRows) do if r.type == "check" then return r.item end end end)(), "t4")
check("FL9 skjult andre flows", findRow(tractorRows, "vehicleIds", "t2"), nil)
check("FL9 skjult blokeret", findRow(tractorRows, "vehicleIds", "t3"), nil)
check("FL9 linjer", textKeys(tractorRows), "spu_guide_flow_otherFlows")
-- blokerede traktorer: foldet sammen som standard (in-game 27/9: 37 linjer der ikke kunne bruges fyldte trinnet);
-- linjen med antallet er et link der folder navn + grund ud og ind
local function blockedLine(rows)
for _, r in ipairs(rows) do if r.type == "text" and r.label.text ~= nil and r.label.text:find("Traktor 3", 1, true) then return r end end
return nil
end
local function blockedLink(rows)
for _, r in ipairs(rows) do if r.type == "link" and r.id == "showBlocked" then return r end end
return nil
end
check("FL9 blokerede foldet sammen som standard", blockedLine(tractorRows), nil)
check("FL9 link med antal", blockedLink(tractorRows) ~= nil and blockedLink(tractorRows).label.key, "spu_guide_flow_blockedShow")
local expanded = FG.choose(assigned, "showBlocked", true)
local expandedRows = FG.rows(at(expanded, "tractors"))
check("FL9 foldet ud viser navn + grund", blockedLine(expandedRows) ~= nil, true)
check("FL9 foldet ud: link skjuler igen", blockedLink(expandedRows).label.key, "spu_guide_flow_blockedHide")
check("FL9 fold ud ændrer ikke kladden", expanded.draft.vehicleIds[1], "t4")
check("FL9 fold sammen igen", blockedLine(FG.rows(at(FG.choose(expanded, "showBlocked", true), "tractors"))), nil)
local inThisRun = FG.open(newRun("supply"), ctx({vehicles = {{id = "t2", text = "T2", otherRunId = 1, otherRunName = "Mælk ud"}}}))
check("FL9 traktor i dette flow er ikke 'andet flow'", countType(FG.rows(at(inThisRun, "tractors")), "check"), 1)
-- ---- resumé ----
local function lineFor(lines, step)
for _, line in ipairs(lines) do if line.step == step then return line end end
return nil
end
local function partText(line)
local parts = {}
for _, part in ipairs(line.parts) do table.insert(parts, part.key or ("=" .. tostring(part.text))) end
return table.concat(parts, ",")
end
local sumIn = FG.summary(FG.choose(withTarget, "vehicleIds", {item = "t1", on = true}))
check("FL10 navn", partText(lineFor(sumIn, "name")), "=Mælk ud")
check("FL10 retning", partText(lineFor(sumIn, "direction")), "spu_direction_in")
check("FL10 til", partText(lineFor(sumIn, "buildings")), "=Kostald")
check("FL10 til-titel", lineFor(sumIn, "buildings").title.key, "spu_guide_flow_step_to")
check("FL10 varer auto", partText(lineFor(sumIn, "goods")), "spu_guide_flow_goodsAuto")
check("FL10 regler", partText(lineFor(sumIn, "rules")), "spu_guide_flow_sumLoopsInf,spu_guide_flow_sumReturn")
check("FL10 vent", partText(lineFor(sumIn, "wait")), "spu_waitPool_all")
check("FL10 traktorer", partText(lineFor(sumIn, "tractors")), "=Traktor 1")
check("FL10 intet lager-trin", lineFor(sumIn, "store"), nil)
-- traktor der ikke findes længere (solgt): aldrig rå id i resuméet (in-game 29/9)
local withGhost = FG.choose(FG.choose(withTarget, "vehicleIds", {item = "t1", on = true}), "vehicleIds", {item = "vehicleGONE", on = true})
check("FL10b resumé springer manglende over", partText(lineFor(FG.summary(withGhost), "tractors")), "=Traktor 1")
local onlyGhost = FG.choose(withTarget, "vehicleIds", {item = "vehicleGONE", on = true})
check("FL10b kun manglende -> ingen traktorer", partText(lineFor(FG.summary(onlyGhost), "tractors")), "spu_guide_flow_sumNoTractors")
local sumOut = FG.summary(FG.choose(FG.choose(policy, "collectRound", true), "sellAtPercent", 95))
check("FL11 retning ud", partText(lineFor(sumOut, "direction")), "spu_direction_out")
check("FL11 fra tom", partText(lineFor(sumOut, "buildings")), "spu_guide_flow_none")
check("FL11 varer", partText(lineFor(sumOut, "goods")), "spu_policy_store")
check("FL11 varenavn foran", lineFor(sumOut, "goods").parts[1].prefix, "Mælk: ")
check("FL11 lager auto", partText(lineFor(sumOut, "store")), "spu_guide_sum_auto")
check("FL11 regler", partText(lineFor(sumOut, "rules")), "spu_guide_flow_sumSellAt,spu_guide_flow_sumStoreAbove,spu_guide_flow_sumRound")
check("FL11 sælg 95", lineFor(sumOut, "rules").parts[1].args[1], 95)
check("FL11 ingen traktorer", partText(lineFor(sumOut, "tractors")), "spu_guide_flow_sumNoTractors")
local manyGoods = FG.summary(FG.choose(FG.choose(FG.choose(supply, "ingredientMode", "manual"), "ingredients", {item = "HAY", on = true}), "ingredients", {item = "STRAW", on = true}))
check("FL12 valgte varer", partText(lineFor(manyGoods, "goods")), "=Hø + Halm")
check("FL12 oversigt: links", countType(FG.rows(G.new(supply.def, supply.draft, supply.context, {step = "summary"})), "link"), 7)
-- ---- resultat ----
local r = FG.result(FG.choose(FG.choose(policy, "storeAuto", false), "storeBuildingIds", {item = "s1", on = true}))
check("FL13 ikke ny", r.isNew, false)
check("FL13 run-id", r.runId, 1)
check("FL13 retning", r.changes.kind, "outbound")
check("FL13 lager valgt", table.concat(r.changes.storeBuildingIds, ","), "s1")
check("FL13 varer", r.changes.products[1].fillType, "MILK")
check("FL13 lager-resumé bruger navn", (function() for _, l in ipairs(FG.summary(FG.choose(FG.choose(policy, "storeAuto", false), "storeBuildingIds", {item = "s1", on = true}))) do if l.step == "store" then return l.parts[1].text end end end)(), "Silo Nord")
local rAuto = FG.result(FG.choose(chosenStore, "storeAuto", true))
check("FL13 lager auto -> tom", #rAuto.changes.storeBuildingIds, 0)
local hiddenStore = FG.open(newRun("outbound", {storeBuildingIds = {"s2"}}), ctx())
check("FL13 lager usynligt: bevares", table.concat(FG.result(hiddenStore).changes.storeBuildingIds, ","), "s2")
local rNew = FG.result(FG.choose(new, "vehicleIds", {item = "t1", on = true}))
check("FL14 nyt", rNew.isNew, true)
check("FL14 navn", rNew.name, "Flow 3")
check("FL14 traktorer", table.concat(rNew.vehicleIds, ","), "t1")
check("FL14 ingen run-id", rNew.runId, nil)
local added, removed = FG.vehicleChanges({"a", "b"}, {"b", "c"})
check("FL15 tilføjet", table.concat(added, ","), "c")
check("FL15 fjernet", table.concat(removed, ","), "a")
-- ---- forhåndsvisning (til controllerens opslag) ----
local markers = {b1 = {101, 102}, b2 = {201}}
local preview = FG.previewRun(withSource.draft, function(id) return markers[id] or {} end)
check("FL16 kilder -> markører", table.concat(preview.sourceWayPointIds, ","), "201")
local previewIn = FG.previewRun(withTarget.draft, function(id) return markers[id] or {} end)
check("FL16 mål -> markører", table.concat(previewIn.targetWayPointIds, ","), "101,102")
check("FL16 primært mål", previewIn.targetWayPointId, 101)
check("FL16 kladde urørt", withTarget.draft.targetWayPointIds == nil or #withTarget.draft.targetWayPointIds == 0, true)
-- ---- solo-traktor + ledig vogn (in-game 29/9: flow kunne ikke oprettes uden at omdøbe en vogns parkering) ----
local soloCtx = ctx({
vehicles = {
{id = "s1", label = "Solo 1", text = "Solo 1", solo = true},
{id = "s2", label = "Solo 2", text = "Solo 2", solo = true},
{id = "s3", label = "Solo 3", text = "Solo 3", solo = true},
{id = "t1", label = "Traktor 1", text = "Traktor 1 — OK"},
},
wagons = {{id = "w1", label = "P Disponibel Vogn 4"}, {id = "w2", label = "P Disponibel Vogn 5"}},
})
local soloGuide = FG.open(newRun("supply"), soloCtx)
local one = FG.choose(soloGuide, "vehicleIds", {item = "s1", on = true})
check("WG1 solo tildeles første ledige vogn", one.draft.wagons.s1, "w1")
local two = FG.choose(one, "vehicleIds", {item = "s2", on = true})
check("WG2 næste solo får næste vogn", two.draft.wagons.s2, "w2")
check("WG2 første beholder sin", two.draft.wagons.s1, "w1")
local three = FG.choose(two, "vehicleIds", {item = "s3", on = true})
check("WG3 ingen ledig vogn tilbage -> ingen tildeling", three.draft.wagons.s3, nil)
local soloRows = FG.rows(at(three, "tractors"))
local wagonRow = findRow(soloRows, "wagon", "s1")
check("WG4 vognvalg under solo-traktor", wagonRow ~= nil and wagonRow.type, "choice")
check("WG4 vognvalg viser tildelt vogn", wagonRow ~= nil and wagonRow.value, "w1")
check("WG4 valgmuligheder uden de andres vogne", wagonRow ~= nil and #wagonRow.options, 1)
local noFree = false
for _, row in ipairs(soloRows) do if row.label ~= nil and row.label.key == "spu_guide_flow_noFreeWagon" then noFree = true end end
check("WG5 solo uden ledig vogn får besked", noFree, true)
check("WG5 almindelig traktor har intet vognvalg", findRow(FG.rows(at(FG.choose(soloGuide, "vehicleIds", {item = "t1", on = true}), "tractors")), "wagon", "t1"), nil)
local swapped = FG.choose(FG.choose(one, "vehicleIds", {item = "s2", on = false}), "wagon", {item = "s1", value = "w2"})
check("WG6 skift vogn", swapped.draft.wagons.s1, "w2")
local off = FG.choose(two, "vehicleIds", {item = "s1", on = false})
check("WG7 fravalgt traktor slipper vognen", off.draft.wagons.s1, nil)
local freed = FG.choose(off, "vehicleIds", {item = "s3", on = true})
check("WG7 frigjort vogn går til næste", freed.draft.wagons.s3, "w1")
check("WG8 resultat har vognene", FG.result(two).wagons.s2, "w2")
check("WG8 resultat uden fravalgte", FG.result(off).wagons.s1, nil)
local remembered = newRun("supply", {vehicleIds = {"s2"}})
remembered.wagons = {s2 = "w2"}
local reopened = FG.open(remembered, soloCtx)
check("WG9 husket vogn i kladden", reopened.draft.wagons.s2, "w2")
check("WG9 ny solo tager ikke den huskede", FG.choose(reopened, "vehicleIds", {item = "s1", on = true}).draft.wagons.s1, "w1")
check("WG9 kladden kopierer (run urørt)", (function() local g = FG.choose(reopened, "wagon", {item = "s2", value = "w1"}); return remembered.wagons.s2 end)(), "w2")
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)