Files
gen1recomp/tests/engine/push_battle_transition.lua
thibautbus cef286b170 Add unit tests
Regression coverage for the four Yellow Pallet Town fixes on this
branch:

- tests/parity_J.lua: the old-man-style demo bag shows x1 in Yellow
  (SimulatedInputBattleItemList), not just the pre-existing x50
  (pokered's OldManItemList) case.
- tests/engine/push_battle_transition.lua: Commands.pushBattle calls
  ctx.overworld:pushBattle when available and falls back with a
  logged warning otherwise. Logger.warn is spied (pcall-safe, always
  restored) rather than read off the shared Logger.history ring
  buffer, so the fallback checks don't leave noise behind for
  whatever else runs in the same process.
- tests/parity_yellow_pallet_pikachu.lua: drives the real onStep
  closure through the real StateStack/OverworldState, with no mocked
  battle -- Red never plays Music_MuseumGuy for this escort (Blue
  shares the same code path: onStep only branches on
  GameVersion.isYellow(), never isBlue(), so a separate Blue run
  would exercise nothing new), and Yellow's hold before the Pikachu
  battle is armed for exactly 2 frames before handing off to
  BattleTransition rather than a bare stack push. Stops there rather
  than also driving the demo battle to completion just to assert
  Music_MuseumGuy fires in Yellow (that fix's own coverage): the
  extra coupling to unrelated battle menu/bag/throw frame budgets
  wasn't worth it for one more assertion. That side stays manually
  verified. scenario() restores Game/GameVersion and re-inits
  StateStack on the way out.

All three pass standalone (luajit tests/<path>). The Yellow E2E test
hits the same pre-existing "Music.playMap is nil" gap three other
map-warping parity tests already hit inside the aggregated
tests/run_tests.lua run (missing data/generated/audio.lua in this dev
environment) -- confirmed by diffing the identical error text against
parity_warp_after_warp_step.lua, which also passes clean standalone.
2026-08-11 16:55:06 +02:00

77 lines
3.1 KiB
Lua

-- Commands.pushBattle (src/script/Commands.lua): the shared entry point
-- for start_battle, old_man_demo, and the PALLET_TOWN Pikachu catch
-- (data/scripts/story2.lua). Every one of those call sites used to carry
-- its own copy of "if ctx.overworld.pushBattle then ... else
-- ctx.game.stack:push(battle) end"; this locks the dedup in so a future
-- edit to one call site can't silently drop the transition wipe for the
-- others.
-- luajit tests/engine/push_battle_transition.lua
package.path = "./?.lua;./?/init.lua;" .. package.path
love = love or require("tests.love_stub")
local T = require("tests.harness")
local check = T.check
local eq = T.eq
local Commands = require("src.script.Commands")
local Logger = require("src.core.Logger")
local battle = { id = "the battle" }
-- Runs fn() with Logger.warn spied instead of hitting the real
-- print()/Logger.history ring buffer, and returns the last formatted
-- warning (or nil if none). pcall-wrapped so an error inside fn() still
-- restores Logger.warn before propagating -- a leaked spy would swallow
-- every later warning in the same process silently.
local function withWarnSpy(fn)
local warned
local origWarn = Logger.warn
Logger.warn = function(fmt, ...) warned = string.format(fmt, ...) end
local ok, err = pcall(fn)
Logger.warn = origWarn
if not ok then error(err, 0) end
return warned
end
-- ctx.overworld has a real pushBattle: it must be used, not a bare stack
-- push, so the flash/wipe transition and the battle-theme start survive.
do
local pushed
local ow = { pushBattle = function(self, b) pushed = b end }
local stackPushed
local ctx = { overworld = ow, game = { stack = {
push = function(_, b) stackPushed = b end } } }
Commands.pushBattle(ctx, battle)
eq(pushed, battle, "ctx.overworld:pushBattle is called with the battle")
check(stackPushed == nil, "the bare stack push is not also taken")
end
-- ctx.overworld without a pushBattle method (a partial test double, per
-- BattleState:finish's "no live children" contract) falls back to a
-- bare stack push and logs, rather than silently skipping the transition.
do
local stackPushed
local ctx = { overworld = {}, game = { stack = {
push = function(_, b) stackPushed = b end } } }
local warned = withWarnSpy(function() Commands.pushBattle(ctx, battle) end)
eq(stackPushed, battle, "falls back to ctx.game.stack:push")
check(warned ~= nil, "the fallback logs a warning")
check(warned and warned:find("pushBattle") ~= nil,
"the warning names pushBattle")
end
-- ctx.overworld absent entirely (headless script tests that never set
-- one up): same fallback, no crash on the ctx.overworld.pushBattle read
-- -- and still spied, since this also takes the warning path.
do
local stackPushed
local ctx = { game = { stack = {
push = function(_, b) stackPushed = b end } } }
local warned = withWarnSpy(function() Commands.pushBattle(ctx, battle) end)
eq(stackPushed, battle, "falls back to ctx.game.stack:push with no overworld")
check(warned ~= nil, "this fallback also logs a warning")
end
T.finish("push_battle_transition")