Files
Scoreboard/web/src/admin/tests/base-data-settings.test.ts
T
AskaEth b5f40642ff feat: 公告持久化(底栏常驻)+ 团体可单独报名 + 榜单系数开关
公告:
- 服务端把当前公告落库到 settings(display.announce_text / announce_seconds),
  作为 display.announce 随 snapshot 与 /api/settings 下发(非 admin 快照也保留)
- announce 指令除转发外写库;新增 announce_clear 清空并广播;两者写审计
- realtime 协议登记 announce_clear(补上,使清除能即时推到大屏)
- 大屏把「全屏推送」与「底栏常驻」拆开:推送 seconds 秒后只退出全屏态,公告留在底栏,
  直到被替换或清除;新增公告状态机(可测)与底栏取值优先级
  推送中 > 服务端持久值 > 「暂无公告」;管理端加「清除公告」与当前公告状态

团体报名:
- EntryPicker 团体/对抗模式允许不勾队员直接报名该单位(此前必须勾人,导致
  「只想给单位报个团体项目」做不到、单位下没有运动员时完全报不了)
- 核实录入页团体行:显示/填名次/保存/发布/算分闭环无缺陷(用真实服务端跑过),
  抽出共享的 entryRowLabel 供两处复用

榜单口径:
- 基础数据 → 积分模板页新增「榜单口径(全局)」卡片:individualToTeamWeight
  数字输入(默认 1、0–100),说明「个人项目得分按该系数折算计入单位总分」,
  保存走 PUT /api/settings;因该值在服务端是全局键、模板表里没有此字段,
  故做成独立卡片并标注全局,不塞进模板表单

自检:服务端 11 包全绿;前端 vue-tsc + build、node --test 99/99。
2026-09-10 11:06:58 +08:00

73 lines
2.9 KiB
TypeScript
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.
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { createPinia, setActivePinia } from 'pinia'
import type { EventSettings } from '@/shared'
/**
* 榜单口径(standings.individual_to_team_weight)的保存契约。
*
* adminApi 的 http 实例在模块初始化时就绑定了 `globalThis.fetch`,所以这里先替换 fetch,
* 再用动态 import 拿到 store 与控制器,才能观察到真实请求的 URL / 方法与载荷。
*/
const SETTINGS: EventSettings = {
event: { name: '测试赛事', subtitle: null, host: null, logoPath: null, primaryColor: null },
display: { background: null, logo: null, title: '测试赛事', subtitle: null, primaryColor: null, rotateSeconds: 15 },
standings: {
individualToTeamWeight: 1,
rankingMode: 'points',
showIndividualBoard: true,
showIndividualMedals: true,
},
defaultProfileId: null,
requireApproval: false,
}
interface Captured {
url: string
method: string
body: Record<string, unknown> | null
}
function stubFetch(weight: number): Captured {
const captured: Captured = { url: '', method: '', body: null }
globalThis.fetch = (async (input: unknown, init?: RequestInit) => {
captured.url = String(input)
captured.method = init?.method ?? 'GET'
captured.body = init?.body === undefined ? null : (JSON.parse(String(init.body)) as Record<string, unknown>)
const data: EventSettings = { ...SETTINGS, standings: { ...SETTINGS.standings, individualToTeamWeight: weight } }
return new Response(JSON.stringify({ ok: true, data }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
})
}) as typeof fetch
return captured
}
test('保存榜单口径 PUT /api/settings,提交完整 standings 并回写 event store', async () => {
const captured = stubFetch(0.5)
const { useEventStore } = await import('../stores/event')
const { useBaseData } = await import('../components/baseData/useBaseData')
setActivePinia(createPinia())
const event = useEventStore()
event.settings = { ...SETTINGS }
const data = useBaseData()
const saved = await data.saveIndividualToTeamWeight(0.5)
assert.equal(captured.url, '/api/settings')
assert.equal(captured.method, 'PUT')
const standings = captured.body?.standings as Record<string, unknown>
assert.equal(standings.individualToTeamWeight, 0.5)
// 只提交一个字段会把其余口径留在服务端旧值上,所以整个 standings 子对象原样回传。
assert.equal(standings.rankingMode, 'points')
assert.equal(standings.showIndividualBoard, true)
assert.equal(standings.showIndividualMedals, true)
assert.equal(saved.standings.individualToTeamWeight, 0.5)
// 成绩录入页与积分预览直接读 event store 的设置,不回写就等于「保存成功但预览没变」。
assert.equal(event.settings?.standings.individualToTeamWeight, 0.5)
assert.equal(data.settings.value?.standings.individualToTeamWeight, 0.5)
})