mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-03 16:16:38 -04:00
202 lines
7.4 KiB
Lua
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
|