'use strict'; /** * 「跟随系统」开 / 关 时主题显示一致性的接线测试。 * * 历史问题:theme:get / applyThemeChange 旧实现返回的是 appConfig.theme(raw), * 渲染端拿到后直接设 data-theme ——「跟随系统」开着的状态下,初次打开设置窗口 * 显示的是 raw 主题(比如 midnight),只有等到系统主题切换广播来才换。 * settings 窗口根本收不到那条广播(没切换),于是永远显示错的。 * * 修法:main.js 返回 effectiveTheme(带 rawTheme 字段兜底),渲染端按 * effectiveTheme 设 DOM。theme-boot 也读 effectiveTheme 以避免冷启动闪。 * * 这里钉住三件事,避免后续改动把 effective 字段删了回退到 raw: * 1. main.js 的 theme:get / applyThemeChange / broadcastTheme 三个出口 * 都给 effectiveTheme 字段; * 2. 渲染端(renderer + settings)优先用 effectiveTheme 设置 data-theme; * 3. theme-boot.js 在缓存命中时也用 effectiveTheme。 */ const assert = require('node:assert'); const fs = require('node:fs'); const path = require('node:path'); const root = path.join(__dirname, '..'); const read = (p) => fs.readFileSync(path.join(root, p), 'utf8'); const mainSrc = read('main.js'); const rendererSrc = read('renderer/renderer.js'); const settingsSrc = read('renderer/settings.js'); const themeBootSrc = read('renderer/theme-boot.js'); let passed = 0; function check(name, fn) { try { fn(); console.log(' ok -', name); passed++; } catch (e) { console.error(' FAIL -', name, e.message); process.exitCode = 1; } } // ---------- 主进程:所有主题出口都带 effectiveTheme ---------- check('config.effectiveTheme 存在并被 main.js 引用', () => { assert.match(mainSrc, /configMod\.effectiveTheme\s*\(/, 'main.js 没用 configMod.effectiveTheme —— 跟随系统仍可能在 IPC 出口回退到 raw theme'); }); check('broadcastTheme 广播 effectiveTheme 字段', () => { const fn = mainSrc.match(/function\s+broadcastTheme\s*\([\s\S]*?\n\}/); assert.ok(fn, '找不到 broadcastTheme'); assert.match(fn[0], /effectiveTheme\s*:/, 'broadcastTheme 没带 effectiveTheme 字段,订阅的窗口拿不到跟随系统状态'); }); check('applyThemeChange 返回值带 effectiveTheme', () => { const fn = mainSrc.match(/function\s+applyThemeChange\s*\([\s\S]*?\n\}/); assert.ok(fn, '找不到 applyThemeChange'); assert.match(fn[0], /effectiveTheme\s*:/, 'applyThemeChange 返回值没带 effectiveTheme —— settings.js 收到 raw theme 会盖掉刚广播的 effective'); }); check('theme:get 处理器返回 effectiveTheme', () => { const handler = mainSrc.match(/ipcMain\.handle\(\s*'theme:get'[\s\S]*?\n\s{2}\}\s*\)\s*;/); assert.ok(handler, '找不到 theme:get 处理器'); assert.match(handler[0], /effectiveTheme/, 'theme:get 返回值没带 effectiveTheme —— 启动时设置窗口 / 主窗口会显示 raw 主题'); }); // ---------- 渲染端:用 effectiveTheme 设 data-theme ---------- check('renderer.js 的 applyTheme 用 effectiveTheme 设 data-theme', () => { const fn = rendererSrc.match(/async\s+function\s+applyTheme\s*\([\s\S]*?\n\}/); assert.ok(fn, '找不到 applyTheme'); // 必须显式读 cfg.effectiveTheme 而不是 cfg.theme assert.match(fn[0], /cfg\.effectiveTheme\s*\|\|\s*cfg\.theme/, 'applyTheme 没读 effectiveTheme ——「跟随系统」开时 data-theme 是 raw 主题'); }); check('renderer.js 的 applyTheme 在 localStorage 里同时缓存 raw 和 effective', () => { // theme-boot 启动读 cache,需要 effectiveTheme 才能避免冷启动闪 const fn = rendererSrc.match(/async\s+function\s+applyTheme\s*\([\s\S]*?\n\}/); assert.ok(fn, '找不到 applyTheme'); assert.match(fn[0], /rawTheme[\s\S]*?effectiveTheme/, 'applyTheme 没把 raw 和 effective 同时写 localStorage —— 启动会有闪'); }); check('renderer.js 的 onSystemChanged 不再写 localStorage(避免覆盖 raw)', () => { const fn = rendererSrc.match(/onSystemChanged\(\s*\(payload\)\s*=>\s*\{[\s\S]*?\}\s*\)/); assert.ok(fn, '找不到 onSystemChanged 回调'); // 注释也明确写了为什么不写:payload 里没 rawTheme assert.ok( !/localStorage\.setItem/.test(fn[0]), 'onSystemChanged 在写 localStorage —— payload 没有 rawTheme,下次启动会拿不到 raw' ); // DOM 更新仍然要用 effective assert.match(fn[0], /effectiveTheme\s*\|\|\s*payload\.theme/, 'onSystemChanged 没读 effectiveTheme'); }); check('settings.js initTheme 用 effectiveTheme', () => { const fn = settingsSrc.match(/async\s+function\s+initTheme\s*\([\s\S]*?\n\}/); assert.ok(fn, '找不到 initTheme'); assert.match(fn[0], /cfg\.effectiveTheme\s*\|\|\s*cfg\.theme/, 'initTheme 没读 effectiveTheme —— 设置窗口初次打开「跟随系统」开时显示 raw'); }); check('settings.js onSystemChanged 用 effectiveTheme', () => { const fn = settingsSrc.match(/onSystemChanged\(\s*\(payload\)\s*=>\s*\{[\s\S]*?\}\s*\)/); assert.ok(fn, '找不到 settings.js onSystemChanged'); assert.match(fn[0], /effectiveTheme\s*\|\|\s*payload\.theme/, 'settings.js onSystemChanged 没读 effectiveTheme'); }); // ---------- theme-boot:冷启动也用 effectiveTheme 避免闪 ---------- check('theme-boot.js 优先读 effectiveTheme,避免「跟随系统」开时冷启动闪', () => { assert.match(themeBootSrc, /effectiveTheme\s*\|\|\s*cached\.theme/, 'theme-boot.js 没读 effectiveTheme —— 冷启动在「跟随系统」开时会闪一下 raw 主题'); }); console.log(`\n${passed} checks passed`);