test(timeout): copri startTimeout/stopTimeout e il timer server-side dei 30s
Unit: attivazione con squadra/video/inizio, rifiuto di squadre non valide, azzeramento completo con stopTimeout. Integration (fake timers): startedAt iniettato dal server, auto-chiusura a 30s con broadcast e persistenza, annullamento del timer su stop manuale e riarmo su nuovo avvio.
This commit is contained in:
@@ -164,6 +164,117 @@ describe('WebSocket Integration (websocket-handler.js)', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// TIME OUT (timer server-side)
|
||||||
|
// =============================================
|
||||||
|
describe('Time out', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers()
|
||||||
|
})
|
||||||
|
|
||||||
|
function inviaStartTimeout(controller, overrides = {}) {
|
||||||
|
controller.emit('message', JSON.stringify({
|
||||||
|
type: 'action',
|
||||||
|
action: { type: 'startTimeout', team: 'home', video: 'spot.mp4', ...overrides }
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
it('dovrebbe iniettare startedAt lato server (ignorando quello del client)', () => {
|
||||||
|
vi.setSystemTime(50000)
|
||||||
|
const controller = connectAndRegister(wss, 'controller')
|
||||||
|
|
||||||
|
inviaStartTimeout(controller, { startedAt: 12345 })
|
||||||
|
|
||||||
|
const state = handler.getState()
|
||||||
|
expect(state.timeout).toBe(true)
|
||||||
|
expect(state.timeoutStartedAt).toBe(50000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('dovrebbe chiudere automaticamente il time out dopo 30 secondi', () => {
|
||||||
|
const controller = connectAndRegister(wss, 'controller')
|
||||||
|
const display = connectAndRegister(wss, 'display')
|
||||||
|
|
||||||
|
inviaStartTimeout(controller)
|
||||||
|
expect(handler.getState().timeout).toBe(true)
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(29999)
|
||||||
|
expect(handler.getState().timeout).toBe(true)
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(1)
|
||||||
|
const state = handler.getState()
|
||||||
|
expect(state.timeout).toBe(false)
|
||||||
|
expect(state.timeoutTeam).toBe(null)
|
||||||
|
expect(state.timeoutVideo).toBe(null)
|
||||||
|
expect(state.timeoutStartedAt).toBe(null)
|
||||||
|
|
||||||
|
// La chiusura automatica viene broadcastata ai client
|
||||||
|
const msg = lastSent(display)
|
||||||
|
expect(msg.type).toBe('state')
|
||||||
|
expect(msg.state.timeout).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('la chiusura automatica dovrebbe persistere lo stato (onStateChange)', () => {
|
||||||
|
const onStateChange = vi.fn()
|
||||||
|
wss = new MockWebSocketServer()
|
||||||
|
handler = setupWebSocketHandler(wss, { onStateChange })
|
||||||
|
const controller = connectAndRegister(wss, 'controller')
|
||||||
|
|
||||||
|
inviaStartTimeout(controller)
|
||||||
|
onStateChange.mockClear()
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(30000)
|
||||||
|
expect(onStateChange).toHaveBeenCalledTimes(1)
|
||||||
|
expect(onStateChange.mock.calls[0][0].timeout).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('stopTimeout manuale dovrebbe annullare il timer pendente', () => {
|
||||||
|
const controller = connectAndRegister(wss, 'controller')
|
||||||
|
|
||||||
|
inviaStartTimeout(controller)
|
||||||
|
controller.emit('message', JSON.stringify({
|
||||||
|
type: 'action',
|
||||||
|
action: { type: 'stopTimeout' }
|
||||||
|
}))
|
||||||
|
expect(handler.getState().timeout).toBe(false)
|
||||||
|
controller.send.mockClear()
|
||||||
|
|
||||||
|
// Allo scadere dei 30s non deve partire nessun broadcast fantasma
|
||||||
|
vi.advanceTimersByTime(30000)
|
||||||
|
expect(controller.send).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('un nuovo startTimeout dovrebbe riarmare il timer da capo', () => {
|
||||||
|
const controller = connectAndRegister(wss, 'controller')
|
||||||
|
|
||||||
|
inviaStartTimeout(controller)
|
||||||
|
vi.advanceTimersByTime(20000)
|
||||||
|
|
||||||
|
// Riavvio: il timer riparte da zero
|
||||||
|
inviaStartTimeout(controller, { team: 'guest' })
|
||||||
|
vi.advanceTimersByTime(20000)
|
||||||
|
expect(handler.getState().timeout).toBe(true)
|
||||||
|
expect(handler.getState().timeoutTeam).toBe('guest')
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(10000)
|
||||||
|
expect(handler.getState().timeout).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('startTimeout con squadra non valida non dovrebbe armare il timer', () => {
|
||||||
|
const controller = connectAndRegister(wss, 'controller')
|
||||||
|
|
||||||
|
inviaStartTimeout(controller, { team: 'arbitro' })
|
||||||
|
expect(handler.getState().timeout).toBe(false)
|
||||||
|
controller.send.mockClear()
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(30000)
|
||||||
|
expect(controller.send).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// =============================================
|
// =============================================
|
||||||
// BROADCAST MULTI-CLIENT
|
// BROADCAST MULTI-CLIENT
|
||||||
// =============================================
|
// =============================================
|
||||||
|
|||||||
@@ -80,8 +80,11 @@ describe('Game Logic (gameState.js)', () => {
|
|||||||
expect(state.visuStriscia).toBe(true)
|
expect(state.visuStriscia).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('dovrebbe avere timeout false', () => {
|
it('dovrebbe avere timeout false e campi timeout nulli', () => {
|
||||||
expect(state.timeout).toBe(false)
|
expect(state.timeout).toBe(false)
|
||||||
|
expect(state.timeoutTeam).toBe(null)
|
||||||
|
expect(state.timeoutVideo).toBe(null)
|
||||||
|
expect(state.timeoutStartedAt).toBe(null)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -484,12 +487,50 @@ describe('Game Logic (gameState.js)', () => {
|
|||||||
expect(s.order).toBe(false)
|
expect(s.order).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('toggleTimeout: false → true → false', () => {
|
})
|
||||||
expect(state.timeout).toBe(false)
|
|
||||||
const s1 = applyAction(state, { type: 'toggleTimeout' })
|
// =============================================
|
||||||
expect(s1.timeout).toBe(true)
|
// TIME OUT (startTimeout, stopTimeout)
|
||||||
const s2 = applyAction(s1, { type: 'toggleTimeout' })
|
// =============================================
|
||||||
expect(s2.timeout).toBe(false)
|
describe('Time out', () => {
|
||||||
|
it('startTimeout dovrebbe attivare il time out con squadra, video e inizio', () => {
|
||||||
|
const s = applyAction(state, { type: 'startTimeout', team: 'home', video: 'spot.mp4', startedAt: 1000 })
|
||||||
|
expect(s.timeout).toBe(true)
|
||||||
|
expect(s.timeoutTeam).toBe('home')
|
||||||
|
expect(s.timeoutVideo).toBe('spot.mp4')
|
||||||
|
expect(s.timeoutStartedAt).toBe(1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('startTimeout senza video dovrebbe lasciare timeoutVideo null', () => {
|
||||||
|
const s = applyAction(state, { type: 'startTimeout', team: 'guest', startedAt: 1000 })
|
||||||
|
expect(s.timeout).toBe(true)
|
||||||
|
expect(s.timeoutTeam).toBe('guest')
|
||||||
|
expect(s.timeoutVideo).toBe(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('startTimeout senza squadra valida dovrebbe essere ignorato', () => {
|
||||||
|
for (const team of [undefined, null, 'arbitro', '']) {
|
||||||
|
const s = applyAction(state, { type: 'startTimeout', team, video: 'spot.mp4', startedAt: 1000 })
|
||||||
|
expect(s.timeout).toBe(false)
|
||||||
|
expect(s.timeoutTeam).toBe(null)
|
||||||
|
expect(s.timeoutVideo).toBe(null)
|
||||||
|
expect(s.timeoutStartedAt).toBe(null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('stopTimeout dovrebbe azzerare tutti i campi del time out', () => {
|
||||||
|
const attivo = applyAction(state, { type: 'startTimeout', team: 'home', video: 'spot.mp4', startedAt: 1000 })
|
||||||
|
const s = applyAction(attivo, { type: 'stopTimeout' })
|
||||||
|
expect(s.timeout).toBe(false)
|
||||||
|
expect(s.timeoutTeam).toBe(null)
|
||||||
|
expect(s.timeoutVideo).toBe(null)
|
||||||
|
expect(s.timeoutStartedAt).toBe(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('stopTimeout su time out già inattivo non dovrebbe avere effetti', () => {
|
||||||
|
const s = applyAction(state, { type: 'stopTimeout' })
|
||||||
|
expect(s.timeout).toBe(false)
|
||||||
|
expect(s.timeoutTeam).toBe(null)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user