import {test} from 'node:test'; import assert from 'node:assert/strict'; import {readFileSync} from 'node:fs'; import {nativeLayout,defaultText,textSize,imageShape,designUrl,newElementId} from '../src/lib/native-layout.ts'; const layouts=JSON.parse(readFileSync(new URL('../src/content/templates/layouts.json',import.meta.url))); test('all 27 native pages are independent of source images and supply actual text',()=>{ for(const original of layouts){const layout=nativeLayout(original),serialized=JSON.stringify(layout);assert.ok(!serialized.includes('data:image'));assert.ok(!serialized.includes('raster'));assert.ok(!serialized.includes('source'));assert.ok(serialized.length<7000); for(const slot of layout.elements.filter(s=>s.kind==='text')){assert.ok(defaultText(layout,slot).length);for(const text of [defaultText(layout,slot),'','新的文字\n第二行','']){const size=textSize(layout,slot,text);assert.ok(Number.isFinite(size)&&size>0)}} } }); test('geometric templates retain their distinct silhouettes without bitmap masks',()=>{ assert.equal(imageShape(layouts[4],layouts[4].elements.find(s=>s.id==='I03')).borderRadius,'50%'); assert.ok(imageShape(layouts[21],layouts[21].elements.find(s=>s.id==='I02')).clipPath.startsWith('polygon(')); assert.notEqual(imageShape(layouts[21],layouts[21].elements.find(s=>s.id==='I02')).clipPath,imageShape(layouts[26],layouts[26].elements.find(s=>s.id==='I02')).clipPath); assert.equal(designUrl('ref-01','abc'),'/studio/template-layout?id=abc'); assert.equal(designUrl('standard-01','abc'),'/studio/template-layout?id=abc'); }); test('element IDs work on HTTP origins without secure-context randomUUID',()=>{const saved=Object.getOwnPropertyDescriptor(globalThis,'crypto');let calls=0;Object.defineProperty(globalThis,'crypto',{configurable:true,value:{getRandomValues(array){for(let i=0;i{ const cases=[['ref-07','T02',97/73.7143],['ref-08','T01',112/85.2389],['ref-19','T01',287/217.055],['ref-19','T02',298/225.274],['ref-19','T03',313/237.451],['ref-19','T04',306/231.363],['ref-21','T01',195/160.307]]; for(const [id,slotId,glyphExtent] of cases){ const layout=layouts.find(l=>l.id===id),slot=layout.elements.find(s=>s.id===slotId); const size=textSize(layout,slot,defaultText(layout,slot))*860/layout.width; const crossAxis=slot.direction==='rotated'?slot.box[2]:slot.box[3]; assert.ok(Math.ceil(size*glyphExtent)<=crossAxis*860/layout.width+2,`${id}/${slotId}: fallback glyphs must fit`); } });