From ccaed2d0dea8fd0a96f18fa06b0f4a24a08d5a87 Mon Sep 17 00:00:00 2001 From: Calic Date: Sun, 14 Jun 2026 18:48:18 +0200 Subject: [PATCH] =?UTF-8?q?test:=20lib-core.crafting-test=20v0.2.0=20?= =?UTF-8?q?=E2=80=94=20Locale-API=20assertions=20+=20bw-compat=20regressio?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- init.lua | 197 +++++++++++++++++++++++++++++++++++++++++++++++++++ manifest.lib | 4 +- 2 files changed, 199 insertions(+), 2 deletions(-) diff --git a/init.lua b/init.lua index df3e62d..488275c 100644 --- a/init.lua +++ b/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 diff --git a/manifest.lib b/manifest.lib index 4dbe2ed..5f8ef72 100644 --- a/manifest.lib +++ b/manifest.lib @@ -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"} ]