1) 大屏背景图不生效:App.vue 里 .bg 与 .bg-fallback 都是 absolute inset:0, 而兜底渐变是不透明的且在图片之后绘制 → 配了背景图也只看得到渐变。 改成 v-if/v-else 互斥(用无头 Chromium 真渲染对照验证:修复后取到图片像素, 回退版本只剩渐变)。新增 background-layer.test.ts 锁住层次关系。 2) 规则/config 仍在安装目录,覆盖安装/卸载会连配置一起删:现在三样都落在 %LOCALAPPDATA%\ScoreboardConsole\(与数据目录同一基座): - console\config.json(Console 读写都用这份) - config.yaml(Console 用绝对路径 -config 传给服务端与 passwd) - rules\(首次从安装目录复制过去,之后只认用户目录那份) 首次运行复制迁移、已存在不覆盖、不可写则回落安装目录并记日志(不影响启动); 开发态行为不变。备份脚本与文档同步。 注意 resolveExecutable 必须显式传部署包根目录,否则会去用户目录找 scoreboard.exe。 自检:服务端 11 包全绿;console 语法 16 文件 + node:test 20/20; 前端 vue-tsc + build、node --test 69/69。
200 lines
9.1 KiB
JavaScript
200 lines
9.1 KiB
JavaScript
'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 {
|
||
resolveUserRoot,
|
||
planUserPaths,
|
||
prepareUserPaths,
|
||
withConfigArg
|
||
} = require('./userpaths')
|
||
|
||
// 用绝对路径字符串拼测试目录,避免依赖运行平台的盘符写法
|
||
const INSTALL = path.resolve('/scoreboard-install')
|
||
const LOCAL = path.resolve('/localappdata')
|
||
const USER_ROOT = path.join(LOCAL, 'ScoreboardConsole')
|
||
|
||
function makeRoot(t) {
|
||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'scoreboard-userpaths-'))
|
||
t.after(() => fs.rmSync(root, { recursive: true, force: true }))
|
||
return root
|
||
}
|
||
|
||
function writeFile(file, text) {
|
||
fs.mkdirSync(path.dirname(file), { recursive: true })
|
||
fs.writeFileSync(file, text)
|
||
}
|
||
|
||
function readFile(file) {
|
||
return fs.readFileSync(file, 'utf8')
|
||
}
|
||
|
||
// 打包态 + 有 %LOCALAPPDATA% 是「接管」的唯一组合
|
||
function planFor({ installDir = INSTALL, env = { LOCALAPPDATA: LOCAL }, isPackaged = true } = {}) {
|
||
return planUserPaths({ installDir, userRoot: resolveUserRoot(env), isPackaged })
|
||
}
|
||
|
||
test('打包态三项都落到用户根下,与数据目录同一基座', () => {
|
||
const plan = planFor()
|
||
assert.equal(plan.mode, 'user')
|
||
assert.equal(plan.root, USER_ROOT)
|
||
assert.equal(plan.items.consoleConfig.target, path.join(USER_ROOT, 'console', 'config.json'))
|
||
assert.equal(plan.items.serverConfig.target, path.join(USER_ROOT, 'config.yaml'))
|
||
assert.equal(plan.items.rulesDir.target, path.join(USER_ROOT, 'rules'))
|
||
})
|
||
|
||
test('开发态不接管,三项都指向安装目录里随包的那份', () => {
|
||
const plan = planFor({ isPackaged: false })
|
||
assert.equal(plan.mode, 'install')
|
||
assert.equal(plan.reason, 'dev')
|
||
assert.equal(plan.items.consoleConfig.target, path.join(INSTALL, 'config.json'))
|
||
assert.equal(plan.items.serverConfig.target, path.join(INSTALL, 'config.yaml'))
|
||
assert.equal(plan.items.rulesDir.target, path.join(INSTALL, 'rules'))
|
||
})
|
||
|
||
test('拿不到 %LOCALAPPDATA% 时回落安装目录并记日志', () => {
|
||
const plan = planFor({ env: {} })
|
||
assert.equal(plan.mode, 'install')
|
||
assert.equal(plan.reason, 'no-localappdata')
|
||
|
||
const lines = []
|
||
const paths = prepareUserPaths({ plan, log: (line) => lines.push(line) })
|
||
assert.equal(paths.consoleConfig, path.join(INSTALL, 'config.json'))
|
||
assert.equal(paths.rulesDir, path.join(INSTALL, 'rules'))
|
||
assert.equal(paths.serverConfig, null)
|
||
assert.match(lines.join('\n'), /拿不到 %LOCALAPPDATA%/)
|
||
})
|
||
|
||
test('首次运行把随包的三项复制过去,安装目录那份留作模板', (t) => {
|
||
const root = makeRoot(t)
|
||
const installDir = path.join(root, 'install')
|
||
const userRoot = path.join(root, 'localappdata', 'ScoreboardConsole')
|
||
writeFile(path.join(installDir, 'config.json'), '{"server":{"port":19541}}')
|
||
writeFile(path.join(installDir, 'config.yaml'), 'server:\n port: 19541\n')
|
||
writeFile(path.join(installDir, 'rules', 'cup.yaml'), 'format: scoreboard-rules\n')
|
||
writeFile(path.join(installDir, 'rules', 'README.md'), '说明\n')
|
||
|
||
const lines = []
|
||
const paths = prepareUserPaths({
|
||
plan: planUserPaths({ installDir, userRoot, isPackaged: true }),
|
||
log: (line) => lines.push(line)
|
||
})
|
||
|
||
assert.equal(paths.consoleConfig, path.join(userRoot, 'console', 'config.json'))
|
||
assert.equal(paths.serverConfig, path.join(userRoot, 'config.yaml'))
|
||
assert.equal(paths.rulesDir, path.join(userRoot, 'rules'))
|
||
assert.equal(readFile(paths.consoleConfig), '{"server":{"port":19541}}')
|
||
assert.equal(readFile(paths.serverConfig), 'server:\n port: 19541\n')
|
||
assert.equal(readFile(path.join(userRoot, 'rules', 'cup.yaml')), 'format: scoreboard-rules\n')
|
||
assert.equal(readFile(path.join(userRoot, 'rules', 'README.md')), '说明\n')
|
||
// 复制不是移动:安装目录那三份必须原样留着当模板
|
||
assert.equal(readFile(path.join(installDir, 'config.json')), '{"server":{"port":19541}}')
|
||
assert.equal(readFile(path.join(installDir, 'config.yaml')), 'server:\n port: 19541\n')
|
||
assert.equal(fs.existsSync(path.join(installDir, 'rules', 'cup.yaml')), true)
|
||
assert.equal(lines.filter((line) => line.startsWith('已把 ')).length, 3)
|
||
})
|
||
|
||
test('用户目录已有那份就一律用它,不覆盖也不重拷', (t) => {
|
||
const root = makeRoot(t)
|
||
const installDir = path.join(root, 'install')
|
||
const userRoot = path.join(root, 'localappdata', 'ScoreboardConsole')
|
||
writeFile(path.join(installDir, 'config.json'), '{"server":{"port":1111}}')
|
||
writeFile(path.join(installDir, 'config.yaml'), 'server:\n port: 1111\n')
|
||
writeFile(path.join(installDir, 'rules', 'cup.yaml'), 'template\n')
|
||
writeFile(path.join(userRoot, 'console', 'config.json'), '{"server":{"port":2222}}')
|
||
writeFile(path.join(userRoot, 'config.yaml'), 'server:\n port: 2222\n')
|
||
writeFile(path.join(userRoot, 'rules', 'mine.yaml'), 'mine\n')
|
||
|
||
const lines = []
|
||
const paths = prepareUserPaths({
|
||
plan: planUserPaths({ installDir, userRoot, isPackaged: true }),
|
||
log: (line) => lines.push(line)
|
||
})
|
||
|
||
assert.equal(readFile(paths.consoleConfig), '{"server":{"port":2222}}')
|
||
assert.equal(readFile(paths.serverConfig), 'server:\n port: 2222\n')
|
||
assert.equal(readFile(path.join(userRoot, 'rules', 'mine.yaml')), 'mine\n')
|
||
// 用户目录里的 rules\ 已存在(哪怕内容和模板不同)→ 不补拷模板里的 cup.yaml
|
||
assert.equal(fs.existsSync(path.join(userRoot, 'rules', 'cup.yaml')), false)
|
||
assert.equal(lines.filter((line) => line.startsWith('已把 ')).length, 0)
|
||
})
|
||
|
||
test('用户目录写不进去时记日志并回落到安装目录,不抛异常', (t) => {
|
||
const root = makeRoot(t)
|
||
const installDir = path.join(root, 'install')
|
||
writeFile(path.join(installDir, 'config.json'), '{}')
|
||
writeFile(path.join(installDir, 'config.yaml'), 'server: {}\n')
|
||
writeFile(path.join(installDir, 'rules', 'cup.yaml'), 'template\n')
|
||
// 用户根的位置被一个文件占着:mkdir/rename 必然失败(ENOTDIR)
|
||
const userRoot = path.join(root, 'blocker')
|
||
writeFile(userRoot, 'not a dir')
|
||
|
||
const lines = []
|
||
const paths = prepareUserPaths({
|
||
plan: planUserPaths({ installDir, userRoot, isPackaged: true }),
|
||
log: (line) => lines.push(line)
|
||
})
|
||
|
||
assert.equal(paths.consoleConfig, path.join(installDir, 'config.json'))
|
||
assert.equal(paths.serverConfig, null)
|
||
assert.equal(paths.rulesDir, path.join(installDir, 'rules'))
|
||
assert.equal(readFile(paths.consoleConfig), '{}')
|
||
assert.equal(readFile(path.join(installDir, 'rules', 'cup.yaml')), 'template\n')
|
||
assert.equal(lines.filter((line) => line.includes('继续用安装目录里的')).length, 3)
|
||
})
|
||
|
||
test('两边都没有的那一份跳过复制,路径仍指向用户目录', (t) => {
|
||
const root = makeRoot(t)
|
||
const installDir = path.join(root, 'install')
|
||
const userRoot = path.join(root, 'localappdata', 'ScoreboardConsole')
|
||
fs.mkdirSync(installDir, { recursive: true })
|
||
|
||
const lines = []
|
||
const paths = prepareUserPaths({
|
||
plan: planUserPaths({ installDir, userRoot, isPackaged: true }),
|
||
log: (line) => lines.push(line)
|
||
})
|
||
|
||
// 不接管就没人去读用户目录,服务端会按 config.Load 的默认值跑
|
||
assert.equal(paths.serverConfig, path.join(userRoot, 'config.yaml'))
|
||
assert.equal(fs.existsSync(paths.serverConfig), false)
|
||
assert.equal(lines.filter((line) => line.includes('本次跳过复制')).length, 3)
|
||
})
|
||
|
||
test('规则目录拷贝到位后不留临时目录,重复运行不重做', (t) => {
|
||
const root = makeRoot(t)
|
||
const installDir = path.join(root, 'install')
|
||
const userRoot = path.join(root, 'localappdata', 'ScoreboardConsole')
|
||
writeFile(path.join(installDir, 'config.json'), '{}\n')
|
||
writeFile(path.join(installDir, 'config.yaml'), 'server: {}\n')
|
||
writeFile(path.join(installDir, 'rules', 'cup.yaml'), 'template\n')
|
||
const plan = planUserPaths({ installDir, userRoot, isPackaged: true })
|
||
|
||
const first = prepareUserPaths({ plan, log: () => {} })
|
||
assert.deepEqual(fs.readdirSync(userRoot).sort(), ['config.yaml', 'console', 'rules'])
|
||
assert.deepEqual(fs.readdirSync(first.rulesDir), ['cup.yaml'])
|
||
|
||
// 第二次启动:目标都在了,不再复制(用户改过的内容不会被模板顶掉)
|
||
writeFile(path.join(userRoot, 'config.yaml'), 'server:\n port: 3333\n')
|
||
const second = prepareUserPaths({ plan, log: () => {} })
|
||
assert.deepEqual(second, first)
|
||
assert.equal(readFile(path.join(userRoot, 'config.yaml')), 'server:\n port: 3333\n')
|
||
})
|
||
|
||
test('-config 的值换成解析出的绝对路径,其余参数原样保留', () => {
|
||
assert.deepEqual(withConfigArg(['-config', 'config.yaml'], '/u/config.yaml'), ['-config', '/u/config.yaml'])
|
||
assert.deepEqual(
|
||
withConfigArg(['-config', 'config.yaml', '-project', 'cup'], '/u/config.yaml'),
|
||
['-config', '/u/config.yaml', '-project', 'cup']
|
||
)
|
||
// 参数里没写 -config 就补一条,否则用户目录里那份永远不会被读到
|
||
assert.deepEqual(withConfigArg(['-project', 'cup'], '/u/config.yaml'), ['-project', 'cup', '-config', '/u/config.yaml'])
|
||
// 不接管(开发态)原样返回
|
||
assert.deepEqual(withConfigArg(['-config', 'config.yaml'], null), ['-config', 'config.yaml'])
|
||
assert.deepEqual(withConfigArg(null, '/u/config.yaml'), ['-config', '/u/config.yaml'])
|
||
})
|