From 73aceec4d4c974ebe1ceb8dd30f1ed4137392eaf Mon Sep 17 00:00:00 2001 From: Axel Meyer Date: Sat, 13 Jun 2026 13:55:07 +0200 Subject: [PATCH] test(composition): assert set_tag round-trip + container-block validation 8 new assertions covering v0.2 surface: - set_tag adds entity to tag-index (assert 15) - set_tag removes entity from tag-index (assert 16) - set_tag is idempotent for both add and remove (asserts 17-18) - set_tag loud-errors on non-composition entity (assert 19) - define_template accepts container={kind="list"} (assert 20) - define_template loud-errors on container={kind="grid"} (assert 21) - define_template loud-errors on container with weight_max (assert 22) - slots Loud-Error regression: still fires after v0.2 changes (assert 23) Manifest version bumped to 0.2.0; composition dep bumped to 0.2.0. --- init.lua | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++-- manifest.lib | 4 +-- 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/init.lua b/init.lua index 8d7861d..cc89e53 100644 --- a/init.lua +++ b/init.lua @@ -1,5 +1,6 @@ --- lib-core.composition-test — Phase A.1 --- 14 assertions: 4 define_template + 4 create + 3 list_by_* + 2 destroy + 1 list_templates. +-- lib-core.composition-test — Phase A.1 + B.1 +-- 23 assertions: 4 define_template + 4 create + 3 list_by_* + 2 destroy + 1 list_templates +-- + 8 B.1 (set_tag round-trip + container-block validation) + 1 slots regression. -- Pattern follows P.2.4 Test-Module-Pattern; TAP output via engine.test.* local composition = require("lib-core.composition") @@ -106,6 +107,93 @@ function M.run_tests(ctx) -- list_templates is non-empty after define T.assert(#composition.list_templates() >= 1, "list_templates returns at least 1 entry after define_template") + + -- ================================================================ + -- B.1 — set_tag + container-block validation (8) + -- ================================================================ + + -- Need a live entity to test set_tag against. + local tagtest_entity = composition.create{ template = "sign" } + + -- 1. set_tag(e, "foo", true) → list_by_tag("foo") contains e. + composition.set_tag(tagtest_entity, "foo", true) + local foo_list_after_add = composition.list_by_tag("foo") + local found_in_foo = false + for _, e in ipairs(foo_list_after_add) do + if e == tagtest_entity then found_in_foo = true; break end + end + T.assert(found_in_foo, "set_tag(e,'foo',true) adds e to list_by_tag('foo')") + + -- 2. set_tag(e, "foo", false) → list_by_tag("foo") does not contain e. + composition.set_tag(tagtest_entity, "foo", false) + local foo_list_after_remove = composition.list_by_tag("foo") + local still_in_foo = false + for _, e in ipairs(foo_list_after_remove) do + if e == tagtest_entity then still_in_foo = true; break end + end + T.assert(not still_in_foo, "set_tag(e,'foo',false) removes e from list_by_tag('foo')") + + -- 3. set_tag idempotent — double true and double false must not error + -- and must not duplicate / extra-remove entries. + composition.set_tag(tagtest_entity, "bar", true) + composition.set_tag(tagtest_entity, "bar", true) -- second call: idempotent + local bar_list = composition.list_by_tag("bar") + local bar_count = 0 + for _, e in ipairs(bar_list) do + if e == tagtest_entity then bar_count = bar_count + 1 end + end + T.equals(bar_count, 1, "set_tag idempotent: double-true does not duplicate in index") + + composition.set_tag(tagtest_entity, "bar", false) + composition.set_tag(tagtest_entity, "bar", false) -- second call: idempotent + local bar_list_after = composition.list_by_tag("bar") + local bar_count_after = 0 + for _, e in ipairs(bar_list_after) do + if e == tagtest_entity then bar_count_after = bar_count_after + 1 end + end + T.equals(bar_count_after, 0, "set_tag idempotent: double-false does not error or leave stale entry") + + -- 4. set_tag on a foreign (non-composition) entity → Loud-Error. + local alien = entity.create() + local ok_alien = pcall(composition.set_tag, alien, "any", true) + T.assert(not ok_alien, "set_tag loud-errors on entity not created by composition") + + -- 5. define_template with container={kind="list"} → accepted (no error). + local ok_container_list = pcall(composition.define_template, { + id = "backpack", + properties = { + sprite_atlas = "sprites/items.png", + sprite_uv = "0 0 32 32", + position = {x = 0, y = 0}, + }, + tags = {"renderable"}, + container = { kind = "list" }, + }) + T.assert(ok_container_list, "define_template accepts container={kind='list'}") + + -- 6. define_template with container={kind="grid"} → Loud-Error. + local ok_container_grid = pcall(composition.define_template, { + id = "grid_bag", + container = { kind = "grid" }, + }) + T.assert(not ok_container_grid, "define_template loud-errors on container={kind='grid'}") + + -- 7. define_template with container={kind="list", weight_max=10} → Loud-Error + -- (unimplemented constraint field declared). + local ok_weight_max = pcall(composition.define_template, { + id = "heavy_bag", + container = { kind = "list", weight_max = 10 }, + }) + T.assert(not ok_weight_max, + "define_template loud-errors on container with unimplemented constraint weight_max") + + -- 8. define_template with slots={...} → still Loud-Error (Phase D, regression). + local ok_slots_regression = pcall(composition.define_template, { + id = "composite_sword", + slots = { blade = {} }, + }) + T.assert(not ok_slots_regression, + "define_template still loud-errors on 'slots' block (Phase D regression)") end return M diff --git a/manifest.lib b/manifest.lib index bf20260..70f9353 100644 --- a/manifest.lib +++ b/manifest.lib @@ -1,9 +1,9 @@ { "id": "lib-core.composition-test", "role": "test", - "version": "0.1.0", + "version": "0.2.0", "api_min": "0.1", "deps": [ - {"id": "lib-core.composition", "version": "0.1.0"} + {"id": "lib-core.composition", "version": "0.2.0"} ] }