diff --git a/tests/engine/battle_ball_dodge_romtext.lua b/tests/engine/battle_ball_dodge_romtext.lua new file mode 100644 index 00000000..f49c96a3 --- /dev/null +++ b/tests/engine/battle_ball_dodge_romtext.lua @@ -0,0 +1,87 @@ +-- BattleState:throwBall()'s can't-be-caught path used to queue two +-- separate Strings() literals ("It dodged the\nthrown BALL!" then "This +-- POKéMON\ncan't be caught!"). The real ROM label _ItemUseBallText00 +-- combines both as one \f-paged string. TextBox.new() would split \f +-- itself, but throwBall() queues through self:sayNext(), which goes +-- through the battle queue's own BattleState:startMessage() -- and that +-- one only splits on \n/\v, not \f (confirmed live: the \f landed +-- mid-line and the second sentence overflowed off the box instead of +-- starting a fresh page). The fix resolves the label once, then splits +-- it the same way TextBox.lua does and queues one sayNext per page. This +-- test fakes the label and checks the two pages reach the queue as two +-- separate messages, in order, not merged into one with a raw \f still +-- inside it. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local Data = T.fixtures.fresh() +require("src.render.Font").load(Data) + +local BattleState = require("src.battle.BattleState") +local Pokemon = require("src.pokemon.Pokemon") +local SaveData = require("src.core.SaveData") + +local function mkbattle() + local save = SaveData.newGame() + save.party = { Pokemon.new(Data, "FIXMON_A", 10) } + local game = { data = Data, save = save, + stack = { top = function() return nil end, push = function() end } } + local battle = BattleState.newWild(game, "FIXMON_C", 8) + battle.ghost = true -- forces the can't-be-caught path + return battle +end + +-- throwBall defers its message-queuing work into self.queue via one +-- top-level self:act(fn); run only that one function to reach the +-- say() calls the fix touches. It's the last entry throwBall itself +-- appends (after the immediate sayAuto), and it further queues its own +-- self:act(function() self:executeAction(...) end) for the actual enemy +-- turn -- deliberately NOT run here (out of scope, and running the queue +-- generically after mutation risks looping into a real turn simulation) +local function runThrowBallAct(battle) + for i = #battle.queue, 1, -1 do + if battle.queue[i].fn then + battle.queue[i].fn() + return + end + end +end + +local function textEntries(battle) + local out = {} + for _, entry in ipairs(battle.queue) do + if entry.text then out[#out + 1] = entry.text end + end + return out +end + +-- translated: the faked label's two \f-separated pages reach the queue +-- as two separate messages, in order, and neither one still contains a +-- raw \f (which would mean the battle queue's own renderer has to deal +-- with it, and it can't) +do + local battle = mkbattle() + Data.text._ItemUseBallText00 = "FAKE-DODGE!\fFAKE-CANTCATCH!" + battle:throwBall("FIX_BALL") + runThrowBallAct(battle) + local texts = textEntries(battle) + T.eq(texts[1], "FAKE-DODGE!", "page 1 reaches the queue on its own") + T.eq(texts[2], "FAKE-CANTCATCH!", "page 2 follows right after, still in order") + for _, t in ipairs(texts) do + T.check(not t:find("\f", 1, true), "no queued message still carries a raw \\f") + end + Data.text._ItemUseBallText00 = nil +end + +-- vanilla: no catalog entry still falls back to the two English pages, +-- split the same way +do + local battle = mkbattle() + battle:throwBall("FIX_BALL") + runThrowBallAct(battle) + local texts = textEntries(battle) + T.eq(texts[1], "It dodged the\nthrown BALL!", "vanilla page 1") + T.eq(texts[2], "This POKéMON\ncan't be caught!", "vanilla page 2") +end + +T.finish("battle_ball_dodge_romtext") diff --git a/tests/engine/battle_catch_messages_romtext.lua b/tests/engine/battle_catch_messages_romtext.lua new file mode 100644 index 00000000..b0feb333 --- /dev/null +++ b/tests/engine/battle_catch_messages_romtext.lua @@ -0,0 +1,77 @@ +-- BattleState:storeCaughtMon() queues up to two plain-Lua-literal +-- messages: the new-Pokedex-data line (_ItemUseBallText06) and, when the +-- party is full, the box-transfer line (_ItemUseBallText07/08, keyed on +-- EVENT_MET_BILL -- two full, independently-translated ROM strings, not +-- one template with a substituted PC name). This test fakes all three +-- labels and checks the queued messages use them, not the English +-- literals. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local Data = T.fixtures.fresh() +require("src.render.Font").load(Data) + +local BattleState = require("src.battle.BattleState") +local Pokemon = require("src.pokemon.Pokemon") +local SaveData = require("src.core.SaveData") + +local function findText(battle, needle) + for _, entry in ipairs(battle.queue) do + if entry.text and entry.text:find(needle, 1, true) then return entry.text end + end + return nil +end + +local function mkbattle(partySize, metBill) + local save = SaveData.newGame() + save.party = {} + for i = 1, partySize do + save.party[i] = Pokemon.new(Data, "FIXMON_A", 5) + end + save.flags = save.flags or {} + save.flags.EVENT_MET_BILL = metBill + local game = { data = Data, save = save, + stack = { top = function() return nil end, push = function() end } } + return BattleState.newWild(game, "FIXMON_C", 8) +end + +-- empty party: Party.add succeeds, only the new-Pokedex-data message fires +do + local battle = mkbattle(0, false) + Data.text._ItemUseBallText06 = "FAKE-DEX {RAM:wEnemyMonNick} FAKE!" + battle:storeCaughtMon() + T.eq(findText(battle, "FAKE-DEX"), "FAKE-DEX " .. battle.enemy.name .. " FAKE!", + "a translated _ItemUseBallText06 reaches the new-Pokedex-data message") + Data.text._ItemUseBallText06 = nil +end + +-- full party, EVENT_MET_BILL true: box transfer via _ItemUseBallText07 +do + local battle = mkbattle(6, true) + Data.text._ItemUseBallText07 = "FAKE-BILL {RAM:wBoxMonNicks} FAKE!" + battle:storeCaughtMon() + T.eq(findText(battle, "FAKE-BILL"), "FAKE-BILL " .. battle.enemy.name .. " FAKE!", + "EVENT_MET_BILL true routes through the translated _ItemUseBallText07") + Data.text._ItemUseBallText07 = nil +end + +-- full party, EVENT_MET_BILL false: box transfer via _ItemUseBallText08 +do + local battle = mkbattle(6, false) + Data.text._ItemUseBallText08 = "FAKE-SOMEONE {RAM:wBoxMonNicks} FAKE!" + battle:storeCaughtMon() + T.eq(findText(battle, "FAKE-SOMEONE"), "FAKE-SOMEONE " .. battle.enemy.name .. " FAKE!", + "EVENT_MET_BILL false routes through the translated _ItemUseBallText08") + Data.text._ItemUseBallText08 = nil +end + +-- vanilla, full party, EVENT_MET_BILL true: English literal, BILL's PC +do + local battle = mkbattle(6, true) + battle:storeCaughtMon() + T.eq(findText(battle, "transferred"), + battle.enemy.name .. " was\ntransferred to\nBILL's PC!", + "no catalog entry falls back to the English BILL's-PC literal") +end + +T.finish("battle_catch_messages_romtext") diff --git a/tests/engine/battle_fainted_message_romtext.lua b/tests/engine/battle_fainted_message_romtext.lua new file mode 100644 index 00000000..6f1ce0b7 --- /dev/null +++ b/tests/engine/battle_fainted_message_romtext.lua @@ -0,0 +1,64 @@ +-- BattleState:onFaint's "%s\nfainted!" collapsed two distinct ROM +-- strings (_EnemyMonFaintedText already carries its own "Enemy" wording; +-- _PlayerMonFaintedText does not) into one literal, substituted with +-- displayName(battler) -- which itself runs the enemy name through a +-- SEPARATE Strings("Enemy %s", ...) call. The fix passes the raw +-- battler.name and lets each label supply its own wording. This test +-- fakes both labels and checks the raw name reaches the right one, with +-- no "Enemy" ever duplicated. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local Data = T.fixtures.fresh() +require("src.render.Font").load(Data) + +local BattleState = require("src.battle.BattleState") +local Pokemon = require("src.pokemon.Pokemon") +local SaveData = require("src.core.SaveData") + +local function mkbattle() + local save = SaveData.newGame() + save.party = { Pokemon.new(Data, "FIXMON_A", 10) } + local game = { data = Data, save = save, + stack = { top = function() return nil end, push = function() end } } + return BattleState.newWild(game, "FIXMON_C", 8) +end + +-- finds the queued say-message text (onFaint queues several entries: a +-- wait, then the say) +local function findText(battle) + for _, entry in ipairs(battle.queue) do + if entry.text then return entry.text end + end + return nil +end + +-- enemy: translated _EnemyMonFaintedText reaches onFaint, raw name only +do + local battle = mkbattle() + Data.text._EnemyMonFaintedText = "FAKE-ENEMY {RAM:wEnemyMonNick} FAKE!" + battle:onFaint(battle.enemy) + T.eq(findText(battle), "FAKE-ENEMY " .. battle.enemy.name .. " FAKE!", + "a translated _EnemyMonFaintedText reaches the enemy faint message") + Data.text._EnemyMonFaintedText = nil +end + +-- enemy vanilla: the English literal already carries "Enemy " itself +do + local battle = mkbattle() + battle:onFaint(battle.enemy) + T.eq(findText(battle), "Enemy " .. battle.enemy.name .. "\nfainted!", + "no catalog entry falls back to the English literal, Enemy included") +end + +-- player: translated _PlayerMonFaintedText reaches onFaint +do + local battle = mkbattle() + Data.text._PlayerMonFaintedText = "FAKE-PLAYER {RAM:wBattleMonNick} FAKE!" + battle:onFaint(battle.player) + T.eq(findText(battle), "FAKE-PLAYER " .. battle.player.name .. " FAKE!", + "a translated _PlayerMonFaintedText reaches the player faint message") + Data.text._PlayerMonFaintedText = nil +end + +T.finish("battle_fainted_message_romtext") diff --git a/tests/engine/box_release_confirmation_romtext.lua b/tests/engine/box_release_confirmation_romtext.lua new file mode 100644 index 00000000..c2db6709 --- /dev/null +++ b/tests/engine/box_release_confirmation_romtext.lua @@ -0,0 +1,129 @@ +-- BoxMenu's RELEASE confirmation ("Once released,\n%s is\ngone forever. +-- OK?") used to be a bare Lua literal. tests/engine/pc_release.lua only +-- ever runs with an empty Data.text, so it can't tell a properly-wired +-- t._OnceReleasedText or "..." fallback apart from a literal that never +-- looked at t at all -- every assertion there passes either way. This +-- test drives the same interactive release flow with a faked +-- Data.text._OnceReleasedText and checks the pushed TextBox's raw text +-- (captured via a TextBox.new spy, so real pagination/choice behavior is +-- untouched) uses the translated value, not the English literal. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local Data = T.fixtures.fresh() +local ids = T.fixtures.ids +require("src.render.Font").load(Data) + +local Pokemon = require("src.pokemon.Pokemon") +local Boxes = require("src.pokemon.Boxes") +local TextBox = require("src.render.TextBox") +local BoxMenu = require("src.ui.BoxMenu") +local ListMenu = require("src.ui.ListMenu") +local ChoiceBox = require("src.ui.ChoiceBox") +local SaveData = require("src.core.SaveData") +local Sound = require("src.core.Sound") + +local realCry, realPlay = Sound.playCry, Sound.play +Sound.playCry = function() end +Sound.play = function() end + +-- spy: records every raw text TextBox.new receives, without disturbing +-- pagination/choice re-push, so the interactive flow behaves exactly like +-- pc_release.lua's +local captured +local realNew = TextBox.new +TextBox.new = function(game, text, onDone, opts) + captured[#captured + 1] = text + return realNew(game, text, onDone, opts) +end + +local stack = { states = {} } +function stack:push(s) self.states[#self.states + 1] = s end +function stack:pop() + local t = self.states[#self.states] + self.states[#self.states] = nil + return t +end +function stack:top() return self.states[#self.states] end +function stack:update(dt) + local t = self:top() + if t and t.update then t:update(dt) end +end + +local pressed = {} +local function press(btn) + pressed = { [btn] = true } + stack:update(1 / 60) + pressed = {} +end + +local function topMt() return getmetatable(stack:top()) end +local function mash(btn, cond, n) + for _ = 1, (n or 400) do + if cond() then return true end + press(btn) + end + return false +end + +local function mkGame() + stack.states = {} + captured = {} + local game = { + data = Data, + save = SaveData.newGame(), + stack = stack, + input = { + wasPressed = function(_, key) return pressed[key] or false end, + isDown = function() return false end, + }, + } + game.save.options = game.save.options or {} + game.save.options.textSpeed = 1 + local box = Boxes.active(game.save) + box[1] = Pokemon.new(Data, ids.species[1], 5) + return game, box +end + +local function releaseFirstMon(game) + stack:push(BoxMenu.new(game)) + press("down"); press("down"); press("a") -- open RELEASE list + T.check(topMt() == ListMenu, "RELEASE opens the box list") + press("a") -- choose the first (only) mon + T.check(mash("a", function() return topMt() == ChoiceBox end), + "confirm choice opens") + -- the confirmation TextBox is captured[1] the moment it was pushed, + -- before this mash even ran + return captured[1] +end + +-- monName (BoxMenu.lua): mon.nickname or def.name +local function monName(box) + local mon = box[1] + local def = Data.pokemon[mon.species] + return mon.nickname or def.name +end + +-- translated: the fake value must reach the pushed TextBox +do + local game, box = mkGame() + local name = monName(box) + Data.text._OnceReleasedText = "FAKE {RAM:wStringBuffer} released!" + local text = releaseFirstMon(game) + T.eq(text, "FAKE " .. name .. " released!", + "a translated _OnceReleasedText reaches the release confirmation") + Data.text._OnceReleasedText = nil +end + +-- vanilla: with no catalog entry, the English literal still substitutes +do + local game, box = mkGame() + local name = monName(box) + local text = releaseFirstMon(game) + T.eq(text, "Once released,\n" .. name .. " is\ngone forever. OK?", + "no catalog entry still falls back to the English literal") +end + +TextBox.new = realNew +Sound.playCry, Sound.play = realCry, realPlay +T.finish("box_release_confirmation_romtext") diff --git a/tests/engine/overworld_field_faint_heal_romtext.lua b/tests/engine/overworld_field_faint_heal_romtext.lua new file mode 100644 index 00000000..2e2344dd --- /dev/null +++ b/tests/engine/overworld_field_faint_heal_romtext.lua @@ -0,0 +1,139 @@ +-- Two OverworldController.lua messages used to be bare Lua literals: +-- applyFieldPoison()'s "%s\nfainted!" (the third of three collapsed +-- fainted-message ROM strings, _PokemonFaintedText) and +-- useSoftboiledFieldMove()'s "It won't have\nany effect."/"%s's HP\nwas +-- restored!" (the same _ItemUseNoEffectText/_PotionText labels +-- ItemEffects.lua's real potion message already uses -- _PotionText's +-- second slot is the actual amount healed, which the old literal never +-- showed at all). +-- +-- Uses the debug.setupvalue technique already established in +-- oaks_pc_flow.lua to fake the module-level Game/TextBox upvalues +-- ROM-free, without going through the heavy OverworldState:enter(). +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local Data = T.fixtures.fresh() + +local SaveData = require("src.core.SaveData") +local Pokemon = require("src.pokemon.Pokemon") +local OW = require("src.world.OverworldController") + +local function setUpvalue(fn, name, val) + local i = 1 + while true do + local n = debug.getupvalue(fn, i) + if not n then return false end + if n == name then debug.setupvalue(fn, i, val); return true end + i = i + 1 + end +end + +local pushed = {} +local textBoxStub = { + new = function(_, text, onDone, opts) + return { text = text, onDone = onDone, opts = opts } + end, +} +local realSound = package.loaded["src.core.Sound"] +package.loaded["src.core.Sound"] = { play = function() end, playCry = function() end } + +local function mkGame() + local save = SaveData.newGame() + save.party = { Pokemon.new(Data, "FIXMON_A", 20) } + pushed = {} + return { + data = Data, save = save, + stack = { push = function(_, item) pushed[#pushed + 1] = item end }, + } +end + +for _, name in ipairs({ "applyFieldPoison", "useSoftboiledFieldMove" }) do + T.check(setUpvalue(OW[name], "Game", mkGame()), ("Game upvalue on %s"):format(name)) + T.check(setUpvalue(OW[name], "TextBox", textBoxStub), ("TextBox upvalue on %s"):format(name)) +end + +local fakeSelf = setmetatable({}, { __index = OW }) + +-- ---- applyFieldPoison: _PokemonFaintedText ---- +do + local game = mkGame() + setUpvalue(OW.applyFieldPoison, "Game", game) + local mon = game.save.party[1] + mon.status = "PSN" + mon.hp = 1 -- one poison tick (1 dmg by default) faints it + game.save.poisonSteps = 3 -- (3+1) % 4 == 0: this step ticks poison + + Data.text._PokemonFaintedText = "FAKE {RAM:wNameBuffer} FAKE!" + fakeSelf:applyFieldPoison() + T.eq(pushed[1] and pushed[1].text, "FAKE " .. (mon.nickname or "FIXMON A") .. " FAKE!", + "a translated _PokemonFaintedText reaches the field-poison faint message") + Data.text._PokemonFaintedText = nil +end + +-- vanilla: no catalog entry, English literal +do + local game = mkGame() + setUpvalue(OW.applyFieldPoison, "Game", game) + local mon = game.save.party[1] + mon.status = "PSN" + mon.hp = 1 + game.save.poisonSteps = 3 + + fakeSelf:applyFieldPoison() + T.eq(pushed[1] and pushed[1].text, + (mon.nickname or "FIXMON A") .. "\nfainted!", + "no catalog entry falls back to the English fainted literal") +end + +-- ---- useSoftboiledFieldMove: _ItemUseNoEffectText / _PotionText ---- +do + local game = mkGame() + setUpvalue(OW.useSoftboiledFieldMove, "Game", game) + local user = Pokemon.new(Data, "FIXMON_A", 20) + local target = Pokemon.new(Data, "FIXMON_B", 20) + target.hp = target.stats.hp -- already full: no effect + + Data.text._ItemUseNoEffectText = "FAKE-NOEFFECT!" + local ok = fakeSelf:useSoftboiledFieldMove(user, target) + T.check(ok == false, "a full-HP target reports no effect") + T.eq(pushed[1] and pushed[1].text, "FAKE-NOEFFECT!", + "a translated _ItemUseNoEffectText reaches the no-effect message") + Data.text._ItemUseNoEffectText = nil +end + +do + local game = mkGame() + setUpvalue(OW.useSoftboiledFieldMove, "Game", game) + local user = Pokemon.new(Data, "FIXMON_A", 20) + local target = Pokemon.new(Data, "FIXMON_B", 20) + target.hp = target.stats.hp - 10 -- missing exactly 10 HP + + Data.text._PotionText = "FAKE {RAM:wNameBuffer} healed {NUM:wHPBarHPDifference, 2, 3}!" + local ok = fakeSelf:useSoftboiledFieldMove(user, target) + T.check(ok == true, "a damaged target heals successfully") + T.eq(pushed[1] and pushed[1].text, + "FAKE " .. (target.nickname or "FIXMON B") .. " healed 10!", + "a translated _PotionText reaches the heal message, amount included") + Data.text._PotionText = nil +end + +-- vanilla: no catalog entry, the fallback's single %s slot still fills +-- correctly (the amount is silently dropped by design, same as +-- ItemEffects.lua's own _PotionText fallback -- not a regression, this +-- matches the pre-fix literal's behavior exactly) +do + local game = mkGame() + setUpvalue(OW.useSoftboiledFieldMove, "Game", game) + local user = Pokemon.new(Data, "FIXMON_A", 20) + local target = Pokemon.new(Data, "FIXMON_B", 20) + target.hp = target.stats.hp - 10 + local ok = fakeSelf:useSoftboiledFieldMove(user, target) + T.check(ok == true, "a damaged target heals successfully (vanilla)") + T.eq(pushed[1] and pushed[1].text, + (target.nickname or "FIXMON B") .. "'s HP\nwas restored!", + "no catalog entry falls back to the English literal (no amount shown)") +end + +package.loaded["src.core.Sound"] = realSound +T.finish("overworld_field_faint_heal_romtext") diff --git a/tests/engine/overworld_hidden_item_romtext.lua b/tests/engine/overworld_hidden_item_romtext.lua new file mode 100644 index 00000000..f1804e49 --- /dev/null +++ b/tests/engine/overworld_hidden_item_romtext.lua @@ -0,0 +1,85 @@ +-- OverworldState:tryHiddenObject()'s "%s found\n%s!" message used to +-- substitute both the player name and the item name into one bare Lua +-- literal. The real _FoundHiddenItemText label leads with a {PLAYER} +-- named token, which romText auto-fills from a 2-arg call in the same +-- order the literal already used -- this test checks both slots land +-- correctly (an accidental argument swap is the easy mistake this shape +-- invites). +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local Data = T.fixtures.fresh() + +local SaveData = require("src.core.SaveData") +local OW = require("src.world.OverworldController") + +local function setUpvalue(fn, name, val) + local i = 1 + while true do + local n = debug.getupvalue(fn, i) + if not n then return false end + if n == name then debug.setupvalue(fn, i, val); return true end + i = i + 1 + end +end + +local pushed = {} +local textBoxStub = { + new = function(_, text, onDone, opts) + return { text = text, onDone = onDone, opts = opts } + end, + soundOpts = function() return {} end, +} + +local MAP_ID = "FIX_TOWN" +Data.field.hiddenItems[MAP_ID] = { { x = 3, y = 3, item = "FIX_BALL" } } + +local function mkGame() + local save = SaveData.newGame() + save.player.name = "FAKEPLAYER" + pushed = {} + return { + data = Data, save = save, + stack = { push = function(_, item) pushed[#pushed + 1] = item end }, + } +end + +T.check(setUpvalue(OW.tryHiddenObject, "Game", mkGame()), "Game upvalue on tryHiddenObject") +T.check(setUpvalue(OW.tryHiddenObject, "TextBox", textBoxStub), "TextBox upvalue on tryHiddenObject") + +local fakeSelf = setmetatable({ map = { id = MAP_ID } }, { __index = OW }) + +-- translated: player name and item name both land in the right slots +do + local game = mkGame() + setUpvalue(OW.tryHiddenObject, "Game", game) + Data.text._FoundHiddenItemText = "FAKE {PLAYER} found FAKE {RAM:wNameBuffer} FAKE!" + local found = fakeSelf:tryHiddenObject(3, 3) + T.check(found == true, "the hidden item at (3,3) is found") + T.eq(pushed[1] and pushed[1].text, + "FAKE FAKEPLAYER found FAKE FIX BALL FAKE!", + "a translated _FoundHiddenItemText fills both {PLAYER} and the item name") + Data.text._FoundHiddenItemText = nil +end + +-- vanilla: no catalog entry, so romText falls back to plain +-- Strings(fallback, ...) -- the fallback literal is "%s found\n%s!" (both +-- slots plain %s, matching the pre-fix literal's own shape), not the +-- {PLAYER} token the real label uses, since Strings() never does +-- {TOKEN} substitution on its own. (A {PLAYER}-token fallback would +-- still render correctly too, since the real TextBox.new always runs +-- TextBox.substitute over whatever text it's given -- but this test +-- stubs TextBox without that call, and the fallback shouldn't lean on a +-- substitution pass happening downstream regardless.) +do + local game = mkGame() + setUpvalue(OW.tryHiddenObject, "Game", game) + game.save.hiddenTaken = {} -- fresh spot + local found = fakeSelf:tryHiddenObject(3, 3) + T.check(found == true, "the hidden item is found again in a fresh game") + T.eq(pushed[1] and pushed[1].text, "FAKEPLAYER found\nFIX BALL!", + "with no catalog entry, the fallback still fills both the player " + .. "and item name via plain %s substitution") +end + +T.finish("overworld_hidden_item_romtext") diff --git a/tests/engine/slot_machine_lined_up_romtext.lua b/tests/engine/slot_machine_lined_up_romtext.lua new file mode 100644 index 00000000..699007c4 --- /dev/null +++ b/tests/engine/slot_machine_lined_up_romtext.lua @@ -0,0 +1,39 @@ +-- SlotMachine:resolveWin's "%s lined up!\nScored %d coins!" message used +-- to interpolate the symbol id AND the payout into one bare Lua literal. +-- The real _LinedUpText label has no slot for the symbol at all (the +-- original ROM drew it separately) -- only for the coin count. This test +-- fakes _LinedUpText and checks the symbol is concatenated in front of the +-- translated suffix, with the payout correctly substituted into it. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local Data = T.fixtures.fresh() + +local SlotMachine = require("src.ui.SlotMachine") + +local function mkSelf() + local game = { data = Data, save = { coins = 0 } } + return setmetatable({ game = game, allowMatchesCounter = 0 }, SlotMachine) +end + +-- translated: the fake suffix reaches self.message, with the symbol +-- concatenated in front and the payout substituted into the fake text +do + local self = mkSelf() + Data.text._LinedUpText = " FAKE-SUFFIX {RAM:wStringBuffer}!" + self:resolveWin({ symbol = "CHERRY", payout = 8 }) + T.eq(self.message, "CHERRY FAKE-SUFFIX 8!", + "a translated _LinedUpText reaches the lined-up message") + Data.text._LinedUpText = nil +end + +-- vanilla: with no catalog entry, the English literal still substitutes, +-- with its own leading space (the original had no slot for the symbol) +do + local self = mkSelf() + self:resolveWin({ symbol = "CHERRY", payout = 8 }) + T.eq(self.message, "CHERRY lined up!\nScored 8 coins!", + "no catalog entry still falls back to the English literal") +end + +T.finish("slot_machine_lined_up_romtext")