Files
ComfyAssets-ComfyUI_PromptM…/tests/js/workspace-layout.test.js
T
Vito e7cfcc2bfb style(workspace): align browsing with the approved concept (#219)
* style(workspace): align approved shell and visual fixtures

* test(workflows): preserve evidence across browser projects

* style(workspace): match approved image browsing geometry

* style(workspace): match natural library card layout

* fix(workspace): retain wheel motion during row correction

* fix(workspace): contain zoomed controls and image metadata (#215)
2026-10-18 02:53:26 -07:00

138 lines
7.5 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const source = import('../../web/js/workspace/image-layout.mjs');
test('layouts preserve data order, remain within width and never overlap', async () => {
const {layoutImages} = await source;
const images=Array.from({length:200},(_,i)=>({width:i%2?1024:640,height:i%2?640:1024}));
for(const width of [280,760,1400]) for(const mode of ['grid','list','masonry']) {
const {positions,height}=layoutImages(images,width,mode);
assert.equal(positions.length,200);
for(const [i,a] of positions.entries()) {
assert.ok(a.x>=0 && a.y>=0 && a.width>0 && a.height>0);
assert.ok(a.x+a.width<=width+.01);
assert.ok(a.y+a.height<=height+.01);
for(const b of positions.slice(i+1)) assert.ok(a.x+a.width<=b.x+.01 || b.x+b.width<=a.x+.01 || a.y+a.height<=b.y+.01 || b.y+b.height<=a.y+.01);
}
}
});
test('empty, unknown dimensions and narrow containers keep valid geometry',async()=>{
const {layoutImages}=await source;
assert.deepEqual(layoutImages([],0,'grid'),{positions:[],height:0,maxHeight:0});
for(const mode of ['grid','list','masonry']) {
const result=layoutImages([{}, {width:0,height:null}],1,mode);
assert.ok(Number.isFinite(result.height));
}
});
test('virtual window retains visible rows and focused card while bounding rendered media',async()=>{
const {layoutImages,visibleImages}=await source;
const {positions}=layoutImages(Array(5000).fill({}),900,'masonry');
const indexes=visibleImages(positions,10000,900,0);
assert.ok(indexes.length<100);
assert.ok(indexes.includes(0));
assert.ok(indexes.some(i=>positions[i].y>=10000 && positions[i].y<10900));
assert.deepEqual(indexes,[...indexes].sort((a,b)=>a-b));
assert.equal(new Set(indexes).size,indexes.length);
assert.ok(visibleImages(positions,0,800).includes(0));
});
test('large virtual collections inspect a bounded range rather than every loaded position', async () => {
const {layoutImages,visibleImages}=await source;
for(const mode of ['grid','list','masonry']) {
const geometry=layoutImages(Array.from({length:50000},(_,index)=>({width:640,height:index%2?320:1024})),900,mode);
for(let index=1;index<geometry.positions.length;index++) assert.ok(geometry.positions[index].y>=geometry.positions[index-1].y);
let reads=0;
const positions=new Proxy(geometry.positions,{get(target,key,receiver){
if(typeof key==='string' && /^\d+$/.test(key)) reads++;
return Reflect.get(target,key,receiver);
}});
const top=geometry.positions[25000].y;
const indexes=visibleImages(positions,top,900,49999,geometry.maxHeight);
assert.ok(reads<500,`${mode} read ${reads} positions`);
const expected=geometry.positions.flatMap((position,index)=>(index===49999 ||
(position.y+position.height>=top-900 && position.y<=top+1800))?[index]:[]);
assert.deepEqual(indexes,expected);
}
});
test('binary-searched windows handle boundaries, empty collections and focus without duplicates',async()=>{
const {layoutImages,visibleImages}=await source;
assert.deepEqual(visibleImages([],0,800,0),[]);
const {positions}=layoutImages(Array(1000).fill({}),900,'grid');
for(const focused of [-1,0,999,1000]) {
for(const top of [0,10000,1000000]) {
const actual=visibleImages(positions,top,600,focused);
const expected=positions.flatMap((position,index)=>(index===focused ||
(position.y+position.height>=top-600 && position.y<=top+1200))?[index]:[]);
assert.deepEqual(actual,expected);
}
}
assert.deepEqual(visibleImages(positions,1000000,0),[]);
});
test('reported maximum height keeps unusually tall custom layouts inside the virtual window',async()=>{
const {layoutImages,visibleImages}=await source;
const geometry=layoutImages([{}, {}, {}],10000,'grid',20000);
assert.equal(geometry.maxHeight,7554.5);
assert.deepEqual(visibleImages(geometry.positions,6000,200,-1,geometry.maxHeight),[0]);
});
test('approved workspace densities share viewport metrics and reject unknown values', async () => {
const {workspaceImageMetrics, layoutImages} = await source;
assert.equal(typeof workspaceImageMetrics, 'function', 'workspace density metrics must be exported');
for (const [width, expected] of [[1600,[3,2,4]], [1250,[2,2,3]], [950,[2,2,3]], [700,[2,2,2]], [390,[2,2,2]], [1700,[4,2,5]]]) {
assert.deepEqual(['comfortable','large','compact'].map(density => workspaceImageMetrics(width,density).columns), expected);
for (const density of ['comfortable','large','compact']) {
const metrics = workspaceImageMetrics(width,density);
assert.deepEqual(metrics, {columns: expected[['comfortable','large','compact'].indexOf(density)], gap:13, footerHeight:54, listHeight:97});
assert.equal(layoutImages([{},{}],990,'list',240,metrics).positions[1].x,0);
}
}
assert.throws(() => workspaceImageMetrics(1600,'unknown'), TypeError);
assert.deepEqual(workspaceImageMetrics(1600),workspaceImageMetrics(1600,'comfortable'));
});
test('approved grid uses inner media at four to three and explicit columns with thirteen pixel gaps', async () => {
const {workspaceImageMetrics,layoutImages} = await source;
assert.equal(typeof workspaceImageMetrics,'function');
const metrics = workspaceImageMetrics(1600);
const {positions} = layoutImages(Array(4).fill({}),990,'grid',240,metrics);
assert.equal(positions[1].x-positions[0].width,13);
assert.equal(positions[3].x,0);
assert.equal(positions[3].y-positions[0].height,13);
assert.equal(positions[0].height-54-2,(positions[0].width-2)*.75);
assert.equal(layoutImages([{}],990,'list',240,metrics).height,97);
const custom=layoutImages([{}],990,'grid',240,{columns:2,gap:20,footerHeight:60,listHeight:100});
assert.equal(custom.positions[0].width,485);
assert.equal(custom.height,(485-2)*.75+62);
assert.equal(layoutImages([{}],990,'list',240,{listHeight:100}).height,100);
});
test('masonry uses real finite proportions and falls back for invalid dimensions', async () => {
const {layoutImages}=await source;
const images=[{width:100,height:1000},{width:1000,height:100},{width:Infinity,height:10},{width:10,height:NaN}];
const {positions}=layoutImages(images,990,'masonry',240,{columns:3});
assert.equal(positions[0].height,(positions[0].width-2)*10+56);
assert.equal(positions[1].height,(positions[1].width-2)/10+56);
for (const position of positions) assert.ok(Number.isFinite(position.height));
});
test('gallery width limits density columns without changing ordinary workspace geometry', async () => {
const {workspaceImageMetrics, layoutImages} = await source;
for (const [viewport, width, density, columns] of [
[1600,990,'comfortable',3], [1600,990,'compact',4],
[951,428,'compact',3], [390,362,'comfortable',2],
[1600,190,'comfortable',1], [1600,190,'compact',1],
[390,167,'large',1], [390,167,'compact',1], [390,69.5,'comfortable',1], [390,0,'comfortable',1],
]) {
const metrics = workspaceImageMetrics(viewport,density,width);
assert.equal(metrics.columns,columns,`${viewport}/${width}/${density}`);
const {positions} = layoutImages(Array(8).fill({}),width,'grid',240,metrics);
assert.equal(positions[columns].x,0);
assert.ok(positions[columns].y > 0);
}
});