YD-WEB/scripts/battle-layout-test.mjs
ltx 84b7b0f893 feat(website): add competition results and service deployment
- Add the event results page with schedule, rankings, Battle bracket, athletes, records, bilingual labels, and navigation entry.
- Add the read/write competition data service, a complete Excel example, empty public state, and validation smoke tests.
- Handle Furion statusCode/data responses from the production API and retain static fallback for the public page.
- Document Docker sidecar deployment beside test_eventapi, persistent storage, private admin access, Nginx routing, and OSS asset rewriting.

Verification:
- node server/competition-api.smoke.mjs
- node scripts/competition-response-test.mjs
- node scripts/battle-layout-test.mjs
- npm run build -- --base=https://assets.example.test/static/test/
- npm run rewrite:oss -- https://assets.example.test/static/test/
- Production /api/competition/state returned HTTP 200 after the sidecar started.

Deployment note: publish the updated YD-WEB static build before public results can consume the unwrapped API response. The event remains unpublished until an administrator saves and publishes data.
2026-10-20 13:38:23 +08:00

35 lines
1.4 KiB
JavaScript

import assert from 'node:assert/strict';
import { buildBattleLayout } from '../src/data/battle-layout.js';
const battle = {
rounds: [
{ matches: [
{ id: 'R1A', aPlayer: 'A', bPlayer: 'B', winner: 'A' },
{ id: 'R1B', aPlayer: 'C', bPlayer: 'D', winner: 'C' },
{ id: 'R1C', aPlayer: 'E', bPlayer: 'F', winner: 'E' },
{ id: 'R1D', aPlayer: 'G', bPlayer: 'H', winner: 'G' },
] },
{ matches: [
{ id: 'R2A', aPlayer: 'A', bPlayer: 'E', winner: 'A' },
{ id: 'R2B', aPlayer: 'C', bPlayer: 'G', winner: 'G' },
] },
{ matches: [{ id: 'FINAL', aPlayer: 'A', bPlayer: 'G', winner: 'A' }] },
],
};
const layout = buildBattleLayout(battle);
assert.equal(layout.lastRoundIndex, 2);
assert.deepEqual(layout.sources.get('FINAL'), { aFrom: 'R2A', bFrom: 'R2B' });
assert.deepEqual(layout.sources.get('R2A'), { aFrom: 'R1A', bFrom: 'R1C' });
assert.deepEqual([...layout.sides.left].sort(), ['R1A', 'R1C', 'R2A']);
assert.deepEqual([...layout.sides.right].sort(), ['R1B', 'R1D', 'R2B']);
const explicit = buildBattleLayout({ rounds: [
{ matches: [{ id: 'A', winner: '' }, { id: 'B', winner: '' }] },
{ matches: [{ id: 'F', aFrom: 'A', bFrom: 'B' }] },
] });
assert.deepEqual(explicit.sources.get('F'), { aFrom: 'A', bFrom: 'B' });
assert.equal(buildBattleLayout({ rounds: [{ matches: [{ id: 'ONLY' }] }] }).lastRoundIndex, 0);
console.log('Battle 轮次和晋级来源解析通过');