Files
evening_detective_screen/electron/new-screens.test.ts
T
2026-10-23 03:02:55 +07:00

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)
})