// Own offline YCode UI only. Original runtime evidence is collected separately through REA. if (!process.versions.electron) { const env = { ...process.env }; delete env.ELECTRON_RUN_AS_NODE const { spawn, spawnSync } = require('node:child_process') const server = spawn(process.execPath, [require('node:path').join(__dirname, 'verify-ui.mjs'), '0'], { env, windowsHide: true, stdio: ['ignore', 'pipe', 'inherit'] }) let started = false const timer = setTimeout(() => { if (!started) { server.kill(); process.exitCode = 1 } }, 10000) server.stdout.on('data', bytes => { const port = bytes.toString().match(/127\.0\.0\.1:(\d+)/)?.[1] if (!port || started) return started = true; clearTimeout(timer) try { const result = spawnSync(require('electron'), [__filename], { env: { ...env, YCODE_DIFF_QA_PORT: port }, windowsHide: true, stdio: 'inherit', timeout: 60000 }) process.exitCode = result.status ?? 1 } finally { server.kill() } }) server.on('error', error => { clearTimeout(timer); console.error(error); process.exitCode = 1 }) } else { const { app, BrowserWindow } = require('electron') const fs = require('node:fs/promises'), path = require('node:path'), assert = require('node:assert/strict') app.setPath('userData', path.resolve('.tmp/pierre-diff-qa-user-data')) app.disableHardwareAcceleration() let win const errors = [] const read = code => win.webContents.executeJavaScript(code) async function waitFor(code) { for (let n = 0; n < 200; n++) { if (await read(code)) return await new Promise(resolve => setTimeout(resolve, 40)) } throw new Error('Condition failed: ' + code) } async function open(name, fileName, expectBody = true) { await read(`Array.from(document.querySelectorAll('.msg__artifact')).find(n=>n.textContent===${JSON.stringify(name)}).click()`) await waitFor(`document.querySelector('diffs-container')?.shadowRoot?.querySelector('[data-title]')?.textContent.includes(${JSON.stringify(fileName)})${expectBody ? " && !!document.querySelector('diffs-container')?.shadowRoot?.querySelector('pre')" : ''}`) } const diffState = `(()=>{const c=document.querySelector('diffs-container'),root=c.shadowRoot,pre=root.querySelector('pre'),editor=document.querySelector('[class*="_pierre-diff-editor_"]');return { mode:pre?.getAttribute('data-diff-type')??null,text:pre?.textContent??'',changeType:root.querySelector('[data-change-type]')?.getAttribute('data-change-type'), additions:root.querySelector('[data-additions-count]')?.textContent,deletions:root.querySelector('[data-deletions-count]')?.textContent, codeColumns:root.querySelectorAll('code[data-code]').length,lineNumbers:root.querySelectorAll('[data-line-number-content]').length, wordSpans:root.querySelectorAll('[data-diff-span]').length,fontSize:pre?getComputedStyle(pre).fontSize:null,lineHeight:pre?getComputedStyle(pre).lineHeight:null, tokenColors:[...new Set(Array.from(root.querySelectorAll('[data-line] span')).map(n=>getComputedStyle(n).color))], editorBackground:getComputedStyle(editor).backgroundColor,theme:root.querySelector('[data-theme-css]')?.textContent, lineTypes:Array.from(root.querySelectorAll('[data-content] [data-line-type]')).map(n=>n.getAttribute('data-line-type'))}})()` async function shot(file) { await read('document.fonts.ready') await new Promise(resolve => { win.webContents.once('paint', resolve); win.webContents.invalidate() }) await fs.writeFile(path.resolve('.tmp/' + file), (await win.webContents.capturePage()).toPNG()) } async function theme(value) { await read("document.querySelector('button[title=设置]').click()") await read("Array.from(document.querySelectorAll('.settings__nav-item')).find(n=>n.textContent==='外观').click()") await read(`Array.from(document.querySelectorAll('.segmented__item')).find(n=>n.textContent.trim()===${JSON.stringify(value === 'dark' ? '深色' : '浅色')}).click()`) await waitFor(`document.documentElement.dataset.theme===${JSON.stringify(value)}`) await read("document.querySelector('.overlay').click()") } app.whenReady().then(async () => { win = new BrowserWindow({ show: false, useContentSize: true, width: 1336, height: 901, webPreferences: { offscreen: true, contextIsolation: true } }) win.webContents.on('console-message', event => { if (event.level === 'error' || event.level === 3) errors.push(event.message) }) await win.loadURL(`http://127.0.0.1:${process.env.YCODE_DIFF_QA_PORT || '8792'}/?fixture=pierre-diff`) await waitFor("document.querySelectorAll('.msg__artifact').length===4") await open('修改 Diff', 'sample.ts') const initial = await read(`(()=>{const c=document.querySelector('diffs-container');return {html:c.outerHTML,shadow:c.shadowRoot.innerHTML,toolbar:Array.from(document.querySelectorAll('[class*="pierre-diff-editor"] button')).map(b=>({title:b.title,label:b.getAttribute('aria-label'),html:b.outerHTML}))}})()`) await fs.writeFile(path.resolve('.tmp/pierre-diff-initial-dom.json'), JSON.stringify(initial, null, 2)) assert.match(initial.shadow, /export const value/) await waitFor("new Set(Array.from(document.querySelector('diffs-container').shadowRoot.querySelectorAll('[data-line] span')).map(n=>getComputedStyle(n).color)).size > 1") const split = await read(diffState) assert.equal(split.mode, 'split') assert.equal(split.codeColumns, 2) assert.ok(split.lineNumbers >= 2) assert.ok(split.wordSpans >= 2) assert.equal(split.additions, '+1') assert.equal(split.deletions, '-1') assert.equal(split.fontSize, '13px') assert.equal(split.lineHeight, '20px') assert.equal(split.editorBackground, 'rgb(255, 255, 255)') const chatMarkdown = await read("(()=>{const s=getComputedStyle(document.querySelector('.msg--assistant .markdown-content'));return {padding:s.padding,background:s.backgroundColor}})()") assert.deepEqual(chatMarkdown, { padding: '0px', background: 'rgba(0, 0, 0, 0)' }) await shot('conversation-pierre-diff-2026-10-15.png') await read("document.querySelector('button[aria-label=\"Switch to unified view\"]').click()") await waitFor("document.querySelector('diffs-container').shadowRoot.querySelector('pre').getAttribute('data-diff-type')==='single'") const unified = await read(diffState) assert.equal(unified.codeColumns, 1) assert.match(unified.text, /export const value = 1/) assert.match(unified.text, /export const value = 2/) await read("document.querySelector('button[aria-label=\"Switch to split view\"]').click()") await waitFor("document.querySelector('diffs-container').shadowRoot.querySelector('pre').getAttribute('data-diff-type')==='split'") await theme('dark') const dark = await read(diffState) assert.equal(dark.editorBackground, 'rgb(7, 7, 7)') assert.match(dark.theme, /color-scheme: dark/) await shot('conversation-pierre-diff-dark-2026-10-15.png') await theme('light') await open('新增 Diff', 'new.txt') const added = await read(diffState) assert.match(added.text, /created/) assert.ok(added.lineTypes.includes('change-addition')) assert.ok(!added.lineTypes.includes('change-deletion')) await open('删除 Diff', 'empty.txt') const deleted = await read(diffState) assert.match(deleted.text, /removed/) assert.ok(deleted.lineTypes.includes('change-deletion')) assert.ok(!deleted.lineTypes.includes('change-addition')) await open('相同 Diff', 'same.txt', false) const unchanged = await read(diffState) assert.equal(unchanged.codeColumns, 0, 'Source zero-hunk diff renders its header with no code body') assert.equal(unchanged.lineNumbers, 0) assert.equal(unchanged.wordSpans, 0) await win.loadURL(`http://127.0.0.1:${process.env.YCODE_DIFF_QA_PORT}/?fixture=artifact-source`) await waitFor("!!document.querySelector('[data-tool-call-id=\"source-edit\"]')") await read("document.querySelectorAll('.cr-collapse--completed > .cr-collapse__header, .cr-collapse--process > .cr-collapse__header').forEach(n=>{if(n.getAttribute('aria-expanded')==='false')n.click()})") await read("document.querySelector('[data-tool-call-id=\"source-edit\"] .cr-tool-head__main').click()") await waitFor("document.querySelector('[data-tool-call-id=\"source-edit\"] .cr-tool-exp__content-shell').getAttribute('aria-hidden')==='false'") const write = await read(`(()=>{const root=document.querySelector('[data-tool-call-id="source-edit"]'),viewport=root.querySelector('.cr-tool-diff__viewport'),line=root.querySelector('.cr-tool-diff__line');return {text:root.textContent,hasAlert:!!root.querySelector('[role=alert]'),role:root.querySelector('.cr-tool-head__primary').getAttribute('role'),lineHeight:getComputedStyle(line).lineHeight,viewportHeight:viewport.getBoundingClientRect().height,nativeExpandMotion:CSS.supports('interpolate-size','allow-keywords')}})()`) assert.equal(write.hasAlert, false) assert.equal(write.role, 'link') assert.match(write.text, /export const value = 1/) assert.match(write.text, /export const value = 2/) assert.equal(write.lineHeight, '21px', 'Source write diff uses 12px × 1.75; 20px is only its virtualizer estimate') assert.ok(write.viewportHeight > 0) assert.equal(write.nativeExpandMotion, true) await read("document.querySelector('[data-tool-call-id=\"source-edit\"] .cr-tool-head__primary').focus()") win.webContents.sendInputEvent({type:'keyDown',keyCode:'Return'}) win.webContents.sendInputEvent({type:'char',keyCode:'\r'}) win.webContents.sendInputEvent({type:'keyUp',keyCode:'Return'}) await waitFor("document.querySelector('diffs-container')?.shadowRoot?.querySelector('[data-title]')?.textContent.includes('example.ts')") const fromWrite = await read(diffState) assert.match(fromWrite.text, /export const value = 1/) assert.match(fromWrite.text, /export const value = 2/) assert.equal(await read("document.querySelector('[data-tool-call-id=\"source-edit\"] .cr-tool-exp__content-shell').getAttribute('aria-hidden')"), 'false') await shot('conversation-write-file-preview-2026-10-15.png') await read("document.querySelector('[data-tool-call-id=\"source-many\"] .cr-tool-head__main').click()") await waitFor("document.querySelector('[data-tool-call-id=\"source-many\"] .cr-tool-diff__viewport').clientHeight>0") const virtualRows = await read(`(()=>{const r=document.querySelector('[data-tool-call-id="source-many"]');return {initialRows:r.querySelectorAll('.cr-tool-diff__virtual-line').length,spacerHeight:r.querySelector('.cr-tool-diff__spacer').getBoundingClientRect().height,transition:getComputedStyle(r.querySelector('.cr-tool-exp__content-shell')).transitionDuration}})()`) assert.ok(virtualRows.initialRows > 0 && virtualRows.initialRows < 300) assert.ok(virtualRows.spacerHeight >= 6000) assert.equal(virtualRows.transition, '0s') await read("(()=>{const e=document.querySelector('[data-tool-call-id=\"source-many\"] .cr-tool-diff__viewport');e.scrollTop=e.scrollHeight;e.dispatchEvent(new Event('scroll'))})()") await waitFor("document.querySelector('[data-tool-call-id=\"source-many\"] .cr-tool-diff__spacer').textContent.includes('value299')") virtualRows.finalRows = await read("document.querySelectorAll('[data-tool-call-id=\"source-many\"] .cr-tool-diff__virtual-line').length") assert.ok(virtualRows.finalRows > 0 && virtualRows.finalRows < 300) assert.deepEqual(errors, []) const report = { scope: 'YCode fixture only; original pixel parity unverified', split, unified, dark, added, deleted, unchanged, chatMarkdown, write, fromWrite, virtualRows, consoleErrors: errors } await fs.writeFile(path.resolve('.tmp/pierre-diff-ui-report.json'), JSON.stringify(report, null, 2)) console.log(JSON.stringify({ scope: report.scope, splitUnified: true, syntaxHighlight: true, darkLight: true, addedDeletedUnchanged: true, chatMarkdownUnaffected: true, writeInlineDiffAndKeyboardOpen: true, consoleErrors: errors })) win.destroy(); app.quit() }).catch(async error => { console.error(error) console.error(JSON.stringify(errors)) if (win && !win.isDestroyed()) { console.error(await read('document.body.innerText')) await fs.writeFile(path.resolve('.tmp/pierre-diff-failure-dom.json'), JSON.stringify(await read(`(()=>{const root=document.querySelector('.resource-preview__body');return {html:root?.outerHTML,nodes:root?Array.from(root.querySelectorAll('*')).slice(0,40).map(n=>({tag:n.tagName,cl:n.className,bounds:n.getBoundingClientRect().toJSON(),height:getComputedStyle(n).height,display:getComputedStyle(n).display})):[]}})()`), null, 2)) win.destroy() } app.exit(1) }) }