8884e100f9
Overlay 190 upstream items (V8.22→8.31) preserving trade-pro theme, registry tests, and design docs. Re-applied full patch set on identical anchors (discovery/env_for/blocks.css/type-mapping/locale-override/ theme_color/frontend entries/test_theme_pro_v3 mods) plus compose pins. Verified: theme suites 35/35; dual-baseline full run 415 passed vs pristine 386+29 ours, FAILED-set diff EMPTY (same 10 Windows env fails). Live: container 8.31.1-dev.1 healthy, hesupipe 192 entries intact, trade-pro build 203 pages 0 errors. Co-Authored-By: Claude Code <noreply@anthropic.com>
77 lines
4.3 KiB
JavaScript
77 lines
4.3 KiB
JavaScript
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:'<script>x</script>'}),false);
|
|
assert.equal(ok('inline-text','hello',{field:'title',value:'x'.repeat(501)}),false);
|
|
});
|