Files
Scoreboard/console/main/config.test.js
T
AskaEth efa049c827 feat(platform): Console 支持 Linux(arm64),Windows 行为逐字不变;版本 dev_0.5.10
需求:让积分榜能在飞腾 D2000(ARM64)的 Linux 上跑起来,**同时不破坏 Windows**。

## 一、Go 服务端:交叉编译即可(**已实测**)
- SQLite 用的是 `modernc.org/sqlite`(纯 Go 驱动),全项目**没有一处 `import "C"`** → `CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build` 直接出;
- 产物是**静态链接**(`ELF 64-bit LSB executable, ARM aarch64, statically linked`)→ **不依赖 glibc 版本**,对国产系统友好;
- 本机(arm64)**实际启动验证**:数据目录、局域网地址探测、数据库迁移(0001/0002/0003)全部正常;
- 产物:`debug/服务端-arm64/scoreboard`(29 MB,`-s -w`)。

## 二、Console(Electron 宿主):平台差异收口到一个新文件
- **新增 `console/main/platform.js`**:平台差异集中在这 45 行里,其余代码不写平台判断。
  ⚠️ **`platform` 参数缺省按 Windows 解析** —— 漏传参数走的就是改动前那条老路(这比"改了再小心"更保守:**默认行为不变,新行为必须显式传参**)。
