'use strict'; // 在真实 Electron 窗口尺寸(420x690,与 main.js openSettingsWindow 一致)下 // 验证设置窗口的标签页与布局。 // 运行:electron test/settings-tabs.test.js const assert = require('node:assert'); const path = require('node:path'); const { app, BrowserWindow, ipcMain } = require('electron'); const cfg = { autoLaunch: false, maxItems: 0, displayLimit: 300, pollMs: 1000, dataDir: 'C:\\Users\\guan\\.clipboard-app', theme: 'linear', accent: 'purple', followSystem: false, }; ipcMain.handle('settings:get', () => ({ ...cfg })); ipcMain.handle('settings:set', () => ({ ...cfg })); ipcMain.handle('settings:clear-history', () => 0); ipcMain.handle('settings:open-dir', () => {}); ipcMain.handle('settings:choose-dir', () => ({ ok: false })); ipcMain.handle('theme:get', () => ({ ...cfg })); ipcMain.handle('theme:set', (_e, { theme }) => ({ theme, accent: cfg.accent })); ipcMain.handle('theme:set-accent', (_e, { accent }) => ({ theme: cfg.theme, accent })); ipcMain.handle('theme:set-follow-system', () => ({ theme: cfg.theme, accent: cfg.accent })); // 与 settings-render.test.js 同理:settings.js 启动时会订阅主题变更, // stub 缺这个 handler 会往终端打一条未捕获错误(此测试不断言 console, // 但留着噪音会掩盖真问题)。 ipcMain.handle('theme:subscribe', () => true); let passed = 0; async function check(name, fn) { try { await fn(); console.log(' ok -', name); passed++; } catch (e) { console.error(' FAIL -', name, '\n', e.message); process.exitCode = 1; } } app.whenReady().then(async () => { const win = new BrowserWindow({ width: 420, height: 690, show: false, frame: false, resizable: false, webPreferences: { preload: path.join(__dirname, '..', 'preload.js'), contextIsolation: true, nodeIntegration: false, sandbox: false, }, }); await win.loadFile(path.join(__dirname, '..', 'renderer', 'settings.html')); await new Promise((r) => setTimeout(r, 900)); const js = (code) => win.webContents.executeJavaScript(code); await check('三个标签存在且文案顺序正确', async () => { const labels = JSON.parse(await js( `JSON.stringify([...document.querySelectorAll('#tab-bar [role="tab"]')].map(t => t.textContent.trim()))` )); assert.deepStrictEqual(labels, ['外观', '通用', '关于']); }); await check('初始只显示「外观」页', async () => { const r = JSON.parse(await js(`JSON.stringify({ visible: [...document.querySelectorAll('.tab-panel')].filter(p => !p.hidden).map(p => p.id), selected: document.querySelector('[role="tab"][aria-selected="true"]').textContent.trim(), })`)); assert.deepStrictEqual(r.visible, ['panel-appearance']); assert.strictEqual(r.selected, '外观'); }); await check('点击「通用」切到通用页并互斥', async () => { const r = JSON.parse(await js(`(() => { document.getElementById('tab-general').click(); return JSON.stringify({ visible: [...document.querySelectorAll('.tab-panel')].filter(p => !p.hidden).map(p => p.id), cur: document.getElementById('tab-general').getAttribute('aria-selected'), prev: document.getElementById('tab-appearance').getAttribute('aria-selected'), }); })()`)); assert.deepStrictEqual(r.visible, ['panel-general']); assert.strictEqual(r.cur, 'true'); assert.strictEqual(r.prev, 'false'); }); await check('→ 键切到下一个标签,← 键回上一个', async () => { const r = JSON.parse(await js(`(() => { const bar = document.getElementById('tab-bar'); const cur = () => document.querySelector('[role="tab"][aria-selected="true"]').textContent.trim(); const key = (k) => bar.dispatchEvent(new KeyboardEvent('keydown', { key: k, bubbles: true })); document.getElementById('tab-appearance').click(); key('ArrowRight'); const right = cur(); key('ArrowLeft'); const back = cur(); return JSON.stringify({ right, back }); })()`)); assert.strictEqual(r.right, '通用'); assert.strictEqual(r.back, '外观'); }); await check('每一页在 690px 窗口内都放得下(不需要滚动)', async () => { const rows = JSON.parse(await js(`JSON.stringify( [...document.querySelectorAll('#tab-bar [role="tab"]')].map(t => { t.click(); const p = document.getElementById(t.getAttribute('aria-controls')); return { id: p.id, clientH: p.clientHeight, scrollH: p.scrollHeight }; }) )`)); for (const r of rows) { assert.ok(r.scrollH <= r.clientH + 1, `${r.id} 内容溢出:${r.scrollH}px > 可视 ${r.clientH}px`); } }); console.log(`\n${passed} checks passed`); app.exit(process.exitCode ? 1 : 0); });