From 7f6a1e4320f1acced9d25622d9f66b6e71098c30 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sun, 13 Sep 2026 12:41:33 +0200 Subject: [PATCH] test: cover Session and Ledger --- tests/testSlotAction.cpp | 156 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 tests/testSlotAction.cpp diff --git a/tests/testSlotAction.cpp b/tests/testSlotAction.cpp new file mode 100644 index 0000000..c33fe23 --- /dev/null +++ b/tests/testSlotAction.cpp @@ -0,0 +1,156 @@ +#include "testHarness.h" +#include "vf/SlotAction.h" + +#include +#include +#include + +using namespace vf; + +namespace { + +const CraftingRegistry& sharedRegistry() { + static CraftingRegistry registry; + return registry; +} + +struct Session { + ContainerMenu menu; + CursorStack cursor; + DragState drag; + + explicit Session(MenuKind kind) : menu(kind, &sharedRegistry()) {} + + SlotActionResult click(ClickKind kind, int slotIndex) { + return applySlotAction(menu, cursor, drag, clickAt(kind, slotIndex)); + } + SlotActionResult hotbar(int slotIndex, int button) { + return applySlotAction(menu, cursor, drag, hotbarSwapAt(slotIndex, button)); + } +}; + +// The seven items the random walk shuffles around. Tools are in the mix so the +// walk also has stacks that refuse to merge. +const std::array walkItems = {ItemId::Stone, ItemId::Planks, ItemId::Sand, + ItemId::CoalLump, ItemId::Apple, ItemId::IronPickaxe, + ItemId::RawBeef}; + +struct Ledger { + std::array counts{}; + + void snapshot(const ContainerMenu& menu, const CursorStack& cursor) { + for (std::size_t i = 0; i < walkItems.size(); ++i) { + counts[i] = totalAcrossMenuAndCursor(menu, cursor, walkItems[i]); + } + } +}; + +void seedMenu(ContainerMenu& menu, uint32_t& rng) { + for (int i = 0; i < menu.slotCount(); ++i) { + if (menu.slot(i).takeOnly) continue; + if (stackRandom(rng) % 3u == 0u) continue; + ItemId item = walkItems[stackRandom(rng) % walkItems.size()]; + MetaStack stack = makeStack(item, 1 + static_cast(stackRandom(rng) % 40u)); + if (!menu.canPlace(i, stack)) continue; + menu.slot(i).cell.insert(stack); + } +} + +SlotAction randomAction(const ContainerMenu& menu, uint32_t& rng) { + ClickKind kind = static_cast(stackRandom(rng) % + static_cast(ClickKind::Count)); + int slotIndex = static_cast(stackRandom(rng) % static_cast(menu.slotCount())); + // Every eighth action aims outside the window, which is the only way to + // reach the throw-away path with a full cursor. + if (stackRandom(rng) % 8u == 0u) slotIndex = -1; + if (kind == ClickKind::HotbarSwap) { + return hotbarSwapAt(slotIndex, static_cast(stackRandom(rng) % 9u)); + } + return clickAt(kind, slotIndex); +} + +struct WalkCoverage { + int applied = 0; + int dropped = 0; + int distinctKindsApplied = 0; +}; + +WalkCoverage runConservationWalk(MenuKind kind, uint32_t seed, int steps) { + Session session(kind); + uint32_t rng = seed; + seedMenu(session.menu, rng); + + Ledger before; + before.snapshot(session.menu, session.cursor); + + std::array thrown{}; + std::array(ClickKind::Count)> perKind{}; + WalkCoverage coverage; + for (int step = 0; step < steps; ++step) { + SlotAction action = randomAction(session.menu, rng); + SlotActionResult outcome = + applySlotAction(session.menu, session.cursor, session.drag, action); + if (outcome.applied) { + ++coverage.applied; + ++perKind[static_cast(action.kind)]; + } + coverage.dropped += outcome.dropped; + CHECK_EQ(outcome.created, 0); + CHECK_EQ(outcome.dropped, static_cast([&] { + int sum = 0; + for (const MetaStack& lost : outcome.droppedStacks) sum += lost.count; + return sum; + }())); + for (const MetaStack& lost : outcome.droppedStacks) { + for (std::size_t i = 0; i < walkItems.size(); ++i) { + if (lost.item == walkItems[i]) thrown[i] += lost.count; + } + } + } + + Ledger after; + after.snapshot(session.menu, session.cursor); + for (std::size_t i = 0; i < walkItems.size(); ++i) { + CHECK_EQ(after.counts[i] + thrown[i], before.counts[i]); + } + for (int hits : perKind) { + if (hits > 0) ++coverage.distinctKindsApplied; + } + return coverage; +} + +} // namespace + +VF_TEST(everyClickKindHasAName) { + for (int i = 0; i < static_cast(ClickKind::Count); ++i) { + CHECK(std::string(clickKindName(static_cast(i))) != std::string("unknown")); + } +} + +VF_TEST(aLeftClickPicksUpTheWholeStackAndPutsItDownAgain) { + Session session(MenuKind::Chest); + session.menu.slot(0).cell.stack = makeStack(ItemId::Stone, 37); + + SlotActionResult pickup = session.click(ClickKind::Left, 0); + CHECK(pickup.applied); + CHECK_EQ(pickup.moved, 37); + CHECK(pickup.cursorChanged); + CHECK_EQ(session.cursor.held.count, 37); + CHECK(session.menu.stackAt(0).empty()); + + SlotActionResult drop = session.click(ClickKind::Left, 4); + CHECK(drop.applied); + CHECK_EQ(drop.moved, 37); + CHECK(session.cursor.empty()); + CHECK_EQ(session.menu.stackAt(4).count, 37); + + CHECK(!session.click(ClickKind::Left, 9).applied); + CHECK(!session.click(ClickKind::Left, 999).applied); +} + +VF_TEST(aLeftClickMergesIntoAMatchingStackAndSwapsWithAForeignOne) { + Session session(MenuKind::Chest); + session.menu.slot(0).cell.stack = makeStack(ItemId::Sand, 50); + session.menu.slot(1).cell.stack = makeStack(ItemId::Sand, 60); + session.menu.slot(2).cell.stack = makeStack(ItemId::Planks, 8); +