From 4e63c288e6ec55d4f3f587a352bc4f2abefd296f Mon Sep 17 00:00:00 2001 From: Calic Date: Sat, 13 Jun 2026 23:23:52 +0200 Subject: [PATCH] =?UTF-8?q?initial:=20inventory-list-display-test=20v0.1.0?= =?UTF-8?q?=20=E2=80=94=209=20assertions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Covers: create round-trip, non-container loud-error, action register/unregister lifecycle, duplicate label + non-function callback loud-errors, empty default action-set, contents ordering via _test_get_rows, default icon-resolver property reads, custom icon-resolver override via set_icon_resolver. --- LICENSE | 24 +++++++++ README.md | 32 ++++++++++++ init.lua | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++ manifest.core | 6 +++ manifest.lib | 11 ++++ 5 files changed, 211 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 init.lua create mode 100644 manifest.core create mode 100644 manifest.lib diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..81c9d65 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2026 Calic. All rights reserved. + +This software is part of the Sporel platform — **Tier 1 (Official / +Proprietary)** content per the Three-Tier Licensing Model documented in +`meta/docs/archive/design/vision.md §Licensing Model` (current source; +migration to `meta/docs/architecture/licensing-model.md` pending). + +⚠ **WIP — Legal review required before public launch.** The terms below +reflect design intent only; the formalized license framework will be +finalized through legal counsel before the first public release. Until +then, this notice serves as a placeholder defending the platform owner's +rights against unintentional re-licensing. + +No license is granted to copy, modify, distribute, sublicense, or otherwise +use this software in any form without prior written permission from the +copyright holder. + +References: +- Tier 1 (this file): all rights reserved, proprietary, sold/distributed + via official channels (Steam, etc.) +- Tier 2 (Semi-Commercial Co-Development): bilateral contracts, revenue- + share — see vision.md §Licensing Model +- Tier 3 (Community Content): CC BY-NC-SA 4.0 + asymmetric CLA — applies + to community-uploaded libs/modules/assets, not this repo diff --git a/README.md b/README.md new file mode 100644 index 0000000..5ff027f --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# lib-core.inventory-list-display-test + +Test module for `lib-core.inventory-list-display`. Runs 9 assertions +headless via `engine.test.*` (TAP output). + +**Version:** 0.1.0 +**Lib-ID:** lib-core.inventory-list-display-test +**Role:** test +**Requires:** lib-core.inventory-list-display v0.1.0, lib-core.inventory-list v0.1.0, lib-core.composition v0.2.0 + +## Running + +```bash +export PATH="/c/msys64/mingw64/bin:$PATH" +cd ~/Projects/Sporel/sporel-engine +SPOREL_LIBS_DIR=~/Projects/Sporel/sporel-libs SPOREL_CI=1 SPOREL_LOG_STDERR=1 \ + ./build/Sporel.exe --test-libs=lib-core.inventory-list-display-test 2>&1 | tail -15 +``` + +Expected: `pass=N fail=0` where N >= 9. + +## Assertions + +1. `create` returns widget_def with `render`, `handle_input`, and correct `title` +2. `create` with a non-container entity loud-errors +3. `register_action` + `unregister_action` round-trip completes without error +4. Duplicate action label loud-errors +5. Non-function callback loud-errors +6. Default action-set is empty; `unregister_action` on unknown label is a no-op +7. `_test_get_rows` returns items in `inventory.contents` order +8. Default icon-resolver reads `sprite_atlas` and `sprite_uv.*` from item properties +9. Custom icon-resolver is invoked after `set_icon_resolver` diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..fe2eca7 --- /dev/null +++ b/init.lua @@ -0,0 +1,138 @@ +-- lib-core.inventory-list-display-test — v0.1.0 +-- 9 assertions covering widget creation, action lifecycle, resolver +-- overrides, and content ordering. +-- Pattern follows P.2.4 Test-Module-Pattern; TAP output via engine.test.* + +local panel = require("lib-core.panel") +local composition = require("lib-core.composition") +local inventory = require("lib-core.inventory-list") +local display = require("lib-core.inventory-list-display") +local T = engine.test + +local M = {} + +-- ---------------------------------------------------------------- +-- Template definitions (shared across all assertions in this run) +-- All template IDs are unique to this test lib to avoid clashes. +-- ---------------------------------------------------------------- + +-- Container template: kind="list", no constraints. +composition.define_template{ + id = "test_backpack", + properties = { position = {x = 0, y = 0} }, + tags = {"renderable"}, + container = { kind = "list" }, +} + +-- Item template: individual stack_mode + Atlas-UV properties for icon tests. +composition.define_template{ + id = "test_item", + properties = { + stack_mode = "individual", + sprite_atlas = "tex.png", + sprite_uv = {x = 0, y = 0, w = 8, h = 8}, + position = {x = 0, y = 0}, + }, + tags = {"renderable", "item"}, +} + +function M.run_tests(ctx) + local bp = composition.create{template = "test_backpack"} + + -- ================================================================ + -- 1. create returns widget_def with render + handle_input + title + -- ================================================================ + + local widget = display.create(bp, { title = "Backpack" }) + T.assert(widget ~= nil, "create returns non-nil") + T.equals(type(widget.render), "function", "widget has render fn") + T.equals(type(widget.handle_input), "function", "widget has handle_input") + T.equals(widget.title, "Backpack", "title set from opts") + + -- ================================================================ + -- 2. create with non-container entity -> loud-error + -- test_item has no container block, so composition.get_container + -- returns nil, and display.create loud-errors. + -- ================================================================ + + local plain = composition.create{template = "test_item"} + local ok2 = pcall(display.create, plain) + T.assert(not ok2, "create(non_container) loud-errors") + + -- ================================================================ + -- 3. register_action + unregister_action round-trip (no error) + -- ================================================================ + + display.register_action(widget, "Drop", function(item, c) end) + display.register_action(widget, "Inspect", function(item, c) end) + display.unregister_action(widget, "Drop") + T.assert(true, "register/unregister round-trip completes without error") + + -- ================================================================ + -- 4. Duplicate action label -> loud-error + -- "Drop" was unregistered above; re-register then attempt duplicate. + -- ================================================================ + + display.register_action(widget, "Drop", function() end) + local ok4 = pcall(display.register_action, widget, "Drop", function() end) + T.assert(not ok4, "duplicate action label loud-errors") + + -- ================================================================ + -- 5. Non-function callback -> loud-error + -- ================================================================ + + local ok5 = pcall(display.register_action, widget, "X", "not_fn") + T.assert(not ok5, "non-function callback loud-errors") + + -- ================================================================ + -- 6. Default action-set is empty; unregister of unknown label is no-op + -- A fresh widget has no implicit actions. Unregistering a missing + -- label must not error (idempotent). + -- ================================================================ + + local widget2 = display.create(composition.create{template = "test_backpack"}) + display.unregister_action(widget2, "Drop") -- no-op: not registered + T.assert(true, "default action-set empty; unregister of unknown label is no-op") + + -- ================================================================ + -- 7. Rows match inventory.contents order (_test_get_rows) + -- ================================================================ + + local item_a = composition.create{template = "test_item"} + local item_b = composition.create{template = "test_item"} + inventory.add(bp, item_a) + inventory.add(bp, item_b) + local rows = display._test_get_rows(widget) + T.assert(#rows == 2, "rows reflect inventory.contents count") + + -- Verify ordering: item_a was added first, must appear first. + -- Compare by composition.reg_id (stable identity; entity userdata has no __eq). + local rid_a = item_a:get_property("composition.reg_id") + local rid_r1 = rows[1]:get_property("composition.reg_id") + T.assert(rid_a ~= nil and rid_r1 == rid_a, + "rows[1] matches item_a (insertion order preserved)") + + -- ================================================================ + -- 8. Default icon-resolver reads sprite_atlas + sprite_uv from properties + -- ================================================================ + + local icon = display._test_resolve_icon(widget, item_a) + T.assert(icon ~= nil, "default icon-resolver returns non-nil table") + T.equals(icon.atlas, "tex.png", "icon.atlas matches sprite_atlas property") + T.equals(icon.uv.x, 0, "icon.uv.x matches sprite_uv.x property") + + -- ================================================================ + -- 9. Custom icon-resolver invoked after set_icon_resolver + -- ================================================================ + + local custom_called = false + display.set_icon_resolver(widget, function(item) + custom_called = true + return {atlas = "custom.png", uv = {x = 1, y = 2, w = 3, h = 4}} + end) + local icon2 = display._test_resolve_icon(widget, item_a) + T.assert(custom_called, "custom icon-resolver was invoked after set_icon_resolver") + T.equals(icon2.atlas, "custom.png", "custom resolver returns custom atlas") +end + +return M diff --git a/manifest.core b/manifest.core new file mode 100644 index 0000000..17eed1e --- /dev/null +++ b/manifest.core @@ -0,0 +1,6 @@ +{ + "id": "lib-core.inventory-list-display-test.core", + "kind": "lib", + "version": "0.1.0", + "license": "Proprietary" +} diff --git a/manifest.lib b/manifest.lib new file mode 100644 index 0000000..8e2751a --- /dev/null +++ b/manifest.lib @@ -0,0 +1,11 @@ +{ + "id": "lib-core.inventory-list-display-test", + "role": "test", + "version": "0.1.0", + "api_min": "0.1", + "deps": [ + {"id": "lib-core.inventory-list-display", "version": "0.1.0"}, + {"id": "lib-core.inventory-list", "version": "0.1.0"}, + {"id": "lib-core.composition", "version": "0.2.0"} + ] +}