test fix and iOS close hide

This commit is contained in:
bryanthaboi
2026-08-27 07:11:43 -04:00
parent eed246f645
commit bd3e02986d
9 changed files with 141 additions and 60 deletions
+6 -4
View File
@@ -1394,7 +1394,9 @@ local function buildHeader(imp, m)
-- under the gear and the quit X -- "the settings is covering the logo". -- under the gear and the quit X -- "the settings is covering the logo".
-- Reserving the space on both sides costs a little width and cannot -- Reserving the space on both sides costs a little width and cannot
-- overlap at any window size. -- overlap at any window size.
local clusterW = 2 * gear + math.floor(6 * m.s) + m.pad -- iOS has no quit button (the OS owns app exit), so the cluster is the
-- gear alone and the wordmark gets that width back
local clusterW = (imp.ios and gear or 2 * gear + math.floor(6 * m.s)) + m.pad
local boxX = m.x + clusterW local boxX = m.x + clusterW
local boxW = math.max(0, m.w - 2 * clusterW) local boxW = math.max(0, m.w - 2 * clusterW)
if imp.logo and boxW > 0 then if imp.logo and boxW > 0 then
@@ -1428,8 +1430,8 @@ local function buildHeader(imp, m)
-- inboard of it -- but the two are REGISTERED gear first, because the first -- inboard of it -- but the two are REGISTERED gear first, because the first
-- focusable of the first frame adopts the keyboard ring and that must not be -- focusable of the first frame adopts the keyboard ring and that must not be
-- the button that exits the app. -- the button that exits the app.
local quitX = rx - gear local quitX = not imp.ios and rx - gear or nil
rx = quitX - math.floor(6 * m.s) if quitX then rx = quitX - math.floor(6 * m.s) end
-- Settings gear. It now also owns the CONTROL settings (touch overlay -- Settings gear. It now also owns the CONTROL settings (touch overlay
-- editor, reset rebinds), which used to be buttons stacked in the game -- editor, reset rebinds), which used to be buttons stacked in the game
@@ -1441,7 +1443,7 @@ local function buildHeader(imp, m)
chrome.gear.image = imp._gearIcon chrome.gear.image = imp._gearIcon
btn(imp, rx, by, gear, gear, "gear", "", chrome.gear) btn(imp, rx, by, gear, gear, "gear", "", chrome.gear)
btn(imp, quitX, by, gear, gear, "quit", "", chrome.quit) if quitX then btn(imp, quitX, by, gear, gear, "quit", "", chrome.quit) end
-- The self-update control lives in the FOOTER next to the BCG mark (small, -- The self-update control lives in the FOOTER next to the BCG mark (small,
-- out of the wordmark's way -- it used to overlap the logo on a phone). It -- out of the wordmark's way -- it used to overlap the logo on a phone). It
+3 -1
View File
@@ -303,7 +303,9 @@ Game.save.forcedBike = true
ow = pushOW("ROUTE_17", 4, 10, "down") ow = pushOW("ROUTE_17", 4, 10, "down")
ow:flyTo("PALLET_TOWN") ow:flyTo("PALLET_TOWN")
eq(Game.save.forcedBike, nil, "Fly clears BIT_ALWAYS_ON_BIKE") eq(Game.save.forcedBike, nil, "Fly clears BIT_ALWAYS_ON_BIKE")
ow.flyAnim, ow.flyDest, ow.player.inputLocked = nil, nil, false -- undo flyTo -- undo flyTo, flyFade included: it re-arms flyAnim when it drains
ow.flyAnim, ow.flyDest, ow.flyFade = nil, nil, nil
ow.player.inputLocked = false
Game.save.forcedBike = true Game.save.forcedBike = true
ow:warpToHealPoint() ow:warpToHealPoint()
eq(Game.save.forcedBike, nil, "blackout/escape warps clear BIT_ALWAYS_ON_BIKE") eq(Game.save.forcedBike, nil, "blackout/escape warps clear BIT_ALWAYS_ON_BIKE")
+8 -5
View File
@@ -35,12 +35,12 @@ local check, eq = S.check, S.eq
-- rng floor: accuracyRoll compares rng(0, 255) against the scaled accuracy, -- rng floor: accuracyRoll compares rng(0, 255) against the scaled accuracy,
-- so the lowest roll lands HYPNOSIS (60%) every run -- so the lowest roll lands HYPNOSIS (60%) every run
local function freshBattle() local function freshBattle(rng)
Game.save.options.animations = true Game.save.options.animations = true
Game.save.party = { Pokemon.new(Data, "SQUIRTLE", 30) } Game.save.party = { Pokemon.new(Data, "SQUIRTLE", 30) }
local tb = BattleState.newWild(Game, "PIDGEY", 10) local tb = BattleState.newWild(Game, "PIDGEY", 10)
tb.queue, tb.nextInsert = {}, 0 tb.queue, tb.nextInsert = {}, 0
tb.rng = function(a) return a end tb.rng = rng or function(a) return a end
return tb return tb
end end
@@ -53,8 +53,8 @@ local function hitRows(tb)
return out return out
end end
local function typeOf(moveId, isPlayer) local function typeOf(moveId, isPlayer, rng)
local tb = freshBattle() local tb = freshBattle(rng)
local user = isPlayer and tb.player or tb.enemy local user = isPlayer and tb.player or tb.enemy
local target = isPlayer and tb.enemy or tb.player local target = isPlayer and tb.enemy or tb.player
tb:performMove(user, target, { id = moveId, pp = 10 }, false) tb:performMove(user, target, { id = moveId, pp = 10 }, false)
@@ -111,7 +111,10 @@ do
eq(typeOf("HYPNOSIS", false), 3, "and type 3 from the foe") eq(typeOf("HYPNOSIS", false), 3, "and type 3 from the foe")
eq(typeOf("GROWL", true), 6, "GROWL is a primary stat drop: type 6") eq(typeOf("GROWL", true), 6, "GROWL is a primary stat drop: type 6")
eq(typeOf("TAIL_WHIP", true), 6, "so is TAIL_WHIP") eq(typeOf("TAIL_WHIP", true), 6, "so is TAIL_WHIP")
eq(typeOf("SAND_ATTACK", false), 3, "the foe's SAND-ATTACK is type 3") -- StatModifierDownEffect misses on a roll under $40 on the enemy's
-- turn -- engine/battle/effects.asm:552
eq(typeOf("SAND_ATTACK", false, function() return 0x40 end), 3,
"the foe's SAND-ATTACK is type 3")
eq(typeOf("POISONPOWDER", true), 6, "POISONPOWDER is type 6") eq(typeOf("POISONPOWDER", true), 6, "POISONPOWDER is type 6")
eq(typeOf("CONFUSE_RAY", true), 6, "CONFUSE RAY is type 6") eq(typeOf("CONFUSE_RAY", true), 6, "CONFUSE RAY is type 6")
eq(typeOf("DISABLE", true), 6, "DISABLE is type 6") eq(typeOf("DISABLE", true), 6, "DISABLE is type 6")
+31 -15
View File
@@ -90,7 +90,7 @@ end
eq(hooks.onInteract(game, ow, 2, 0), false, eq(hooks.onInteract(game, ow, 2, 0), false,
"the wall left of the blackboard stays silent") "the wall left of the blackboard stays silent")
-- walk the loop: intro -> prompt -> heading box -> blurb -> prompt again -- walk the loop: intro -> held prompt -> heading menu over it
stack = {} stack = {}
hooks.onInteract(game, ow, 3, 0) hooks.onInteract(game, ow, 3, 0)
local intro = stack[#stack] local intro = stack[#stack]
@@ -100,36 +100,52 @@ local prompt = stack[#stack]
check(getmetatable(prompt) == TextBox check(getmetatable(prompt) == TextBox
and pages(prompt):find("heading", 1, true) ~= nil, and pages(prompt):find("heading", 1, true) ~= nil,
"the intro leads into the which-heading prompt") "the intro leads into the which-heading prompt")
prompt.onDone() -- #591: LinkCableHelpText2 ends in `text_end`, so .linkHelpLoop leaves it on
-- screen and runs HandleMenuInput under it
check(prompt.onDone == nil and prompt.stay ~= nil,
"the prompt is held open instead of waiting for A and popping")
prompt.stay.onShown()
local menu = stack[#stack] local menu = stack[#stack]
check(getmetatable(menu) == Menu, "the prompt opens the heading menu") check(getmetatable(menu) == Menu, "the held prompt opens the heading menu")
eq(stack[#stack - 1], prompt, "the menu sits on the still-visible prompt box")
eq(#menu.items, 4, "four headings, as in HowToLinkText") eq(#menu.items, 4, "four headings, as in HowToLinkText")
local labels = {} local labels = {}
for i, item in ipairs(menu.items) do labels[i] = item.label end for i, item in ipairs(menu.items) do labels[i] = item.label end
eq(table.concat(labels, "/"), eq(table.concat(labels, "/"),
"HOW TO LINK/COLOSSEUM/TRADE CENTER/STOP READING", "HOW TO LINK/COLOSSEUM/TRADE CENTER/STOP READING",
"the headings read in HowToLinkText order") "the headings read in HowToLinkText order")
eq(menu.items[4].onSelect, nil, "STOP READING just closes the menu")
check(menu.tw == 15 and menu.th == 10 and menu.tx == 0 and menu.ty == 0, check(menu.tw == 15 and menu.th == 10 and menu.tx == 0 and menu.ty == 0,
"the box is the asm's 15x10 at the top left") "the box is the asm's 15x10 at the top left")
for i = 1, 3 do for i = 1, 3 do
stack = {} stack = { prompt, menu }
menu.index = i
eq(menu.items[i].keepOpen, true,
labels[i] .. " leaves the menu up, as `jp .linkHelpLoop` does")
menu.items[i].onSelect() menu.items[i].onSelect()
local blurb = stack[#stack] local blurb = stack[#stack]
check(getmetatable(blurb) == TextBox, check(getmetatable(blurb) == TextBox, labels[i] .. " prints a text box")
labels[i] .. " prints a text box")
local want = Data.text["_LinkCableInfoText" .. i]:match("^[^\n\011\012]+") local want = Data.text["_LinkCableInfoText" .. i]:match("^[^\n\011\012]+")
check(pages(blurb):find(want, 1, true) ~= nil, check(pages(blurb):find(want, 1, true) ~= nil,
labels[i] .. " prints _LinkCableInfoText" .. i) labels[i] .. " prints _LinkCableInfoText" .. i)
check(type(blurb.onDone) == "function", eq(blurb.onDone, nil,
labels[i] .. " returns to the prompt instead of dropping out") labels[i] .. " pops itself back onto the menu instead of dropping out")
blurb.onDone() table.remove(stack) -- the blurb pops itself once it has been read
check(getmetatable(stack[#stack]) == TextBox, eq(stack[#stack], menu, "the same menu comes back after " .. labels[i])
"the prompt comes back after " .. labels[i]) eq(menu.index, i, "the cursor stays on the heading that was just read")
stack[#stack].onDone() eq(stack[#stack - 1], prompt, "the held prompt is still under it")
check(getmetatable(stack[#stack]) == Menu,
"the menu comes back after " .. labels[i])
end end
-- STOP READING and B share .exit: both close the menu and the prompt box
eq(menu.items[4].keepOpen, nil, "STOP READING closes the menu")
stack = { prompt, menu }
table.remove(stack) -- Menu pops itself before a non-keepOpen onSelect runs
menu.items[4].onSelect()
eq(#stack, 0, "STOP READING closes the menu and the held prompt together")
check(type(menu.onCancel) == "function", "B is watched (PAD_A | PAD_B)")
stack = { prompt, menu }
table.remove(stack)
menu.onCancel()
eq(#stack, 0, "B closes the menu and the held prompt together")
S.finish() S.finish()
+1 -1
View File
@@ -138,4 +138,4 @@ do
eq(#pushed, 0, "loss does not push after-battle text") eq(#pushed, 0, "loss does not push after-battle text")
end end
print("parity_lance: ok") S.finish()
+3
View File
@@ -29,8 +29,11 @@ local Bag = require("src.inventory.Bag")
local realTextBox = package.loaded["src.render.TextBox"] local realTextBox = package.loaded["src.render.TextBox"]
local realBag = package.loaded["src.ui.BagMenu"] local realBag = package.loaded["src.ui.BagMenu"]
local realParty = package.loaded["src.ui.PartyMenu"] local realParty = package.loaded["src.ui.PartyMenu"]
-- soundOpts only builds an opts table, so the real one runs against the stub.
local soundOpts = require("src.render.TextBox").soundOpts
package.loaded["src.render.TextBox"] = { package.loaded["src.render.TextBox"] = {
new = function(_, text, done) return { textBox = true, text = text, done = done } end, new = function(_, text, done) return { textBox = true, text = text, done = done } end,
soundOpts = soundOpts,
} }
package.loaded["src.ui.BagMenu"] = nil package.loaded["src.ui.BagMenu"] = nil
package.loaded["src.ui.PartyMenu"] = nil package.loaded["src.ui.PartyMenu"] = nil
+5
View File
@@ -50,6 +50,9 @@ check(hideout.noSight == nil or hideout.noSight[TEXT] ~= true,
-- the LIFT KEY half: after the battle -- however it started -- the next talk -- the LIFT KEY half: after the battle -- however it started -- the next talk
-- reveals the ball -- reveals the ball
-- put the real module back at the bottom: the tier dofiles every parity file
-- into one process, so a stub left behind here breaks every later suite
local realTextBox = package.loaded["src.render.TextBox"]
package.loaded["src.render.TextBox"] = { package.loaded["src.render.TextBox"] = {
new = function(_, text, done) return { text = text, done = done } end, new = function(_, text, done) return { text = text, done = done } end,
soundOpts = function() return nil end, soundOpts = function() return nil end,
@@ -77,4 +80,6 @@ check(toggles and toggles.ROCKETHIDEOUTB4F_LIFT_KEY == true,
"the LIFT KEY ball is shown") "the LIFT KEY ball is shown")
check(finished, "and the talk hands control back") check(finished, "and the talk hands control back")
package.loaded["src.render.TextBox"] = realTextBox
S.finish() S.finish()
@@ -8,6 +8,11 @@
-- findPendingRom, answered with the first dump whose SHA-1 mapped to any -- findPendingRom, answered with the first dump whose SHA-1 mapped to any
-- not-yet-ready version, so the selection was dropped on the floor. -- not-yet-ready version, so the selection was dropped on the floor.
-- --
-- Since 592daafa the in-launcher Kit.FileBrowser is tried ahead of that scan,
-- so the save-dir scan is now what a build without the Kit browser takes.
-- Kit is stubbed away below to reach it; the last block covers the browser
-- being present.
--
-- Self-contained: `luajit tests/rom_importer_choose_version_test.lua` -- Self-contained: `luajit tests/rom_importer_choose_version_test.lua`
package.path = "./?.lua;./?/init.lua;" .. package.path package.path = "./?.lua;./?/init.lua;" .. package.path
if not _G.love then _G.love = require("tests.love_stub") end if not _G.love then _G.love = require("tests.love_stub") end
@@ -51,6 +56,9 @@ local saved = {
-- to a dialog that may or may not exist on the machine running the suite. -- to a dialog that may or may not exist on the machine running the suite.
-- The fallback under test is the same one every pickerless device takes. -- The fallback under test is the same one every pickerless device takes.
love.system.getOS = function() return "Unknown" end love.system.getOS = function() return "Unknown" end
-- No Kit browser in this build, so Choose falls through to the save-dir scan.
local savedKit = package.loaded["src.ui.kit.Kit"]
package.loaded["src.ui.kit.Kit"] = { FileBrowser = nil }
love.filesystem.getSaveDirectory = function() return "/tmp/pokemon-love2d" end love.filesystem.getSaveDirectory = function() return "/tmp/pokemon-love2d" end
love.filesystem.getDirectoryItems = function() return LISTING end love.filesystem.getDirectoryItems = function() return LISTING end
love.filesystem.getInfo = function(name, filter) love.filesystem.getInfo = function(name, filter)
@@ -116,6 +124,24 @@ ri:choose("red")
check(ri._started == nil, check(ri._started == nil,
"and a version already imported is not extracted a second time") "and a version already imported is not extracted a second time")
-- ------- with the Kit browser present it opens instead of scanning
local opened = nil
package.loaded["src.ui.kit.Kit"] = {
FileBrowser = {
open = function(opts) opened = opts end,
},
}
ri = freshImporter()
ri:choose("blue")
check(ri._started == nil and opened ~= nil,
"a build with the Kit browser opens it rather than scanning the save dir")
eq(opened and opened.title, "Select "
.. (GameVersion.info("blue").displayName or "ROM"),
"titled for the version that was chosen")
package.loaded["src.ui.kit.Kit"] = savedKit
for name, fn in pairs(saved) do for name, fn in pairs(saved) do
if name == "getOS" then love.system.getOS = fn if name == "getOS" then love.system.getOS = fn
elseif name == "hash" then love.data.hash = fn elseif name == "hash" then love.data.hash = fn
+58 -34
View File
@@ -12,6 +12,9 @@ love = require("tests.love_stub")
-- assertions and the RNG seed come off the shared harness; this file keeps -- assertions and the RNG seed come off the shared harness; this file keeps
-- its own tail because the verdict line ("N FAILURES") is what CI greps -- its own tail because the verdict line ("N FAILURES") is what CI greps
local T = require("tests.harness") local T = require("tests.harness")
-- every suite below is dofile'd in this process, so a suite that reaches for
-- T.finish must raise into runSuites' pcall instead of os.exit(0)-ing the tier
_G.POKEPORT_TEST_CHILD = true
-- this suite has always streamed a line per check, and it is the one a -- this suite has always streamed a line per check, and it is the one a
-- developer watches for progress through ~1600 assertions -- developer watches for progress through ~1600 assertions
T.verbose = true T.verbose = true
@@ -437,11 +440,13 @@ check(Damage.compute(ruleset, confused, confused, confMove,
-- (StatModifierDownEffect's side-effect branch skips MoveHitTest) -- (StatModifierDownEffect's side-effect branch skips MoveHitTest)
local MoveEffects = require("src.battle.MoveEffects") local MoveEffects = require("src.battle.MoveEffects")
local sideRng = { rng = function() return 0 end } local sideRng = { rng = function() return 0 end }
-- player attacker: effects.asm:552's 25 percent miss is the enemy's branch
local sideUser = { isPlayer = true, stages = {}, mon = {} }
local misted = { stages = {}, mist = true, name = "MISTY", mon = {} } local misted = { stages = {}, mist = true, name = "MISTY", mon = {} }
MoveEffects.secondary.ATTACK_DOWN_SIDE_EFFECT(sideRng, nil, misted) MoveEffects.secondary.ATTACK_DOWN_SIDE_EFFECT(sideRng, sideUser, misted)
eq(misted.stages.attack, -1, "secondary stat drop pierces MIST") eq(misted.stages.attack, -1, "secondary stat drop pierces MIST")
local misted2 = { stages = {}, mist = true, name = "MISTY", mon = {} } local misted2 = { stages = {}, mist = true, name = "MISTY", mon = {} }
local mistMsgs = MoveEffects.primary.ATTACK_DOWN1_EFFECT(sideRng, nil, misted2) local mistMsgs = MoveEffects.primary.ATTACK_DOWN1_EFFECT(sideRng, sideUser, misted2)
check(misted2.stages.attack == nil check(misted2.stages.attack == nil
and mistMsgs[1]:find("MIST", 1, true) ~= nil, and mistMsgs[1]:find("MIST", 1, true) ~= nil,
"primary stat drop still blocked by MIST") "primary stat drop still blocked by MIST")
@@ -2644,7 +2649,8 @@ do
-- the text-speed cursor clamps at its ends (.pressedLeftInTextSpeed), -- the text-speed cursor clamps at its ends (.pressedLeftInTextSpeed),
-- MUSIC FILTER cycles OFF/1X/2X/3X, and COLORS / TILT / VIDEO MODE -- MUSIC FILTER cycles OFF/1X/2X/3X, and COLORS / TILT / VIDEO MODE
-- cycle their display modes (SHADER FX activates a pushed screen instead). -- cycle their display modes (SHADER FX activates a pushed screen instead).
do -- Nested function so LuaJIT's 200-local main-chunk limit is not hit.
(function()
local OptionsMenu = require("src.ui.OptionsMenu") local OptionsMenu = require("src.ui.OptionsMenu")
local OInput = require("src.core.Input") local OInput = require("src.core.Input")
local PaletteFX = require("src.render.PaletteFX") local PaletteFX = require("src.render.PaletteFX")
@@ -2656,8 +2662,19 @@ do
-- Isolate from earlier save/options writes in this suite -- Isolate from earlier save/options writes in this suite
SD.saveOptions(SD.defaultOptions()) SD.saveOptions(SD.defaultOptions())
local popped = false local popped = false
local om
-- the rows sit on group pages now, so the stub needs a real push/pop/top
-- stack for a group opener to have anywhere to go
local ostack = { states = {} }
function ostack:push(s) self.states[#self.states + 1] = s end
function ostack:pop()
local s = table.remove(self.states)
if s == om then popped = true end
return s
end
function ostack:top() return self.states[#self.states] end
local og = { data = Data, save = SD.newGame(), local og = { data = Data, save = SD.newGame(),
input = OInput, stack = { pop = function() popped = true end }, input = OInput, stack = ostack,
writeOptions = function(self) SD.saveOptions(self.save.options) end, writeOptions = function(self) SD.saveOptions(self.save.options) end,
-- the PERFORMANCE row routes through Game:applyOptions; the -- the PERFORMANCE row routes through Game:applyOptions; the
-- stub carries the headless slice of it (the tier record), -- stub carries the headless slice of it (the tier record),
@@ -2665,24 +2682,29 @@ do
applyOptions = function(self, o) applyOptions = function(self, o)
require("src.core.Performance").applyOptions(o) require("src.core.Performance").applyOptions(o)
end } end }
local om = OptionsMenu.new(og) om = OptionsMenu.new(og)
ostack:push(om)
local OptionRows = require("src.ui.OptionRows")
-- cur is whichever screen the cursor is on: the top level, or the group
-- page seek() walked into
local cur = om
local function press(btn) local function press(btn)
OInput.pressed = { [btn] = true } OInput.pressed = { [btn] = true }
om:update(1 / 60) cur:update(1 / 60)
OInput.pressed = {} OInput.pressed = {}
end end
-- walk the cursor down to a row by id, so a row added to OptionsMenu local function rowAt(s)
-- shifts these blocks instead of silently retargeting them local rows = s.view or s.rows
return rows[s.index]
end
-- put the cursor on a row by id, opening its group page first, so a row
-- added to OptionsMenu shifts these blocks instead of silently
-- retargeting them
local function seek(id) local function seek(id)
local want = -1 while ostack:top() ~= om do ostack:pop() end
for i, row in ipairs(om.rows) do cur = om:focusRow(id) or om
if row.id == id then want = i end local row = rowAt(cur)
end return row ~= nil and row.id == id
for _ = 1, #om.rows do
if om.index == want then break end
press("down")
end
return om.index == want
end end
eq(og.save.options.textSpeed, 3, eq(og.save.options.textSpeed, 3,
"new saves default to MEDIUM text (InitOptions TEXT_DELAY_MEDIUM)") "new saves default to MEDIUM text (InitOptions TEXT_DELAY_MEDIUM)")
@@ -2699,9 +2721,11 @@ do
"A switches the battle screen to the WIDE layout") "A switches the battle screen to the WIDE layout")
press("a") press("a")
eq(og.save.options.battleLayout, "og", "BATTLE LAYOUT wraps back to OG") eq(og.save.options.battleLayout, "og", "BATTLE LAYOUT wraps back to OG")
-- the BATTLE page's sixth row sits past the 4-box viewport
check(seek("battleBg"), "cursor reaches BATTLE BG")
eq(cur.scroll, cur.index - OptionRows.VISIBLE,
"viewport scrolls to keep a deep group row on screen")
check(seek("musicVol"), "cursor reaches MUSIC VOL") check(seek("musicVol"), "cursor reaches MUSIC VOL")
eq(om.scroll, om.index - require("src.ui.OptionRows").VISIBLE,
"viewport scrolls to keep MUSIC VOL on screen")
press("left") press("left")
eq(og.save.options.musicVol, 6, "left lowers MUSIC VOL") eq(og.save.options.musicVol, 6, "left lowers MUSIC VOL")
press("right") press("right")
@@ -2733,19 +2757,18 @@ do
eq(og.save.options.tilt, 0, "TILT wraps back to OFF") eq(og.save.options.tilt, 0, "TILT wraps back to OFF")
check(seek("shaderfx"), "cursor reaches SHADER FX") check(seek("shaderfx"), "cursor reaches SHADER FX")
-- this row activates a pushed ShaderFXScreen rather than cycling in -- this row activates a pushed ShaderFXScreen rather than cycling in
-- place like the rest of this suite's rows; `og.stack` above only -- place like the rest of this suite's rows, so activate() is not called
-- stubs `pop`, not a real push/top stack, so activate() is not -- here -- tests/mod_ui_tests.lua exercises it end to end against a real
-- called here -- tests/mod_ui_tests.lua exercises it end to end against -- game.
-- a real stack. check(rowAt(cur).step == nil, "SHADER FX row has no step()")
check(om.rows[om.index].step == nil, "SHADER FX row has no step()") check(type(rowAt(cur).activate) == "function",
check(type(om.rows[om.index].activate) == "function",
"SHADER FX row has an activate()") "SHADER FX row has an activate()")
check(seek("shaderfx2"), "cursor reaches SHADER FX 2") check(seek("shaderfx2"), "cursor reaches SHADER FX 2")
-- the dual-shader secondary slot: same shared ShaderFXScreen, opened on -- the dual-shader secondary slot: same shared ShaderFXScreen, opened on
-- "secondary" instead -- see the SHADER FX row above for why activate() -- "secondary" instead -- see the SHADER FX row above for why activate()
-- isn't exercised against this stub stack either. -- isn't exercised here either.
check(om.rows[om.index].step == nil, "SHADER FX 2 row has no step() either") check(rowAt(cur).step == nil, "SHADER FX 2 row has no step() either")
check(type(om.rows[om.index].activate) == "function", check(type(rowAt(cur).activate) == "function",
"SHADER FX 2 row has an activate()") "SHADER FX 2 row has an activate()")
check(seek("zoom"), "cursor reaches ZOOM") check(seek("zoom"), "cursor reaches ZOOM")
local ZoomOpt = require("src.render.Zoom") local ZoomOpt = require("src.render.Zoom")
@@ -2804,10 +2827,10 @@ do
check(seek("dateFormat"), "cursor reaches DATE FORMAT") check(seek("dateFormat"), "cursor reaches DATE FORMAT")
check(seek("timeFormat"), "cursor reaches TIME FORMAT") check(seek("timeFormat"), "cursor reaches TIME FORMAT")
press("down") press("down")
-- CANCEL is appended after the descriptor list rather than living in it, so -- CANCEL is appended after the top-level view rather than living in it, so
-- it lands one past #rows and the window holds the last six boxes. Counted -- it lands one past #view and the window holds the last six boxes. Counted
-- off #rows so the next row added here is not read as a wrap bug. -- off #view so the next row added here is not read as a wrap bug.
local cancelRow = #om.rows + 1 local cancelRow = #om.view + 1
eq(om.index, cancelRow, "CANCEL stays the fixed final row") eq(om.index, cancelRow, "CANCEL stays the fixed final row")
eq(om.scroll, cancelRow - 5, "CANCEL keeps the last option boxes on screen") eq(om.scroll, cancelRow - 5, "CANCEL keeps the last option boxes on screen")
om:draw() -- smoke: scrolled layout draws under the headless stub om:draw() -- smoke: scrolled layout draws under the headless stub
@@ -2826,7 +2849,7 @@ do
require("src.render.Zoom").applyOptions(og.save.options) require("src.render.Zoom").applyOptions(og.save.options)
require("src.render.TileRenderer").applyOptions(og.save.options) require("src.render.TileRenderer").applyOptions(og.save.options)
require("src.core.VideoMode").applyOptions(og.save.options) require("src.core.VideoMode").applyOptions(og.save.options)
end end)()
end end
-- ------------------------------------------------------------------ -- ------------------------------------------------------------------
@@ -3224,7 +3247,8 @@ do
local TileRenderer = require("src.render.TileRenderer") local TileRenderer = require("src.render.TileRenderer")
TileRenderer.setSpinning(true) TileRenderer.setSpinning(true)
local before = TileRenderer.spinBlurActive() local before = TileRenderer.spinBlurActive()
for _ = 1, 8 do TileRenderer.tick(1 / 60) end -- the blur alternates once per simulated-joypad step, 16 frames (#1831)
for _ = 1, 16 do TileRenderer.tick(1 / 60) end
check(before ~= TileRenderer.spinBlurActive(), check(before ~= TileRenderer.spinBlurActive(),
"tick(1/60) advances water/spinner clock at fixed 60Hz") "tick(1/60) advances water/spinner clock at fixed 60Hz")
local mid = TileRenderer.spinBlurActive() local mid = TileRenderer.spinBlurActive()