const fs=require('node:fs/promises'),path=require('node:path'),assert=require('node:assert/strict') const output=path.resolve('.tmp/table-ui-regression') const experiment=process.env.YCODE_TABLE_DISABLE_LCD_EXPERIMENT==='1' const layerCase=process.env.YCODE_TABLE_LAYER_CASE||'baseline' assert.ok(['baseline','source-scroll-flow','source-scroll-no-container','source-clip-flow'].includes(layerCase)) const suffix=(layerCase==='baseline'?'':'-'+layerCase)+(experiment?'-lcd-disabled-experiment':'') if(!process.versions.electron){ ;(async()=>{ await fs.mkdir(output,{recursive:true}) const markdown='| 路径 | 持久化状态 | 内容 |\n| --- | --- | --- |\n| 取消 | `cancelled` ✓ | 0 字符 |\n| 正常 | `done` ✓ | 「收到」 |' const sourceLayout=JSON.parse(await fs.readFile('.tmp/rea-assessment-initial/layout.json','utf8')) assert.equal(sourceLayout.evidenceId,'ev_d6e6920ff4e47f73a182fac5312c5f02393366a035445d18c8a6733a8c8a11ed') const contentNodes=sourceLayout.message_content.nodes const paragraph=contentNodes.filter(node=>node.node_index>=1622&&node.node_index<1630&&node.node_name==='#text').map(node=>node.text).join('') const sourceMarkdown='✅ **取消链路修复并双验证通过**:\n\n'+markdown+'\n\n'+paragraph const originalFixture=`
window.qa.quoted.push(quote)}/>
` const sourceFixture=`
window.qa.quoted.push(quote)}/>
` const entry=`import React from 'react';import {createRoot} from 'react-dom/client'; import MarkdownContent from './src/renderer/src/components/MarkdownContent'; import {ReactMarkdown} from './src/renderer/src/workbuddy/markdown/ReactMarkdown';import {ReactMarkdownProvider} from './src/renderer/src/workbuddy/markdown/context';import {createMarkdownParser} from './src/renderer/src/workbuddy/markdown/parser'; import './src/renderer/src/workbuddy/typography/tokens.css';import './src/renderer/src/styles/tokens.css';import './src/renderer/src/styles/app.css'; import './src/renderer/src/workbuddy/conversation/source-styles.css';import './src/renderer/src/workbuddy/conversation/source-adapters.css'; const source=${JSON.stringify(markdown)}; const sourceMarkdown=${JSON.stringify(sourceMarkdown)}; window.qa={quoted:[],opened:[]};const root=createRoot(document.getElementById('root')); root.render(${layerCase==='baseline'?originalFixture:sourceFixture}); window.qa.prepareSourceLayers=()=>{const scroll=document.querySelector('[data-source-scroll]');if(scroll&&${JSON.stringify(layerCase)}!=='source-clip-flow')scroll.scrollTop=15247}; window.qa.mountPath=()=>root.render(
'file',pathClickHandler:{onPathClick:(path)=>window.qa.opened.push(path)}}}>{'| file |\\n| --- |\\n| '+String.fromCharCode(96)+'C:/fixture/very-long-demo-file-to-check-horizontal-scroll.ts'+String.fromCharCode(96)+' |'}
); ` await require('esbuild').build({stdin:{contents:entry,loader:'tsx',resolveDir:process.cwd()},outfile:path.join(output,'fixture.js'),bundle:true,platform:'browser',format:'iife',jsx:'automatic',logLevel:'silent',loader:{'.woff':'file','.woff2':'file','.ttf':'file'}}) if(layerCase!=='baseline'){ const css=await fs.readFile('.tmp/rea-css-recovered/src-CyXHA0IC.css','utf8'),rules=[] assert.equal(require('node:crypto').createHash('sha256').update(css).digest('hex'),'a5e26b537bb50323aefbf25abddad52e288dd3868598be8d255d70886fe77cfe') require('postcss').parse(css).walkRules(rule=>{if(!/^\.cr-document(?:\b|__virtual)|^\.cr-message-list/.test(rule.selector)||rule.selector.includes('__hydration'))return;const copy=rule.clone();if(layerCase==='source-scroll-no-container')copy.walkDecls('container-type',decl=>decl.remove());rules.push(copy.toString())}) await fs.appendFile(path.join(output,'fixture.css'),'\n/* Pinned REA ancestor rules, fixture only. */\n'+rules.join('\n')) } await fs.writeFile(path.join(output,'index.html'),'
') const env={...process.env};delete env.ELECTRON_RUN_AS_NODE const result=require('node:child_process').spawnSync(require('electron'),[__filename],{env,windowsHide:true,stdio:'inherit',timeout:45000}) process.exitCode=result.status??1 })().catch(error=>{console.error(error);process.exitCode=1}) }else{ const {app,BrowserWindow,nativeImage}=require('electron') app.setPath('userData',path.join(output,'user-data')) app.disableHardwareAcceleration() app.commandLine.appendSwitch('use-angle','swiftshader') app.commandLine.appendSwitch('disable-gpu-compositing') if(experiment)app.commandLine.appendSwitch('disable-lcd-text') app.whenReady().then(async()=>{ const errors=[],win=new BrowserWindow({x:0,y:0,width:1336,height:901,useContentSize:true,frame:false,show:false,webPreferences:{contextIsolation:true,nodeIntegration:false,offscreen:false,backgroundThrottling:false}}) win.webContents.on('console-message',event=>{if(event.level==='error')errors.push(event.message)}) await win.loadFile(path.join(output,'index.html')) await new Promise(resolve=>setTimeout(resolve,450)) await win.webContents.executeJavaScript('window.qa.prepareSourceLayers()') await new Promise(resolve=>setTimeout(resolve,50)) const metrics=await win.webContents.executeJavaScript(`(()=>{const root=document.querySelector('.cr-table-block');const selectors=['.cr-table-block__inner','table','th','td','code'];const styles=['display','font-family','font-size','font-weight','line-height','color','background-color','border-radius','border-top-width','border-top-color','padding-top','padding-right','padding-bottom','padding-left'];return {nodes:Object.fromEntries(selectors.map(selector=>{const e=root.querySelector(selector),c=getComputedStyle(e);return [selector,{bounds:e.getBoundingClientRect().toJSON(),styles:Object.fromEntries(styles.map(key=>[key,c.getPropertyValue(key)]))}]})),rows:[...root.querySelectorAll('tr')].map(e=>e.getBoundingClientRect().height),cell:root.querySelector('tbody tr td:nth-child(2)').textContent}})()`) assert.equal(metrics.nodes['.cr-table-block__inner'].styles['border-radius'],'12px') assert.equal(metrics.nodes.th.styles['padding-top'],'8px') assert.equal(metrics.nodes.th.styles['padding-right'],'12px') assert.equal(metrics.nodes.code.styles['background-color'],'rgba(0, 0, 0, 0.05)') assert.equal(metrics.cell,'cancelled ✓') const source=JSON.parse(await fs.readFile('.tmp/rea-assessment-initial/layout.json','utf8')) assert.equal(source.evidenceId,'ev_d6e6920ff4e47f73a182fac5312c5f02393366a035445d18c8a6733a8c8a11ed') const sourceNodes=source.layout.documents[0].nodes for(const [selector,index] of [['.cr-table-block__inner',1592],['table',1593],['th',1596],['td',1604],['code',1607]]){ const expected=sourceNodes.find(node=>node.node_index===index),actual=metrics.nodes[selector] assert.deepEqual([actual.bounds.x,actual.bounds.y,actual.bounds.width,actual.bounds.height],expected.bounds,selector+' source bounds') for(const [name,value] of Object.entries(actual.styles))assert.equal(value,expected.styles[name],selector+' source '+name) } const ancestorGeometry=layerCase==='baseline'?[]:await win.webContents.executeJavaScript("[...document.querySelectorAll('[data-source-node]')].map(e=>({index:Number(e.dataset.sourceNode),bounds:e.getBoundingClientRect().toJSON(),transform:getComputedStyle(e).transform,overflow:getComputedStyle(e).overflow,containerType:getComputedStyle(e).containerType,scrollTop:e.scrollTop}))") for(const actual of ancestorGeometry){ const expected=sourceNodes.find(node=>node.node_index===actual.index) assert.deepEqual([actual.bounds.x,actual.bounds.y,actual.bounds.width,actual.bounds.height],expected.bounds,'source ancestor '+actual.index+' bounds') if(actual.index===777)assert.equal(actual.transform,expected.styles.transform) } // A native source-GPU screenshot, at exactly the original REA table coordinates and panel width. win.showInactive() await new Promise(resolve=>setTimeout(resolve,200)) win.webContents.debugger.attach('1.3') const capture=await Promise.race([win.webContents.debugger.sendCommand('Page.captureScreenshot',{format:'png',fromSurface:true,captureBeyondViewport:false}),new Promise((_,reject)=>setTimeout(()=>reject(Error('Own table screenshot timeout')),8000))]) const image=Buffer.from(capture.data,'base64');await fs.writeFile(path.join(output,'screen'+suffix+'.png'),image) const original=nativeImage.createFromPath(path.resolve('.tmp/rea-assessment-initial/workbuddy.png')) const candidate=nativeImage.createFromBuffer(image),roi={x:324,y:385,width:438,height:134} const before=original.crop(roi).toBitmap(),after=candidate.crop(roi).toBitmap() const textBounds=sourceNodes.filter(node=>node.node_index>=1593&&node.node_index<=1620&&node.node_name==='#text').map(node=>node.bounds) let changed=0,changedOutsideText=0,max=0,sum=0;const outsideTextSamples=[] for(let i=0;ix>=Math.floor(b[0])-1&&x=Math.floor(b[1])-1&&ysetTimeout(resolve,180)) const quote=await win.webContents.executeJavaScript("(()=>{const e=document.querySelector('.cr-message-quote-table-action'),r=e.getBoundingClientRect();return {x:Math.round(r.x+r.width/2),y:Math.round(r.y+r.height/2),opacity:getComputedStyle(e.closest('.cr-message-quote-table-menu')).opacity}})()") assert.equal(quote.opacity,'1') win.webContents.sendInputEvent({type:'mouseDown',button:'left',clickCount:1,x:quote.x,y:quote.y}) win.webContents.sendInputEvent({type:'mouseUp',button:'left',clickCount:1,x:quote.x,y:quote.y}) await new Promise(resolve=>setTimeout(resolve,50)) assert.equal(await win.webContents.executeJavaScript('window.qa.quoted.length'),1) await win.webContents.executeJavaScript('window.qa.mountPath()') await new Promise(resolve=>setTimeout(resolve,100)) const pathPoint=await win.webContents.executeJavaScript("(()=>{const e=document.querySelector('td .cr-clickable-path'),r=e.getBoundingClientRect();return {x:Math.floor(r.x+10),y:Math.floor(r.y+r.height/2)}})()") win.webContents.sendInputEvent({type:'mouseMove',...pathPoint}) win.webContents.sendInputEvent({type:'mouseDown',button:'left',clickCount:1,...pathPoint}) win.webContents.sendInputEvent({type:'mouseUp',button:'left',clickCount:1,...pathPoint}) await new Promise(resolve=>setTimeout(resolve,60)) assert.deepEqual(await win.webContents.executeJavaScript('window.qa.opened'),['C:/fixture/very-long-demo-file-to-check-horizontal-scroll.ts']) assert.equal(await win.webContents.executeJavaScript("(()=>{const e=document.querySelector('.cr-table-block__scroller');e.scrollLeft=0;return !e.dispatchEvent(new WheelEvent('wheel',{deltaX:-100,bubbles:true,cancelable:true}))})()"),true,'source horizontal overscroll guard remains active') assert.equal(errors.length,0) const pixel={roi,pixels:roi.width*roi.height,changed,changedOutsideText,outsideTextSamples,max,meanRgbDelta:sum/(roi.width*roi.height*3),fullPixelParity:changed===0} const gpu=await app.getGPUInfo('complete') const report={metrics,pixel,gpu,gpuFeatureStatus:app.getGPUFeatureStatus(),errors,layerCase,ancestorGeometry,experiment:experiment?'Own-renderer disable-lcd-text negative control; original startup flag not observed':null,sourceFlow:{originalTransform:'matrix(1, 0, 0, 1, 0, 11220)',fixtureTransform:'translateY(11220px)',note:'Original virtual-flow transform retained with the fixed fixture host offset; preserves transformed ancestor painting without reproducing list virtualization.'},referenceEvidence:'ev_d6e6920ff4e47f73a182fac5312c5f02393366a035445d18c8a6733a8c8a11ed'} await fs.writeFile(path.join(output,'report'+suffix+'.json'),JSON.stringify(report,null,2)) console.log(JSON.stringify({rows:metrics.rows,pixel,errors})) win.destroy();app.quit() }).catch(error=>{console.error(error);app.exit(1)}) }