test: lib-core.crafting-test v0.2.0 — Locale-API assertions + bw-compat regression
Adds 6 new TAP assertions (13-18) covering the new Multi-Source /
Single-Sink locale API and the bare-handle bw-compat shim:
13. Bw-Compat: craft(recipe, entity_handle, ctx) still accepts a
bare container-entity-handle; post-state matches the v0.1
single-container behavior (inputs consumed, output added back
to the same container).
14. Form-2 happy-path: sources={c1,c2}, sink=c1, recipe needs one
rock + one stick (c1 has rock, c2 has stick) → c1 ends with the
rock_pick + zero rocks, c2 ends empty.
15. Greedy-drain-order: source1 has 2 rocks, source2 has 5 rocks,
recipe needs 3 rocks → source1 fully drained, source2 has 4 left
(proves left-to-right priority is stable).
16. Aggregate missing-count: source1 has 1 rock, source2 has 1 rock,
recipe needs 3 → can_craft returns missing entry with have=2
(proves cross-source counting in can_craft).
17. Loud-Error empty sources: locale={sources={}, sink=c1} →
error string mentions "crafting", "sources", "non-empty".
18. Loud-Error nil sink: locale={sources={c1}, sink=nil} →
error string mentions "crafting", "sink", "nil".
Adds a sibling fixture helper `extra_container(items)` that creates
a populated container WITHOUT resetting the recipe registry. Needed
for the multi-source cases (14-18): the existing `setup_container`
calls `crafting._test_clear_all()` on every invocation, so it can
only be used once per test; the second-and-later containers come
from `extra_container`. This keeps test isolation per-case while
allowing N>1 containers in a single case.
Manifest bumped 0.1.0 → 0.2.0; dep on lib-core.crafting bumped to
0.2.0 to match the producer. inventory-list 0.1.0 and composition
0.3.0 deps unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
197
init.lua
197
init.lua
@@ -72,6 +72,20 @@ local function setup_container(items)
|
||||
return container
|
||||
end
|
||||
|
||||
-- Sibling helper for multi-source tests: creates an additional container
|
||||
-- without resetting the recipe registry. Use AFTER setup_container() to
|
||||
-- avoid wiping recipes that were just defined for the test.
|
||||
local function extra_container(items)
|
||||
local container = composition.create{ template = "craft_test.container" }
|
||||
for _, spec in ipairs(items) do
|
||||
for _ = 1, spec.count do
|
||||
local e = composition.create{ template = spec.template }
|
||||
inv.add(container, e)
|
||||
end
|
||||
end
|
||||
return container
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Tests
|
||||
-- ---------------------------------------------------------------------
|
||||
@@ -334,6 +348,189 @@ function M.run_tests(ctx)
|
||||
end
|
||||
T.assert(cc12.ok == false and cc12.error == "missing_inputs" and missing_match_12,
|
||||
"12. count-form: 2-of-3 rocks reports missing={template, needed=3, have=2}")
|
||||
|
||||
-- ================================================================
|
||||
-- §13: Bw-Compat — craft(recipe, entity_handle, ctx) still works
|
||||
-- with bare-entity-handle locale (Form 1).
|
||||
-- ================================================================
|
||||
local container13 = setup_container{
|
||||
{ template = "craft_test.rock", count = 1 },
|
||||
{ template = "craft_test.stick", count = 1 },
|
||||
}
|
||||
crafting.define_recipe{
|
||||
id = "rock_pick_r",
|
||||
inputs = {
|
||||
{ template = "craft_test.rock", count = 1 },
|
||||
{ template = "craft_test.stick", count = 1 },
|
||||
},
|
||||
output = { template = "craft_test.rock_pick", count = 1 },
|
||||
}
|
||||
local res13 = crafting.craft("rock_pick_r", container13, {})
|
||||
local pick_count_13 = 0
|
||||
local rock_count_13 = 0
|
||||
local stick_count_13 = 0
|
||||
for _, e in ipairs(inv.contents(container13)) do
|
||||
local tpl = composition.template_of(e)
|
||||
if tpl == "craft_test.rock_pick" then pick_count_13 = pick_count_13 + 1 end
|
||||
if tpl == "craft_test.rock" then rock_count_13 = rock_count_13 + 1 end
|
||||
if tpl == "craft_test.stick" then stick_count_13 = stick_count_13 + 1 end
|
||||
end
|
||||
T.assert(res13.ok == true
|
||||
and res13.crafted_items ~= nil and #res13.crafted_items == 1
|
||||
and res13.consumed ~= nil and #res13.consumed == 2
|
||||
and pick_count_13 == 1 and rock_count_13 == 0 and stick_count_13 == 0,
|
||||
"13. Bw-Compat: craft(recipe, entity_handle, ctx) accepts bare handle, post-state matches v0.1 behavior")
|
||||
|
||||
-- ================================================================
|
||||
-- §14: Form-2 happy-path — 2 sources, sink = source[1].
|
||||
-- c1=1 rock, c2=1 stick, recipe needs both,
|
||||
-- locale={sources={c1,c2}, sink=c1}.
|
||||
-- Post: c1 has 1 rock_pick + 0 rock, c2 has 0 stick.
|
||||
-- ================================================================
|
||||
local c1_14 = setup_container{
|
||||
{ template = "craft_test.rock", count = 1 },
|
||||
}
|
||||
local c2_14 = extra_container{
|
||||
{ template = "craft_test.stick", count = 1 },
|
||||
}
|
||||
crafting.define_recipe{
|
||||
id = "rock_pick_r",
|
||||
inputs = {
|
||||
{ template = "craft_test.rock", count = 1 },
|
||||
{ template = "craft_test.stick", count = 1 },
|
||||
},
|
||||
output = { template = "craft_test.rock_pick", count = 1 },
|
||||
}
|
||||
local res14 = crafting.craft("rock_pick_r",
|
||||
{ sources = {c1_14, c2_14}, sink = c1_14 }, {})
|
||||
|
||||
local c1_pick_14, c1_rock_14, c1_stick_14 = 0, 0, 0
|
||||
for _, e in ipairs(inv.contents(c1_14)) do
|
||||
local tpl = composition.template_of(e)
|
||||
if tpl == "craft_test.rock_pick" then c1_pick_14 = c1_pick_14 + 1 end
|
||||
if tpl == "craft_test.rock" then c1_rock_14 = c1_rock_14 + 1 end
|
||||
if tpl == "craft_test.stick" then c1_stick_14 = c1_stick_14 + 1 end
|
||||
end
|
||||
local c2_pick_14, c2_rock_14, c2_stick_14 = 0, 0, 0
|
||||
for _, e in ipairs(inv.contents(c2_14)) do
|
||||
local tpl = composition.template_of(e)
|
||||
if tpl == "craft_test.rock_pick" then c2_pick_14 = c2_pick_14 + 1 end
|
||||
if tpl == "craft_test.rock" then c2_rock_14 = c2_rock_14 + 1 end
|
||||
if tpl == "craft_test.stick" then c2_stick_14 = c2_stick_14 + 1 end
|
||||
end
|
||||
T.assert(res14.ok == true
|
||||
and c1_pick_14 == 1 and c1_rock_14 == 0 and c1_stick_14 == 0
|
||||
and c2_pick_14 == 0 and c2_rock_14 == 0 and c2_stick_14 == 0,
|
||||
"14. Form-2 happy-path: sources={c1,c2}, sink=c1; c1 ends with rock_pick + no rock, c2 ends empty")
|
||||
|
||||
-- ================================================================
|
||||
-- §15: Greedy-drain-order — source1=2 rocks, source2=5 rocks,
|
||||
-- recipe needs 3 rocks → source1 fully drained, source2 has 4 left.
|
||||
-- ================================================================
|
||||
local c1_15 = setup_container{
|
||||
{ template = "craft_test.rock", count = 2 },
|
||||
}
|
||||
local c2_15 = extra_container{
|
||||
{ template = "craft_test.rock", count = 5 },
|
||||
}
|
||||
crafting.define_recipe{
|
||||
id = "rock_pile_r",
|
||||
inputs = {
|
||||
{ template = "craft_test.rock", count = 3 },
|
||||
},
|
||||
output = { template = "craft_test.rock_pick", count = 1 },
|
||||
}
|
||||
local res15 = crafting.craft("rock_pile_r",
|
||||
{ sources = {c1_15, c2_15}, sink = c1_15 }, {})
|
||||
|
||||
local c1_rock_15 = 0
|
||||
for _, e in ipairs(inv.contents(c1_15)) do
|
||||
if composition.template_of(e) == "craft_test.rock" then
|
||||
c1_rock_15 = c1_rock_15 + 1
|
||||
end
|
||||
end
|
||||
local c2_rock_15 = 0
|
||||
for _, e in ipairs(inv.contents(c2_15)) do
|
||||
if composition.template_of(e) == "craft_test.rock" then
|
||||
c2_rock_15 = c2_rock_15 + 1
|
||||
end
|
||||
end
|
||||
T.assert(res15.ok == true
|
||||
and c1_rock_15 == 0 and c2_rock_15 == 4,
|
||||
"15. Greedy-drain-order: source1 (2 rocks) fully drained, source2 (5 rocks) has 4 remaining after needing 3")
|
||||
|
||||
-- ================================================================
|
||||
-- §16: Aggregate missing-count — source1=1 rock, source2=1 rock,
|
||||
-- recipe needs 3 → missing.have=2.
|
||||
-- ================================================================
|
||||
local c1_16 = setup_container{
|
||||
{ template = "craft_test.rock", count = 1 },
|
||||
}
|
||||
local c2_16 = extra_container{
|
||||
{ template = "craft_test.rock", count = 1 },
|
||||
}
|
||||
crafting.define_recipe{
|
||||
id = "rock_pile_r",
|
||||
inputs = {
|
||||
{ template = "craft_test.rock", count = 3 },
|
||||
},
|
||||
output = { template = "craft_test.rock_pick", count = 1 },
|
||||
}
|
||||
local cc16 = crafting.can_craft("rock_pile_r",
|
||||
{ sources = {c1_16, c2_16}, sink = c1_16 }, {})
|
||||
local missing_match_16 = false
|
||||
if cc16.missing then
|
||||
for _, m in ipairs(cc16.missing) do
|
||||
if m.template == "craft_test.rock"
|
||||
and m.needed == 3 and m.have == 2 then
|
||||
missing_match_16 = true; break
|
||||
end
|
||||
end
|
||||
end
|
||||
T.assert(cc16.ok == false and cc16.error == "missing_inputs"
|
||||
and missing_match_16,
|
||||
"16. Aggregate missing-count across sources: 1+1 rocks vs need 3 reports have=2")
|
||||
|
||||
-- ================================================================
|
||||
-- §17: Loud-Error empty sources — locale={sources={}, sink=c1}
|
||||
-- → error matches "crafting" + "sources" + "non-empty".
|
||||
-- ================================================================
|
||||
local c1_17 = setup_container{
|
||||
{ template = "craft_test.rock", count = 1 },
|
||||
}
|
||||
crafting.define_recipe{
|
||||
id = "rock_pile_r",
|
||||
inputs = { { template = "craft_test.rock", count = 1 } },
|
||||
output = { template = "craft_test.rock_pick", count = 1 },
|
||||
}
|
||||
local ok17, err17 = pcall(crafting.can_craft, "rock_pile_r",
|
||||
{ sources = {}, sink = c1_17 }, {})
|
||||
T.assert(not ok17 and type(err17) == "string"
|
||||
and string.find(err17, "crafting") ~= nil
|
||||
and string.find(err17, "sources") ~= nil
|
||||
and (string.find(err17, "non%-empty") ~= nil
|
||||
or string.find(err17, "non empty") ~= nil),
|
||||
"17. Loud-Error: locale.sources={} → error mentions 'crafting', 'sources', 'non-empty'")
|
||||
|
||||
-- ================================================================
|
||||
-- §18: Loud-Error nil sink — locale={sources={c1}, sink=nil}
|
||||
-- → error matches "crafting" + "sink" + "nil".
|
||||
-- ================================================================
|
||||
local c1_18 = setup_container{
|
||||
{ template = "craft_test.rock", count = 1 },
|
||||
}
|
||||
crafting.define_recipe{
|
||||
id = "rock_pile_r",
|
||||
inputs = { { template = "craft_test.rock", count = 1 } },
|
||||
output = { template = "craft_test.rock_pick", count = 1 },
|
||||
}
|
||||
local ok18, err18 = pcall(crafting.can_craft, "rock_pile_r",
|
||||
{ sources = {c1_18}, sink = nil }, {})
|
||||
T.assert(not ok18 and type(err18) == "string"
|
||||
and string.find(err18, "crafting") ~= nil
|
||||
and string.find(err18, "sink") ~= nil
|
||||
and string.find(err18, "nil") ~= nil,
|
||||
"18. Loud-Error: locale.sink=nil → error mentions 'crafting', 'sink', 'nil'")
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"id": "lib-core.crafting-test",
|
||||
"role": "test",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"api_min": "0.1",
|
||||
"deps": [
|
||||
{"id": "lib-core.crafting", "version": "0.1.0"},
|
||||
{"id": "lib-core.crafting", "version": "0.2.0"},
|
||||
{"id": "lib-core.inventory-list", "version": "0.1.0"},
|
||||
{"id": "lib-core.composition", "version": "0.3.0"}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user