Files
gen1recomp/tests/engine/box_showcase_test.lua
T
2026-10-06 11:00:16 -04:00

162 lines
9.4 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package.path="./?.lua;./?/init.lua;"..package.path
love=love or require("tests.love_stub")
local T=require("tests.harness")
local Store=require("src.box.Store")
local Showcase=require("src.box.Showcase")
local Serializer=require("src.core.SaveSerializer")
local Service=require("src.box.Service")
local state=Store.new();state.nextId=3
for i=1,2 do state.boxes[1].mons[i]={id=i,version="red",generation=1,
mon={species="PIKACHU",level=50,otId=42,ot="OWNER",nickname="PIKA",hp=10,
dvs={attack=i,defense=2,speed=3,special=4}},display={name="PIKA",species="PIKACHU",national=25}} end
local stage=assert(Showcase.add(Showcase.new(),{{box=1,slot=1},{box=1,slot=2}},state))
stage.name,stage.pattern,stage.music="My stage","Stars","Gentle"
stage.pieces[1].rotation,stage.pieces[1].scale,stage.pieces[1].flip=math.pi/2,2,true
stage.pieces[#stage.pieces+1]={kind="Arch",x=.5,y=.8,scale=1,rotation=0,flip=false}
local saved=assert(Showcase.save(state,5,stage))
T.eq(state.stages,nil,"stage editing leaves saved collection unchanged")
T.eq(saved.stages[5].name,"My stage","fifth stage slot saves its name")
local round=assert(Store.validate(assert(Serializer.decode(Serializer.encode(saved)))))
T.check(math.abs(round.stages[5].pieces[1].rotation-math.pi/2)<1e-12,"placement rotations survive persistence")
T.eq(round.stages[5].pieces[1].flip,true,"placement mirroring survives persistence")
T.eq(round.stages[5].music,"Gentle","selected music persists")
T.eq(#Showcase.resolve(round,stage),3,"furniture and Pokémon retain stage order")
local moved=assert(Store.moveGroup(round,{{box=1,slot=1}},25,60))
T.eq(Showcase.resolve(moved,stage)[1].entry.id,1,"stage references survive warehouse rearrangement")
T.check(not Showcase.save(state,6,stage),"stage slot outside 1–5 refuses save")
local bad=Store.copy(stage);bad.pieces[1].scale=0
T.check(not Showcase.save(state,1,bad),"invalid placement refuses save")
bad=Store.copy(stage);bad.pieces[1].x=0/0
T.check(not Showcase.save(state,1,bad),"NaN coordinates refuse save")
T.check(not Showcase.export(state,1,{}),"unsaved stage cannot be exported")
local files={[Store.PATH]=Serializer.encode(saved),["saves/red/slot1.lua"]=Serializer.encode({version="red",generation=1,
party={Store.copy(state.boxes[1].mons[2].mon)},boxes={}})}
local fs={read=function(p)return files[p]end,getInfo=function(p)return files[p] and {type="file"}end,
createDirectory=function()return true end,write=function(p,b)files[p]=b;return true end,remove=function(p)files[p]=nil;return true end}
local source={version="red",slotId="slot1",path="saves/red/slot1.lua"}
local Catalog=require("src.box.Catalog")
local oldCompatible=Catalog.compatible;Catalog.compatible=function()return true end
local service=assert(Service.open(fs))
T.check(service:withdraw(source,{{box=1,slot=1}},1),"withdrawal moves a staged record into a game")
T.eq(Showcase.resolve(service.state,stage)[1].missing,true,"withdrawn entry becomes a missing placement")
T.check(service:deposit(source,{{where="box",box=1,index=1}},2),"unique native identity can return to Box")
T.eq(Showcase.resolve(service.state,stage)[1].entry.id,1,"return restores the original stage reference")
T.eq(Store.count(service.state),2,"return creates no extra live record")
Catalog.compatible=oldCompatible
do
local themed=assert(Store.theme(saved,1,"Showcase","box/showcase/125.png","Gentle"))
local persisted=assert(Store.validate(assert(Serializer.decode(Serializer.encode(themed)))))
T.eq(Showcase.boxMusic(persisted,persisted.boxes[1]),"Gentle","applied theme keeps its music after reload")
persisted.stages[5].music="Night"
T.eq(Showcase.boxMusic(persisted,persisted.boxes[1]),"Gentle","later stage edits do not change the applied music")
local legacy=assert(Store.theme(saved,1,"Showcase","box/showcase/125.png"))
T.eq(Showcase.boxMusic(legacy,legacy.boxes[1]),"Gentle","existing stage exports resolve their music")
local cleared=assert(Store.theme(themed,1,"Forest"))
T.eq(cleared.boxes[1].showcaseMusic,nil,"changing theme clears associated music")
T.eq(Showcase.boxMusic(cleared,cleared.boxes[1]),"Silent","ordinary themes stop music")
T.check(not Store.theme(saved,1,"Showcase","box/showcase/125.png","invalid"),"unknown theme tracks are rejected")
local music=require("src.box.ShowcaseMusic")
local oldRequest,oldAudio=music.request,love.audio
local created,played,stopped,released=0,0,0,0
local pending=2
music.request=function()
if pending>0 then pending=pending-1;return nil,"pending" end
return {}
end
love.audio={newSource=function()
created=created+1
return {setLooping=function(_,v) T.eq(v,true,"theme music loops") end,
setVolume=function() end,play=function() played=played+1 end,stop=function() stopped=stopped+1 end,
release=function() released=released+1 end}
end}
love.sound=love.sound or {};local oldNew=love.sound.newSoundData
love.sound.newSoundData=love.sound.newSoundData or function() end
local panel=require("src.import.BoxPanel")
local imp={_boxState={service={state=themed},box=1}}
panel.update(imp,.016);panel.update(imp,.016)
T.eq(created,0,"Box theme renders across frames before creating a source")
T.eq(Showcase.currentMusic(),"Gentle","pending render keeps the requested track")
panel.update(imp,.016);panel.update(imp,.016)
T.eq(created,1,"Box theme creates one source across repeated frames")
T.eq(played,1,"applied Box music starts automatically")
imp._boxState.toolsPage="Showcase";panel.update(imp,.016)
T.eq(stopped,1,"leaving board stops theme music")
T.eq(released,1,"old source is released")
Showcase.playMusic({music="Night"});panel.update(imp,.016)
T.eq(Showcase.currentMusic(),"Night","editor playback survives its update")
imp._boxState.toolsPage=nil;panel.update(imp,.016)
T.eq(Showcase.currentMusic(),"Gentle","returning to Boxes resumes its theme")
imp._boxState.box=2;panel.update(imp,.016)
T.eq(Showcase.currentMusic(),"Silent","switching to plain Box stops track")
Showcase.stopMusic();music.request,love.audio,love.sound.newSoundData=oldRequest,oldAudio,oldNew
end
do
local music=require("src.box.ShowcaseMusic")
local oldNew=love.sound and love.sound.newSoundData
love.sound=love.sound or {}
local samples=0
love.sound.newSoundData=function(n) return {n=n,setSample=function() samples=samples+1 end} end
local calls,sound,why=0
repeat calls=calls+1;sound,why=music.request("Dream",0) until sound or why~="pending" or calls>100000
T.check(sound~=nil and calls>1,"song rendering yields across calls")
T.eq(music.request("Dream",0),sound,"rendered song is cached")
T.eq(samples,sound.n*2,"every sample is written once")
love.sound.newSoundData=oldNew
end
do
local files={["box/showcase/11.png"]="a",["box/showcase/12.png"]="b",["box/showcase/21.png"]="c",["box/showcase/note.txt"]="d"}
local fs={read=function(p)return files[p]end,getInfo=function(p)return files[p] and {type="file"}end,remove=function(p)files[p]=nil;return true end,
getDirectoryItems=function()local out={} for p in pairs(files) do out[#out+1]=p:match("[^/]+$") end return out end}
local kept=Store.new();kept.boxes[2].theme,kept.boxes[2].wallpaper="Showcase","box/showcase/12.png"
T.eq(Showcase.prune(kept,fs,"box/showcase/21.png"),1,"stale exported wallpaper is removed")
T.eq(files["box/showcase/11.png"],nil,"unreferenced export is gone")
T.check(files["box/showcase/12.png"] and files["box/showcase/21.png"] and files["box/showcase/note.txt"],"referenced, new and foreign files survive")
files["box/showcase/31.png"]="e";files[require("src.box.Transaction").PATH]="pending"
T.eq(Showcase.prune(kept,fs),0,"pending transfer blocks wallpaper cleanup")
end
do
local g=love.graphics
local oldRect=g.rectangle
local count=0
g.rectangle=function() count=count+1 end
local empty=Showcase.new();empty.pattern="Checks"
Showcase.draw(state,empty,0,0,0,100);Showcase.draw(state,empty,0,0,100,-1)
T.eq(count,0,"zero-sized stage draws nothing")
g.rectangle=oldRect
end
do
local g=love.graphics
local oldColor,oldScissor,oldSetScissor=g.setColor,g.getScissor,g.setScissor
local colors,clips={},{}
g.setColor=function(...) colors[#colors+1]={...} end
g.getScissor=function() return 10,20,100,50 end
g.setScissor=function(...) clips[#clips+1]={...} end
for _,background in ipairs(Showcase.BACKGROUNDS) do
colors,clips={},{}
local empty=Showcase.new();empty.background=background
Showcase.draw(state,empty,0,0,568,216)
T.check(math.max(colors[1][1],colors[1][2],colors[1][3])>.1,
background.." uses the launcher's 255 RGB color contract")
T.eq(colors[2][1],1,"pattern white remains white through Theme.col")
T.same(clips[1],{10,20,100,50},"stage respects the existing launcher clip")
T.same(clips[#clips],{10,20,100,50},"stage restores the launcher clip")
end
g.setColor,g.getScissor,g.setScissor=oldColor,oldScissor,oldSetScissor
end
do
local g=love.graphics
local oldLine=g.line
local calls,patterns={},{}
g.line=function(...) calls[#calls+1]={...} end
for _,pattern in ipairs({"Crosses","Bricks","Grid"}) do
calls={};local empty=Showcase.new();empty.pattern=pattern
Showcase.draw(state,empty,0,0,400,200)
patterns[pattern]=Serializer.encode(calls)
end
T.check(patterns.Crosses~=patterns.Bricks,"cross motifs differ from staggered bricks")
T.check(patterns.Crosses~=patterns.Grid,"cross motifs differ from continuous grid")
T.check(patterns.Bricks~=patterns.Grid,"brick seams alternate instead of square grid")
g.line=oldLine
end
T.finish("Box showcase")