Files
gen1recomp/tests/engine/menu_hold_scroll_bug1898.lua

290 lines
10 KiB
Lua

-- home/joypad2.asm:16-53 (#1898)
package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
love = love or require("tests.love_stub")
package.loaded["src.render.Font"] = {
BORDER = { tl = 1, tr = 2, bl = 3, br = 4, h = 5, v = 6 },
draw = function() end,
drawCode = function() end,
drawBox = function() end,
width = function(text) return #tostring(text) * 8 end,
split = function(text)
local spans = {}
for i = 1, #tostring(text) do spans[i] = { from = i, to = i } end
return spans
end,
spansFitting = function(spans) return #spans end,
}
local MenuRepeat = require("src.ui.MenuRepeat")
local ListMenu = require("src.ui.ListMenu")
local function newInput()
local input = { edges = {}, held = {} }
function input:press(button)
self.edges[button] = true
self.held[button] = true
end
function input:release(button)
self.edges[button] = nil
self.held[button] = nil
end
function input:wasPressed(button)
if self.edges[button] then
self.edges[button] = nil
return true
end
return false
end
function input:isDown(button) return self.held[button] == true end
return input
end
do
eq(MenuRepeat.GEN1_DELAY, 30, "half a second before the first repeat")
eq(MenuRepeat.GEN1_RATE, 5, "then 1/12 of a second between repeats")
eq(MenuRepeat.GEN2_DELAY, 15, "JoyTextDelay seeds wTextDelayFrames with 15")
eq(MenuRepeat.GEN2_RATE, 5, "and reseeds it with 5")
local input = newInput()
local state = MenuRepeat.new(MenuRepeat.GEN1_DELAY, MenuRepeat.GEN1_RATE)
input:press("down")
local dir, edge = MenuRepeat.direction(state, input, { "up", "down" })
eq(dir, "down", "the press itself reports immediately")
eq(edge, true, "and is flagged as the edge")
local fired = {}
for frame = 1, 60 do
local d = MenuRepeat.direction(state, input, { "up", "down" })
if d then fired[#fired + 1] = frame end
end
eq(fired[1], 30, "the hold is ignored for half a second")
eq(fired[2], 35, "then repeats every five frames")
eq(fired[3], 40, "and keeps repeating")
eq(#fired, 7, "seven repeats in the second after the press")
input:release("down")
eq(MenuRepeat.direction(state, input, { "up", "down" }), nil,
"letting go stops it")
eq(state.frames, 0, "and clears the counter, so the next hold waits again")
local off = MenuRepeat.new(MenuRepeat.GEN1_DELAY, MenuRepeat.GEN1_RATE, false)
input:press("down")
MenuRepeat.direction(off, input, { "up", "down" })
local repeated = false
for _ = 1, 120 do
if MenuRepeat.direction(off, input, { "up", "down" }) then repeated = true end
end
check(not repeated, "a disabled state stays edge-only")
input:release("down")
end
do
local items = {}
for i = 1, 10 do items[i] = { value = i, label = "ITEM " .. i } end
local input = newInput()
local game = { input = input,
stack = { push = function() end, pop = function() end,
top = function() end } }
local list = ListMenu.new(game, "ITEMS", items, { kind = "bag", itemBox = true })
eq(list.keyRepeat, true,
"DisplayListMenuID runs with hJoy7 = 1, with no mod hook in sight")
eq(list.repeatDelay, 30, "at the cart's half-second delay")
eq(list.repeatRate, 5, "and the cart's repeat rate")
input:press("down")
list:update(1 / 60)
eq(list.index, 2, "the press moves the cursor one row")
for _ = 1, 29 do list:update(1 / 60) end
eq(list.index, 2, "a short hold moves nothing more")
list:update(1 / 60)
eq(list.index, 3, "the 30th held frame is the first repeat")
for _ = 1, 5 do list:update(1 / 60) end
eq(list.index, 4, "and every fifth frame after that")
for _ = 1, 60 do list:update(1 / 60) end
eq(list.index, 10, "holding Down walks to the end of the list")
eq(list.scroll, 7, "with the window scrolled to the bottom")
input:release("down")
input:press("up")
list:update(1 / 60)
for _ = 1, 200 do list:update(1 / 60) end
eq(list.index, 1, "holding Up walks back to the top")
eq(list.scroll, 0, "and the window comes with it")
end
-- home/list_menu.asm:47, 61-64, 338-342, 176-190; home/window.asm:14-18
do
local items = {}
for i = 1, 10 do items[i] = { value = i, label = "ITEM " .. i } end
local input = newInput()
local game = { input = input,
stack = { push = function() end, pop = function() end,
top = function() end } }
local list = ListMenu.new(game, "ITEMS", items, { kind = "bag", itemBox = true })
eq(list.cursorBlank, 0, "the arrow is up before anything moves")
local function step(dir)
input:press(dir)
list:update(1 / 60)
input:release(dir)
end
step("down")
eq(list.index, 2, "the press moves one row")
eq(list.scroll, 0, "inside the rows already printed")
eq(list.cursorBlank, 0, "so nothing reprints and the arrow stays up")
step("down")
eq(list.scroll, 0, "the third row is the last wMaxMenuItem reaches")
eq(list.cursorBlank, 0, "and the arrow is still up")
step("down")
eq(list.index, 4, "the fourth row is past it")
eq(list.scroll, 1, "so wListScrollOffset moves and the entries reprint")
eq(list.cursorBlank, 3, "which wipes the arrow")
list:update(1 / 60)
eq(list.cursorBlank, 2, "it counts down one frame at a time")
list:update(1 / 60)
eq(list.cursorBlank, 1, "over the Delay3 window")
list:update(1 / 60)
eq(list.cursorBlank, 0, "and the arrow comes back")
list:update(1 / 60)
eq(list.cursorBlank, 0, "and stays back while the cursor sits still")
step("down")
eq(list.cursorBlank, 3, "the next scrolling step blanks it again")
step("up")
eq(list.cursorBlank, 2,
"moving back inside the window reprints nothing, so it just counts down")
local plain = ListMenu.new(game, "ITEMS", items, { kind = "generic" })
input:press("down")
for _ = 1, 200 do plain:update(1 / 60) end
input:release("down")
check(plain.scroll > 0, "the generic list scrolled to the bottom")
eq(plain.cursorBlank, 0, "without ever blanking its cursor")
end
do
local data = { pokemon = {}, constants = { dexSize = 151, dexDigits = 3 } }
for n = 1, 151 do
local id = ("DEXMON_%03d"):format(n)
data.pokemon[id] = { id = id, name = ("MON%03d"):format(n), dex = n }
end
local save = { pokedex = { seen = {}, owned = {} } }
for n = 1, 20 do save.pokedex.seen[("DEXMON_%03d"):format(n)] = true end
local input = newInput()
local game = { data = data, save = save, input = input,
stack = { push = function() end, pop = function() end,
top = function() end } }
local PokedexMenu = require("src.ui.PokedexMenu")
local dex = PokedexMenu.new(game, {})
eq(#dex.items, 20, "twenty seen species in the list")
input:press("down")
dex:update(1 / 60)
eq(dex.index, 2, "ShowPokedexMenu's own list moves on the press")
for _ = 1, 29 do dex:update(1 / 60) end
eq(dex.index, 2, "and waits out the same half second")
dex:update(1 / 60)
eq(dex.index, 3, "before the first repeat")
for _ = 1, 200 do dex:update(1 / 60) end
eq(dex.index, 20, "a long hold reaches the last seen species")
-- arrow is never wiped (engine/menus/pokedex.asm:18, 217-221)
eq(dex.cursorBlank, nil, "and the dex cursor is never blanked")
input:release("down")
input:press("right")
dex:update(1 / 60)
eq(dex.scroll, 13, "Right pages the scroll offset and clamps at the end")
for _ = 1, 60 do dex:update(1 / 60) end
eq(dex.scroll, 13, "holding Right at the bottom cannot page past it")
input:release("right")
input:press("left")
dex:update(1 / 60)
eq(dex.scroll, 6, "Left pages back seven rows")
for _ = 1, 40 do dex:update(1 / 60) end
eq(dex.scroll, 0, "and a held Left runs the list back to the top")
input:release("left")
end
do
local PackMenu = require("src.ui.gen2.PackMenu")
local Save = require("src.core.gen2.Save")
local items = {}
local order = {}
for i = 1, 8 do
local id = "FAKEITEM_" .. i
items[id] = { id = id, name = "ITEM " .. i, pocket = "ITEM", index = i,
canToss = true, canSelect = false,
fieldMenu = "ITEMMENU_CLOSE" }
order[i] = id
end
local save = Save.newGame()
save.inventory = {}
for _, id in ipairs(order) do save.inventory[id] = 1 end
save.bagOrder = order
local input = newInput()
local game = { input = input, save = save, options = save.options,
data = { items = items, moves = {}, pokemon = {},
audio = { sfx = {} } },
stack = { push = function() end, pop = function() end } }
local pack = PackMenu.new(game, { save = save, pocket = "ITEM",
onClose = function() end, world = { useFieldItem = function() end } })
pack.gfx = { available = function() return false end, draw = function() end,
colorsAt = function() return nil end }
eq(pack.hold.delay, 15, "the pocket list runs at JoyTextDelay's cadence")
eq(pack.hold.rate, 5, "with the cart's five-frame repeat")
local total = pack:total()
check(total >= 9, "eight items plus the CANCEL row")
input:press("down")
pack:update(1 / 60)
eq(pack.index, 2, "the press moves one row")
for _ = 1, 14 do pack:update(1 / 60) end
eq(pack.index, 2, "fifteen frames of delay first")
pack:update(1 / 60)
eq(pack.index, 3, "then the first repeat")
for _ = 1, 200 do pack:update(1 / 60) end
eq(pack.index, total,
"a held Down stops on the last row rather than wrapping round")
input:release("down")
end
do
local Gen2Dex = require("src.ui.gen2.PokedexMenu")
local entries, seen = {}, {}
for n = 1, 30 do
local id = ("G2MON_%03d"):format(n)
entries[id] = { dex = n, species = id, name = ("MON%03d"):format(n) }
seen[id] = true
end
local input = newInput()
local save = { pokedex = { seen = seen, caught = {} } }
local game = { data = {}, save = save, input = input }
local dex = Gen2Dex.new(game, { save = save,
pokedex = { entries = entries },
pokemon = {} })
eq(dex.hold.delay, 15, "the listing runs at JoyTextDelay's cadence")
check(#dex.rows >= 30, "thirty seen species in the listing")
input:press("down")
dex:update(1 / 60)
eq(dex.index, 2, "the press moves one row")
for _ = 1, 14 do dex:update(1 / 60) end
eq(dex.index, 2, "after the same fifteen-frame delay")
dex:update(1 / 60)
eq(dex.index, 3, "the hold starts repeating")
for _ = 1, 400 do dex:update(1 / 60) end
eq(dex.index, #dex.rows,
"Pokedex_ListingMoveCursorDown stops at the end of the listing")
input:release("down")
end
T.finish("menu hold-to-scroll bug 1898")