/** Current cr submenu bodies with controlled own host data; no original UI access. */ import fs from 'node:fs' import path from 'node:path' import assert from 'node:assert/strict' import{createRequire}from'node:module' import{spawn}from'node:child_process' const require=createRequire(import.meta.url),module=require('electron'),electron=typeof module==='object'&&module.app?module:null if(electron){electron.app.disableHardwareAcceleration();electron.app.commandLine.appendSwitch('use-angle','swiftshader');electron.app.commandLine.appendSwitch('disable-gpu-compositing')} const root=path.resolve('.'),base=path.join(root,'.tmp') if(!electron){const output=fs.mkdtempSync(path.join(base,'composer-submenus-native-')),env={...process.env};delete env.ELECTRON_RUN_AS_NODE;delete env.ELECTRON_RENDERER_URL;const child=spawn(module,['--no-sandbox','--disable-gpu-sandbox',path.resolve('scripts/rea/test-composer-submenus-native.mjs'),output],{env,windowsHide:true,stdio:'inherit'});process.exitCode=await new Promise(resolve=>child.on('exit',code=>resolve(code??1)))} else void(async()=>{ const{app,BrowserWindow}=electron,output=path.resolve(process.argv.at(-1));assert.ok(path.relative(base,output).startsWith('composer-submenus-native-')&&!path.relative(base,output).includes('..'));fs.mkdirSync(path.join(output,'data'));app.setPath('userData',path.join(output,'data'));await app.whenReady() const location=file=>JSON.stringify(path.join(root,file).replaceAll('\\','/')) fs.writeFileSync(path.join(output,'fixture.tsx'),`import React,{useState}from'react';import{createRoot}from'react-dom/client';import{SourceFilesSubmenu,SourceModeSubmenu,SourceConnectorsSubmenu,SourceReferenceFilesSubmenu}from ${location('src/renderer/src/workbuddy/composer/add-menu/SourceSubmenus')};import ${location('src/renderer/src/styles/tokens.css')};import ${location('src/renderer/src/styles/app.css')}; window.qa={local:0,close:0,modes:[],connect:[],toggle:[],cancel:[],repair:[],manage:0,references:[]};const items=[{id:'new',name:'New connector'},{id:'connected',name:'Connected connector',isConnected:true,enabled:true},{id:'busy',name:'Connecting connector',status:'connecting',isCancellable:true},{id:'disabled',name:'Disabled connector',canToggle:false,disabledReason:'CONTROLLED_DISABLED'},{id:'error',name:'Error connector',isConnected:true,enabled:true,errorText:'CONTROLLED_CONNECTION_ERROR',errorKind:'retry'}];function App(){const[tab,setTab]=useState('files'),[disabled,setDisabled]=useState(false),[selected,setSelected]=useState('craft');Object.assign(window.qa,{setTab,setDisabled,setSelected});const close=()=>window.qa.close++;return
{tab==='files'?window.qa.local++}} onCloseMenu={close}/>:tab==='mode'?window.qa.modes.push(mode)}}/>:tab==='connectors'?window.qa.connect.push(item.id),onToggleItem:(item,on)=>window.qa.toggle.push([item.id,on]),onCancelConnect:item=>window.qa.cancel.push(item.id),onRepairItem:item=>window.qa.repair.push(item.id),onSummonMore:()=>window.qa.manage++}} onCloseMenu={close}/>:tab==='references'?window.qa.references.push(item)}} onCloseMenu={close}/>:}
}createRoot(document.getElementById('root')).render();`) await require('esbuild').build({entryPoints:[path.join(output,'fixture.tsx')],bundle:true,platform:'browser',format:'iife',jsx:'automatic',outfile:path.join(output,'fixture.js'),logLevel:'warning'}) fs.writeFileSync(path.join(output,'index.html'),'
') const win=new BrowserWindow({width:900,height:680,frame:false,useContentSize:true,show:false,webPreferences:{contextIsolation:true,nodeIntegration:false,backgroundThrottling:false}}),checks=[],errors=[],measurements={} win.webContents.on('console-message',event=>{if(event.level==='error')errors.push(event.message)});await win.loadFile(path.join(output,'index.html'));win.webContents.debugger.attach('1.3');await win.webContents.debugger.sendCommand('Page.enable');win.showInactive() const read=code=>win.webContents.executeJavaScript(code),cdp=(method,args)=>win.webContents.debugger.sendCommand(method,args),pause=ms=>new Promise(resolve=>setTimeout(resolve,ms)),check=(name,actual,expected)=>{assert.deepEqual(actual,expected,name);checks.push(name)} const wait=async code=>{for(let i=0;i<100;i++){if(await read(code))return;await pause(30)}throw Error('Timeout '+code)} const bounds=selector=>read(`(()=>{const r=document.querySelector(${JSON.stringify(selector)}).getBoundingClientRect();return[r.x,r.y,r.width,r.height]})()`) const click=async selector=>{const r=await bounds(selector);await cdp('Input.dispatchMouseEvent',{type:'mouseMoved',x:r[0]+r[2]/2,y:r[1]+r[3]/2});for(const type of ['mousePressed','mouseReleased'])await cdp('Input.dispatchMouseEvent',{type,x:r[0]+r[2]/2,y:r[1]+r[3]/2,button:'left',clickCount:1});await pause(100)} const update=async code=>{await read(code);await pause(100)},capture=async name=>fs.writeFileSync(path.join(output,name+'.png'),Buffer.from((await cdp('Page.captureScreenshot',{format:'png',fromSurface:true})).data,'base64')) const selectTab=async name=>{await update(`window.qa.setTab(${JSON.stringify(name)})`);await pause(100)} try{ await wait('!!document.querySelector(".cr-files-submenu")');measurements.files=await bounds('.cr-files-submenu');check('source files width200',measurements.files[2],200);check('only actual local file handler shown',await read('[...document.querySelectorAll(".cr-files-submenu__item-label")].map(e=>e.textContent)'),['本地文件']);check('source upload SVG recovered',await read('!!document.querySelector(".cr-files-submenu__item-icon svg path")'),true);await capture('files');await click('.cr-files-submenu__item');check('local file callback exactly once',await read('window.qa.local'),1);check('file selection closes via host',await read('window.qa.close'),1) await selectTab('mode');await wait('document.querySelectorAll(".wb-switch__input").length===2');measurements.mode=await bounds('.cr-mode-submenu');check('source mode width200',measurements.mode[2],200);check('original plan/ask two options',await read('[...document.querySelectorAll(".cr-mode-submenu__row-en")].map(e=>e.textContent)'),['Plan','Ask']) // Independent iframe applies the unmodified retained original CSS. It cannot mask a scoped-recovery selector bug in the actual component. const rawCss=fs.readFileSync('.tmp/rea-css-recovered/src-CBaV_A3S.css','utf8')+'\n'+fs.readFileSync('.tmp/rea-css-recovered/src-Bh3YZ35H.css','utf8') const originalHtml='' await read(`(()=>{const frame=document.createElement('iframe');frame.id='original-css-reference';frame.style.cssText='position:absolute;left:-10000px;width:400px;height:200px';frame.srcdoc=${JSON.stringify(originalHtml)};document.body.append(frame);const probe=document.createElement('button');probe.id='scoped-theme-button';probe.className='wb-button wb-button--ghost wb-button--medium';probe.textContent='Probe';document.querySelector('.yc-source-composer-add-menu').append(probe)})()`) await wait('!!document.querySelector("#original-css-reference").contentDocument.querySelector(".wb-switch__track")') const themePair=()=>read(`(()=>{const frame=document.querySelector('#original-css-reference').contentDocument,own=document.querySelector('.yc-source-composer-add-menu');const measure=(doc,scope)=>({track:doc.defaultView.getComputedStyle(doc.querySelector('.wb-switch__track')).backgroundColor,trackToken:doc.defaultView.getComputedStyle(scope).getPropertyValue('--wb-switch-track-bg').trim(),gray5:doc.defaultView.getComputedStyle(scope).getPropertyValue('--wb-palette-gray-5').trim(),buttonColor:doc.defaultView.getComputedStyle(doc.querySelector('.wb-button')).color,buttonHoverToken:doc.defaultView.getComputedStyle(scope).getPropertyValue('--wb-button-ghost-bg-hover').trim()});return{actual:measure(document,own),original:measure(frame,frame.documentElement)}})()`) await pause(250);measurements.light=await themePair();check('light off actual source token RGB230',measurements.light.actual.track,'rgb(230, 230, 230)');check('light switch and button match unmodified original CSS',measurements.light.actual,measurements.light.original);await capture('mode-light-off') await read(`document.documentElement.dataset.theme='dark';document.querySelector('.yc-source-composer-add-menu').dataset.crTheme='dark';document.querySelector('#original-css-reference').contentDocument.documentElement.dataset.theme='dark'`);await pause(250);measurements.dark=await themePair();check('dark switch and button match unmodified original CSS',measurements.dark.actual,measurements.dark.original);check('dark off stays distinct from light',measurements.dark.actual.track!==measurements.light.actual.track,true);await capture('mode-dark-off') await read(`document.documentElement.dataset.theme='light';document.querySelector('.yc-source-composer-add-menu').dataset.crTheme='light';document.querySelector('#original-css-reference').contentDocument.documentElement.dataset.theme='light'`);await pause(250);check('returning light removes dark token effects',(await themePair()).actual,measurements.light.original) await click('.cr-mode-submenu__row:first-child .wb-switch__control');check('source plan toggles on via actual callback',await read('window.qa.modes.at(-1).id'),'plan');await click('.cr-mode-submenu__row:first-child .wb-switch__control');check('second plan click toggles back to defaultcraft',await read('window.qa.modes.at(-1).id'),'craft');await update('window.qa.setDisabled(true)');check('host disabled controls both inputs',await read('[...document.querySelectorAll(".wb-switch__input")].every(e=>e.disabled)'),true);await capture('mode');await update('window.qa.setDisabled(false)') await selectTab('connectors');await wait('!!document.querySelector(".cr-connectors-submenu input")');measurements.connectors=await bounds('.cr-connectors-submenu');check('source connectors width240',measurements.connectors[2],240);check('source configured items lead list',await read('[...document.querySelectorAll(".cr-connectors-submenu__item-name")].map(e=>e.textContent)'),['Connected connector','Error connector','New connector','Connecting connector','Disabled connector']);await capture('connectors') await click('.cr-connectors-submenu__row-tooltip:nth-child(1) .cr-connectors-submenu__item');check('connected source row toggles actual host off',await read('window.qa.toggle'),[['connected',false]]);await click('.cr-connectors-submenu__row-tooltip:nth-child(3) .cr-connectors-submenu__item');check('new source row invokes actual connect host',await read('window.qa.connect'),['new']);await click('.cr-connectors-submenu__row-tooltip:nth-child(4) .cr-connectors-submenu__item');check('connecting source cancellable row invokes cancel once',await read('window.qa.cancel'),['busy']);await click('.cr-connectors-submenu__row-tooltip:nth-child(5) .cr-connectors-submenu__item');check('disabled source row never connects/toggles',await read('[window.qa.connect.length,window.qa.toggle.length]'),[1,1]);const errorBounds=await bounds('.cr-connectors-submenu__row-tooltip:nth-child(2)');await cdp('Input.dispatchMouseEvent',{type:'mouseMoved',x:errorBounds[0]+errorBounds[2]/2,y:errorBounds[1]+errorBounds[3]/2});await pause(200);await click('.cr-connectors-submenu__error-action');check('error repair uses actual host callback',await read('window.qa.repair'),['error']) await click('.cr-connectors-submenu input');await cdp('Input.insertText',{text:'new'});await pause(250);check('source local connector search',await read('[...document.querySelectorAll(".cr-connectors-submenu__item-name")].map(e=>e.textContent)'),['New connector']);await click('[aria-label="清除搜索"]');check('source clear restores items',await read('document.querySelectorAll(".cr-connectors-submenu__item").length'),5) await read('document.querySelector(".cr-connectors-submenu input").dispatchEvent(new CompositionEvent("compositionstart",{bubbles:true}))');await cdp('Input.insertText',{text:'missing'});check('IME composing retains source previous results',await read('document.querySelectorAll(".cr-connectors-submenu__item").length'),5);await read('document.querySelector(".cr-connectors-submenu input").dispatchEvent(new CompositionEvent("compositionend",{bubbles:true,data:"missing"}))');await pause(100);check('IME commit shows honest empty result',await read('document.querySelectorAll(".cr-connectors-submenu__item").length'),0);check('empty result source text',await read('document.querySelector(".cr-connectors-submenu__empty").textContent'),'未找到连接器');await click('[aria-label="清除搜索"]');await click('.cr-connectors-submenu__footer');check('manage callback and close exactly once',await read('[window.qa.manage,window.qa.close]'),[1,2]) await selectTab('references');measurements.references=await bounds('.cr-reference-files-submenu');check('source references width240',measurements.references[2],240);check('source filename preserves base/ext split',await read('[document.querySelector(".cr-reference-files-submenu__name-base").textContent,document.querySelector(".cr-reference-files-submenu__name-ext").textContent]'),['report.final','.md']);check('source reference icon host mapping renders both',await read('document.querySelectorAll(".cr-reference-files-submenu__item-icon svg").length'),2);await capture('references');await click('.cr-reference-files-submenu__item');check('source selected actual reference identity/path retained',await read('[window.qa.references[0].id,window.qa.references[0].path]'),['file','C:/controlled/report.final.md']);check('reference selection closes once',await read('window.qa.close'),3) await selectTab('empty');check('no file handler means no fabricated source rows',await read('!!document.querySelector(".cr-files-submenu")'),false);check('no renderer exceptions',errors,[]) fs.writeFileSync(path.join(output,'report.json'),JSON.stringify({result:'PASS',checks,measurements,scope:'Source rendering/event behavior with controlled own data; no MCP service/file dialog/model execution claim'},null,2));console.log(JSON.stringify({result:'PASS',checks:checks.length,output}));win.destroy();app.exit(0) }catch(error){await capture('failure');fs.writeFileSync(path.join(output,'failure.json'),JSON.stringify({error:String(error),checks,errors,html:await read('document.body.innerHTML')},null,2));console.error(error);win.destroy();app.exit(1)} })().catch(error=>{console.error(error);electron.app.exit(1)})