-- lib-core.crafting-display-test — v0.1.0 -- 9 assertions covering widget creation, action lifecycle, default-empty, -- row visibility (is_known filter), per-row availability, resolver overrides, -- and the panel widget-contract (render(ctx) populating _render_rows). -- Pattern follows P.2.4 Test-Module-Pattern; TAP output via engine.test.* local composition = require("lib-core.composition") local inventory = require("lib-core.inventory-list") local crafting = require("lib-core.crafting") local display = require("lib-core.crafting-display") local T = engine.test local M = {} -- --------------------------------------------------------------------- -- Template definitions (one-time, shared across tests). -- composition does not expose a clear-all, so we never re-define. -- All template-ids namespaced to this test lib. -- --------------------------------------------------------------------- composition.define_template{ id = "craft_display_test.container", properties = { sprite_path = "sprites/bag.png", position = {x = 0, y = 0}, }, tags = {"renderable"}, container = { kind = "list" }, } composition.define_template{ id = "craft_display_test.rock", properties = { stack_mode = "individual", sprite_path = "sprites/rock.png", position = {x = 0, y = 0}, }, tags = {"renderable", "item"}, } composition.define_template{ id = "craft_display_test.stick", properties = { stack_mode = "individual", sprite_path = "sprites/stick.png", position = {x = 0, y = 0}, }, tags = {"renderable", "item"}, } composition.define_template{ id = "craft_display_test.rock_pick", properties = { stack_mode = "individual", sprite_path = "sprites/pick.png", position = {x = 0, y = 0}, }, tags = {"renderable", "item"}, } -- --------------------------------------------------------------------- -- Fixture helpers -- --------------------------------------------------------------------- local function setup_container(items) crafting._test_clear_all() local container = composition.create{ template = "craft_display_test.container" } for _, spec in ipairs(items) do for _ = 1, spec.count do local e = composition.create{ template = spec.template } inventory.add(container, e) end end return container end -- --------------------------------------------------------------------- -- Tests -- --------------------------------------------------------------------- function M.run_tests(_ctx) -- ================================================================ -- §1: create(container, opts) returns widget_def with render, -- handle_input, title. -- ================================================================ crafting._test_clear_all() local container1 = composition.create{ template = "craft_display_test.container" } local widget1 = display.create(container1, { title = "Workbench" }) T.assert(widget1 ~= nil and type(widget1.render) == "function" and type(widget1.handle_input) == "function" and widget1.title == "Workbench", "1. create returns widget_def with render, handle_input, title set from opts") -- ================================================================ -- §2: register_action + unregister_action round-trip (count 0 -> 1 -> 0). -- ================================================================ crafting._test_clear_all() local container2 = composition.create{ template = "craft_display_test.container" } local widget2 = display.create(container2) local count_before = #widget2._actions display.register_action(widget2, "Craft", function(_rid, _ctx) end) local count_after_reg = #widget2._actions display.unregister_action(widget2, "Craft") local count_after_unreg = #widget2._actions T.assert(count_before == 0 and count_after_reg == 1 and count_after_unreg == 0, "2. register_action + unregister_action round-trip: count goes 0 -> 1 -> 0") -- ================================================================ -- §3: Duplicate label -> Loud-Error matching -- 'crafting%-display.register_action.*duplicate'. -- ================================================================ crafting._test_clear_all() local container3 = composition.create{ template = "craft_display_test.container" } local widget3 = display.create(container3) display.register_action(widget3, "Craft", function() end) local ok3, err3 = pcall(display.register_action, widget3, "Craft", function() end) T.assert(not ok3 and type(err3) == "string" and string.find(err3, "crafting%-display%.register_action") ~= nil and string.find(err3, "duplicate") ~= nil, "3. duplicate label -> Loud-Error matching 'crafting-display.register_action.*duplicate'") -- ================================================================ -- §4: Non-function callback -> Loud-Error matching -- 'crafting%-display.register_action.*callback'. -- ================================================================ crafting._test_clear_all() local container4 = composition.create{ template = "craft_display_test.container" } local widget4 = display.create(container4) local ok4, err4 = pcall(display.register_action, widget4, "Craft", "not_a_function") T.assert(not ok4 and type(err4) == "string" and string.find(err4, "crafting%-display%.register_action") ~= nil and string.find(err4, "callback") ~= nil, "4. non-function callback -> Loud-Error matching 'crafting-display.register_action.*callback'") -- ================================================================ -- §5: Default action-set is empty (count=0 directly after create). -- ================================================================ crafting._test_clear_all() local container5 = composition.create{ template = "craft_display_test.container" } local widget5 = display.create(container5) T.assert(#widget5._actions == 0, "5. default action-set is empty directly after create") -- ================================================================ -- §6: Rows filtered via is_known. Two recipes — one with -- is_known=function()->false → only the visible one appears. -- ================================================================ local container6 = setup_container{} crafting.define_recipe{ id = "visible_r6", inputs = { { template = "craft_display_test.rock", count = 1 } }, output = { template = "craft_display_test.rock_pick", count = 1 }, name = "Visible Recipe", } crafting.define_recipe{ id = "hidden_r6", inputs = { { template = "craft_display_test.rock", count = 1 } }, output = { template = "craft_display_test.rock_pick", count = 1 }, name = "Hidden Recipe", is_known = function(_) return false end, } local widget6 = display.create(container6) local rows6 = display._test_get_rows(widget6) local found_visible = false local found_hidden = false for _, row in ipairs(rows6) do if row.recipe.id == "visible_r6" then found_visible = true end if row.recipe.id == "hidden_r6" then found_hidden = true end end T.assert(#rows6 == 1 and found_visible and not found_hidden, "6. rows filtered via is_known: only known recipes appear in row-list") -- ================================================================ -- §7: Per-row availability flag matches can_craft.ok. -- Container with 1x rock → recipe needing rock available=true, -- recipe needing stick (missing) available=false. -- ================================================================ local container7 = setup_container{ { template = "craft_display_test.rock", count = 1 }, } crafting.define_recipe{ id = "have_rock_r7", inputs = { { template = "craft_display_test.rock", count = 1 } }, output = { template = "craft_display_test.rock_pick", count = 1 }, name = "Rock Recipe", } crafting.define_recipe{ id = "need_stick_r7", inputs = { { template = "craft_display_test.stick", count = 1 } }, output = { template = "craft_display_test.rock_pick", count = 1 }, name = "Stick Recipe", } local widget7 = display.create(container7) local rows7 = display._test_get_rows(widget7) local available_map = {} for _, row in ipairs(rows7) do available_map[row.recipe.id] = row.available end T.assert(#rows7 == 2 and available_map["have_rock_r7"] == true and available_map["need_stick_r7"] == false, "7. per-row availability flag matches can_craft.ok") -- ================================================================ -- §8: Resolver overrides invoked after set_icon/label/summary_resolver. -- ================================================================ local container8 = setup_container{} crafting.define_recipe{ id = "resolver_r8", inputs = { { template = "craft_display_test.rock", count = 1 } }, output = { template = "craft_display_test.rock_pick", count = 1 }, name = "Default Name", } local widget8 = display.create(container8) display.set_icon_resolver(widget8, function(_r) return "ICON_X" end) display.set_label_resolver(widget8, function(_r) return "LABEL_X" end) display.set_summary_resolver(widget8, function(_r) return "SUMMARY_X" end) local recipe8 = crafting.get_recipe("resolver_r8") local icon8 = display._test_resolve_icon(widget8, recipe8) local label8 = display._test_resolve_label(widget8, recipe8) local summary8 = display._test_resolve_summary(widget8, recipe8) T.assert(icon8 == "ICON_X" and label8 == "LABEL_X" and summary8 == "SUMMARY_X", "8. resolver overrides (icon/label/summary) invoked after set_*_resolver") -- ================================================================ -- §9: widget.render(ctx) with a panel-shaped ctx populates -- widget._render_rows. Drives the panel widget contract: -- render(ctx) where ctx = {bounds, theme, is_focused}. Catches -- contract-drift between this lib and lib-core.panel. -- ================================================================ local container9 = setup_container{} crafting.define_recipe{ id = "render_r9", inputs = { { template = "craft_display_test.rock", count = 1 } }, output = { template = "craft_display_test.rock_pick", count = 1 }, name = "Render Recipe", } local widget9 = display.create(container9, { title = "C", widget_id = "cw" }) local ctx9 = { bounds = { x = 0, y = 0, w = 400, h = 300 }, theme = { row_height = 28, padding = 8, text_color = 0xFFFFFFFF, text_color_dim = 0x999999FF, font_size_body = 14, }, is_focused = false, } -- engine.render.draw_text is registered but errors in test_mode (no -- OpenGL context); stub it for the duration of this render call so -- the layout pass runs to completion. local saved_draw_text = engine.render.draw_text engine.render.draw_text = function() end local render_ok, render_err = pcall(widget9.render, ctx9) engine.render.draw_text = saved_draw_text T.assert(render_ok and widget9._render_rows ~= nil and #widget9._render_rows == 1 and widget9._render_rows[1].recipe_id == "render_r9", "9. widget.render(ctx) per panel widget contract populates _render_rows" .. (render_ok and "" or (" (render errored: " .. tostring(render_err) .. ")"))) end return M