'use strict'; const assert = require('node:assert'); const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); // 用临时目录隔离,避免污染真实配置。 // 默认数据目录现在是"项目根/data",不再跟 HOME 走 —— 必须用 // CLIPBOARD_APP_DATA_DIR 覆盖,否则测试会写进仓库里的 data/。 const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'clip-cfg-')); process.env.USERPROFILE = tmpHome; process.env.HOME = tmpHome; process.env.CLIPBOARD_APP_DATA_DIR = path.join(tmpHome, 'data'); const cfg = require('../config.js'); let passed = 0; function check(name, fn) { try { fn(); console.log(' ok -', name); passed++; } catch (e) { console.error(' FAIL -', name, '\n', e.message); process.exitCode = 1; } } check('defaults 结构完整', () => { assert.strictEqual(cfg.DEFAULTS.autoLaunch, false); assert.strictEqual(cfg.DEFAULTS.maxItems, 0); assert.strictEqual(cfg.DEFAULTS.displayLimit, 300); assert.strictEqual(cfg.DEFAULTS.pollMs, 1000); assert.strictEqual(cfg.DEFAULTS.hotkey, 'Ctrl+Alt+V'); assert.ok(typeof cfg.DEFAULTS.dataDir === 'string' && cfg.DEFAULTS.dataDir.length > 0); }); check('默认数据目录不是 ~/.clipboard-app,且被 CLIPBOARD_APP_DATA_DIR 覆盖', () => { // 不去硬编码 'D:\\Clipboard Data':那是个环境相关的常量,谁换机就炸。 // 这里要测的是两条契约:1) 不再是 home 下的 .clipboard-app;2) 环境变量能覆盖。 assert.notStrictEqual(path.resolve(cfg.defaultDataDir()), path.resolve(cfg.legacyDataDir())); assert.strictEqual(path.resolve(cfg.defaultDataDir()), path.resolve(tmpHome, 'data')); // config.json 跟着默认目录走 assert.strictEqual(path.dirname(cfg.getConfigPath()), path.resolve(cfg.defaultDataDir())); }); check('默认数据目录:D 盘可用时首选 D 盘', () => { // 默认目录的契约有两个 case:D 盘存在 → 首选 D:\Clipboard Data; // 不存在 → 降级到 home 下的 Clipboard Data。这条只测前者。 const saved = process.env.CLIPBOARD_APP_DATA_DIR; delete process.env.CLIPBOARD_APP_DATA_DIR; delete require.cache[require.resolve('../config.js')]; const cfg2 = require('../config.js'); // D 盘在 CI / 开发机上不一定存在,强行断言会让套件不可移植。 // 这里只断言「D 盘可用时返回 D 盘」:先探测一次,能用才断言。 process.env.CLIPBOARD_APP_DATA_DIR = saved; if (cfg2.isDirUsable('D:\\Clipboard Data')) { assert.strictEqual(path.resolve(cfg2.defaultDataDir()), path.resolve('D:\\Clipboard Data')); } else { // 当前机器 D 盘不可用 → 跳过本断言(fallback case 在 data-dir-fallback.test.js) console.log(' (D:\\Clipboard Data 不可用,跳过 — 见 data-dir-fallback.test.js)'); } }); check('缺文件时 loadConfig 返回全默认', () => { const c = cfg.loadConfig(); assert.strictEqual(c.maxItems, 0); assert.strictEqual(c.displayLimit, 300); assert.strictEqual(c.pollMs, 1000); assert.strictEqual(c.autoLaunch, false); }); check('sanitize 回落非法枚举值', () => { const c = cfg.sanitize({ maxItems: 999, displayLimit: 42, pollMs: 17, autoLaunch: 'yes', dataDir: 42 }); assert.strictEqual(c.maxItems, 0); assert.strictEqual(c.displayLimit, 300); assert.strictEqual(c.pollMs, 1000); assert.strictEqual(c.autoLaunch, false); assert.strictEqual(typeof c.dataDir, 'string'); }); check('sanitize hotkey:非法值回落默认;合法预设保留;空串 = 禁用不算非法', () => { // 自由组合键不认 —— 设置界面只给预设档位,白名单外的一律回落 assert.strictEqual(cfg.sanitize({ hotkey: 'Ctrl+Shift+F13' }).hotkey, 'Ctrl+Alt+V'); assert.strictEqual(cfg.sanitize({ hotkey: 42 }).hotkey, 'Ctrl+Alt+V'); assert.strictEqual(cfg.sanitize({ hotkey: 'Alt+Shift+V' }).hotkey, 'Alt+Shift+V'); // '' 是明确的「禁用」语义,绝不能回落默认 —— 否则禁不掉 assert.strictEqual(cfg.sanitize({ hotkey: '' }).hotkey, ''); // 老配置没有 hotkey 字段(升级场景)→ 默认,等于自动获得快捷键 assert.strictEqual(cfg.sanitize({}).hotkey, 'Ctrl+Alt+V'); // 默认值本身必须在白名单里(否则首次启动的 register 必炸) assert.ok(cfg.ALLOWED_HOTKEYS.includes(cfg.DEFAULTS.hotkey)); // 白名单里的每一个值都得能通过自己(防 ALLOWED_HOTKEYS 与默认值写岔) for (const k of cfg.ALLOWED_HOTKEYS) { assert.strictEqual(cfg.sanitize({ hotkey: k }).hotkey, k, `白名单成员 ${JSON.stringify(k)} 应通过校验`); } }); check('save 后 load 往返一致', () => { cfg.saveConfig({ autoLaunch: true, maxItems: 1000, displayLimit: 500, pollMs: 500, dataDir: tmpHome, hotkey: 'Ctrl+Alt+C' }); const c = cfg.loadConfig(); assert.strictEqual(c.autoLaunch, true); assert.strictEqual(c.maxItems, 1000); assert.strictEqual(c.displayLimit, 500); assert.strictEqual(c.pollMs, 500); assert.strictEqual(c.dataDir, tmpHome); assert.strictEqual(c.hotkey, 'Ctrl+Alt+C'); }); check('坏 JSON 不抛异常,返回默认', () => { fs.writeFileSync(cfg.getConfigPath(), '{ this is not json '); const c = cfg.loadConfig(); assert.strictEqual(c.maxItems, 0); }); console.log(`\n${passed} checks passed`);