mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-18 11:44:42 +02:00
@@ -94,6 +94,21 @@ return function(game)
|
||||
imp:_useSkin(nil)
|
||||
shot("skins_tab_imported.png")
|
||||
|
||||
-- import button: the picker hands back an absolute host path, not a drop
|
||||
local picked = dir .. "/picked_skin.zip"
|
||||
local pf = io.open(picked, "wb")
|
||||
pf:write(raw)
|
||||
pf:close()
|
||||
imp:_installSkinZip(picked)
|
||||
U.log("path import notice:", imp._skinNotice.text)
|
||||
U.log("picked_skin found:", tostring(TouchSkin.find("picked_skin") ~= nil))
|
||||
U.log("import label:", imp:_skinsImportButtonLabel())
|
||||
shot("skins_tab_path_imported.png")
|
||||
|
||||
love.window.setMode(520, 760, { resizable = true, highdpi = true })
|
||||
U.wait(3)
|
||||
shot("skins_tab_narrow.png")
|
||||
|
||||
U.log("done")
|
||||
love.event.quit()
|
||||
while true do coroutine.yield() end
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
-- Driver: the studio's side of the alt-tab crash. Leaving the window mid-drag
|
||||
-- must drop the drag and the queued click and still draw; main.lua's routing of
|
||||
-- love.focus / love.visible / pad events while the studio owns the window is
|
||||
-- pinned by tests/engine/skin_studio_image_import.lua (a driver run boots a
|
||||
-- game, so main.lua's Studio branch is not live here).
|
||||
-- POKEPORT_DRIVER=tests/drivers/skin_studio_focus_test.lua love .
|
||||
return function(game)
|
||||
local U = dofile("tests/drivers/util.lua")
|
||||
local Studio = require("src.ui.SkinStudio")
|
||||
local TouchControls = require("src.core.TouchControls")
|
||||
|
||||
love.window.setMode(1280, 720, { resizable = true, highdpi = true })
|
||||
U.wait(2)
|
||||
|
||||
Studio.load({ version = "red", skinId = "gb_anim", onClose = function() end })
|
||||
U.wait(2)
|
||||
U.log("skin:", Studio.skin and Studio.skin.id)
|
||||
|
||||
Studio.drag = { kind = "control-move", mx = 1, my = 1, bx = 0, by = 0,
|
||||
bw = 10, bh = 10 }
|
||||
Studio.clicked = true
|
||||
local okFocus, errFocus = pcall(Studio.focus, false)
|
||||
U.log("focus lost:", okFocus, errFocus or "")
|
||||
U.log("drag:", tostring(Studio.drag), "click:", tostring(Studio.clicked))
|
||||
local droppedDrag = Studio.drag == nil and Studio.clicked == false
|
||||
|
||||
-- a held test press must not survive the trip out of the window either
|
||||
Studio.testing = true
|
||||
TouchControls:setPreview(false)
|
||||
TouchControls.held = TouchControls.held or {}
|
||||
TouchControls.held.start = true
|
||||
pcall(Studio.focus, false)
|
||||
local held = next(TouchControls.held or {})
|
||||
U.log("held after focus loss:", tostring(held))
|
||||
|
||||
local okVisible = pcall(Studio.visible, false)
|
||||
U.log("minimize:", okVisible)
|
||||
|
||||
local okDraw, errDraw = pcall(Studio.draw)
|
||||
U.log("draw after alt-tab:", okDraw, errDraw or "")
|
||||
|
||||
Studio.unload()
|
||||
|
||||
if droppedDrag and held == nil and okVisible and okDraw then
|
||||
U.log("RESULT pass")
|
||||
else
|
||||
U.log("RESULT FAIL")
|
||||
end
|
||||
love.event.quit()
|
||||
while true do coroutine.yield() end
|
||||
end
|
||||
@@ -0,0 +1,120 @@
|
||||
-- Driver: imports a bezel image into a fresh skin through the studio's Import
|
||||
-- button and shoots the result, with the native dialog stubbed out (the real
|
||||
-- one blocks on osascript / zenity / PowerShell). Also covers button art and
|
||||
-- the drag-and-drop path.
|
||||
-- SHOT_DIR=/tmp/studioimport POKEPORT_DRIVER=tests/drivers/skin_studio_import_shot.lua love .
|
||||
return function(game)
|
||||
local U = dofile("tests/drivers/util.lua")
|
||||
local Studio = require("src.ui.SkinStudio")
|
||||
local FilePicker = require("src.core.FilePicker")
|
||||
local TouchSkin = require("src.core.TouchSkin")
|
||||
|
||||
local dir = os.getenv("SHOT_DIR") or "/tmp/studioimport"
|
||||
os.execute('mkdir -p "' .. dir .. '" 2>/dev/null')
|
||||
love.window.setMode(1440, 900, { resizable = true, highdpi = true })
|
||||
U.wait(2)
|
||||
|
||||
local pending = nil
|
||||
love.draw = function()
|
||||
Studio.draw()
|
||||
if pending then
|
||||
local path = pending
|
||||
pending = nil
|
||||
love.graphics.captureScreenshot(function(imagedata)
|
||||
local f = io.open(path, "wb")
|
||||
if f then f:write(imagedata:encode("png"):getString()) f:close() end
|
||||
end)
|
||||
end
|
||||
end
|
||||
local function shot(name)
|
||||
pending = dir .. "/" .. name
|
||||
for _ = 1, 90 do
|
||||
if not pending then break end
|
||||
coroutine.yield()
|
||||
end
|
||||
U.wait(3)
|
||||
local f = io.open(dir .. "/" .. name, "rb")
|
||||
U.log(f and "shot" or "FAIL shot", name)
|
||||
if f then f:close() end
|
||||
end
|
||||
|
||||
Studio.load({ version = "red", onClose = function() end })
|
||||
U.wait(3)
|
||||
-- start from New so the run does not depend on the saved skin choice
|
||||
Studio.skin = TouchSkin.newSkin("import_probe")
|
||||
Studio.skinIdField = "import_probe"
|
||||
Studio.pageIndex, Studio.selected, Studio.images = 1, nil, {}
|
||||
U.log("skin:", Studio.skin.id, "bezel:", tostring(Studio.page().imagePath))
|
||||
|
||||
-- what the native dialog would hand back
|
||||
local picked = "assets/skins/gb_anim/img/gb_back.png"
|
||||
local realOpen = FilePicker.open
|
||||
FilePicker.open = function(prompt, kind)
|
||||
U.log("picker prompt:", prompt, "exts:", table.concat(kind.exts, "/"))
|
||||
return picked
|
||||
end
|
||||
|
||||
Studio.importImageFile("bezel")
|
||||
U.log("status:", tostring(Studio.status))
|
||||
U.log("bezel now:", tostring(Studio.page().imagePath),
|
||||
"loaded:", tostring(Studio.page().image ~= nil))
|
||||
U.log("skin root:", Studio.skin.root)
|
||||
U.log("file on disk:",
|
||||
tostring(love.filesystem.getInfo(Studio.skin.root .. "/img/gb_back.png") ~= nil))
|
||||
local viewport = Studio.page().viewport
|
||||
U.log("viewport left alone:", viewport and
|
||||
("%.3f %.3f %.3f %.3f"):format(viewport.x, viewport.y, viewport.w, viewport.h)
|
||||
or "none")
|
||||
shot("import_bezel.png")
|
||||
|
||||
-- button art goes onto the selected control, not the page
|
||||
Studio.addControl()
|
||||
FilePicker.open = function() return "assets/skins/gb_anim/img/gbc_a.png" end
|
||||
Studio.importImageFile("idle")
|
||||
local ctl = Studio.selectedControl()
|
||||
U.log("idle art:", tostring(ctl.imagePath),
|
||||
"bezel untouched:", tostring(Studio.page().imagePath))
|
||||
FilePicker.open = function() return "assets/skins/gb_anim/img/gbc_b.png" end
|
||||
Studio.importImageFile("pressed")
|
||||
U.log("pressed art:", tostring(ctl.pressedImagePath))
|
||||
shot("import_button_art.png")
|
||||
|
||||
-- cancelling the dialog changes nothing
|
||||
local before = Studio.page().imagePath
|
||||
FilePicker.open = function() return nil end
|
||||
Studio.importImageFile("bezel")
|
||||
U.log("after cancel:", tostring(Studio.page().imagePath),
|
||||
"unchanged:", tostring(before == Studio.page().imagePath))
|
||||
|
||||
-- the drop path shares the import, and refuses non-art
|
||||
Studio.imageTarget = "bezel"
|
||||
local raw = love.filesystem.read("assets/skins/tv_crt/img/tv-integer.png")
|
||||
Studio.filedropped({
|
||||
getFilename = function() return "/tmp/tv_frame.png" end,
|
||||
open = function() return true end,
|
||||
read = function() return raw end,
|
||||
close = function() return true end,
|
||||
})
|
||||
U.log("dropped bezel:", tostring(Studio.page().imagePath))
|
||||
Studio.filedropped({
|
||||
getFilename = function() return "/tmp/notes.txt" end,
|
||||
open = function() return true end,
|
||||
read = function() return "nope" end,
|
||||
close = function() return true end,
|
||||
})
|
||||
U.log("after dropping a txt:", tostring(Studio.page().imagePath),
|
||||
"status:", tostring(Studio.status))
|
||||
shot("import_dropped.png")
|
||||
|
||||
love.window.setMode(760, 900, { resizable = true, highdpi = true })
|
||||
U.wait(3)
|
||||
shot("import_narrow.png")
|
||||
|
||||
FilePicker.open = realOpen
|
||||
local saved = TouchSkin.find("import_probe")
|
||||
U.log("skin discoverable:", tostring(saved ~= nil))
|
||||
Studio.unload()
|
||||
U.log("done")
|
||||
love.event.quit()
|
||||
while true do coroutine.yield() end
|
||||
end
|
||||
@@ -0,0 +1,68 @@
|
||||
-- The museum 1F ticket clerk's take-your-time/not-enough-money/come-again
|
||||
-- lines used to be bare English literals, invisible to game.data.text no
|
||||
-- matter what a translation mod put there. tests/engine/museum_money_box_
|
||||
-- bug1335.lua only ever runs with an empty game.data.text, so it can't
|
||||
-- tell a properly-wired t._Key or "..." fallback apart from a literal that
|
||||
-- never looked at t at all -- every assertion there passes either way.
|
||||
-- This test populates game.data.text with translated values and checks
|
||||
-- they actually reach the pushed TextBox, for all three lines.
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.harness")
|
||||
|
||||
package.loaded["src.render.TextBox"] = {
|
||||
new = function(_, text, onDone, opts)
|
||||
return { text = text, onDone = onDone, opts = opts }
|
||||
end,
|
||||
}
|
||||
|
||||
local M = assert(loadfile("data/scripts/story2.lua"))()
|
||||
local clerk = M.MUSEUM_1F.talk.TEXT_MUSEUM1F_SCIENTIST1
|
||||
|
||||
local TRANSLATED = {
|
||||
_Museum1FScientist1TakePlentyOfTimeText = "Prends ton temps\net profite bien !",
|
||||
_Museum1FScientist1DontHaveEnoughMoneyText = "Tu n'as pas assez\nd'argent.",
|
||||
_Museum1FScientist1ComeAgainText = "Reviens vite !",
|
||||
}
|
||||
|
||||
local pushed
|
||||
local function mkGame(cash)
|
||||
pushed = {}
|
||||
return {
|
||||
data = { text = TRANSLATED },
|
||||
save = { money = cash, flags = {} },
|
||||
stack = { push = function(_, box) pushed[#pushed + 1] = box end },
|
||||
}
|
||||
end
|
||||
|
||||
-- already ticketed: take-your-time line
|
||||
local g = mkGame(3000)
|
||||
g.save.flags.EVENT_BOUGHT_MUSEUM_TICKET = true
|
||||
clerk(g, nil, nil, function() end)
|
||||
T.eq(pushed[1].text, TRANSLATED._Museum1FScientist1TakePlentyOfTimeText,
|
||||
"an existing ticket holder gets the translated take-your-time line")
|
||||
|
||||
-- YES but short on cash: not-enough-money line
|
||||
g = mkGame(20)
|
||||
clerk(g, nil, nil, function() end)
|
||||
pushed[1].opts.choice(true)
|
||||
T.eq(pushed[2].text, TRANSLATED._Museum1FScientist1DontHaveEnoughMoneyText,
|
||||
"short on cash opens the translated not-enough-money box")
|
||||
|
||||
-- NO: come-again line
|
||||
g = mkGame(3000)
|
||||
clerk(g, nil, nil, function() end)
|
||||
pushed[1].opts.choice(false)
|
||||
T.eq(pushed[2].text, TRANSLATED._Museum1FScientist1ComeAgainText,
|
||||
"declining opens the translated come-again box")
|
||||
|
||||
-- unpopulated game.data.text still falls back to the English literal
|
||||
g = { data = { text = {} }, save = { money = 3000, flags = {} },
|
||||
stack = { push = function(_, box) pushed[#pushed + 1] = box end } }
|
||||
pushed = {}
|
||||
g.save.flags.EVENT_BOUGHT_MUSEUM_TICKET = true
|
||||
clerk(g, nil, nil, function() end)
|
||||
T.check(tostring(pushed[1].text):find("Take your time", 1, true) ~= nil,
|
||||
"an empty catalog still falls back to the English take-your-time line")
|
||||
|
||||
T.finish("museum_1f_clerk_translation_test")
|
||||
@@ -0,0 +1,118 @@
|
||||
-- Skins tab Import button: picker plumbing + path/drop install (SKINIMP-01..05).
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
if not _G.love then _G.love = require("tests.love_stub") end
|
||||
|
||||
local S = require("tests.harness").suite("skin import picker")
|
||||
local eq = S.eq
|
||||
local check = S.check
|
||||
|
||||
local RomImporter = require("src.import.RomImporter")
|
||||
local TouchSkin = require("src.core.TouchSkin")
|
||||
|
||||
love.system = love.system or {}
|
||||
local saved = {
|
||||
pickFile = love.system.pickFile,
|
||||
getPickedFile = love.system.getPickedFile,
|
||||
getPickError = love.system.getPickError,
|
||||
installArchive = TouchSkin.installArchive,
|
||||
}
|
||||
|
||||
local installed
|
||||
TouchSkin.installArchive = function(name, data)
|
||||
installed = { name = name, data = data }
|
||||
if not data or data == "" then return nil, "empty archive" end
|
||||
return (name:match("([^/\\]+)$") or name):gsub("%.[Zz][Ii][Pp]$", "")
|
||||
end
|
||||
|
||||
local function freshImporter(fields)
|
||||
local imp = RomImporter.new(function() end, { launcher = true })
|
||||
imp.tab = "skins"
|
||||
imp._skins = {}
|
||||
imp._ensureSkins = function(self) return self._skins end
|
||||
for k, v in pairs(fields or {}) do imp[k] = v end
|
||||
return imp
|
||||
end
|
||||
|
||||
-- SKINIMP-01: a picker path installs as a skin, not a mod
|
||||
installed = nil
|
||||
local imp = freshImporter()
|
||||
local tmp = os.tmpname() .. ".zip"
|
||||
local f = assert(io.open(tmp, "wb"))
|
||||
f:write("PK\3\4skin-archive")
|
||||
f:close()
|
||||
imp:_installSkinZip(tmp)
|
||||
os.remove(tmp)
|
||||
check(installed ~= nil, "an absolute picker path reaches TouchSkin.installArchive")
|
||||
eq(installed.data, "PK\3\4skin-archive", "the picked file's bytes are installed")
|
||||
check(imp._skinNotice and imp._skinNotice.ok, "a good pick reports success")
|
||||
|
||||
-- SKINIMP-02: a dropped file still installs, and both paths land on the tab
|
||||
installed = nil
|
||||
imp = freshImporter({ tab = "red" })
|
||||
imp:filedropped({
|
||||
getFilename = function() return "/tmp/dropped_skin.zip" end,
|
||||
getSize = function() return 4 end,
|
||||
open = function() return true end,
|
||||
read = function() return "PKZP" end,
|
||||
close = function() return true end,
|
||||
})
|
||||
check(installed == nil, "a zip dropped off the skins tab is still a mod")
|
||||
installed = nil
|
||||
imp = freshImporter()
|
||||
imp:filedropped({
|
||||
getFilename = function() return "/tmp/dropped_skin.zip" end,
|
||||
getSize = function() return 4 end,
|
||||
open = function() return true end,
|
||||
read = function() return "PKZP" end,
|
||||
close = function() return true end,
|
||||
})
|
||||
eq(installed and installed.name, "/tmp/dropped_skin.zip",
|
||||
"a zip dropped on the skins tab installs as a skin")
|
||||
eq(imp.tab, "skins", "the skin install stays on the skins tab")
|
||||
|
||||
-- SKINIMP-03: the mobile bridge borrows the mod picker kind
|
||||
local requestedKind
|
||||
love.system.pickFile = function(kind)
|
||||
requestedKind = kind
|
||||
return true
|
||||
end
|
||||
love.system.getPickedFile = function() return nil end
|
||||
love.system.getPickError = function() return nil end
|
||||
imp = freshImporter({ nativePicker = true })
|
||||
imp:chooseSkin()
|
||||
eq(requestedKind, "mod", "chooseSkin opens the .zip picker")
|
||||
eq(imp.pickerPendingKind, "skin", "the pending pick is routed to the skins tab")
|
||||
|
||||
-- SKINIMP-04: the picked file comes back through update() as a skin
|
||||
installed = nil
|
||||
love.system.getPickedFile = function()
|
||||
love.system.getPickedFile = function() return nil end
|
||||
return "/picked/from_bridge.zip"
|
||||
end
|
||||
imp._installSkinZip = function(self, source)
|
||||
self.installedSource = source
|
||||
self._skinNotice = { ok = true, text = "Imported from_bridge" }
|
||||
end
|
||||
imp:update(0)
|
||||
eq(imp.installedSource, "/picked/from_bridge.zip",
|
||||
"update() hands the picked path to the skin installer")
|
||||
eq(imp.pickerPendingKind, nil, "the pending kind clears once consumed")
|
||||
|
||||
-- SKINIMP-05: a picker error lands on the skins notice, not the mods one
|
||||
love.system.getPickedFile = function() return nil end
|
||||
love.system.getPickError = function()
|
||||
love.system.getPickError = function() return nil end
|
||||
return "picker refused"
|
||||
end
|
||||
imp = freshImporter({ nativePicker = true, pickerPendingKind = "skin" })
|
||||
imp:update(0)
|
||||
check(imp._skinNotice and not imp._skinNotice.ok,
|
||||
"a failed skin pick reports on the skins tab")
|
||||
eq(imp.modNotice, nil, "a failed skin pick leaves the mods notice alone")
|
||||
|
||||
love.system.pickFile = saved.pickFile
|
||||
love.system.getPickedFile = saved.getPickedFile
|
||||
love.system.getPickError = saved.getPickError
|
||||
TouchSkin.installArchive = saved.installArchive
|
||||
|
||||
S.finish()
|
||||
@@ -0,0 +1,137 @@
|
||||
-- Skin Studio art import: the native image picker behind the "Import" buttons
|
||||
-- (bezel / idle / pressed), and the alt-tab crash that took the studio down --
|
||||
-- love.focus fell through to Game:focus with no game booted.
|
||||
-- luajit tests/engine/skin_studio_image_import.lua
|
||||
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
if not _G.love then _G.love = require("tests.love_stub") end
|
||||
|
||||
local T = require("tests.harness")
|
||||
local check, eq = T.check, T.eq
|
||||
|
||||
local FilePicker = require("src.core.FilePicker")
|
||||
local TouchSkin = require("src.core.TouchSkin")
|
||||
local Studio = require("src.ui.SkinStudio")
|
||||
|
||||
local function session()
|
||||
Studio.skin = TouchSkin.newSkin("t")
|
||||
Studio.skinIdField = "t"
|
||||
Studio.pageIndex = 1
|
||||
Studio.selected = nil
|
||||
Studio.canvasIndex = 1
|
||||
Studio.drag = nil
|
||||
Studio.dirty = false
|
||||
Studio.status = nil
|
||||
Studio.testing = false
|
||||
Studio.images = {}
|
||||
Studio.imageTarget = "idle"
|
||||
return Studio.skin
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------ file picker
|
||||
|
||||
check(FilePicker.matches("bezel.png", FilePicker.IMAGE), "png is art")
|
||||
check(FilePicker.matches("BEZEL.PNG", FilePicker.IMAGE), "case does not matter")
|
||||
check(FilePicker.matches("shot.jpg", FilePicker.IMAGE), "jpg is art")
|
||||
check(FilePicker.matches("shot.jpeg", FilePicker.IMAGE), "jpeg is art")
|
||||
check(not FilePicker.matches("skin.zip", FilePicker.IMAGE), "a zip is not art")
|
||||
eq(FilePicker.basename("/home/me/art/bezel.png"), "bezel.png", "posix basename")
|
||||
eq(FilePicker.basename("C:\\Users\\me\\bezel.png"), "bezel.png", "windows basename")
|
||||
eq(FilePicker.basename("bezel.png"), "bezel.png", "a bare name is its own basename")
|
||||
|
||||
-- ----------------------------------------------------------- import target
|
||||
|
||||
session()
|
||||
Studio.addControl()
|
||||
local ctl = Studio.selectedControl()
|
||||
check(Studio.adoptImage("bezel.png", "PNGDATA", "bezel"), "bezel import succeeds")
|
||||
eq(Studio.page().imagePath, "img/bezel.png", "bezel art lands on the page")
|
||||
eq(ctl.imagePath, nil, "and not on the selected control")
|
||||
check(Studio.dirty, "importing marks the skin dirty")
|
||||
check(Studio.status:find("bezel", 1, true) ~= nil, "the status names the target")
|
||||
|
||||
check(Studio.adoptImage("a_button.png", "PNGDATA", "idle"), "idle import succeeds")
|
||||
eq(ctl.imagePath, "img/a_button.png", "idle art lands on the control")
|
||||
eq(Studio.page().imagePath, "img/bezel.png", "and leaves the bezel alone")
|
||||
|
||||
check(Studio.adoptImage("a_down.png", "PNGDATA", "pressed"), "pressed import succeeds")
|
||||
eq(ctl.pressedImagePath, "img/a_down.png", "pressed art lands on the control")
|
||||
eq(ctl.imagePath, "img/a_button.png", "idle art survives")
|
||||
|
||||
check(not Studio.adoptImage("notes.txt", "junk", "bezel"), "non-art is refused")
|
||||
eq(Studio.page().imagePath, "img/bezel.png", "and changes nothing")
|
||||
|
||||
-- the imported files are listed for the cycle buttons
|
||||
local listed = {}
|
||||
for _, rel in ipairs(Studio.images or {}) do listed[rel] = true end
|
||||
check(listed["img/bezel.png"], "the imported bezel joins the image list")
|
||||
|
||||
-- -------------------------------------------------------- picker plumbing
|
||||
|
||||
local realOpen, realRead = FilePicker.open, FilePicker.read
|
||||
local asked
|
||||
FilePicker.open = function(prompt, kind)
|
||||
asked = { prompt = prompt, kind = kind }
|
||||
return "/tmp/some folder/frame.png"
|
||||
end
|
||||
FilePicker.read = function() return "PNGDATA" end
|
||||
|
||||
session()
|
||||
Studio.importImageFile("bezel")
|
||||
check(asked ~= nil, "the bezel button opens a picker")
|
||||
eq(asked.kind, FilePicker.IMAGE, "and asks for images, not archives")
|
||||
check(asked.prompt:lower():find("bezel", 1, true) ~= nil, "with a bezel prompt")
|
||||
eq(Studio.page().imagePath, "img/frame.png", "the pick becomes the bezel")
|
||||
eq(Studio.imageTarget, "bezel", "and the target sticks for the cycle button")
|
||||
|
||||
-- no control selected: an art import says so instead of writing to the page
|
||||
session()
|
||||
asked = nil
|
||||
Studio.importImageFile("idle")
|
||||
check(asked == nil, "no picker opens without a control to paint")
|
||||
eq(Studio.page().imagePath, nil, "and the bezel is not overwritten")
|
||||
check(Studio.status ~= nil, "the studio explains why")
|
||||
|
||||
-- a cancelled dialog leaves the skin exactly as it was
|
||||
session()
|
||||
FilePicker.open = function() return nil end
|
||||
Studio.importImageFile("bezel")
|
||||
eq(Studio.page().imagePath, nil, "cancelling imports nothing")
|
||||
check(not Studio.dirty, "and does not dirty the skin")
|
||||
|
||||
FilePicker.open, FilePicker.read = realOpen, realRead
|
||||
|
||||
-- --------------------------------------------------------- focus / alt-tab
|
||||
|
||||
session()
|
||||
Studio.drag = { kind = "control-move", mx = 1, my = 1, bx = 0, by = 0, bw = 1, bh = 1 }
|
||||
Studio.clicked = true
|
||||
Studio.focus(false)
|
||||
eq(Studio.drag, nil, "losing focus drops the in-flight drag")
|
||||
eq(Studio.clicked, false, "and the queued click")
|
||||
check(pcall(Studio.visible, false), "minimizing is survivable too")
|
||||
|
||||
-- main.lua used to hand focus / visibility / pad events straight to Game while
|
||||
-- the studio owned the window, and the launcher has no Game -- alt-tab took the
|
||||
-- app down with "attempt to index a nil value (global 'Game')".
|
||||
local f = assert(io.open("main.lua", "r"))
|
||||
local src = f:read("*a")
|
||||
f:close()
|
||||
|
||||
local checked = 0
|
||||
for name, body in ("\n" .. src):gmatch("\nfunction love%.([%w_]+)%b()\n(.-)\nend\n") do
|
||||
if body:find("Game:") then
|
||||
checked = checked + 1
|
||||
check(body:find("Studio") ~= nil,
|
||||
"love." .. name .. " checks Studio before dispatching to Game")
|
||||
check(body:find("not Game then") ~= nil
|
||||
or body:find("if Game then") ~= nil
|
||||
or body:find("Game and ") ~= nil,
|
||||
"love." .. name .. " tolerates a nil Game (launcher / studio session)")
|
||||
end
|
||||
end
|
||||
check(checked >= 10, "the handler scan actually found handlers (" .. checked .. ")")
|
||||
check(src:find("Studio.focus", 1, true) ~= nil, "love.focus routes to the studio")
|
||||
check(src:find("Studio.visible", 1, true) ~= nil, "so does love.visible")
|
||||
|
||||
T.finish("skin_studio_image_import")
|
||||
@@ -490,6 +490,11 @@ check("hurt is easier to catch", hurtRate > fullRate, true)
|
||||
local asleepRate = Catching.rate({
|
||||
maxHp = 60, hp = 1, catchRate = 45, ball = "POKE_BALL", status = "sleep" })
|
||||
check("sleep adds 10", asleepRate, math.min(255, hurtRate + 10))
|
||||
checkNear("preview converts the exact byte roll", Catching.chance({
|
||||
maxHp = 60, hp = 60, catchRate = 45, ball = "POKE_BALL" }),
|
||||
fullRate * 100 / 256, 0.000001)
|
||||
check("master ball preview is certain", Catching.chance({
|
||||
maxHp = 60, hp = 60, catchRate = 1, ball = "MASTER_BALL" }), 100)
|
||||
-- The cart's bug: burn/poison/paralysis add nothing.
|
||||
check("poison adds nothing (cart bug)", Catching.rate({
|
||||
maxHp = 60, hp = 1, catchRate = 45, ball = "POKE_BALL",
|
||||
|
||||
@@ -282,6 +282,16 @@ local function runToMenu(screen, cap)
|
||||
return false
|
||||
end
|
||||
|
||||
do
|
||||
local screen = newScreen({ inventory = {
|
||||
MASTER_BALL = 1, POKE_BALL = 1,
|
||||
} })
|
||||
eq(screen:catchChance("MASTER_BALL"), 100,
|
||||
"the battle screen exposes a certain Master Ball preview")
|
||||
check(type(screen:catchChance("POKE_BALL")) == "number",
|
||||
"the battle screen exposes the live wild catch preview")
|
||||
end
|
||||
|
||||
-- ---- BattleMenu empties the textbox ---------------------------------------
|
||||
do
|
||||
local screen = newScreen()
|
||||
|
||||
@@ -163,14 +163,23 @@ function screen2:chooseMove(slot)
|
||||
return true
|
||||
end
|
||||
function screen2:cancelMove() self.phase = "menu" return true end
|
||||
function screen2:catchChance(ball)
|
||||
return ball == "MASTER_BALL" and 100 or 37.5
|
||||
end
|
||||
local game2 = {
|
||||
data = {
|
||||
pokemon = { CHIKORITA = { name = "CHIKORITA" },
|
||||
RATTATA = { name = "RATTATA" } },
|
||||
moves = { TACKLE = { name = "TACKLE", type = "NORMAL",
|
||||
power = 35, accuracy = 95, pp = 35 } },
|
||||
items = {
|
||||
MASTER_BALL = { name = "MASTER BALL", pocket = "BALL" },
|
||||
POTION = { name = "POTION", pocket = "ITEM" },
|
||||
},
|
||||
},
|
||||
save = { party = { player2 } }, stack = { states = { screen2 } },
|
||||
save = { party = { player2 }, inventory = {
|
||||
MASTER_BALL = 1, POTION = 2,
|
||||
} }, stack = { states = { screen2 } },
|
||||
}
|
||||
|
||||
local api2 = require("src.battle.gen2.BattleAPI").new(game2)
|
||||
@@ -179,6 +188,9 @@ check(snapshot2 and snapshot2.kind == "wild" and snapshot2.prompt == "menu",
|
||||
"Gold battle is discovered through its screen id")
|
||||
eq(snapshot2.player.maxHp, 21, "Gold max HP uses the mon field")
|
||||
eq(snapshot2.moves[1].name, "TACKLE", "Gold moves are copied")
|
||||
eq(#snapshot2.items, 1, "Gold exposes balls without guessing targeted items")
|
||||
eq(snapshot2.items[1].catchChance, 100,
|
||||
"Gold ball records expose the exact catch preview")
|
||||
snapshot2.player.hp = 0
|
||||
snapshot2.moves[1].pp = 0
|
||||
eq(player2.hp, 20, "changing a snapshot cannot change a Gold Pokemon")
|
||||
|
||||
@@ -166,6 +166,47 @@ eq(ri._requiredImported.importId, "source", "focus routes to the pending declara
|
||||
check(love.filesystem.getInfo("picked_required_import.bin") == nil,
|
||||
"focus removes the staged required-file pick")
|
||||
|
||||
-- Android releases with the updated launcher but the older native bridge do
|
||||
-- not advertise required_import. They still support the established ROM SAF
|
||||
-- picker, whose result must be quarantined to the pending dependency request.
|
||||
love.system.pickFileKinds = function() return "rom,mod,sav" end
|
||||
pickCalls = {}
|
||||
ri = freshImporter({ red = true, blue = true })
|
||||
ri.nativePicker = true
|
||||
ri.mobileFileBridge = true
|
||||
ri.mods = { {
|
||||
id = "needs_source",
|
||||
manifest = { id = "needs_source", name = "Needs Source",
|
||||
required_imports = { { id = "source", name = "Source", file = "source.bin",
|
||||
format = "raw", md5 = { "00000000000000000000000000000000" } } } },
|
||||
} }
|
||||
ri:chooseRequiredImport("needs_source", "source")
|
||||
eq(pickCalls[1], "rom", "legacy Android bridge falls back to its ROM SAF picker")
|
||||
check(ri.requiredImportLegacyRomPick,
|
||||
"legacy Android ROM picker result is marked as a required import")
|
||||
ri._importRequiredSource = function(self, modId, importId, source)
|
||||
self._requiredImported = { modId = modId, importId = importId, source = source }
|
||||
return true
|
||||
end
|
||||
love.filesystem.write("picked_rom.gb", "source bytes")
|
||||
ri:focus(true)
|
||||
eq(ri._requiredImported.source, "picked_rom.gb",
|
||||
"legacy Android ROM staging name is routed to the required import")
|
||||
eq(ri._requiredImported.modId, "needs_source",
|
||||
"legacy Android picker preserves the requested mod")
|
||||
check(love.filesystem.getInfo("picked_rom.gb") == nil,
|
||||
"legacy Android dependency pick is removed after import")
|
||||
|
||||
-- A legacy bridge reports a failed copy using that same staging basename; it
|
||||
-- must stay on the dependency page rather than becoming a game-ROM error.
|
||||
ri:chooseRequiredImport("needs_source", "source")
|
||||
love.filesystem.write("pick_error.flag", "picked_rom.gb")
|
||||
ri:focus(true)
|
||||
check(ri.modNotice ~= nil and ri.modNotice.ok == false,
|
||||
"legacy Android picker errors are shown as dependency import errors")
|
||||
check(ri.pickerPendingKind == nil,
|
||||
"legacy Android picker error clears the pending dependency request")
|
||||
|
||||
love.system.getOS = saved.getOS
|
||||
love.system.pickFile = saved.pickFile
|
||||
love.system.pickFileKinds = saved.pickFileKinds
|
||||
@@ -174,5 +215,6 @@ love.filesystem.remove("usb_mod.zip")
|
||||
love.filesystem.remove("picked_mod.zip")
|
||||
love.filesystem.remove("picked_save.sav")
|
||||
love.filesystem.remove("picked_required_import.bin")
|
||||
love.filesystem.remove("picked_rom.gb")
|
||||
|
||||
S.finish()
|
||||
|
||||
Reference in New Issue
Block a user