// Actual production YCode file-tree clicks, full file IPC, source Monaco, isolated native App. const fs = require('node:fs/promises'), path = require('node:path'), assert = require('node:assert/strict') if (!process.versions.electron) { ;(async () => { const directory = await fs.mkdtemp(path.resolve('.tmp/source-code-preview-')), crypto = require('node:crypto'), now = Date.now() await Promise.all(['data', 'home', 'space-a', 'space-b'].map(name => fs.mkdir(path.join(directory, name)))) const sessions = ['A', 'B'].map((name, index) => ({ id: crypto.randomUUID(), title: 'Code ' + name, cwd: path.join(directory, 'space-' + name.toLowerCase()), createdAt: now, updatedAt: now - index, messages: [{ id: crypto.randomUUID(), role: 'user', content: 'Persisted code-preview fixture.', createdAt: now, status: 'done' }] })) await fs.writeFile(path.join(directory, 'data', 'sessions.json'), JSON.stringify({ sessions })) await fs.writeFile(path.join(sessions[0].cwd, 'fixture.ts'), Array.from({ length: 500 }, (_, index) => `const SPACE_A_${index + 1}: number = ${index + 1}; // 中文 AlphaNeedle`).join('\n')) await fs.writeFile(path.join(sessions[1].cwd, 'fixture.ts'), 'const SPACE_B: boolean = true;\n// Independent native workspace') const env = { ...process.env, YCODE_QA_DIRECTORY: directory, YCODE_USER_DATA_DIR: path.join(directory, 'data'), YCODE_QA_CODE_SESSIONS: JSON.stringify(sessions), YCODE_DISABLE_GPU: '1', YCODE_QA_OFFSCREEN: '1' } delete env.ELECTRON_RUN_AS_NODE; delete env.ELECTRON_RENDERER_URL const child = require('node:child_process').spawn(require('electron'), ['--no-sandbox', '--disable-gpu-sandbox', __filename], { env, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] }) let output = ''; child.stdout.on('data', bytes => { output += bytes; if (bytes.toString().includes('CODE_PREVIEW_')) process.stdout.write(bytes) }); child.stderr.on('data', bytes => { output += bytes }) const timer = setTimeout(() => child.kill(), 90000) child.once('exit', async code => { clearTimeout(timer); await fs.writeFile(path.join(directory, 'process.log'), output); console.log(JSON.stringify({ directory, exitCode: code })); if (code) console.error(output.slice(-7000)); process.exitCode = code ?? 1 }) })().catch(error => { console.error(error); process.exitCode = 1 }) } else { const { app, BrowserWindow, session } = require('electron'); app.disableHardwareAcceleration() const { pathToFileURL } = require('node:url'), directory = process.env.YCODE_QA_DIRECTORY, [a, b] = JSON.parse(process.env.YCODE_QA_CODE_SESSIONS) assert.ok(path.relative(path.resolve('.tmp'), directory).startsWith('source-code-preview-')) app.setPath('userData', path.join(directory, 'data')); app.setPath('sessionData', path.join(directory, 'data')); app.setPath('home', path.join(directory, 'home')) let main, settings, phase = 'boot'; const checks = [], errors = [], network = [], snapshots = {} const pause = ms => new Promise(resolve => setTimeout(resolve, ms)) async function read(source, win = main) { let timer; try { return await Promise.race([win.webContents.executeJavaScript(source), new Promise((_, reject) => { timer = setTimeout(() => reject(Error('Renderer timeout')), 15000) })]) } catch(error) { throw Error(error.message + ' — ' + source.slice(0, 160)) } finally { clearTimeout(timer) } } async function wait(source, label, win = main) { for (let i = 0; i < 400; i++) { const value = await read(source, win); if (value) return value; await pause(25) } throw Error('Timeout: ' + label) } function check(label, actual, expected) { const pass = typeof expected === 'function' ? expected(actual) : JSON.stringify(actual) === JSON.stringify(expected); checks.push({ label, pass, actual, expected: typeof expected === 'function' ? expected.toString() : expected }) } async function click(selector, win = main) { await read(`(()=>{const n=document.querySelector(${JSON.stringify(selector)});if(!n)throw Error('Missing click target');n.click()})()`, win); await pause(70) } async function step(label) { phase = label; await fs.writeFile(path.join(directory, 'phase.txt'), label) } async function openFile() { if (!(await read('!!document.querySelector(".yc-workbench")'))) await click('.source-chat-header [title="打开右侧栏"]') if (!(await read('!!document.querySelector("[data-entry-key=fileTree]")'))) await click('[aria-label="新标签页"]') await click('[data-entry-key="fileTree"]'); await wait('[...document.querySelectorAll(".yc-workbench-tree-node")].some(n=>n.textContent.includes("fixture.ts"))', 'native tree loaded') await read('[...document.querySelectorAll(".yc-workbench-tree-node")].find(n=>n.textContent.includes("fixture.ts")).click()') await wait('!!document.querySelector(".source-code-preview[data-model-lines]")', 'actual source Monaco editor mounted') } async function snapshot(name) { return snapshots[name] = await read(`(()=>{const n=document.querySelector('.source-code-preview'),m=n?.querySelector('.monaco-editor'),r=m?.getBoundingClientRect();return {state:n?.dataset.sourceCodeState,language:n?.dataset.language,lines:Number(n?.dataset.modelLines),options:JSON.parse(n?.dataset.sourceOptions||'{}'),owner:n?.dataset.sessionId,text:[...document.querySelectorAll('.view-line')].map(n=>n.textContent).join('\\n'),syntaxClasses:[...new Set([...document.querySelectorAll('.view-line span')].map(n=>n.className))],search:!!document.querySelector('[class*=_search-widget_xh5da]'),theme:m?.className,bg:m?getComputedStyle(m).backgroundColor:null,bounds:r?[r.x,r.y,r.width,r.height]:null,range:!!document.querySelector('.source-code-preview-range')}})()`) } async function shot(name) { const image = await main.webContents.debugger.sendCommand('Page.captureScreenshot', { format: 'png', fromSurface: true, captureBeyondViewport: false }); await fs.writeFile(path.join(directory, name + '.png'), Buffer.from(image.data, 'base64')) } ;(async () => { await import(pathToFileURL(path.resolve('out/main/index.mjs')).href); await app.whenReady() session.defaultSession.webRequest.onBeforeRequest((details, callback) => { if (/^https?:/.test(details.url)) network.push(details.url); callback({ cancel: /^https?:/.test(details.url) }) }) for (let i = 0; i < 200; i++) { main = BrowserWindow.getAllWindows().find(win => win.getTitle() === 'YCode'); if (main && !main.webContents.isLoading()) break; await pause(25) } assert.ok(main); main.webContents.on('console-message', event => { if (event.level === 'error' || event.level >= 3) errors.push(event.message) }) await wait('!!document.querySelector(".source-chat-header")', 'actual App'); main.webContents.debugger.attach('1.3') await main.webContents.debugger.sendCommand('Emulation.setDeviceMetricsOverride', { width: 1336, height: 901, deviceScaleFactor: 1, mobile: false }) await step('native-file-tree-and-full-source-editor'); await openFile(); await pause(250) let state = await snapshot('space-a') check('real file-tree click mounts original Monaco engine', state.state, 'ready') check('original source language mapping uses TypeScript', state.language, 'typescript') check('full native file becomes model, beyond400line preview window', state.lines, 500) check('source readonly/13px/20px/wrapon/minimapfalse', [state.options.readOnly, state.options.fontSize, state.options.lineHeight, state.options.wordWrap, state.options.minimap?.enabled], [true, 13, 20, 'on', false]) check('source line numbers and scrollbar dimensions', [state.options.lineNumbers, state.options.scrollbar?.verticalScrollbarSize, state.options.scrollbar?.horizontalScrollbarSize], ['on', 10, 10]) check('source tab size4', state.options.tabSize, 4) await wait('[...document.querySelectorAll(".view-line span")].some(n=>/^mtk[2-9][0-9]*$/.test(n.className))', 'actual colored Monarch token beyond default mtk1') state = await snapshot('space-a-colored') check('Monarch tokens produce actual distinct syntax classes', state.syntaxClasses.filter(value => /^mtk\d+$/.test(value)).length, value => value > 1) await shot('code-light') await step('source-editor-search-and-range') await read('document.querySelector(".monaco-editor textarea").focus()') main.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'F', modifiers: ['control'] }); main.webContents.sendInputEvent({ type: 'keyUp', keyCode: 'F', modifiers: ['control'] }) await wait('!!document.querySelector("[class*=_search-widget__input_xh5da] input")', 'source CtrlF widget') await read(`(()=>{const n=document.querySelector('[class*=_search-widget__input_xh5da] input');Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set.call(n,'AlphaNeedle');n.dispatchEvent(new Event('input',{bubbles:true}))})()`); await pause(150) check('source search has500 matches over full model', await read('document.querySelector("[class*=_search-widget__match-count_xh5da]")?.textContent'), value => value?.includes('500')) await shot('code-search') await read(`document.querySelector('[class*=_search-widget__input_xh5da] input').dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}))`) await wait('!document.querySelector("[class*=_search-widget_xh5da]")', 'source search Escape closes') const firstModel = await read('document.querySelector(".source-code-preview").dataset.modelUri') await read(`window.dispatchEvent(new CustomEvent('ycode:preview-resource',{detail:{path:${JSON.stringify(path.join(a.cwd, 'fixture.ts'))},range:{start:450,end:452}}}))`) await wait('!!document.querySelector(".source-code-preview-range")', 'host requested line range decoration'); await pause(200) state = await snapshot('line-range'); check('line450 range scrolls full model', state.text.includes('SPACE_A_450'), true) check('range preview still has500 model lines', state.lines, 500) check('range450 reuses original500line model identity', await read('document.querySelector(".source-code-preview").dataset.modelUri'), firstModel) await read(`window.dispatchEvent(new CustomEvent('ycode:preview-resource',{detail:{path:${JSON.stringify(path.join(a.cwd, 'fixture.ts'))},range:{start:20,end:21}}}))`) await wait('document.querySelector(".source-code-preview")?.dataset.rangeStart==="20"', 'same model moves to line20') await wait('[...document.querySelectorAll(".view-line")].some(n=>n.textContent.includes("SPACE_A_20:"))', 'line20 range painted') check('consecutive450→20 has one actual model range decoration', await read('[document.querySelector(".source-code-preview").dataset.modelUri,document.querySelector(".source-code-preview").dataset.rangeCount]'), [firstModel, '1']) await read(`window.dispatchEvent(new CustomEvent('ycode:preview-resource',{detail:{path:${JSON.stringify(path.join(a.cwd, 'fixture.ts'))}}}))`) await wait('document.querySelector(".source-code-preview")?.dataset.rangeCount==="0"&&!document.querySelector(".source-code-preview-range")', 'no-range clears prior model decoration') check('clearing range keeps same500line model', await read('[document.querySelector(".source-code-preview").dataset.modelUri,document.querySelector(".source-code-preview").dataset.modelLines]'), [firstModel, '500']) await step('real-appearance-theme-and-session-ownership') await read('window.ycode.app.openSettings("appearance")') settings = await (async () => { for (let i = 0; i < 200; i++) { const win = BrowserWindow.getAllWindows().find(win => win !== main); if (win && !win.webContents.isLoading()) return win; await pause(25) } throw Error('Settings missing') })() await wait('!!document.querySelector(".segmented")', 'native appearance UI', settings) await read('[...document.querySelectorAll(".segmented__item")].find(n=>n.textContent==="深色").click()', settings) await wait('document.documentElement.dataset.theme==="dark"', 'shared dark theme'); await pause(200) state = await snapshot('dark'); check('actual source Monaco receives dark theme', state.theme, value => value?.includes('vs-dark')) await shot('code-dark'); await click('.settings-window-close', settings) await click(`[data-session-id="${b.id}"]`); await pause(300); await openFile(); await wait('[...document.querySelectorAll(".view-line")].some(n=>n.textContent.includes("SPACE_B"))', 'second native model painted'); state = await snapshot('space-b') check('second session editor owner is correct', state.owner, b.id) check('same filename uses independent second workspace complete model', state.lines, 2) check('second workspace contains its content only', [state.text.includes('SPACE_B'), state.text.includes('SPACE_A')], [true, false]) await click(`[data-session-id="${a.id}"]`); await wait('document.querySelector(".source-code-preview")?.dataset.modelLines==="500"', 'return to first session model') check('session return restores source model and clears oldsearch', (await snapshot('returned-a')).search, false) await step('real-failure-fallback-and-network-check') await click('.sidebar-next-collapse-toggle'); await pause(350); await fs.unlink(path.join(a.cwd, 'fixture.ts')) await click('.source-chat-header [title="打开右侧栏"]'); await wait('document.querySelector(".source-code-preview")?.dataset.sourceCodeState==="error"', 'real file removal fails honest full read') check('real read failure shows cached original text fallback', await read('document.querySelector(".source-code-preview")?.textContent.includes("原始文本")&&!!document.querySelector(".yc-workbench-text pre")'), true) check('no WorkBuddy/CDN/HTTP requests from loader or workers', network, []) check('no renderer errors', errors, []) const report = { result: checks.every(item => item.pass) ? 'PASS' : 'FAIL', scope: 'Production App/native file tree/preload/IPC/exact shipped Monaco. Temp conversations/files only; no original live access; source readonly preview, not disk editor or full pixel acceptance.', checks, snapshots, network, errors } await fs.writeFile(path.join(directory, 'report.json'), JSON.stringify(report, null, 2)); console.log('CODE_PREVIEW_INTEGRATION_' + report.result + ' ' + directory) for (const item of checks.filter(check => !check.pass)) console.log('CODE_PREVIEW_FAILURE ' + JSON.stringify(item)); app.exit(report.result === 'PASS' ? 0 : 1) })().catch(async error => { await fs.writeFile(path.join(directory, 'failure.json'), JSON.stringify({ phase, error: String(error), stack: error.stack, checks, snapshots, network, errors }, null, 2)); console.error(error); if (main?.webContents.debugger.isAttached()) await shot('failure').catch(() => undefined); app.exit(1) }) }