Files
probably-rendering-one-more…/apps/wiki/tests/layout-reference.test.tsx
T
codex-bot a8515e575a
wiki CI and deployment / verify-and-deploy (push) Successful in 16m40s
Replace approximate recipes with 27 source-matched component layouts
2026-10-26 16:02:03 +08:00

48 lines
4.4 KiB
TypeScript

import React from 'react';
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {readFile} from 'node:fs/promises';
import {createHash} from 'node:crypto';
import path from 'node:path';
import sharp from 'sharp';
import {renderToStaticMarkup} from 'react-dom/server';
import {transformSync} from 'esbuild';
import manifest from '../src/content/layouts/reference/manifest.json';
import ReferenceLayout,{layoutIds,validateReplacements} from '../src/content/layouts/reference/ReferenceLayout';
import {textMergeTarget} from '../src/content/layouts/reference/group-text';
import {layoutDocuments,htmlExample,reactExample,nextExample,legacyLayoutIds} from '../src/content/layouts/documents';
import {getServerSideProps} from '../src/pages/explore/[id]';
const publicRoot=path.resolve(import.meta.dirname,'../public');
const asset=(url:string)=>path.join(publicRoot,url);
test('27 source layouts have stable row-major IDs, 5/5/5/6/6 positions and scoped components',async()=>{
assert.deepEqual(manifest.map(l=>l.id),[...layoutIds]);assert.equal(layoutDocuments.length,28);
assert.deepEqual([1,2,3,4,5].map(row=>manifest.filter(l=>l.source.row===row).length),[5,5,5,6,6]);
const hash=createHash('sha256').update(await readFile(asset('/layout-reference/source.png'))).digest('hex');
for(const l of manifest){assert.equal(l.source.sha256,hash);assert.equal(new Set(l.elements.map(e=>e.id)).size,l.elements.length);for(const e of l.elements){assert.ok(e.asset.startsWith('/layout-reference/'+l.id+'/'));assert.ok(e.pixels>0);assert.equal(e.id[0],{text:'T',image:'I',decoration:'D'}[e.kind]);if(e.reading==='unreadable')assert.equal(e.text,null)}}
});
test('all 27 extracted layer composites equal their original source crops at every RGB pixel',async()=>{
for(const l of manifest){
const result=await sharp(asset(l.background)).composite(l.elements.map(e=>({input:asset(e.asset),left:e.box[0],top:e.box[1]}))).removeAlpha().raw().toBuffer();
const reference=await sharp(asset(l.reference)).removeAlpha().raw().toBuffer();assert.ok(result.equals(reference),l.id+' physical layer composite must match');
}
});
test('a residual end glyph stays in T01 instead of creating a second word; other columns stay independent',()=>{
assert.equal(textMergeTarget([38,24,6,6],[{box:[2,20,38,15],direction:'horizontal'}]),0);
assert.equal(textMergeTarget([90,24,6,6],[{box:[2,20,38,15],direction:'horizontal'}]),-1);
const first=manifest[0];assert.deepEqual(first.elements.filter(e=>e.kind==='text').map(e=>e.id),['T01','T02']);
assert.ok(first.elements.find(e=>e.id==='T01')!.box[0]+first.elements.find(e=>e.id==='T01')!.box[2]>=44);
const date=manifest.find(l=>l.id==='ref-21')!.elements.find(e=>e.id==='T03')!;assert.equal(date.text,'03');assert.equal(date.kind,'text');
const grayWord=manifest.find(l=>l.id==='ref-23')!.elements.find(e=>e.text==='BULUBLUE')!;assert.equal(grayWord.kind,'text');
});
test('component replacement rejects foreign IDs and wrong kinds; default React renders exact source assets',()=>{
assert.throws(()=>validateReplacements('ref-01',{text:{'ref-02/T01':'wrong'}}));
assert.throws(()=>validateReplacements('ref-01',{text:{'ref-01/I01':'wrong'}}));
assert.throws(()=>validateReplacements('missing',{}));
for(const id of layoutIds){const html=renderToStaticMarkup(<ReferenceLayout id={id}/>);assert.ok(html.includes('data-layout-id="'+id+'"'));assert.equal((html.match(/data-component=/g)||[]).length,manifest.find(l=>l.id===id)!.elements.length)}
const edited=renderToStaticMarkup(<ReferenceLayout id="ref-01" text={{'ref-01/T01':'我的品牌'}} images={{'ref-01/I01':'/my-photo.jpg'}}/>);assert.ok(edited.includes('我的品牌'));assert.ok(edited.includes('mask-image'));assert.ok(edited.includes('T01-underlay.png'));
});
test('HTML, React and Next.js examples bind each source ID and retired approximate posts redirect to Router',async()=>{
for(const l of manifest){assert.ok(htmlExample(l.id).includes("const id='"+l.id+"'"));transformSync(reactExample(l.id),{loader:'tsx'});transformSync(nextExample(l.id),{loader:'tsx'});const html=await readFile(asset('/layout-reference/'+l.id+'/index.html'),'utf8');assert.ok(html.includes('data-toggle="text"'));assert.ok(html.includes('data-toggle="image"'));assert.ok(html.includes('manifest='+JSON.stringify(l).replaceAll('<','\\u003c')))}
for(const id of legacyLayoutIds){const response=await getServerSideProps({params:{id},query:{},res:{},req:{}} as never);assert.ok('redirect' in response)}
});