up
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import { performance } from 'node:perf_hooks'
|
||||
import { isDeepStrictEqual } from 'node:util'
|
||||
import type {
|
||||
Catalog,
|
||||
Channel,
|
||||
@@ -348,8 +349,11 @@ export class Controller {
|
||||
const key = this.state.catalog.key
|
||||
if (value !== null && !key) throw new Error('Сначала выберите пакет игры')
|
||||
const snapshot = value === null ? null : parseResults(value, key!)
|
||||
const hasChanged =
|
||||
!isDeepStrictEqual(this.state.results?.questions, snapshot?.questions) ||
|
||||
!isDeepStrictEqual(this.state.results?.teams, snapshot?.teams)
|
||||
this.state.results = snapshot
|
||||
if (this.state.scene.mode === 6) this.invalidateScene()
|
||||
if (hasChanged && this.state.scene.mode === 6) this.invalidateScene()
|
||||
this.broadcast()
|
||||
}
|
||||
reportScene(value: unknown) {
|
||||
|
||||
@@ -415,6 +415,55 @@ test('музыкальная громкость не возвращает ста
|
||||
)
|
||||
})
|
||||
|
||||
test('перестановка прежних треков останавливает музыку до повторной подготовки', async (t) => {
|
||||
const f = await fixture(t)
|
||||
await f.prepare()
|
||||
f.controller.output(true)
|
||||
send(f.controller, { action: 'play', channel: 'music' })
|
||||
const previous = parseRemote({ music: objectMusic(1) })
|
||||
const next = parseRemote({
|
||||
music: { ...objectMusic(2), playlist: { ID: 1, tracks: [{ ID: 2 }, { ID: 1 }] } },
|
||||
})
|
||||
assert.throws(() => applyRemote(f.controller, previous, next), /Музыка изменилась/)
|
||||
assert.equal(f.controller.state.channels.music.desired, 'stopped')
|
||||
assert.deepEqual(f.controller.state.catalog.playlists[0]!.tracks.map((track) => track.id), [1, 2])
|
||||
})
|
||||
|
||||
test('назначение звука восстанавливается после обрыва и открытия окна без повтора команд', async (t) => {
|
||||
const f = await fixture(t)
|
||||
await f.prepare()
|
||||
f.controller.output(true)
|
||||
await f.online.poll()
|
||||
assert.equal(f.controller.state.muted, false)
|
||||
send(f.controller, { action: 'mute', enabled: true })
|
||||
f.model.offline = true
|
||||
await f.online.poll()
|
||||
f.model.offline = false
|
||||
await f.online.poll()
|
||||
assert.equal(f.controller.state.muted, true)
|
||||
|
||||
send(f.controller, { action: 'mute', enabled: false })
|
||||
f.model.offline = true
|
||||
await f.online.poll()
|
||||
f.model.remote.soundEnabled = false
|
||||
f.model.offline = false
|
||||
await f.online.poll()
|
||||
await f.online.poll()
|
||||
assert.equal(f.controller.state.muted, true)
|
||||
assert.equal(f.controller.state.channels.main.desired, 'stopped')
|
||||
|
||||
await f.online.act({ action: 'sound' })
|
||||
f.model.remote.soundEnabled = true
|
||||
await f.online.poll()
|
||||
assert.equal(f.controller.state.muted, false)
|
||||
f.controller.output(false)
|
||||
f.controller.output(true)
|
||||
f.model.remote.soundEnabled = false
|
||||
await f.online.poll()
|
||||
assert.equal(f.controller.state.muted, true)
|
||||
assert.equal(f.controller.state.channels.main.desired, 'stopped')
|
||||
})
|
||||
|
||||
test('истечение JWT не обрывает показ и оставляет доступным повторный вход', async (t) => {
|
||||
const f = await fixture(t)
|
||||
await f.prepare()
|
||||
|
||||
+4
-5
@@ -90,7 +90,7 @@ export class Online {
|
||||
private previous: RemoteState | null = null
|
||||
private pollBusy = false
|
||||
private pollSession = ''
|
||||
private initialSound = true
|
||||
private lastSound: boolean | null = null
|
||||
private lastResults = -Infinity
|
||||
private resultsBusy = false
|
||||
private advanceBusy = false
|
||||
@@ -179,7 +179,6 @@ export class Online {
|
||||
this.screen = c.screen
|
||||
this.state.screenId = c.screen.id
|
||||
this.state.connection = 'waiting'
|
||||
this.initialSound = true
|
||||
}
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code !== 'ENOENT')
|
||||
@@ -221,6 +220,7 @@ export class Online {
|
||||
this.generation++
|
||||
this.screen = null
|
||||
this.previous = null
|
||||
this.lastSound = null
|
||||
this.state.soundEnabled = null
|
||||
this.state.screenId = null
|
||||
this.state.connection = 'disabled'
|
||||
@@ -483,7 +483,6 @@ export class Online {
|
||||
this.screen = s
|
||||
this.state.screenId = s.id
|
||||
this.state.connection = 'waiting'
|
||||
this.initialSound = true
|
||||
await this.persistSecrets()
|
||||
return
|
||||
}
|
||||
@@ -624,10 +623,10 @@ export class Online {
|
||||
this.state.soundEnabled = next.sound
|
||||
if (this.state.connection === 'offline') this.state.error = ''
|
||||
this.state.connection = 'connected'
|
||||
if (!previous && this.initialSound) {
|
||||
this.initialSound = false
|
||||
if (!previous && this.lastSound !== next.sound) {
|
||||
send(this.controller, { action: 'mute', enabled: !next.sound })
|
||||
}
|
||||
this.lastSound = next.sound
|
||||
if (previous) {
|
||||
try {
|
||||
applyRemote(this.controller, previous, next)
|
||||
|
||||
@@ -252,6 +252,34 @@ test('снимок результатов ограничен текущей иг
|
||||
f.c.selectPackage(null)
|
||||
assert.equal(f.c.state.results, null)
|
||||
})
|
||||
test('новая дата тех же результатов сохраняет готовую сцену; изменение таблицы обновляет её', () => {
|
||||
const f = fixture()
|
||||
f.c.output(true)
|
||||
f.send({ action: 'mode', mode: 6 })
|
||||
const data = snapshot(f.key)
|
||||
f.c.setResults(data)
|
||||
const revision = f.c.state.scene.revision
|
||||
f.c.reportScene({ session: f.c.state.session, revision, status: 'ready' })
|
||||
|
||||
data.capturedAt = '2026-10-17T10:00:05Z'
|
||||
f.c.setResults(structuredClone(data))
|
||||
assert.equal(f.c.state.results!.capturedAt, '2026-10-17T10:00:05.000Z')
|
||||
assert.equal(f.c.state.scene.revision, revision)
|
||||
assert.equal(f.c.state.scene.status, 'ready')
|
||||
|
||||
data.teams[0]!.totalScore = 10
|
||||
f.c.setResults(data)
|
||||
assert.equal(f.c.state.scene.revision, revision + 1)
|
||||
assert.equal(f.c.state.results!.teams[0]!.totalScore, 10)
|
||||
data.questions[0]!.text = 'Исправленный вопрос'
|
||||
f.c.setResults(data)
|
||||
assert.equal(f.c.state.scene.revision, revision + 2)
|
||||
f.c.setResults(null)
|
||||
assert.equal(f.c.state.scene.revision, revision + 3)
|
||||
f.c.setResults(null)
|
||||
assert.equal(f.c.state.scene.revision, revision + 3)
|
||||
})
|
||||
|
||||
test('не принимает повреждённые снимки, повторные команды и неизвестные коды ответов', () => {
|
||||
const f = fixture()
|
||||
for (const mutate of [
|
||||
|
||||
@@ -189,7 +189,7 @@ export function applyRemote(controller: Controller, previous: RemoteState, next:
|
||||
if (
|
||||
!playlist ||
|
||||
playlist.tracks.length !== m.trackIds.length ||
|
||||
m.trackIds.some((id) => !playlist.tracks.some((t) => t.id === id))
|
||||
m.trackIds.some((id, index) => playlist.tracks[index]!.id !== id)
|
||||
) {
|
||||
if (controller.state.channels.music.source)
|
||||
send(controller, { action: 'stop', channel: 'music' })
|
||||
|
||||
Reference in New Issue
Block a user