Files
gen1recomp/tests/save_editor_task8_tests.lua
T
bryanthaboi f5e02b8dbe CLOSES #2619
2026-10-02 07:07:05 -04:00

202 lines
7.4 KiB
Lua

-- Headless tests for the save editor's map browser behaviour.
-- Run from repo root: luajit tests/save_editor_task8_tests.lua
-- (also chained from tests/run_save_editor_tests.lua so CI covers it)
--
-- The spawn-point writes and the outdoor rule live in tools/save-editor/Ops.lua
-- and are asserted there; the panel-owned bits still tested here are the ones
-- with no other home: map selection, keyboard panning and the zoom clamp.
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 Ops = require("Ops")
local MapLoader = require("src.world.MapLoader")
local MapBrowser = require("MapBrowser")
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
-- outdoor detection ---------------------------------------------------
do
local S = newState()
local pallet = MapLoader.load(Data, "PALLET_TOWN")
check(Ops.isOutdoor(S, pallet), "PALLET_TOWN is outdoor (OVERWORLD tileset)")
local indoor
for id in pairs(Data.maps) do
local ok, map = pcall(MapLoader.load, Data, id)
if ok and not Ops.isOutdoor(S, map) then indoor = map break end
end
check(indoor ~= nil, "the dataset has at least one indoor map")
-- a visited fly spot counts as outdoor even when the tileset does not
if indoor then
S.save.visited = S.save.visited or {}
S.save.visited[indoor.id] = true
check(Ops.isOutdoor(S, indoor),
"a visited map counts as outdoor even with an indoor tileset")
S.save.visited[indoor.id] = nil
end
end
-- spawn points --------------------------------------------------------
do
local S = newState()
S.mapId = "VIRIDIAN_CITY"
S.mapClickCell = nil
S.dirty = false
check(Ops.setPlayerHere(S) == false, "setPlayerHere refuses with no cell selected")
check(S.dirty == false, "a refused setPlayerHere does not dirty the save")
check(S.status:match("Click a cell first") ~= nil,
"a refused setPlayerHere explains itself")
S.mapClickCell = { cx = 7, cy = 9 }
check(Ops.setPlayerHere(S) == true, "setPlayerHere writes with a cell selected")
eq(S.save.player.map, "VIRIDIAN_CITY", "setPlayerHere moves the player's map")
eq(S.save.player.x, 7, "setPlayerHere moves the player's x")
eq(S.save.player.y, 9, "setPlayerHere moves the player's y")
check(S.dirty, "setPlayerHere dirties the save")
check(Ops.setLastHeal(S) == true, "setLastHeal writes with a cell selected")
eq(S.save.lastHeal.map, "VIRIDIAN_CITY", "setLastHeal records the map")
eq(S.save.lastHeal.x, 7, "setLastHeal records the cell")
end
do
-- lastOutdoor refuses a map the game would not accept as an outdoor source
local S = newState()
local indoor
for id in pairs(Data.maps) do
local ok, map = pcall(MapLoader.load, Data, id)
if ok and not Ops.isOutdoor(S, map) then indoor = map break end
end
check(indoor ~= nil, "found an indoor map to try")
S.mapId = indoor.id
S.mapClickCell = { cx = 1, cy = 1 }
S.dirty = false
check(Ops.setLastOutdoor(S, indoor) == false,
"setLastOutdoor refuses a non-outdoor map")
check(S.dirty == false, "a refused setLastOutdoor does not dirty the save")
check(S.status:match("outdoor") ~= nil, "a refused setLastOutdoor explains itself")
local pallet = MapLoader.load(Data, "PALLET_TOWN")
S.mapId = "PALLET_TOWN"
check(Ops.setLastOutdoor(S, pallet) == true, "setLastOutdoor accepts an outdoor map")
eq(S.save.lastOutdoor.id, "PALLET_TOWN", "setLastOutdoor records the map id")
eq(S.save.lastOutdoor.x, 1, "setLastOutdoor records the cell")
end
-- panel-owned view state ----------------------------------------------
do
local S = newState()
MapBrowser.select(S, "VIRIDIAN_CITY")
eq(S.mapId, "VIRIDIAN_CITY", "select switches the viewed map")
eq(S.mapClickCell, nil, "select drops the previous cell selection")
eq(S._mapCenteredFor, nil,
"select defers centring to the next draw, which knows the viewport size")
check(S.status:match("VIRIDIAN_CITY") ~= nil, "select says what it is showing")
end
do
local S = newState()
S.mapCamX, S.mapCamY = 0, 0
MapBrowser.keypressed(S, "right")
eq(S.mapCamX, 16, "right pans one cell east")
MapBrowser.keypressed(S, "left")
eq(S.mapCamX, 0, "left pans back")
MapBrowser.keypressed(S, "down")
eq(S.mapCamY, 16, "down pans one cell south")
MapBrowser.keypressed(S, "w")
eq(S.mapCamY, 0, "WASD pans as well as the arrows")
local before = S.mapCamX
MapBrowser.keypressed(S, "escape")
eq(S.mapCamX, before, "a non-pan key does not move the camera")
end
do
local S = newState()
S.mapZoom = 2
MapBrowser.wheelmoved(S, 1)
check(S.mapZoom > 2, "wheel up zooms in")
for _ = 1, 40 do MapBrowser.wheelmoved(S, 1) end
eq(S.mapZoom, 4, "zoom clamps at 4x")
for _ = 1, 80 do MapBrowser.wheelmoved(S, -1) end
eq(S.mapZoom, 0.03125, "zoom can show large maps on a phone")
end
do
local S = newState()
S._mapViewW, S._mapViewH = 280, 160
MapBrowser.fit(S, { widthCells = 10, heightCells = 16 })
check(S.mapZoom * 160 <= 280 and S.mapZoom * 256 <= 160, "Fit shows the whole indoor map")
check(S.mapAutoFit, "Fit keeps the map fitted after resizing")
local cx = S.mapCamX + S._mapViewW / (2 * S.mapZoom)
local cy = S.mapCamY + S._mapViewH / (2 * S.mapZoom)
MapBrowser.wheelmoved(S, 1)
eq(S.mapCamX + S._mapViewW / (2 * S.mapZoom), cx, "zoom keeps the viewed center fixed horizontally")
eq(S.mapCamY + S._mapViewH / (2 * S.mapZoom), cy, "zoom keeps the viewed center fixed vertically")
check(not S.mapAutoFit, "manual zoom leaves Fit mode")
local zoom = S.mapZoom
MapBrowser.wheelmoved(S, 0)
eq(S.mapZoom, zoom, "zero wheel motion does not change zoom")
end
do
local S = newState()
S.tab, S.mapSection, S._mapStacked = "map", "view", true
S.mapZoom, S.mapCamX, S.mapCamY = 1, 10, 20
S._mapViewRect = { x = 20, y = 100, w = 280, h = 300 }
check(not MapBrowser.touchpressed(S, "outside", 10, 90), "pinch ignores touches outside the map")
check(not MapBrowser.touchpressed(S, "first", 100, 200), "first finger starts a normal pan")
check(MapBrowser.touchpressed(S, "second", 200, 200), "second finger starts a pinch")
check(MapBrowser.touchmoved(S, "second", 300, 200), "pinch movement is handled")
eq(S.mapZoom, 2, "doubling the finger distance doubles zoom")
eq(S.mapCamX + (200 - 20) / S.mapZoom, 140, "pinch keeps its map point under the midpoint")
eq(S.mapCamY + (200 - 100) / S.mapZoom, 120, "pinch preserves its vertical anchor")
check(MapBrowser.touchreleased(S, "second"), "lifting a finger ends the pinch")
check(S._mapPinch == nil and S._mapTouches.first ~= nil, "remaining finger can keep panning")
MapBrowser.clearTouches(S)
check(S._mapTouches == nil, "navigation clears map gestures")
S.navPopup = {}
check(not MapBrowser.touchpressed(S, "modal", 100, 200), "a popup shields map gestures")
end
print(string.format("save editor task 8 tests: %d passed, %d failed", passed, failed))
if failed > 0 then os.exit(1) end