102 lines
4.1 KiB
TypeScript
102 lines
4.1 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
import { test } from 'node:test'
|
|
import { Controller } from './controller.js'
|
|
import { parsePlan } from './screen-plan.js'
|
|
import { parseRemote, applyRemote } from './remote-state.js'
|
|
import { parseTeams } from './teams.js'
|
|
import { buildScreenPackage, parseScreenPackage } from './screen-package.js'
|
|
import { changeTimerType, validQR } from '../src/utils/presentation.js'
|
|
|
|
test('QR validation and timer captions preserve custom text', () => {
|
|
for (const url of ['', 'javascript:alert(1)', 'https:///missing', 'https://u:p@example.org'])
|
|
assert.equal(validQR(url), false)
|
|
assert.equal(validQR('https://example.org/?q=1#join'), true)
|
|
assert.equal(validQR('https://example.org', 'я'.repeat(5001)), false)
|
|
assert.equal(changeTimerType('Перерыв', 0, 1), 'До начала игры')
|
|
assert.equal(changeTimerType('До начала игры', 1, 2), 'До конца игры')
|
|
assert.equal(changeTimerType('Обсуждение', 0, 2), 'Обсуждение')
|
|
assert.equal(changeTimerType('', 2, 0), 'Перерыв')
|
|
const c = new Controller(() => {})
|
|
c.command({ session: c.state.session, sequence: 1, action: 'timer-set', timerType: 1, label: '', durationSeconds: 60 })
|
|
assert.equal(c.state.timer.label, 'До начала игры')
|
|
})
|
|
|
|
test('new screens survive plan/package round trip and local playback', () => {
|
|
const plan = parsePlan({
|
|
steps: [
|
|
{ mode: 'SCREEN_MODE_QR', qrUrl: 'https://example.org/join', text: 'Вступайте' },
|
|
{ mode: 'SCREEN_MODE_TEAMS' },
|
|
{ mode: 7, timerType: 'SCREEN_TIMER_TYPE_UNTIL_END', timerDurationSeconds: 60 },
|
|
],
|
|
})
|
|
assert.equal(plan.steps[2]!.timerLabel, 'До конца игры')
|
|
const pkg = buildScreenPackage('https://example.org', 1, {
|
|
game: { game: { ID: 1, name: 'Игра' } },
|
|
plan: { plan },
|
|
music: {},
|
|
})
|
|
assert.deepEqual(parseScreenPackage(pkg).steps, pkg.steps)
|
|
const c = new Controller(() => {})
|
|
c.setPlan(plan.steps)
|
|
const command = (index: number) =>
|
|
c.command({ session: c.state.session, sequence: c.state.sequence + 1, action: 'step', index })
|
|
command(0)
|
|
assert.equal(c.state.scene.qrUrl, 'https://example.org/join')
|
|
assert.equal(c.state.scene.text, 'Вступайте')
|
|
command(1)
|
|
assert.equal(c.state.scene.mode, 10)
|
|
command(2)
|
|
assert.equal(c.state.timer.timerType, 2)
|
|
assert.equal(c.state.timer.status, 'stopped')
|
|
assert.throws(() => parsePlan({ steps: [{ mode: 9, qrUrl: 'javascript:alert(1)' }] }))
|
|
assert.throws(() => parsePlan({ steps: [{ mode: 7, timerDurationSeconds: 60, timerType: 3 }] }))
|
|
const legacy = parsePlan({
|
|
steps: [{ mode: 7, timerLabel: 'Мой текст', timerDurationSeconds: 60 }],
|
|
})
|
|
assert.equal(legacy.steps[0]!.timerType ?? 0, 0)
|
|
assert.equal(legacy.steps[0]!.timerLabel, 'Мой текст')
|
|
})
|
|
|
|
test('remote QR changes and timer types reach the output controller', () => {
|
|
const c = new Controller(() => {})
|
|
const previous = parseRemote({ mode: 1, stateVersion: '1' })
|
|
const qr = parseRemote({
|
|
mode: 9,
|
|
stateVersion: '2',
|
|
qrUrl: 'https://example.org',
|
|
text: 'Текст',
|
|
})
|
|
applyRemote(c, previous, qr)
|
|
assert.equal(c.state.scene.qrUrl, qr.qrUrl)
|
|
const timer = parseRemote({
|
|
mode: 7,
|
|
stateVersion: '3',
|
|
timerType: 'SCREEN_TIMER_TYPE_BEFORE_GAME',
|
|
})
|
|
applyRemote(c, qr, timer)
|
|
assert.equal(c.state.timer.timerType, 1)
|
|
})
|
|
|
|
test('team snapshots sort names, strip private fields and reject another game', () => {
|
|
const packageKey = 'a'.repeat(64)
|
|
const snapshot = {
|
|
packageKey,
|
|
capturedAt: '2026-10-23T12:00:00Z',
|
|
teams: [
|
|
{ id: '2', name: 'Ястребы', password: 'SECRET' },
|
|
{ id: '1', name: 'Агенты' },
|
|
],
|
|
}
|
|
const parsed = parseTeams(snapshot, packageKey)
|
|
assert.deepEqual(
|
|
parsed.teams.map((team) => team.name),
|
|
['Агенты', 'Ястребы'],
|
|
)
|
|
assert.equal(JSON.stringify(parsed).includes('SECRET'), false)
|
|
assert.throws(() => parseTeams(snapshot, 'b'.repeat(64)))
|
|
assert.throws(() =>
|
|
parseTeams({ ...snapshot, teams: [snapshot.teams[0], snapshot.teams[0]] }, packageKey),
|
|
)
|
|
assert.equal(parseTeams({ ...snapshot, teams: [] }, packageKey).teams.length, 0)
|
|
})
|