77 lines
6.2 KiB
JavaScript
77 lines
6.2 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import {readFileSync} from 'node:fs'
|
|
import ts from 'typescript'
|
|
import {folderAssets,reconcileSelection,activeJobs,previewHidden} from '../shared/studio2/viewer.mjs'
|
|
import {useAsHero,useAsSource,restoreAll} from '../shared/studio2/input.mjs'
|
|
import {toggleStyle,parseBatch,stylePrompt} from '../shared/studio2/styles.mjs'
|
|
import {compilePrompt,validateRequest} from '../shared/studio2/contracts.mjs'
|
|
import {cachedLoras} from '../shared/studio2/lora-cache.mjs'
|
|
|
|
test('deleted library files never return from job outputs; selection falls back or clears',()=>{
|
|
const deleted={id:'gone',folderId:'f'},keep={id:'keep',folderId:'f',createdAt:2};const jobs=[{request:{folderId:'f'},outputs:[deleted]}]
|
|
let assets=folderAssets({stills:[keep],clips:[]},jobs,'f');assert.deepEqual(assets.map(a=>a.id),['keep']);assert.equal(reconcileSelection(deleted,assets).id,'keep');assert.equal(reconcileSelection(deleted,[]),null)
|
|
})
|
|
test('bench includes only active execution phases and clear finished retains internal cleanup records',()=>{
|
|
assert.deepEqual(activeJobs(['waiting','waking','submitting','rendering','saving','complete','cancelled','failed'].map(state=>({state}))).map(j=>j.state),['waking','submitting','rendering','saving'])
|
|
const rows=[{id:'done',state:'complete',request:{folderId:'f'},cleanupPending:[{}]},{id:'active',state:'rendering',request:{folderId:'f'}}],saved=[]
|
|
const src=readFileSync(new URL('../server/api/studio-2/clear-finished.post.ts',import.meta.url),'utf8').replace(/^import .*;\r?\n/gm,'').replace('export default ','const handler=')
|
|
const js=ts.transpileModule(src,{compilerOptions:{target:ts.ScriptTarget.ES2022}}).outputText
|
|
const fn=new Function('defineEventHandler','assertLibraryOwner','listStudioJobs','records','assertFolderAccess','saveRecord',js+';return handler')(f=>f,()=>({owner:'o'}),()=>[],()=>rows,()=>{},r=>saved.push(r))
|
|
assert.equal(fn({}).cleared,1);assert.equal(rows[0].dismissed,true);assert.equal(rows[0].cleanupPending.length,1);assert.equal(rows[1].dismissed,undefined)
|
|
})
|
|
test('hidden xAIGen canvas and family render no image or video elements',async()=>{
|
|
const revealed=new Set()
|
|
assert.equal(previewHidden({id:'a',role:'input'},revealed,true),true)
|
|
assert.equal(previewHidden({id:'a',role:'input'},new Set(['a']),true),false)
|
|
assert.equal(previewHidden({id:'a'},revealed,false),false)
|
|
const src=readFileSync(new URL('../components/studio2/Bench.vue',import.meta.url),'utf8')
|
|
assert.match(src,/aria-label="Current family outputs"/)
|
|
assert.match(src,/preview hidden/)
|
|
assert.doesNotMatch(src,/Hide input stills/)
|
|
assert.doesNotMatch(src,/Hide output preview/)
|
|
})
|
|
test('cached LoRAs survive sleeping GPU and failed explicit refresh',async()=>{
|
|
const cache={image:['Klein/portrait.safetensors'],video:['motion.safetensors']};let queried=0
|
|
const opts={read:()=>cache,write:()=>assert.fail('Do not erase cache'),live:async()=>{queried++;throw Error('Comfy asleep')}}
|
|
assert.deepEqual(await cachedLoras(opts),cache);assert.equal(queried,1)
|
|
assert.deepEqual(await cachedLoras({...opts,fresh:true}),cache);assert.equal(queried,2)
|
|
})
|
|
test('bench load and Refresh always live-scan and replace disk so new Comfy names appear',async()=>{
|
|
let store={image:['old.safetensors'],video:['old-vid.safetensors']}
|
|
const next=await cachedLoras({
|
|
read:()=>store,
|
|
write:value=>{store=value},
|
|
live:async()=>({image:['old.safetensors','klein-pokemon_anime_style_klein9b.safetensors'],video:['old-vid.safetensors']}),
|
|
fresh:false
|
|
})
|
|
assert.ok(next.image.includes('klein-pokemon_anime_style_klein9b.safetensors'))
|
|
assert.deepEqual(store.image,next.image)
|
|
const removed=await cachedLoras({
|
|
read:()=>store,
|
|
write:value=>{store=value},
|
|
live:async()=>({image:['klein-pokemon_anime_style_klein9b.safetensors'],video:[]}),
|
|
fresh:true
|
|
})
|
|
assert.deepEqual(removed,{image:['klein-pokemon_anime_style_klein9b.safetensors'],video:[]})
|
|
})
|
|
test('bench mounts and Refresh LoRAs request a live scan',()=>{
|
|
const src=readFileSync(new URL('../components/studio2/Bench.vue',import.meta.url),'utf8')
|
|
assert.match(src,/void loadLoras\(true\)/)
|
|
assert.match(src,/loadLoras\(true\)/)
|
|
assert.match(src,/\/api\/studio-2\/loras'\+\(fresh\?'\?fresh=1':''\)/)
|
|
})
|
|
test('source/hero actions are independent and full input restores structured settings',()=>{
|
|
const form={identityStillId:'hero',imageAId:'source',promptSections:{action:'old'},settings:{}}
|
|
assert.equal(useAsHero(form,{id:'new'}).imageAId,'source');assert.equal(useAsSource(form,{id:'new'}).identityStillId,'hero')
|
|
const saved={mode:'edit',identityStillId:'h',imageAId:'s',imageStyles:{positive:['Comic'],negative:['Skin pores']},promptSections:{action:'New',cameraBeats:[{moves:['hold']}]},settings:{steps:24,loraStack:[{name:'example.safetensors',strengthModel:.4,strengthClip:.6}]}}
|
|
const restored=restoreAll(form,{id:'result',studio2:saved});assert.equal(restored.imageAId,'result');assert.equal(restored.identityStillId,'h');assert.deepEqual(restored.settings.loraStack,saved.settings.loraStack);assert.deepEqual(restored.imageStyles,saved.imageStyles);assert.equal(restored.promptSections.action,'New');const clip=restoreAll(form,{id:'clip',kind:'clip',studio2:{...saved,mode:'video',engine:'minimax'}});assert.equal(clip.startClipId,'clip');assert.equal(clip.mode,'extend');assert.equal(clip.promptSections.action,'New')
|
|
})
|
|
test('image chips compile positive and negative independently; video chips do not leak',()=>{
|
|
let styles=toggleStyle({},'Comic');styles=toggleStyle(styles,'Skin pores',true);styles=toggleStyle(styles,'Comic',true);assert.equal(stylePrompt(styles),'');assert.match(stylePrompt(styles,true),/Comic/)
|
|
styles=toggleStyle(styles,'Photorealistic');const q=validateRequest({mode:'edit',engine:'flux',folderId:'f',imageAId:'s',imageStyles:styles,promptSections:{action:'Restaurant',cameraBeats:[{moves:['hold']}],locks:'VIDEO ONLY'}})
|
|
assert.match(q.compiledPrompt,/Photorealistic/);assert.doesNotMatch(q.compiledPrompt,/Comic|pores|tripod|hold|VIDEO ONLY/i);assert.deepEqual(q.imageStyles,styles)
|
|
assert.doesNotMatch(compilePrompt(q.promptSections,false,q),/Picture 1/)
|
|
assert.deepEqual(parseBatch('A wrapped\nsentence\n\nSecond shot\r\n\r\nThird'),['A wrapped\nsentence','Second shot','Third'])
|
|
})
|