Files
Scoreboard/console/main/autostart.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

108 lines
4.9 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 { AUTOSTART_FILE, resolveAutostartFile, isAutostartEnabled, setAutostart } = require('./autostart')
// 用绝对路径字符串拼测试目录,避免依赖运行平台的盘符写法
const HOME = path.resolve('/home/operator')
function makeRoot(t) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'scoreboard-autostart-'))
t.after(() => fs.rmSync(root, { recursive: true, force: true }))
return root
}
test('自启文件在 $XDG_CONFIG_HOME/autostart 下,缺省用 ~/.config/autostart', () => {
const xdg = path.resolve('/xdg/config')
assert.equal(
resolveAutostartFile({ platform: 'linux', env: { XDG_CONFIG_HOME: xdg } }),
path.join(xdg, 'autostart', AUTOSTART_FILE)
)
assert.equal(
resolveAutostartFile({ platform: 'linux', env: { HOME } }),
path.join(HOME, '.config', 'autostart', AUTOSTART_FILE)
)
})
test('非 Linux 平台没有这个文件(Windows/macOS 走系统登录项)', () => {
assert.equal(resolveAutostartFile({ platform: 'win32', env: { HOME } }), null)
assert.equal(resolveAutostartFile({ platform: 'darwin', env: { HOME } }), null)
assert.throws(
() => setAutostart({ platform: 'win32', env: { HOME }, exePath: '/x/Scoreboard_Console', enabled: true }),
/Linux/
)
})
test('开:写出 .desktop,Exec 指向 Console 自己;关:把文件删掉', (t) => {
const root = makeRoot(t)
const env = { XDG_CONFIG_HOME: root }
const exePath = path.join(root, 'install', 'Scoreboard_Console')
const file = resolveAutostartFile({ platform: 'linux', env })
assert.equal(isAutostartEnabled({ platform: 'linux', env }), false)
assert.equal(fs.existsSync(file), false)
setAutostart({ platform: 'linux', env, exePath, enabled: true })
assert.equal(isAutostartEnabled({ platform: 'linux', env }), true)
const text = fs.readFileSync(file, 'utf8')
assert.match(text, /^\[Desktop Entry\]$/m)
assert.match(text, /^Type=Application$/m)
assert.match(text, /^Exec=.*Scoreboard_Console.*$/m)
assert.match(text, /^X-GNOME-Autostart-enabled=true$/m)
// 重复开一次是幂等的(每次启动 Console 都会按配置重写一遍)
setAutostart({ platform: 'linux', env, exePath, enabled: true })
assert.equal(fs.readFileSync(file, 'utf8'), text)
assert.equal(isAutostartEnabled({ platform: 'linux', env }), true)
setAutostart({ platform: 'linux', env, exePath, enabled: false })
assert.equal(fs.existsSync(file), false)
assert.equal(isAutostartEnabled({ platform: 'linux', env }), false)
// 关两次不报错:现场连点开关、或文件本来就不在(目录也一并没了)
setAutostart({ platform: 'linux', env, exePath, enabled: false })
})
test('安装路径带空格时 Exec 加引号,否则自启会被拆成两个参数', (t) => {
const root = makeRoot(t)
const env = { XDG_CONFIG_HOME: root }
const exePath = path.join(root, 'Scoreboard Console', 'Scoreboard_Console')
setAutostart({ platform: 'linux', env, exePath, enabled: true })
const text = fs.readFileSync(resolveAutostartFile({ platform: 'linux', env }), 'utf8')
assert.match(text, new RegExp(`^Exec="${exePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"$`, 'm'))
})
test('文件被标了 Hidden=true(别的桌面环境关闭自启时的写法)视为关闭', (t) => {
const root = makeRoot(t)
const env = { XDG_CONFIG_HOME: root }
const file = resolveAutostartFile({ platform: 'linux', env })
setAutostart({ platform: 'linux', env, exePath: '/x/Scoreboard_Console', enabled: true })
assert.equal(isAutostartEnabled({ platform: 'linux', env }), true)
// 现场从桌面环境的「启动应用程序」里关掉自启时,各家的写法不一致:
// XDG 规范里 Hidden=true 就是「忽略这个文件」,必须认出来
fs.writeFileSync(file, fs.readFileSync(file, 'utf8').replace('Hidden=false', 'Hidden=true'), 'utf8')
assert.equal(isAutostartEnabled({ platform: 'linux', env }), false)
})
test('拿不到 $XDG_CONFIG_HOME 也拿不到 $HOME 时开关都报错,不静默当成功', () => {
const env = {}
assert.equal(resolveAutostartFile({ platform: 'linux', env }), null)
assert.equal(isAutostartEnabled({ platform: 'linux', env }), false)
assert.throws(() => setAutostart({ platform: 'linux', env, exePath: '/x/Scoreboard_Console', enabled: true }), /XDG_CONFIG_HOME/)
assert.throws(() => setAutostart({ platform: 'linux', env, exePath: '/x/Scoreboard_Console', enabled: false }), /XDG_CONFIG_HOME/)
})
test('拿不到 Console 自身路径时不写一个空 Exec 出去', (t) => {
const root = makeRoot(t)
const env = { XDG_CONFIG_HOME: root }
assert.throws(() => setAutostart({ platform: 'linux', env, exePath: '', enabled: true }), /路径/)
assert.equal(isAutostartEnabled({ platform: 'linux', env }), false)
})