92 lines
6.0 KiB
JavaScript
92 lines
6.0 KiB
JavaScript
// Native YCode-only fixture check against the saved REA same-state homepage layout.
|
|
const fs = require('node:fs')
|
|
const path = require('node:path')
|
|
const assert = require('node:assert/strict')
|
|
const { app, BrowserWindow } = require('electron')
|
|
app.disableHardwareAcceleration()
|
|
const root = path.resolve(__dirname, '..')
|
|
const output = path.join(root, '.tmp', 'home-style-parity-20261016')
|
|
assert.ok(output.startsWith(path.join(root, '.tmp') + path.sep))
|
|
fs.mkdirSync(output, { recursive: true })
|
|
app.setPath('userData', path.join(output, 'user-data'))
|
|
const evidence = JSON.parse(fs.readFileSync(path.join(root, '.tmp/rea-home-static-20261016/layout.json'), 'utf8'))
|
|
const sourceNodes = evidence.layout.documents[0].nodes
|
|
const source = index => sourceNodes.find(node => node.node_index === index)
|
|
const selectors = ['.wb-home-header__title', '.quick-actions__item', '.wb-scene-tabs',
|
|
'.wb-home-composer__input-slot', '.wb-composer__surface', '.cr-input-container']
|
|
const properties = ['font-family', 'font-size', 'font-weight', 'line-height', 'color',
|
|
'border-top-color', 'box-shadow', 'padding-top', 'gap']
|
|
app.whenReady().then(async () => {
|
|
await require('esbuild').build({ entryPoints: [path.join(root, '.tmp/source-home-fixture.tsx')],
|
|
bundle: true, outfile: path.join(output, 'fixture.js'), platform: 'browser', format: 'iife',
|
|
jsx: 'automatic', loader: { '.png': 'file', '.woff2': 'file' }, logLevel: 'warning' })
|
|
// Same fixed viewport/main-content coordinates as the previous fixture.
|
|
fs.writeFileSync(path.join(output, 'index.html'), fs.readFileSync(path.join(root, '.tmp/home-native-ui/index.html')))
|
|
const win = new BrowserWindow({ width: 1336, height: 901, useContentSize: true, frame: false,
|
|
show: false, webPreferences: { contextIsolation: true, nodeIntegration: false, backgroundThrottling: false, offscreen: true } })
|
|
const errors = []
|
|
win.webContents.on('console-message', (_event, level, message) => { if (level === 3) errors.push(message) })
|
|
await win.loadFile(path.join(output, 'index.html'))
|
|
await new Promise(resolve => setTimeout(resolve, 1350))
|
|
const measure = () => win.webContents.executeJavaScript(`(${function (selectors, properties) {
|
|
return selectors.flatMap(selector => Array.from(document.querySelectorAll(selector), (el, index) => {
|
|
const r = el.getBoundingClientRect(), s = getComputedStyle(el)
|
|
return { selector, index, text: el.textContent, bounds: [r.x, r.y, r.width, r.height],
|
|
focused: el.matches(':focus-within'), styles: Object.fromEntries(properties.map(key => [key, s.getPropertyValue(key)])) }
|
|
}))
|
|
}.toString()})(${JSON.stringify(selectors)},${JSON.stringify(properties)})`)
|
|
const unfocused = await measure()
|
|
win.webContents.debugger.attach('1.3')
|
|
// Own app only: real native pointer events put the textarea in the source focus state.
|
|
for (const type of ['mousePressed', 'mouseReleased']) await win.webContents.debugger.sendCommand('Input.dispatchMouseEvent',
|
|
{ type, x: 500, y: 395, button: 'left', clickCount: 1 })
|
|
await win.webContents.capturePage()
|
|
await new Promise(resolve => setTimeout(resolve, 250))
|
|
const focused = await measure()
|
|
fs.writeFileSync(path.join(output, 'focused.png'), (await win.webContents.capturePage()).toPNG())
|
|
const find = (selector, index = 0) => focused.find(node => node.selector === selector && node.index === index)
|
|
const title = find('.wb-home-header__title'), ppt = find('.quick-actions__item', 3)
|
|
const container = find('.cr-input-container'), outer = find('.wb-composer__surface')
|
|
const checks = {
|
|
titleSize: title.bounds.slice(2), titleColor: title.styles.color,
|
|
pptWidth: ppt.bounds[2], chipsFont: ppt.styles['font-family'], chipsLineHeight: ppt.styles['line-height'],
|
|
focused: container.focused, focusedShadow: container.styles['box-shadow'],
|
|
focusedBorder: container.styles['border-top-color'], outerShadow: outer.styles['box-shadow'],
|
|
xDeltaDueToUnchangedFixtureSidebar: title.bounds[0] - source(791).bounds[0], errors,
|
|
}
|
|
fs.writeFileSync(path.join(output, 'report.json'), JSON.stringify({
|
|
evidenceId: evidence.evidenceId, capturedAt: new Date().toISOString(), checks, unfocused, focused,
|
|
scope: 'Fresh YCode fixture bundle; source work scene and real pointer focus; sidebar width and data remain fixture-owned.',
|
|
}, null, 2))
|
|
assert.deepEqual(checks.titleSize, source(791).bounds.slice(2))
|
|
assert.equal(checks.titleColor, source(791).styles.color)
|
|
assert.equal(checks.pptWidth, source(833).bounds[2])
|
|
assert.equal(checks.chipsFont, source(833).styles['font-family'])
|
|
assert.equal(checks.chipsLineHeight, source(833).styles['line-height'])
|
|
for (let index = 0; index < 7; index += 1) {
|
|
const chip = find('.quick-actions__item', index), original = source(827 + index * 2)
|
|
assert.deepEqual(chip.bounds.slice(1), original.bounds.slice(1))
|
|
assert.equal(chip.bounds[0] - original.bounds[0], -2.125)
|
|
for (const property of ['font-family', 'font-size', 'font-weight', 'line-height']) {
|
|
assert.equal(chip.styles[property], original.styles[property])
|
|
}
|
|
}
|
|
const beforeFocus = unfocused.find(node => node.selector === '.cr-input-container')
|
|
assert.equal(beforeFocus.focused, false)
|
|
assert.notEqual(beforeFocus.styles['box-shadow'], checks.focusedShadow)
|
|
assert.equal(checks.focused, true)
|
|
assert.equal(checks.focusedShadow, source(848).styles['box-shadow'])
|
|
assert.equal(checks.focusedBorder, source(848).styles['border-top-color'])
|
|
assert.equal(checks.outerShadow, source(845).styles['box-shadow'])
|
|
assert.equal(checks.xDeltaDueToUnchangedFixtureSidebar, -2.125)
|
|
for (const [selector, index] of [['.wb-scene-tabs', 795], ['.wb-home-composer__input-slot', 841], ['.cr-input-container', 848]]) {
|
|
const node = find(selector), original = source(index)
|
|
assert.deepEqual(node.bounds.slice(1), original.bounds.slice(1))
|
|
}
|
|
assert.deepEqual(errors, [])
|
|
win.webContents.debugger.detach()
|
|
win.destroy()
|
|
console.log(JSON.stringify({ status: 'PASS', output, checks }))
|
|
app.quit()
|
|
}).catch(error => { console.error(error); app.exit(1) })
|