- `userpaths.js` / `datadir.js`:Windows 分支 `%LOCALAPPDATA%\ScoreboardConsole\` **逐字保留**;Linux 分支按 XDG(`$XDG_DATA_HOME`,缺省 `~/.local/share`)。两平台各自的"拿不到用户目录"提示文案分开写(免得现场去设一个本平台不存在的环境变量)。
- `config.js`:服务端文件名按平台取(Windows `scoreboard.exe` / Linux `scoreboard`)。
- **新增 `console/main/autostart.js`**:Linux 用 `~/.config/autostart/*.desktop`(开=写文件、关=删文件;Linux 没有"已注册但未启用"这个中间态)。
- 托盘与窗口行为:Linux 的通知区多数不派发 double-click,因此按平台分支成单击。

## 三、怎么保证没弄坏 Windows
1. **设计上**:平台差异集中 + 缺省值就是 Windows(见上);
2. **逐行核对 diff**:Windows 分支那几行原样未动;
3. **测试**:Console 由 **68 → 96 例**,新增全是平台分支用例,**既有断言一条没改**;
4. **真机验证**:构建机重打 Windows 包并跑(这一道才算收口)。

## 四、自检
全量自检两轮**全绿、`EXIT=0`**:服务端 14 包 / 前端 **944 例** / Console **96 例**。

## 版本
`dev_0.5.9` → **`dev_0.5.10`**;安装器素材同步重生成(照例只动 `brand.bmp` 两张)。
2026-09-15 00:53:16 +08:00

117 lines
5.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict'
const assert = require('node:assert/strict')
const fs = require('node:fs')
const os = require('node:os')
const path = require('node:path')
const test = require('node:test')
const { DEFAULTS, loadConfig } = require('./config')
function makeDir(t) {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'scoreboard-config-'))
t.after(() => fs.rmSync(dir, { recursive: true, force: true }))
return dir
}
// 现场机器上那份用户配置:内容原样写进文件,读的也必须是文件里真实写了什么
// platform 与 env 一样是入参,缺省按 Windows 解析(改动前的既有口径)
function loadWith(t, raw, platform = 'win32') {
const dir = makeDir(t)
const file = path.join(dir, 'config.json')
fs.writeFileSync(file, typeof raw === 'string' ? raw : JSON.stringify(raw, null, 2), 'utf8')
return loadConfig(file, platform)
}
test('缺省的 allowClickThrough 不穿透(默认值就是 false)', (t) => {
assert.equal(DEFAULTS.display.allowClickThrough, false)
const cfg = loadWith(t, { display: {} })
assert.equal(cfg.values.display.allowClickThrough, false)
assert.deepEqual(cfg.notices, [])
})
test('显式写 allowClickThrough: true 才穿透,且不产生废弃提示', (t) => {
const cfg = loadWith(t, { display: { allowClickThrough: true } })
assert.equal(cfg.values.display.allowClickThrough, true)
assert.deepEqual(cfg.notices, [])
})
// 本次修复的核心回归钉子:老机器升级后,用户配置文件里那个旧版模板写下的
// ignoreMouse: true 必须彻底失效(userpaths.js 不会覆盖用户文件,只能靠不读旧键)
test('老配置里的旧键 ignoreMouse 已失效:既不生效、也不再是穿透开关', (t) => {
const cfg = loadWith(t, { display: { ignoreMouse: true } })
assert.equal('ignoreMouse' in DEFAULTS.display, false, 'DEFAULTS 里不能再有旧键')
assert.equal(cfg.values.display.allowClickThrough, false, '旧键 true 不能把穿透带回来')
assert.equal(cfg.values.display.ignoreMouse, undefined, '旧键不该被 merge 进最终配置')
// 但必须留下一条一次性提示,让现场知道该改哪个键
assert.equal(cfg.notices.length, 1)
const [notice] = cfg.notices
assert.match(notice, /display\.ignoreMouse/)
assert.match(notice, /display\.allowClickThrough/)
assert.match(notice, /废弃/, '要明说这个键已经不用了')
assert.match(notice, /不穿透/, '要明说现在的默认是不穿透')
assert.match(notice, /true/, '要给出「确实想穿透」的写法')
})
test('旧键提示只跟着「文件里写了旧键」出现,缺省或新键都不提示', (t) => {
// 旧版模板整份配置(现场最常见的形态):只提示一次,不会按重排/每帧刷
const legacyTemplate = loadWith(t, {
display: { selection: 'all-secondary', kiosk: true, ignoreMouse: true }
})
assert.equal(legacyTemplate.notices.length, 1)
// 新模板(已去掉旧键、写上 new key)不再提示
const currentTemplate = loadWith(t, {
display: { selection: 'all-secondary', kiosk: true, allowClickThrough: false }
})
assert.deepEqual(currentTemplate.notices, [])
// 两个键都在:仍然按「有旧键」提示一次(提示说的是旧键不生效,与新键无关)
const both = loadWith(t, { display: { ignoreMouse: true, allowClickThrough: true } })
assert.equal(both.notices.length, 1)
assert.equal(both.values.display.allowClickThrough, true, '新键显式 true 仍然算数')
})
test('配置文件读不出来/内容不是对象时不报废弃键、也不炸', (t) => {
const missing = loadConfig(path.join(makeDir(t), 'nope.json'))
assert.deepEqual(missing.notices, [])
assert.equal(missing.warning !== null, true)
assert.equal(missing.values.display.allowClickThrough, false)
const broken = loadWith(t, '{ 这不是 JSON')
assert.deepEqual(broken.notices, [])
assert.deepEqual(loadWith(t, 'null').notices, [])
assert.deepEqual(loadWith(t, '[1,2,3]').notices, [])
})
// 服务端可执行文件名按平台取:Windows 是 scoreboard.exe,Linux(UOS / 麒麟)上没有 .exe。
// 内置默认值保持 Windows 那个名字不变(改动前的口径),按平台换值只发生在 loadConfig 里。
test('Windows 下不写 exe 时默认仍是 scoreboard.exe', (t) => {
assert.equal(loadWith(t, { server: {} }).values.server.exe, 'scoreboard.exe')
assert.equal(DEFAULTS.server.exe, 'scoreboard.exe')
})
test('Linux 下不写 exe 时默认是 scoreboard', (t) => {
assert.equal(loadWith(t, { server: {} }, 'linux').values.server.exe, 'scoreboard')
})
test('Linux 下配置文件里写着模板留下的 scoreboard.exe 时按 scoreboard 走', (t) => {
// 随包 config.json 模板与老机器的用户配置里都是 scoreboard.exe。那不是现场改过的值,
// 而是模板写进去的默认值;Linux 上照它去找一个 .exe 只会「未找到服务端程序」。
// 与 RENAMED_KEYS 同一个道理:模板落进老文件里的值,光改默认值救不了。
assert.equal(loadWith(t, { server: { exe: 'scoreboard.exe' } }, 'linux').values.server.exe, 'scoreboard')
})
test('Windows 下文件里写着别的名字就照用(两边都不做猜测)', (t) => {
assert.equal(loadWith(t, { server: { exe: 'my-scoreboard.exe' } }).values.server.exe, 'my-scoreboard.exe')
assert.equal(loadWith(t, { server: { exe: 'my-scoreboard' } }, 'linux').values.server.exe, 'my-scoreboard')
})
test('server 段被写成 null 时 loadConfig 不炸(与改动前一致)', (t) => {
const cfg = loadWith(t, { server: null }, 'linux')
assert.equal(cfg.values.server, null)
})