Files
gen1recomp/tests/save_editor_task8_tests.lua
T
2026-07-17 20:30:02 -04:00

269 lines
9.5 KiB
Lua

-- Headless tests for tools/save-editor/panels/MapBrowser.lua.
-- Run from repo root: lua5.4 tests/save_editor_task8_tests.lua
-- (love_stub lacks push/pop/scale/scissor; MapBrowser skips real
-- rendering under those but still runs all click/button logic, which is
-- what these tests exercise via Kit.beginFrame like the other panels.)
package.path = package.path .. ";./?.lua;./?/init.lua;./tools/save-editor/?.lua"
.. ";./tools/save-editor/panels/?.lua"
local love_stub = require("tests.love_stub")
love = love_stub
local passed, failed = 0, 0
local function check(cond, msg)
if cond then
passed = passed + 1
else
failed = failed + 1
print("FAIL: " .. msg)
end
end
local function eq(a, b, msg)
check(a == b, msg .. string.format(" (got %s, want %s)", tostring(a), tostring(b)))
end
print("== save editor task 8 (map browser) tests ==")
local Data = require("src.core.Data")
Data:load()
local SaveData = require("src.core.SaveData")
local State = require("State")
local Kit = require("Kit")
local MapBrowser = require("MapBrowser")
local LIST_W, LIST_H, ROW_H = 200, 300, 20
local MAX_ROWS = math.floor(LIST_H / ROW_H)
local VIEW_W, VIEW_H = 480, 432
local function newState()
local S = State.new()
S.data = Data
S.save = SaveData.newGame()
S.mapId = S.save.player.map -- PALLET_TOWN
return S
end
local px, py = 12, 80
local vx, vy = px + LIST_W + 20, py + 24
-- ---------------------------------------------------------------- list
do
local S = newState()
local ids = {}
for id in pairs(Data.maps) do table.insert(ids, id) end
table.sort(ids)
check(#ids > 200, "generated data has lots of maps")
-- click the 3rd row of the map id list -> selects that map, no crash
-- despite love_stub missing push/pop/scale/scissor
Kit.beginFrame(px + 10, py + 24 + 2 * ROW_H + 5, true)
MapBrowser.draw(S, Kit, px, py)
eq(S.mapId, ids[3], "clicking list row 3 selects the 3rd sorted map id")
check(S.mapClickCell == nil, "switching maps clears any selected cell")
end
do
local S = newState()
-- Next then Prev should return to the first page
Kit.beginFrame(px + 64 + 5, py + 24 + MAX_ROWS * ROW_H + 8 + 5, true) -- Next
MapBrowser.draw(S, Kit, px, py)
eq(S.mapListScroll, MAX_ROWS, "Next advances one page")
Kit.beginFrame(px + 5, py + 24 + MAX_ROWS * ROW_H + 8 + 5, true) -- Prev
MapBrowser.draw(S, Kit, px, py)
eq(S.mapListScroll, 0, "Prev returns to page 0")
end
-- ------------------------------------------------------------- click-to-cell
do
local S = newState() -- PALLET_TOWN
-- (5,6) is a known-walkable, non-warp cell (tests/run_tests.lua uses
-- the same ground truth); zoom 2 means 32 screen px per cell.
local mx = vx + 5 * 16 * S.mapZoom + 4
local my = vy + 6 * 16 * S.mapZoom + 4
Kit.beginFrame(mx, my, true)
MapBrowser.draw(S, Kit, px, py)
check(S.mapClickCell ~= nil, "clicking inside the viewport selects a cell")
if S.mapClickCell then
eq(S.mapClickCell.cx, 5, "selected cell cx")
eq(S.mapClickCell.cy, 6, "selected cell cy")
end
-- clicking outside the viewport (e.g. over the list) must not select a cell
S.mapClickCell = nil
Kit.beginFrame(px + 5, py + 5, true)
MapBrowser.draw(S, Kit, px, py)
check(S.mapClickCell == nil, "clicking outside the viewport doesn't select a cell")
end
-- ---------------------------------------------------------------- set player
do
local S = newState()
S.mapClickCell = { cx = 3, cy = 4 }
local by = vy + VIEW_H + 8
Kit.beginFrame(vx + 10, by + 22 + 10, true) -- Set player here
MapBrowser.draw(S, Kit, px, py)
eq(S.save.player.map, S.mapId, "Set player here updates player.map")
eq(S.save.player.x, 3, "Set player here updates player.x")
eq(S.save.player.y, 4, "Set player here updates player.y")
check(S.dirty == true, "Set player here marks dirty")
end
do
local S = newState()
local by = vy + VIEW_H + 8
Kit.beginFrame(vx + 10, by + 22 + 10, true) -- Set player here, no cell selected
MapBrowser.draw(S, Kit, px, py)
check(S.status:match("Click a cell first"), "Set player here without a selection warns")
end
-- ------------------------------------------------------------- lastOutdoor
do
local S = newState() -- PALLET_TOWN has connections -> outdoor
S.mapClickCell = { cx = 5, cy = 6 }
local by = vy + VIEW_H + 8
Kit.beginFrame(vx + 150 + 10, by + 22 + 10, true) -- Set lastOutdoor here
MapBrowser.draw(S, Kit, px, py)
check(S.save.lastOutdoor ~= nil, "Set lastOutdoor here sets lastOutdoor")
if S.save.lastOutdoor then
eq(S.save.lastOutdoor.id, "PALLET_TOWN", "lastOutdoor.id")
eq(S.save.lastOutdoor.x, 5, "lastOutdoor.x")
eq(S.save.lastOutdoor.y, 6, "lastOutdoor.y")
end
end
do
-- an interior with no connections and not in save.visited -> rejected
local S = newState()
S.mapId = "REDS_HOUSE_1F"
S.mapClickCell = { cx = 1, cy = 1 }
local by = vy + VIEW_H + 8
Kit.beginFrame(vx + 150 + 10, by + 22 + 10, true)
MapBrowser.draw(S, Kit, px, py)
check(S.save.lastOutdoor == nil, "Set lastOutdoor here refuses a non-outdoor map")
check(S.status:match("outdoor"), "status explains the refusal")
end
-- ---------------------------------------------------------------- lastHeal
do
local S = newState()
S.mapClickCell = { cx = 2, cy = 8 }
local by = vy + VIEW_H + 8
Kit.beginFrame(vx + 320 + 10, by + 22 + 10, true) -- Set lastHeal here
MapBrowser.draw(S, Kit, px, py)
check(S.save.lastHeal ~= nil, "Set lastHeal here sets lastHeal")
eq(S.save.lastHeal.map, S.mapId, "lastHeal.map")
eq(S.save.lastHeal.x, 2, "lastHeal.x")
eq(S.save.lastHeal.y, 8, "lastHeal.y")
end
-- --------------------------------------------------------------- warp jump
do
local S = newState()
S.mapId = "PALLET_TOWN"
local map = require("src.world.MapLoader").load(Data, "PALLET_TOWN")
check(#map.def.warps > 0, "Pallet Town has warps to test with")
local w = map.def.warps[1]
local mx = vx + w.x * 16 * S.mapZoom + 4
local my = vy + w.y * 16 * S.mapZoom + 4
Kit.beginFrame(mx, my, true)
MapBrowser.draw(S, Kit, px, py)
check(S.mapId ~= "PALLET_TOWN" or w.destMap == "PALLET_TOWN",
"clicking a warp cell jumps S.mapId to its destination")
check(S.status:match("Followed warp"), "warp click sets a status message")
end
do
-- LAST_MAP warp with no remembered outdoor map must not crash, and
-- must not silently move the view.
local S = newState()
S.mapId = "REDS_HOUSE_1F"
local MapLoader = require("src.world.MapLoader")
local map = MapLoader.load(Data, "REDS_HOUSE_1F")
local lastMapWarp
for _, w in ipairs(map.def.warps) do
if w.destMap == "LAST_MAP" then lastMapWarp = w end
end
if lastMapWarp then
S.save.lastOutdoor = nil
local mx = vx + lastMapWarp.x * 16 * S.mapZoom + 4
local my = vy + lastMapWarp.y * 16 * S.mapZoom + 4
Kit.beginFrame(mx, my, true)
local ok = pcall(MapBrowser.draw, S, Kit, px, py)
check(ok, "LAST_MAP warp with no lastOutdoor doesn't crash")
eq(S.mapId, "REDS_HOUSE_1F", "LAST_MAP warp with no lastOutdoor doesn't move the view")
check(S.status:match("lastOutdoor"), "status explains the skipped warp")
else
check(true, "REDS_HOUSE_1F has no LAST_MAP warp to test (skipped)")
end
end
do
-- Indigo Plateau uses tileset PLATEAU -> plateau.png (not indigo.png).
-- Following its lobby door must remember lastOutdoor so the lobby's
-- LAST_MAP mats return here (same as the game's outsideTilesets).
local S = newState()
S.mapId = "INDIGO_PLATEAU"
S.save.lastOutdoor = { id = "ROUTE_22", x = 8, y = 5 }
local MapLoader = require("src.world.MapLoader")
local indigo = MapLoader.load(Data, "INDIGO_PLATEAU")
eq(indigo.tileset.image, "assets/generated/tilesets/plateau.png",
"Indigo Plateau tileset image is plateau.png")
local door = indigo.def.warps[1]
local mx = vx + door.x * 16 * S.mapZoom + 4
local my = vy + door.y * 16 * S.mapZoom + 4
Kit.beginFrame(mx, my, true)
MapBrowser.draw(S, Kit, px, py)
eq(S.mapId, "INDIGO_PLATEAU_LOBBY", "Indigo door warp jumps to the lobby")
check(S.save.lastOutdoor and S.save.lastOutdoor.id == "INDIGO_PLATEAU",
"following Indigo door remembers lastOutdoor as INDIGO_PLATEAU")
local lobby = MapLoader.load(Data, "INDIGO_PLATEAU_LOBBY")
local exitWarp
for _, w in ipairs(lobby.def.warps) do
if w.destMap == "LAST_MAP" then exitWarp = w break end
end
check(exitWarp ~= nil, "Indigo lobby has a LAST_MAP exit")
-- re-zero the camera so the exit-cell click math matches cellAtScreen
S.mapCamX, S.mapCamY = 0, 0
mx = vx + exitWarp.x * 16 * S.mapZoom + 4
my = vy + exitWarp.y * 16 * S.mapZoom + 4
Kit.beginFrame(mx, my, true)
MapBrowser.draw(S, Kit, px, py)
eq(S.mapId, "INDIGO_PLATEAU", "Indigo lobby LAST_MAP exit returns to the plateau")
end
-- --------------------------------------------------------- zoom / pan input
do
local S = newState()
local z0 = S.mapZoom
MapBrowser.wheelmoved(S, 1)
check(S.mapZoom > z0, "wheelmoved(+) zooms in")
MapBrowser.wheelmoved(S, -1)
MapBrowser.wheelmoved(S, -1)
check(S.mapZoom < z0, "wheelmoved(-) zooms out")
S.mapZoom = 1
for _ = 1, 20 do MapBrowser.wheelmoved(S, -1) end
check(S.mapZoom >= 1, "zoom clamps at a minimum")
end
do
local S = newState()
S.mapCamX, S.mapCamY = 0, 0
MapBrowser.keypressed(S, "d")
eq(S.mapCamX, 16, "keypressed d pans camera right by one cell")
MapBrowser.keypressed(S, "down")
eq(S.mapCamY, 16, "keypressed down pans camera down by one cell")
MapBrowser.keypressed(S, "unrelatedkey")
eq(S.mapCamX, 16, "unrelated keys don't pan the camera")
end
print(string.format("save editor task 8 tests: %d passed, %d failed", passed, failed))
if failed > 0 then os.exit(1) end