-- Automated test suite for Game 3 Poké Mart system (shop_menu.lua, marts.lua, bag.lua). require("tests.game3_cache").requireData("game3_shop_menu_test") local ShopMenu = require("src.ui.game3.shop_menu") local Bag = require("src.core.game3.bag") local ItemsData = require("src.core.game3.items_data") local Marts = require("src.core.game3.marts") local Adapters = require("src.core.game3.scripting.adapters") local Message = require("src.ui.game3.message") local Stack = require("src.ui.game3.stack") local function make_input(pressedMap) pressedMap = pressedMap or {} return { wasPressed = function(self, key) return pressedMap[key] == true end, isDown = function(self, key) return pressedMap[key] == true end, } end local function assert_eq(a, b, msg) if a ~= b then error(string.format("ASSERTION FAILED: %s (expected %s, got %s)", msg or "", tostring(b), tostring(a)), 2) end end local function assert_true(val, msg) if not val then error(string.format("ASSERTION FAILED: %s (expected true, got %s)", msg or "", tostring(val)), 2) end end print("=== [TEST 1] Marts data lookup ===") Marts.ensure() local viridianItems = Marts.itemsFor(135731380) or Marts.itemsFor("g3:081649b8") or Marts.itemsFor(135678392) assert_true(viridianItems ~= nil and #viridianItems > 0, "Marts.itemsFor resolved stock list") print("Resolved mart items count:", #viridianItems) print("=== [TEST 2] ShopMenu Opening and Root Navigation ===") local closed = false local session = { money = 5000, bag = Bag.new(), } ShopMenu.show({ items = { 4, 13, 19, 20 }, -- POKE_BALL, POTION, ANTIDOTE, PARALYZE_HEAL session = session, onClose = function() closed = true end, }) assert_true(ShopMenu.isOpen(), "Shop is open") assert_eq(ShopMenu.mode, "root", "Initial mode is root") assert_eq(ShopMenu.cursor, 1, "Initial cursor on BUY") -- Navigate root menu ShopMenu.handleInput(make_input({ down = true })) assert_eq(ShopMenu.cursor, 2, "Cursor moved to SELL") ShopMenu.handleInput(make_input({ down = true })) assert_eq(ShopMenu.cursor, 3, "Cursor moved to SEE YA!") ShopMenu.handleInput(make_input({ down = true })) assert_eq(ShopMenu.cursor, 1, "Cursor wrapped back to BUY") print("=== [TEST 3] Enter Buy Menu & Quantity Fast-Scroll ===") ShopMenu.handleInput(make_input({ a = true })) assert_eq(ShopMenu.mode, "buy", "Entered BUY mode") assert_eq(ShopMenu.cursor, 1, "Cursor at first item (POKE_BALL)") -- Select Poké Ball (ID 4, price 200) ShopMenu.handleInput(make_input({ a = true })) assert_eq(ShopMenu.mode, "buy_qty", "Entered buy_qty mode") assert_eq(ShopMenu.qty, 1, "Initial quantity is 1") -- Fast scroll: Right (+10) -> qty = 11 ShopMenu.handleInput(make_input({ right = true })) assert_eq(ShopMenu.qty, 11, "Fast-scrolled right to 11") -- Left (-10) -> qty = 1 ShopMenu.handleInput(make_input({ left = true })) assert_eq(ShopMenu.qty, 1, "Fast-scrolled left to 1") -- Set qty to 10 ShopMenu.handleInput(make_input({ right = true })) ShopMenu.handleInput(make_input({ down = true })) assert_eq(ShopMenu.qty, 10, "Qty set to 10") print("=== [TEST 4] Buy Confirmation ===") -- Press A to confirm quantity -> mode is buy_confirm ShopMenu.handleInput(make_input({ a = true })) assert_eq(ShopMenu.mode, "buy_confirm", "Entered buy_confirm mode") assert_eq(ShopMenu.yesNoCursor, 1, "YES selected") -- Confirm purchase: 10 Poké Balls @ 200 = 2000 ShopMenu.handleInput(make_input({ a = true })) assert_eq(ShopMenu.mode, "buy_msg", "Entered buy_msg mode with success message") assert_eq(session.money, 3000, "Money deducted correctly (5000 - 2000 = 3000)") assert_eq(Bag.get(session.bag, 4), 10, "Bag received 10 Poké Balls") assert_eq(Bag.get(session.bag, 12), 0, "FRLG gives no Premier Ball bonus (shop.c:978 BuyMenuTryMakePurchase)") assert_eq(ShopMenu._status, "Here you are!\nThank you!", "Status is gText_HereYouGoThankYou") -- Dismiss message with A ShopMenu.handleInput(make_input({ a = true })) assert_eq(ShopMenu.mode, "buy", "Returned to buy mode") print("=== [TEST 5] 99 Poké Balls ===") session.money = 50000 ShopMenu.cursor = 1 -- Poké Ball ShopMenu.handleInput(make_input({ a = true })) assert_eq(ShopMenu.mode, "buy_qty", "Entered buy_qty mode") -- Fast scroll to 99 for _ = 1, 10 do ShopMenu.handleInput(make_input({ right = true })) end assert_eq(ShopMenu.qty, 99, "Qty clamped at 99") -- Confirm quantity ShopMenu.handleInput(make_input({ a = true })) assert_eq(ShopMenu.mode, "buy_confirm", "Entered buy_confirm") -- Confirm buy ShopMenu.handleInput(make_input({ a = true })) assert_eq(session.money, 50000 - (99 * 200), "Money deducted for 99 balls") assert_eq(Bag.get(session.bag, 4), 109, "Bag has 109 Poké Balls") assert_eq(Bag.get(session.bag, 12), 0, "no Premier Ball for 99 balls either") -- Dismiss message ShopMenu.handleInput(make_input({ a = true })) assert_eq(ShopMenu.mode, "buy", "Returned to buy mode") print("=== [TEST 6] 10 Great Balls ===") -- Add Great Ball (ID 3, price 600) to shop items ShopMenu._items = { 3, 4 } ShopMenu.cursor = 1 -- Great Ball ShopMenu.handleInput(make_input({ a = true })) assert_eq(ShopMenu.mode, "buy_qty", "Entered buy_qty for Great Ball") ShopMenu.handleInput(make_input({ right = true })) -- +10 -> 11 ShopMenu.handleInput(make_input({ down = true })) -- -> 10 assert_eq(ShopMenu.qty, 10, "10 Great Balls selected") ShopMenu.handleInput(make_input({ a = true })) -- Confirm qty ShopMenu.handleInput(make_input({ a = true })) -- Confirm buy assert_eq(Bag.get(session.bag, 3), 10, "Bag received 10 Great Balls") assert_eq(Bag.get(session.bag, 12), 0, "Great Balls yield no Premier Ball") ShopMenu.handleInput(make_input({ a = true })) -- Dismiss msg print("=== [TEST 7] Insufficient Funds & Full Bag Handlers ===") session.money = 100 -- Less than 200 for Poké Ball ShopMenu.cursor = 2 -- Poké Ball ShopMenu.handleInput(make_input({ a = true })) assert_eq(ShopMenu.mode, "buy_msg", "Entered buy_msg for insufficient funds") assert_eq(ShopMenu._status, "You don't have enough money.", "Error message set") ShopMenu.handleInput(make_input({ a = true })) assert_eq(ShopMenu.mode, "buy", "Returned safely to buy mode without soft-lock") print("=== [TEST 8] 0-Price Sell Filter & Sell Flow ===") -- Give player items: Master Ball (ID 1, price 0), Oak's Parcel (ID 360, price 0), Potion (ID 13, price 300, sell 150), Antidote (ID 19, price 100, sell 50) Bag.add(session.bag, 1, 1) -- Master Ball Bag.add(session.bag, 360, 1) -- Oak's Parcel Bag.add(session.bag, 13, 5) -- Potion (5) Bag.add(session.bag, 19, 2) -- Antidote (2) -- Cancel out of BUY back to ROOT ShopMenu.handleInput(make_input({ b = true })) assert_eq(ShopMenu.mode, "root", "Returned to root") assert_eq(ShopMenu._status, "Is there anything else I can do?", "Clerk greeting updated") -- Move to SELL and enter ShopMenu.handleInput(make_input({ down = true })) assert_eq(ShopMenu.cursor, 2, "Cursor on SELL") ShopMenu.handleInput(make_input({ a = true })) local Fade = require("src.ui.game3.fade") for _ = 1, 240 do if not Fade.isActive() then break end Fade.tick(1 / 60) end local BagMenu = require("src.ui.game3.bag_menu") -- pokefirered/src/shop.c:288 CB2_GoToSellMenu assert_eq(ShopMenu.mode, "sell", "Entered sell mode") assert_true(BagMenu.isOpen() and BagMenu._location == "shop", "SELL opened the bag in shop mode") -- pokefirered/src/shop.c:330 BagMenu.close() assert_eq(ShopMenu.mode, "root", "Returned to root from sell") assert_eq(ShopMenu._status, "Is there anything else I can do?", "Clerk asks again after selling") print("=== [TEST 9] Close Poké Mart and Resume VM ===") -- Exit via SEE YA! (cursor 3) ShopMenu.cursor = 3 ShopMenu.handleInput(make_input({ a = true })) assert_false = function(v, m) if v then error(m or "assert false failed") end end assert_false(ShopMenu.isOpen(), "Shop is closed") assert_true(closed, "onClose callback fired successfully") print("=== [TEST 10] Script Adapter openShop Integration ===") local adapterDone = false Message.show("Some stayed clerk message...", { stay = true }) assert_true(Message.isOpen(), "Field message was open before openShop") local adapters = Adapters.host(nil, nil, nil) adapters.openShop(135731380, function() adapterDone = true end) assert_false(Message.isOpen(), "Field Message was cleanly closed by openShop") assert_true(ShopMenu.isOpen(), "ShopMenu is open via adapter") -- Close shop ShopMenu.close() assert_false(ShopMenu.isOpen(), "ShopMenu closed") assert_true(adapterDone, "Adapter done callback invoked") print("=== [TEST 11] Shop Camera Mode State ===") ShopMenu.show({ items = { 4, 13 } }) assert_true(ShopMenu.isOpen(), "Shop open") assert_false(ShopMenu.isShopCamera(), "Root mode is not shop camera") local inputA = make_input({ a = true }) ShopMenu.handleInput(inputA) assert_eq(ShopMenu.mode, "buy", "Switched to buy mode") assert_true(ShopMenu.isShopCamera(), "Buy mode activates shop camera") ShopMenu.close() assert_false(ShopMenu.isShopCamera(), "Closed shop deactivates shop camera") print("=== [TEST 12] MoneyBox Cleanup on Shop Close ===") local MoneyBox = require("src.ui.game3.money_box") MoneyBox.show(19, 1, 1000) assert_true(MoneyBox.isVisible(), "MoneyBox initially visible") ShopMenu.show({ items = { 4, 13 } }) assert_false(MoneyBox.isVisible(), "MoneyBox hidden when ShopMenu opens") ShopMenu.close() assert_false(MoneyBox.isVisible(), "MoneyBox remains hidden after ShopMenu closes") print("\nALL 12 POKÉ MART TESTS PASSED!")