const test = require('node:test'); const assert = require('node:assert/strict'); const {reorder,duplicate,filterBlocks,historyState,clampNumber,shouldAcceptPreview}=require('../app/static/editor-helpers.js'); test('move item up and down without mutating input',()=>{ const source=[{id:'a'},{id:'b'},{id:'c'}]; assert.deepEqual(reorder(source,0,2).map(x=>x.id),['b','c','a']); assert.deepEqual(reorder(source,2,0).map(x=>x.id),['c','a','b']); assert.deepEqual(source.map(x=>x.id),['a','b','c']); assert.equal(reorder(source,-1,1),null); assert.equal(reorder(source,1,5),null); assert.equal(reorder(source,1,1).length,3); }); test('clone nested layout and recursively regenerate every component ID',()=>{ const nested={id:'original',type:'layout',columns:[{blocks:[{id:'nested',type:'richtext'}]},{blocks:[]}]}; let n=0; const clone=duplicate(nested,()=>`copy-${++n}`); assert.equal(clone.id,'copy-1');assert.equal(clone.columns[0].blocks[0].id,'copy-2'); clone.columns[0].blocks[0].type='hero'; assert.equal(nested.columns[0].blocks[0].type,'richtext'); }); test('find by title, type, translated name, nested layout child',()=>{ const list=[{type:'hero',title:'Global Trade'},{type:'layout',columns:[{blocks:[{type:'richtext',title:'移动端布局'}]}]},{type:'cta'}]; const spec={hero:{name:'横幅'},layout:{name:'多列'},cta:{name:'行动号召'}}; assert.deepEqual(filterBlocks(list,'gLoBaL',spec).map(x=>x.index),[0]); assert.deepEqual(filterBlocks(list,'移动端',spec).map(x=>x.index),[1]); assert.deepEqual(filterBlocks(list,'行动号召',spec).map(x=>x.index),[2]); assert.equal(filterBlocks(list,'',spec).length,3); assert.equal(filterBlocks(list,'不存在',spec).length,0); }); test('undo redo disabled at correct boundaries',()=>{ assert.deepEqual(historyState(['s1'],0),{canUndo:false,canRedo:false}); assert.deepEqual(historyState(['s1','s2'],0),{canUndo:false,canRedo:true}); assert.deepEqual(historyState(['s1','s2'],1),{canUndo:true,canRedo:false}); }); test('preview accepts only latest request from current page',()=>{ assert.equal(shouldAcceptPreview('about',12,'about',12),true); assert.equal(shouldAcceptPreview('about',11,'about',12),false); assert.equal(shouldAcceptPreview('about',12,'home',12),false); }); test('numeric style input is clamped',()=>{ assert.equal(clampNumber(180,0,120,10),120); assert.equal(clampNumber(-5,0,120,10),0); assert.equal(clampNumber('not-number',0,120,10),10); }); const {findTopIndex,reorderById,adjacentWidths,safeCanvasCommand}=require('../app/static/editor-helpers.js'); test('canvas top-level ID commands reorder only known blocks',()=>{ const rows=[{id:'one',type:'hero'},{id:'two',type:'layout',columns:[{width:1},{width:1}]},{id:'three',type:'cta'}]; assert.equal(findTopIndex(rows,'three'),2); assert.deepEqual(reorderById(rows,'one','three').map(x=>x.id),['two','three','one']); assert.equal(reorderById(rows,'one','missing'),null); assert.deepEqual(rows.map(x=>x.id),['one','two','three']); }); test('canvas validates width limits, list length and types',()=>{ const layout={id:'L',type:'layout',columns:[{width:1},{width:1}]}; assert.deepEqual(adjacentWidths(layout,[1.6,.4]),null); assert.deepEqual(adjacentWidths(layout,[1.6,.5]),[1.6,.5]); assert.equal(adjacentWidths(layout,[1]),null); assert.equal(adjacentWidths(layout,[1,Infinity]),null); assert.equal(adjacentWidths({type:'hero'},[1,1]),null); }); test('canvas rejects invented block ids, actions and unsafe text operations',()=>{ const rows=[{id:'hello',type:'hero'},{id:'cols',type:'layout',columns:[{width:1},{width:1}]}]; const fields={hero:{fields:['title','description','image']},layout:{fields:['columns']}}; const ok=(action,id='hello',rest={})=>safeCanvasCommand({action,blockId:id,...rest},rows,fields); assert.equal(ok('select'),true); assert.equal(ok('duplicate'),true); assert.equal(ok('delete','fake'),false); assert.equal(ok('execute'),false); assert.equal(ok('reorder','hello',{targetId:'cols'}),true); assert.equal(ok('reorder','hello',{targetId:'missing'}),false); assert.equal(ok('resize-columns','cols',{widths:[2,1]}),true); assert.equal(ok('resize-columns','cols',{widths:[5,1]}),false); assert.equal(ok('inline-text','hello',{field:'title',value:'test'}),true); assert.equal(ok('inline-text','hello',{field:'body',value:''}),false); assert.equal(ok('inline-text','hello',{field:'title',value:'x'.repeat(501)}),false); });