chore: establish YCode replication baseline and TODO

This commit is contained in:
xiaohan.yuan committed 2026-10-17 02:43:49 +08:00
commit 6dc59f89ba
1118 files changed
+196002

No files matched your search

+162
View File
@@ -0,0 +1,162 @@
// 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)
})
}