feat(settings): replicate the WorkBuddy keyboard shortcuts panel and wire global bindings (TODO 12)
Recover the WorkBuddy 5.8.3 快捷键 page from REA evidence (keyboard-shortcuts-DOrivHwf.css + tokens): 49 recovered module rules, scoped light/dark tokens, the 24-row 命令/按键绑定/操作 sheet (panel 638x620, header 39, scroll 638x533, rows 41/45/44, keycaps 22) and the recording/ESC-cancel interaction. Wire the panel into SettingsDialog and keep only real bindings honest: 14 of 24 rows are marked supported, the remaining rows stay visually 1:1 but are labelled 尚未接入 and cannot pretend to save custom keys. Add Ctrl+K search focus, Ctrl+B / Ctrl+Shift+B left/right sidebar toggles, Ctrl+N new conversation (App -> Sidebar path) and Esc-to-stop while generating; fix the sidebar search focus race introduced by conditional mounting. Verify with scripts/verify-shortcuts-settings.cjs: 32 assertions covering geometry, row/key order, search filtering, recording/cancel, supported ids, dark theme surfaces and the four new global shortcuts in a real main window. All existing regressions stay green.
This commit is contained in:
1 parent
b36b52831b
commit
19460f0b13
19 files changed
+1008
-12
No files matched your search
@@ -0,0 +1,288 @@
|
||||
// TODO 12 验收:设置 → 快捷键页的 1:1 结构/几何 + 搜索/录制/取消 + 深浅主题 + 真实绑定标注。
|
||||
// 真实隔离 YCode(真实 preload/IPC/渲染进程/文件系统)+ 原生设置窗口,视口 900×680(与原版一致)。
|
||||
//
|
||||
// 原版依据(REA ev_5f3148…/ev_f4fd41…/ev_b3e014…/ev_528724…):
|
||||
// `.keyboard-shortcuts-panel` [232,40,638,620]、toolbar 638×32、search 516×32、恢复默认 110×32、
|
||||
// list [232,88,638,572]、header 39、scroll [232,127,638,533];24 行(1–16 高 41、17–23 高 45、末行 44),
|
||||
// 键帽高 22;前 16 行可编辑并有删除按钮,后 8 行 not-editable 且操作列为「—」。
|
||||
const fs = require('node:fs/promises')
|
||||
const path = require('node:path')
|
||||
const assert = require('node:assert/strict')
|
||||
|
||||
const EXPECTED_LABELS = ['打开全局搜索', '打开设置', '语音录制开关', '对话内搜索', '全局搜索', '发送消息',
|
||||
'输入时换行', '新建对话', '停止生成', '上一个任务', '下一个任务', '定位当前任务', '切换左侧栏', '切换右侧栏',
|
||||
'进入/退出全屏', '唤起/隐藏主窗口', '放大内容字号', '缩小内容字号', '重置内容字号', '唤起选择器',
|
||||
'唤起斜杠命令', '按住后点击,始终外部打开', '新建标签页', '关闭标签页']
|
||||
const EXPECTED_KEYS = ['Ctrl+K', 'Ctrl+,', 'Alt', 'Ctrl+F', 'Ctrl+K', 'Enter', 'Shift+Enter', 'Ctrl+N', 'Esc',
|
||||
'Ctrl+[', 'Ctrl+]', 'Ctrl+Shift+E', 'Ctrl+B', 'Ctrl+Shift+B', 'F11', 'Shift+Alt+W', 'Ctrl+"+"', 'Ctrl+"-"',
|
||||
'Ctrl+0', '@', '/', 'Ctrl', 'Ctrl+T', 'Ctrl+W']
|
||||
const SUPPORTED_IDS = ['global-search-open', 'open-settings', 'global-search', 'send-message', 'newline',
|
||||
'new-conversation', 'stop-generation', 'toggle-left-sidebar', 'toggle-right-sidebar', 'font-increase',
|
||||
'font-decrease', 'font-reset', 'new-tab', 'close-tab']
|
||||
|
||||
if (!process.versions.electron) {
|
||||
;(async () => {
|
||||
const directory = await fs.mkdtemp(path.resolve('.tmp/shortcuts-settings-'))
|
||||
const data = path.join(directory, 'data'), home = path.join(directory, 'home')
|
||||
await fs.mkdir(data, { recursive: true })
|
||||
await fs.mkdir(home, { recursive: true })
|
||||
const env = { ...process.env, YCODE_QA_DIRECTORY: directory, YCODE_USER_DATA_DIR: data, YCODE_OMP_HOME: home,
|
||||
YCODE_DISABLE_GPU: '1', YCODE_DISABLE_SYSTEM_INTEGRATION: '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('SHORTCUTS_SETTINGS_')) process.stdout.write(bytes) })
|
||||
child.stderr.on('data', bytes => { output += bytes })
|
||||
const timer = setTimeout(() => child.kill(), 300000)
|
||||
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(-4000))
|
||||
process.exitCode = code ? 1 : 0
|
||||
})
|
||||
})().catch(error => { console.error(error); process.exitCode = 1 })
|
||||
} else {
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
const { pathToFileURL } = require('node:url')
|
||||
const directory = process.env.YCODE_QA_DIRECTORY
|
||||
app.setPath('home', path.join(directory, 'home'))
|
||||
const checks = [], snapshots = {}, rendererErrors = []
|
||||
let main, settings, phase = 'boot'
|
||||
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: ' + source.slice(0, 120))), 15000)
|
||||
})])
|
||||
} finally { clearTimeout(timer) }
|
||||
}
|
||||
async function wait(source, label, win = main) {
|
||||
for (let index = 0; index < 300; index++) { const value = await read(source, win); if (value) return value; await pause(40) }
|
||||
throw Error('Timeout: ' + label)
|
||||
}
|
||||
async function step(label) { phase = label; await fs.writeFile(path.join(directory, 'phase.txt'), 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 })
|
||||
}
|
||||
const near = (value, target, tolerance = 0.75) => Math.abs(value - target) <= tolerance
|
||||
function observe(win) {
|
||||
win.webContents.on('console-message', event => { if (event.level === 'error' || event.level >= 3) rendererErrors.push({ windowId: win.id, message: event.message }) })
|
||||
}
|
||||
async function openSettings(section) {
|
||||
await read(`window.ycode.app.openSettings(${JSON.stringify(section)})`)
|
||||
for (let index = 0; index < 200; index++) {
|
||||
settings = BrowserWindow.getAllWindows().find(win => win !== main)
|
||||
if (settings && !settings.webContents.isLoading()) break
|
||||
await pause(25)
|
||||
}
|
||||
assert.ok(settings, 'settings window')
|
||||
await wait('!!document.querySelector(".settings-window-root")', 'settings root', settings)
|
||||
await pause(350)
|
||||
return settings
|
||||
}
|
||||
async function sendKey(key, code, keyCode, modifiers = 0) {
|
||||
if (!settings.webContents.debugger.isAttached()) settings.webContents.debugger.attach('1.3')
|
||||
await settings.webContents.debugger.sendCommand('Input.dispatchKeyEvent', { type: 'keyDown', key, code, windowsVirtualKeyCode: keyCode, nativeVirtualKeyCode: keyCode, modifiers })
|
||||
await settings.webContents.debugger.sendCommand('Input.dispatchKeyEvent', { type: 'keyUp', key, code, windowsVirtualKeyCode: keyCode, nativeVirtualKeyCode: keyCode, modifiers })
|
||||
await pause(120)
|
||||
}
|
||||
async function sendMainKey(key, code, keyCode, modifiers = 0) {
|
||||
if (!main.webContents.debugger.isAttached()) main.webContents.debugger.attach('1.3')
|
||||
main.showInactive(); main.webContents.focus()
|
||||
await main.webContents.debugger.sendCommand('Input.dispatchKeyEvent', { type: 'keyDown', key, code, windowsVirtualKeyCode: keyCode, nativeVirtualKeyCode: keyCode, modifiers })
|
||||
await main.webContents.debugger.sendCommand('Input.dispatchKeyEvent', { type: 'keyUp', key, code, windowsVirtualKeyCode: keyCode, nativeVirtualKeyCode: keyCode, modifiers })
|
||||
await pause(150)
|
||||
}
|
||||
|
||||
const layoutSource = `(()=>{
|
||||
const round = value => Math.round(value * 1000) / 1000
|
||||
const rect = node => { if (!node) return null; const r = node.getBoundingClientRect()
|
||||
return { x: round(r.x), y: round(r.y), width: round(r.width), height: round(r.height) } }
|
||||
const panel = document.querySelector('.keyboard-shortcuts-panel')
|
||||
const rows = [...document.querySelectorAll('.shortcut-item')]
|
||||
return {
|
||||
panel: rect(panel),
|
||||
toolbar: rect(document.querySelector('.keyboard-shortcuts-panel__toolbar')),
|
||||
search: rect(document.querySelector('.keyboard-shortcuts-panel__search')),
|
||||
restore: rect(document.querySelector('.keyboard-shortcuts-panel__toolbar .wb-button')),
|
||||
restoreDisabled: (() => { const node = document.querySelector('.keyboard-shortcuts-panel__toolbar .wb-button'); return node ? node.disabled === true : null })(),
|
||||
list: rect(document.querySelector('.keyboard-shortcuts-panel__list')),
|
||||
header: rect(document.querySelector('.keyboard-shortcuts-panel__column-header')),
|
||||
scroll: rect(document.querySelector('.keyboard-shortcuts-panel__list-scroll')),
|
||||
headerTexts: [...document.querySelectorAll('.keyboard-shortcuts-panel__column-header span')].map(node => node.textContent.trim()),
|
||||
rows: rows.map(row => ({
|
||||
id: row.dataset.shortcutId,
|
||||
supported: row.dataset.shortcutSupported,
|
||||
label: row.querySelector('.shortcut-item__label')?.textContent?.trim(),
|
||||
keys: [...row.querySelectorAll('.shortcut-item__key')].map(node => node.textContent.trim()),
|
||||
prefix: row.querySelector('.shortcut-item__prefix')?.textContent?.trim(),
|
||||
notEditable: row.classList.contains('shortcut-item--not-editable'),
|
||||
recording: row.classList.contains('shortcut-item--recording'),
|
||||
hasDelete: !!row.querySelector('.shortcut-item__action-btn--delete'),
|
||||
placeholder: row.querySelector('.shortcut-item__actions-placeholder')?.textContent?.trim(),
|
||||
rect: rect(row),
|
||||
keyRect: rect(row.querySelector('.shortcut-item__key')),
|
||||
title: row.querySelector('.shortcut-item__binding-tooltip-anchor')?.getAttribute('title'),
|
||||
rowBackground: getComputedStyle(row).backgroundColor,
|
||||
labelColor: getComputedStyle(row.querySelector('.shortcut-item__label')).color,
|
||||
keyBackground: getComputedStyle(row.querySelector('.shortcut-item__key')).backgroundColor
|
||||
})),
|
||||
recordingText: document.querySelector('.shortcut-item__recording-text')?.textContent?.trim(),
|
||||
escCancel: document.querySelector('.shortcut-item__esc-cancel')?.textContent?.trim(),
|
||||
errorHint: document.querySelector('.shortcut-item__error-hint')?.textContent?.trim(),
|
||||
empty: document.querySelector('.keyboard-shortcuts-panel__empty')?.textContent?.trim(),
|
||||
theme: document.documentElement.dataset.theme ?? 'light'
|
||||
}})()`
|
||||
|
||||
;(async () => {
|
||||
await import(pathToFileURL(path.resolve('out/main/index.mjs')).href); await app.whenReady()
|
||||
for (let index = 0; index < 200; index++) {
|
||||
main = BrowserWindow.getAllWindows().find(win => win.getTitle() === 'YCode')
|
||||
if (main && !main.webContents.isLoading()) break
|
||||
await pause(25)
|
||||
}
|
||||
assert.ok(main, 'main window'); observe(main)
|
||||
await wait('!!window.ycode', 'preload api')
|
||||
|
||||
await step('global-shortcuts-main-window')
|
||||
// CDP modifiers 位掩码:Alt=1、Ctrl=2、Meta=4、Shift=8。
|
||||
const collapsedBefore = await read(`localStorage.getItem('ycode:sidebar-collapsed')`)
|
||||
await sendMainKey('b', 'KeyB', 66, 2)
|
||||
const collapsedAfter = await wait(`localStorage.getItem('ycode:sidebar-collapsed')==='1'?'1':null`, 'sidebar collapsed by Ctrl+B')
|
||||
check('Ctrl+B collapses the left sidebar and persists the state', [collapsedBefore, collapsedAfter], ['0', '1'])
|
||||
await sendMainKey('b', 'KeyB', 66, 2)
|
||||
check('Ctrl+B expands the left sidebar again', await wait(`localStorage.getItem('ycode:sidebar-collapsed')==='0'?'0':null`, 'sidebar expanded by Ctrl+B'), '0')
|
||||
await sendMainKey('k', 'KeyK', 75, 2)
|
||||
const searchState = await wait(`(()=>{const input=document.querySelector('.sidebar__search-input')
|
||||
if(!input)return null
|
||||
const active=document.activeElement
|
||||
return { active: active===input, activeClass: String(active?.className ?? active?.tagName ?? '') }})()`, 'search box visible after Ctrl+K')
|
||||
snapshots.ctrlK = searchState
|
||||
check('Ctrl+K shows and focuses the sidebar search box', searchState.active, true)
|
||||
await sendMainKey('b', 'KeyB', 66, 10)
|
||||
check('Ctrl+Shift+B opens the right panel', await wait(`document.querySelector('.app-shell')?.dataset.right==='shown'?'shown':null`, 'right panel opened'), 'shown')
|
||||
await sendMainKey('b', 'KeyB', 66, 10)
|
||||
check('Ctrl+Shift+B closes the right panel', await wait(`document.querySelector('.app-shell')?.dataset.right==='hidden'?'hidden':null`, 'right panel closed'), 'hidden')
|
||||
await read(`window.__qaNewTask=0;window.addEventListener('ycode:new-task',()=>{window.__qaNewTask++},{once:true});true`)
|
||||
await sendMainKey('n', 'KeyN', 78, 2)
|
||||
check('Ctrl+N dispatches the sidebar 新建任务 path', await wait(`window.__qaNewTask===1?1:null`, 'new task shortcut dispatched'), 1)
|
||||
|
||||
await step('open-shortcuts-settings')
|
||||
await openSettings('keyboardShortcuts'); observe(settings)
|
||||
await wait('!!document.querySelector(".keyboard-shortcuts-panel")', 'shortcuts panel', settings)
|
||||
const nav = await read(`(()=>{const item=document.querySelector('[data-settings-tab="keyboardShortcuts"]')
|
||||
return { exists: !!item, disabled: item?.getAttribute('aria-disabled'), active: document.querySelector('.settings-navigation__item--active')?.dataset.settingsTab }})()`, settings)
|
||||
check('快捷键 nav entry is enabled and active', [nav.exists, nav.disabled !== 'true', nav.active], [true, true, 'keyboardShortcuts'])
|
||||
|
||||
const light = await read(layoutSource, settings)
|
||||
snapshots.light = light
|
||||
check('panel keeps the recovered 638x620 box at 232,40',
|
||||
[near(light.panel.x, 232), near(light.panel.y, 40), near(light.panel.width, 638), near(light.panel.height, 620)], [true, true, true, true])
|
||||
check('toolbar/search/restore keep the recovered 638x32, 516x32 and 110x32 geometry',
|
||||
[[near(light.toolbar.width, 638), near(light.toolbar.height, 32)].every(Boolean),
|
||||
[near(light.search.width, 516), near(light.search.height, 32)].every(Boolean),
|
||||
[near(light.restore.x, 760), near(light.restore.width, 110), near(light.restore.height, 32)].every(Boolean)], [true, true, true])
|
||||
check('全部恢复默认 stays disabled like the original', light.restoreDisabled, true)
|
||||
check('list/header/scroll keep 638x572, 39 and 638x533 at 232,88/127',
|
||||
[[near(light.list.x, 232), near(light.list.y, 88), near(light.list.width, 638), near(light.list.height, 572)].every(Boolean),
|
||||
near(light.header.height, 39), [near(light.scroll.y, 127), near(light.scroll.height, 533)].every(Boolean)], [true, true, true])
|
||||
check('column headers read 命令 / 按键绑定 / 操作', light.headerTexts, ['命令', '按键绑定', '操作'])
|
||||
check('sheet renders the recovered 24 rows in order', light.rows.map(row => row.label), EXPECTED_LABELS)
|
||||
check('every row keeps the recovered key binding text', light.rows.map(row => row.keys.join('+') || null), EXPECTED_KEYS)
|
||||
check('row heights follow the recovered 41 / 45 / 44 pattern',
|
||||
light.rows.map((row, index) => index < 16 ? near(row.rect.height, 41) : index < 23 ? near(row.rect.height, 45) : near(row.rect.height, 44)),
|
||||
Array.from({ length: 24 }, () => true))
|
||||
check('keycaps stay 22px high', light.rows.every(row => near(row.keyRect.height, 22)), true)
|
||||
check('first 16 rows expose the delete action and stay editable', light.rows.slice(0, 16).every(row => row.hasDelete && !row.notEditable), true)
|
||||
check('last 8 rows are read-only with an em dash placeholder', light.rows.slice(16).every(row => !row.hasDelete && row.notEditable && row.placeholder === '—'), true)
|
||||
check('语音录制开关 keeps the plain 长按 prefix before the Alt key',
|
||||
[light.rows[2].prefix, light.rows[2].keys], ['长按', ['Alt']])
|
||||
check('supported rows match the shortcuts YCode really binds',
|
||||
light.rows.filter(row => row.supported === 'true').map(row => row.id), SUPPORTED_IDS)
|
||||
check('unsupported rows are labelled as not wired in their tooltip',
|
||||
light.rows.filter(row => row.supported === 'false').every(row => (row.title ?? '').includes('尚未接入')), true)
|
||||
|
||||
await step('shortcuts-search')
|
||||
await read(`(()=>{const input=document.querySelector('.keyboard-shortcuts-panel .wb-input__inner')
|
||||
const setter=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set
|
||||
setter.call(input,'语音'); input.dispatchEvent(new Event('input',{bubbles:true})); return true})()`, settings)
|
||||
const filtered = await wait(`(()=>{const rows=[...document.querySelectorAll('.shortcut-item')]
|
||||
return rows.length===1?rows.map(row=>row.querySelector('.shortcut-item__label').textContent.trim()):null})()`, 'filtered to one row', settings)
|
||||
check('search 语音 filters the sheet to 语音录制开关', filtered, ['语音录制开关'])
|
||||
await read(`(()=>{const input=document.querySelector('.keyboard-shortcuts-panel .wb-input__inner')
|
||||
const setter=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set
|
||||
setter.call(input,'zzzz'); input.dispatchEvent(new Event('input',{bubbles:true})); return true})()`, settings)
|
||||
const empty = await wait(`document.querySelector('.keyboard-shortcuts-panel__empty')?.textContent?.trim()`, 'empty state', settings)
|
||||
check('a query without matches shows the empty state', empty, '未找到匹配的快捷键')
|
||||
await read(`(()=>{const input=document.querySelector('.keyboard-shortcuts-panel .wb-input__inner')
|
||||
const setter=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set
|
||||
setter.call(input,''); input.dispatchEvent(new Event('input',{bubbles:true})); return true})()`, settings)
|
||||
await wait(`document.querySelectorAll('.shortcut-item').length===24`, 'full sheet restored', settings)
|
||||
check('clearing the query restores all 24 rows', await read(`document.querySelectorAll('.shortcut-item').length`, settings), 24)
|
||||
|
||||
await step('shortcuts-recording')
|
||||
await read(`document.querySelector('[data-shortcut-id="global-search-open"] .shortcut-item__binding-tooltip-anchor').click()`, settings)
|
||||
const recording = await wait(`(()=>{const row=document.querySelector('[data-shortcut-id="global-search-open"]')
|
||||
if(!row?.classList.contains('shortcut-item--recording'))return null
|
||||
return { text: row.querySelector('.shortcut-item__recording-text')?.textContent?.trim(),
|
||||
cancel: row.querySelector('.shortcut-item__esc-cancel')?.textContent?.trim(),
|
||||
cursor: !!row.querySelector('.shortcut-item__recording-cursor') }})()`, 'recording state', settings)
|
||||
snapshots.recording = recording
|
||||
check('clicking a binding enters the recovered 按下快捷键 / ESC 取消 recording state',
|
||||
[recording.text, recording.cancel, recording.cursor], ['按下快捷键', 'ESC 取消', true])
|
||||
await sendKey('Escape', 'Escape', 27)
|
||||
const cancelled = await wait(`(()=>{const row=document.querySelector('[data-shortcut-id="global-search-open"]')
|
||||
if(!row||row.classList.contains('shortcut-item--recording'))return null
|
||||
return row.querySelector('.shortcut-item__key')?.textContent?.trim()})()`, 'recording cancelled', settings)
|
||||
check('Escape cancels recording and keeps the original Ctrl+K binding', cancelled, 'Ctrl+K')
|
||||
await read(`document.querySelector('[data-shortcut-id="global-search-open"] .shortcut-item__binding-tooltip-anchor').click()`, settings)
|
||||
await wait(`!!document.querySelector('.shortcut-item--recording')`, 'recording again', settings)
|
||||
await sendKey('a', 'KeyA', 65)
|
||||
const errorHint = await wait(`document.querySelector('.shortcut-item__error-hint')?.textContent?.trim()`, 'edit unsupported hint', settings)
|
||||
check('capturing a key reports that custom bindings are not wired and keeps the default',
|
||||
[errorHint, await read(`document.querySelector('[data-shortcut-id="global-search-open"] .shortcut-item__key')?.textContent?.trim()`, settings)],
|
||||
['自定义键位尚未接入,已保留默认绑定', 'Ctrl+K'])
|
||||
|
||||
await step('shortcuts-dark-theme')
|
||||
await openSettings('appearance')
|
||||
await wait('!!document.querySelector(".appearance-card")', 'appearance panel', settings)
|
||||
await read(`document.querySelector('[aria-label="深色"].appearance-card').click()`, settings)
|
||||
await wait(`document.documentElement.dataset.theme==='dark'`, 'dark theme', settings)
|
||||
await openSettings('keyboardShortcuts')
|
||||
await wait(`document.documentElement.dataset.theme==='dark'&&!!document.querySelector('.keyboard-shortcuts-panel')`, 'dark shortcuts panel', settings)
|
||||
const dark = await read(layoutSource, settings)
|
||||
snapshots.dark = dark
|
||||
check('dark theme keeps every recovered geometry', [near(dark.panel.width, 638), near(dark.panel.height, 620),
|
||||
near(dark.list.height, 572), near(dark.header.height, 39)], [true, true, true, true])
|
||||
check('dark rows use the recovered 31,31,31 surface with light text',
|
||||
[dark.rows.every(row => row.rowBackground === 'rgb(31, 31, 31)'), dark.rows.every(row => row.labelColor === 'rgb(242, 243, 245)')], [true, true])
|
||||
check('dark keycaps use the recovered 8% white fill',
|
||||
dark.rows.every(row => /^color\(srgb 1 1 1 \/ 0\.08\)$/.test(row.keyBackground) || row.keyBackground === 'rgba(255, 255, 255, 0.08)'), true)
|
||||
|
||||
await step('shortcuts-restore-light')
|
||||
await openSettings('appearance')
|
||||
await wait('!!document.querySelector(".appearance-card")', 'appearance panel again', settings)
|
||||
await read(`document.querySelector('[aria-label="浅色"].appearance-card').click()`, settings)
|
||||
await wait(`document.documentElement.dataset.theme==='light'`, 'light theme restored', settings)
|
||||
await openSettings('keyboardShortcuts')
|
||||
const restored = await read(layoutSource, settings)
|
||||
check('selecting 浅色 restores the light sheet', [restored.theme, restored.rows.length, restored.rows[0].rowBackground], ['light', 24, 'rgb(255, 255, 255)'])
|
||||
|
||||
await step('shortcuts-errors')
|
||||
check('no renderer error logs', rendererErrors, [])
|
||||
|
||||
const result = checks.every(item => item.pass) ? 'PASS' : 'FAIL'
|
||||
await fs.writeFile(path.join(directory, 'report.json'), JSON.stringify({ result, phase, checks, snapshots }, null, 2))
|
||||
console.log('SHORTCUTS_SETTINGS_' + result + ' ' + directory)
|
||||
for (const failure of checks.filter(item => !item.pass)) console.log('SHORTCUTS_SETTINGS_FAILURE ' + JSON.stringify(failure))
|
||||
app.exit(result === 'PASS' ? 0 : 1)
|
||||
})().catch(async error => {
|
||||
console.error(error)
|
||||
try { await fs.writeFile(path.join(directory, 'report.json'), JSON.stringify({ result: 'ERROR', phase, checks, snapshots, error: String(error?.stack ?? error) }, null, 2)) } catch { /* ignore */ }
|
||||
app.exit(1)
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user