commit f270a2b35425f1da10aaa4cd742ea87c43788c09 Author: Calic Date: Sun Jun 14 01:36:21 2026 +0200 initial: notify-display-test v0.1.0 — 10 assertions 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/init.lua b/init.lua new file mode 100644 index 0000000..2ecefd8 --- /dev/null +++ b/init.lua @@ -0,0 +1,143 @@ +-- lib-core.notify-display-test v0.1.0 — 10 assertions +-- Exercises: attach_mode (log/toast/detail), validation, toast lifecycle, +-- widget factories, detail overwrite, detach cleanup, widget-channel tracking. + +local notify = require("lib-core.notify") +local panel = require("lib-core.panel") +local display = require("lib-core.notify-display") +local T = engine.test + +local M = {} + +-- Helper: create a fresh set of test channels and reset all state +local function setup() + notify._test_clear_all() + -- panel has no _test_clear_all; unregister any widgets we might have registered + pcall(panel.unregister, "game-log") + pcall(panel.unregister, "detail-panel") + pcall(panel.unregister, "log-a") + pcall(panel.unregister, "log-b") + display._test_reset() +end + +function M.run_tests(_ctx) + + -- ================================================================== + -- 1. attach_mode("log") succeeds with valid panel_widget_id + -- ================================================================== + setup() + notify.create_channel{ id = "log-ch1", filter = {"event"}, display_mode = "log" } + local ok_log = pcall(display.attach_mode, "log", { panel_widget_id = "game-log" }) + T.assert(ok_log, "attach_mode('log', {panel_widget_id='game-log'}) succeeds") + + -- ================================================================== + -- 2. attach_mode("toast") with invalid position → loud-error + -- ================================================================== + setup() + local ok_bad_pos = pcall(display.attach_mode, "toast", { position = "invalid" }) + T.assert(not ok_bad_pos, "attach_mode('toast') with invalid position raises an error") + + -- ================================================================== + -- 3. attach_mode("detail") with valid panel_widget_id succeeds + -- ================================================================== + setup() + notify.create_channel{ id = "det-ch", filter = {"detail-ev"}, display_mode = "detail" } + local ok_detail = pcall(display.attach_mode, "detail", { panel_widget_id = "detail-panel" }) + T.assert(ok_detail, "attach_mode('detail', {panel_widget_id='detail-panel'}) succeeds") + + -- ================================================================== + -- 4. attach toast → post → _test_get_active_toasts returns 1 entry + -- ================================================================== + setup() + notify.create_channel{ id = "toast-ch", filter = {"hud"}, display_mode = "toast" } + display.attach_mode("toast", { position = "top-right", default_ttl = 10.0 }) + notify.post{ tags = {"hud"}, text = "alert!" } + local toasts = display._test_get_active_toasts() + T.equals(#toasts, 1, "post to toast-channel produces 1 active toast") + T.equals(toasts[1].msg.text, "alert!", "toast message text is correct") + + -- ================================================================== + -- 5. update(dt) past TTL → toast removed + -- ================================================================== + -- Test 5: TTL expiry via engine.time monkey-patch. + -- INTENTIONALLY no setup() call — depends on the toast posted in test 4 + -- (active_toasts is preserved across tests when setup is skipped). + -- Reuse state from test 4; toasts[1].spawn_time is 0 (engine.time absent in test) + -- Force-expire by calling update with a dt larger than the spawn_time offset. + -- spawn_time = 0, ttl = 10.0; now = 0 → not expired. + -- We monkey-patch engine.time to return a future timestamp. + local saved_engine_time = engine.time + engine.time = { now = function() return 100.0 end } + display.update(0) + engine.time = saved_engine_time + toasts = display._test_get_active_toasts() + T.equals(#toasts, 0, "update() past TTL removes expired toasts") + + -- ================================================================== + -- 6. create_log_widget returns table with render / handle_input / title + -- ================================================================== + local log_widget = display.create_log_widget({ widget_id = "game-log", title = "Events" }) + T.assert(type(log_widget) == "table", "create_log_widget returns a table") + T.assert(type(log_widget.render) == "function","log widget_def has render function") + T.assert(type(log_widget.handle_input) == "function", + "log widget_def has handle_input function") + T.equals(log_widget.title, "Events", "log widget title is set from opts") + + -- ================================================================== + -- 7. create_detail_widget + post → _test_get_last_detail returns msg + -- ================================================================== + setup() + notify.create_channel{ id = "dw-ch", filter = {"detail-ev"}, display_mode = "detail" } + + local det_widget = display.create_detail_widget({ widget_id = "detail-panel", title = "Info" }) + T.assert(type(det_widget) == "table", "create_detail_widget returns a table") + T.assert(type(det_widget.render) == "function","detail widget_def has render function") + + -- Register widget first so that panel.open inside the subscribe callback succeeds + panel.register("detail-panel", det_widget) + display.attach_mode("detail", { panel_widget_id = "detail-panel" }) + notify.post{ tags = {"detail-ev"}, text = "important!" } + local last = display._test_get_last_detail() + T.assert(last ~= nil, "_test_get_last_detail is set after detail post") + T.equals(last.text, "important!", "detail message text is correct") + T.assert(panel.is_open(), "panel is open after detail message auto-open") + + -- ================================================================== + -- 8. Two log-channels mapped to same panel_widget_id → 2 entries tracked + -- ================================================================== + setup() + notify.create_channel{ id = "log-a", filter = {"ev-a"}, display_mode = "log" } + notify.create_channel{ id = "log-b", filter = {"ev-b"}, display_mode = "log" } + display.attach_mode("log", { panel_widget_id = "game-log" }) + local tracked = display._test_get_widget_channels("game-log") + T.equals(#tracked, 2, "two log-channels both tracked under same panel_widget_id") + + -- ================================================================== + -- 9. detach_mode("toast") removes toast-channel subscriptions + -- ================================================================== + setup() + notify.create_channel{ id = "toast-a", filter = {"t"}, display_mode = "toast" } + display.attach_mode("toast", {}) + local count_before = display._test_subscriber_count() + display.detach_mode("toast") + local count_after = display._test_subscriber_count() + T.assert(count_before > count_after, + "detach_mode('toast') reduces subscriber count") + T.equals(count_after, 0, "subscriber count is 0 after detach_mode('toast')") + + -- ================================================================== + -- 10. Detail overwrite: post msg-A then msg-B → last_detail == B + -- ================================================================== + setup() + notify.create_channel{ id = "ow-ch", filter = {"ow"}, display_mode = "detail" } + local ow_widget = display.create_detail_widget({ widget_id = "detail-panel", title = "D" }) + panel.register("detail-panel", ow_widget) + display.attach_mode("detail", { panel_widget_id = "detail-panel" }) + notify.post{ tags = {"ow"}, text = "A" } + notify.post{ tags = {"ow"}, text = "B" } + local overwritten = display._test_get_last_detail() + T.equals(overwritten.text, "B", + "second detail post overwrites first (_test_get_last_detail.text == 'B')") +end + +return M diff --git a/manifest.core b/manifest.core new file mode 100644 index 0000000..754e521 --- /dev/null +++ b/manifest.core @@ -0,0 +1,6 @@ +{ + "id": "lib-core.notify-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..59681ab --- /dev/null +++ b/manifest.lib @@ -0,0 +1,11 @@ +{ + "id": "lib-core.notify-display-test", + "role": "test", + "version": "0.1.0", + "api_min": "0.1", + "deps": [ + {"id": "lib-core.notify-display", "version": "0.1.0"}, + {"id": "lib-core.notify", "version": "0.1.0"}, + {"id": "lib-core.panel", "version": "0.1.0"} + ] +}