test(vitest): amplia la suite con test unitari, integrazione, componenti e stress
- aggiunge test per gameState e utilita server - aggiunge test di integrazione WebSocket - aggiunge test componenti Vue (ControllerPage/DisplayPage) - aggiunge test stress su carico WebSocket - aggiorna configurazione Vitest per includere nuove cartelle e ambiente componenti - aggiorna script npm e dipendenze di test
This commit is contained in:
255
tests/component/ControllerPage.test.js
Normal file
255
tests/component/ControllerPage.test.js
Normal file
@@ -0,0 +1,255 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ControllerPage from '../../src/components/ControllerPage.vue'
|
||||
|
||||
// Mock globale WebSocket per jsdom
|
||||
class MockWebSocket {
|
||||
static OPEN = 1
|
||||
static CONNECTING = 0
|
||||
readyState = 0
|
||||
onopen = null
|
||||
onclose = null
|
||||
onmessage = null
|
||||
onerror = null
|
||||
send = vi.fn()
|
||||
close = vi.fn()
|
||||
constructor() {
|
||||
// Simula connessione immediata
|
||||
setTimeout(() => {
|
||||
this.readyState = 1
|
||||
if (this.onopen) this.onopen()
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
|
||||
vi.stubGlobal('WebSocket', MockWebSocket)
|
||||
|
||||
// Helper per creare il componente con stato personalizzato
|
||||
function mountController(stateOverrides = {}) {
|
||||
const wrapper = mount(ControllerPage, {
|
||||
global: {
|
||||
stubs: { 'w-app': true, 'w-button': true }
|
||||
}
|
||||
})
|
||||
if (Object.keys(stateOverrides).length > 0) {
|
||||
wrapper.vm.state = { ...wrapper.vm.state, ...stateOverrides }
|
||||
}
|
||||
return wrapper
|
||||
}
|
||||
|
||||
describe('ControllerPage.vue', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// RENDERING INIZIALE
|
||||
// =============================================
|
||||
describe('Rendering iniziale', () => {
|
||||
it('dovrebbe mostrare i nomi dei team', () => {
|
||||
const wrapper = mountController()
|
||||
const text = wrapper.text()
|
||||
expect(text).toContain('Antoniana')
|
||||
expect(text).toContain('Guest')
|
||||
})
|
||||
|
||||
it('dovrebbe mostrare punteggio 0-0', () => {
|
||||
const wrapper = mountController()
|
||||
const pts = wrapper.findAll('.team-pts')
|
||||
expect(pts[0].text()).toBe('0')
|
||||
expect(pts[1].text()).toBe('0')
|
||||
})
|
||||
|
||||
it('dovrebbe mostrare SET 0 per entrambi i team', () => {
|
||||
const wrapper = mountController()
|
||||
const sets = wrapper.findAll('.team-set')
|
||||
expect(sets[0].text()).toContain('SET 0')
|
||||
expect(sets[1].text()).toContain('SET 0')
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// CLICK PUNTEGGIO
|
||||
// =============================================
|
||||
describe('Click punteggio', () => {
|
||||
it('dovrebbe chiamare sendAction con incPunt home al click sul team home', async () => {
|
||||
const wrapper = mountController()
|
||||
const spy = vi.spyOn(wrapper.vm, 'sendAction')
|
||||
await wrapper.find('.team-score.home-bg').trigger('click')
|
||||
expect(spy).toHaveBeenCalledWith({ type: 'incPunt', team: 'home' })
|
||||
})
|
||||
|
||||
it('dovrebbe chiamare sendAction con incPunt guest al click sul team guest', async () => {
|
||||
const wrapper = mountController()
|
||||
const spy = vi.spyOn(wrapper.vm, 'sendAction')
|
||||
await wrapper.find('.team-score.guest-bg').trigger('click')
|
||||
expect(spy).toHaveBeenCalledWith({ type: 'incPunt', team: 'guest' })
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// BOTTONE CAMBIO PALLA
|
||||
// =============================================
|
||||
describe('Cambio Palla', () => {
|
||||
it('dovrebbe essere abilitato a 0-0', () => {
|
||||
const wrapper = mountController()
|
||||
const btn = wrapper.findAll('.btn-ctrl').find(b => b.text().includes('Cambio Palla'))
|
||||
expect(btn.attributes('disabled')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('dovrebbe essere disabilitato se il punteggio non è 0-0', async () => {
|
||||
const wrapper = mountController()
|
||||
wrapper.vm.state.sp.punt.home = 5
|
||||
await wrapper.vm.$nextTick()
|
||||
const btn = wrapper.findAll('.btn-ctrl').find(b => b.text().includes('Cambio Palla'))
|
||||
expect(btn.attributes('disabled')).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// DIALOG RESET
|
||||
// =============================================
|
||||
describe('Dialog Reset', () => {
|
||||
it('click Reset dovrebbe aprire la conferma', async () => {
|
||||
const wrapper = mountController()
|
||||
expect(wrapper.find('.overlay').exists()).toBe(false)
|
||||
await wrapper.find('.btn-danger').trigger('click')
|
||||
expect(wrapper.vm.confirmReset).toBe(true)
|
||||
expect(wrapper.find('.overlay').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('click NO dovrebbe chiudere la conferma', async () => {
|
||||
const wrapper = mountController()
|
||||
wrapper.vm.confirmReset = true
|
||||
await wrapper.vm.$nextTick()
|
||||
await wrapper.find('.btn-cancel').trigger('click')
|
||||
expect(wrapper.vm.confirmReset).toBe(false)
|
||||
})
|
||||
|
||||
it('click SI dovrebbe chiamare doReset', async () => {
|
||||
const wrapper = mountController()
|
||||
const spy = vi.spyOn(wrapper.vm, 'sendAction')
|
||||
wrapper.vm.confirmReset = true
|
||||
await wrapper.vm.$nextTick()
|
||||
await wrapper.find('.btn-confirm').trigger('click')
|
||||
expect(spy).toHaveBeenCalledWith({ type: 'resetta' })
|
||||
expect(wrapper.vm.confirmReset).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// COMPUTED cambiValid
|
||||
// =============================================
|
||||
describe('cambiValid', () => {
|
||||
it('dovrebbe essere false se tutti i campi sono vuoti', () => {
|
||||
const wrapper = mountController()
|
||||
wrapper.vm.cambiData = [{ in: '', out: '' }, { in: '', out: '' }]
|
||||
expect(wrapper.vm.cambiValid).toBe(false)
|
||||
})
|
||||
|
||||
it('dovrebbe essere true con un cambio completo', () => {
|
||||
const wrapper = mountController()
|
||||
wrapper.vm.cambiData = [{ in: '10', out: '1' }, { in: '', out: '' }]
|
||||
expect(wrapper.vm.cambiValid).toBe(true)
|
||||
})
|
||||
|
||||
it('dovrebbe essere false con un cambio parziale (solo IN)', () => {
|
||||
const wrapper = mountController()
|
||||
wrapper.vm.cambiData = [{ in: '10', out: '' }, { in: '', out: '' }]
|
||||
expect(wrapper.vm.cambiValid).toBe(false)
|
||||
})
|
||||
|
||||
it('dovrebbe essere false con un cambio parziale (solo OUT)', () => {
|
||||
const wrapper = mountController()
|
||||
wrapper.vm.cambiData = [{ in: '', out: '1' }, { in: '', out: '' }]
|
||||
expect(wrapper.vm.cambiValid).toBe(false)
|
||||
})
|
||||
|
||||
it('dovrebbe essere true con due cambi completi', () => {
|
||||
const wrapper = mountController()
|
||||
wrapper.vm.cambiData = [{ in: '10', out: '1' }, { in: '11', out: '2' }]
|
||||
expect(wrapper.vm.cambiValid).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// SPEAK
|
||||
// =============================================
|
||||
describe('speak', () => {
|
||||
it('dovrebbe generare "zero a zero" a 0-0', () => {
|
||||
const wrapper = mountController()
|
||||
wrapper.vm.wsConnected = true
|
||||
wrapper.vm.ws = { readyState: 1, send: vi.fn() }
|
||||
wrapper.vm.speak()
|
||||
const sent = JSON.parse(wrapper.vm.ws.send.mock.calls[0][0])
|
||||
expect(sent.type).toBe('speak')
|
||||
expect(sent.text).toBe('zero a zero')
|
||||
})
|
||||
|
||||
it('dovrebbe generare "N pari" a punteggio uguale', () => {
|
||||
const wrapper = mountController()
|
||||
wrapper.vm.state.sp.punt.home = 5
|
||||
wrapper.vm.state.sp.punt.guest = 5
|
||||
wrapper.vm.wsConnected = true
|
||||
wrapper.vm.ws = { readyState: 1, send: vi.fn() }
|
||||
wrapper.vm.speak()
|
||||
const sent = JSON.parse(wrapper.vm.ws.send.mock.calls[0][0])
|
||||
expect(sent.text).toBe('5 pari')
|
||||
})
|
||||
|
||||
it('dovrebbe annunciare prima il punteggio di chi batte (home serve)', () => {
|
||||
const wrapper = mountController()
|
||||
wrapper.vm.state.sp.punt.home = 15
|
||||
wrapper.vm.state.sp.punt.guest = 10
|
||||
wrapper.vm.state.sp.servHome = true
|
||||
wrapper.vm.wsConnected = true
|
||||
wrapper.vm.ws = { readyState: 1, send: vi.fn() }
|
||||
wrapper.vm.speak()
|
||||
const sent = JSON.parse(wrapper.vm.ws.send.mock.calls[0][0])
|
||||
expect(sent.text).toBe('15 a 10')
|
||||
})
|
||||
|
||||
it('dovrebbe annunciare prima il punteggio di chi batte (guest serve)', () => {
|
||||
const wrapper = mountController()
|
||||
wrapper.vm.state.sp.punt.home = 10
|
||||
wrapper.vm.state.sp.punt.guest = 15
|
||||
wrapper.vm.state.sp.servHome = false
|
||||
wrapper.vm.wsConnected = true
|
||||
wrapper.vm.ws = { readyState: 1, send: vi.fn() }
|
||||
wrapper.vm.speak()
|
||||
const sent = JSON.parse(wrapper.vm.ws.send.mock.calls[0][0])
|
||||
expect(sent.text).toBe('15 a 10')
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// BARRA CONNESSIONE
|
||||
// =============================================
|
||||
describe('Barra connessione', () => {
|
||||
it('dovrebbe avere classe "connected" quando connesso', async () => {
|
||||
const wrapper = mountController()
|
||||
wrapper.vm.wsConnected = true
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('.conn-bar').classes()).toContain('connected')
|
||||
})
|
||||
|
||||
it('non dovrebbe avere classe "connected" quando disconnesso', () => {
|
||||
const wrapper = mountController()
|
||||
wrapper.vm.wsConnected = false
|
||||
expect(wrapper.find('.conn-bar').classes()).not.toContain('connected')
|
||||
})
|
||||
|
||||
it('dovrebbe mostrare "Connesso" quando connesso', async () => {
|
||||
const wrapper = mountController()
|
||||
wrapper.vm.wsConnected = true
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('.conn-bar').text()).toContain('Connesso')
|
||||
})
|
||||
})
|
||||
})
|
||||
195
tests/component/DisplayPage.test.js
Normal file
195
tests/component/DisplayPage.test.js
Normal file
@@ -0,0 +1,195 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import DisplayPage from '../../src/components/DisplayPage.vue'
|
||||
|
||||
// Mock globale WebSocket per jsdom
|
||||
class MockWebSocket {
|
||||
static OPEN = 1
|
||||
static CONNECTING = 0
|
||||
readyState = 0
|
||||
onopen = null
|
||||
onclose = null
|
||||
onmessage = null
|
||||
onerror = null
|
||||
send = vi.fn()
|
||||
close = vi.fn()
|
||||
constructor() {
|
||||
setTimeout(() => {
|
||||
this.readyState = 1
|
||||
if (this.onopen) this.onopen()
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
|
||||
vi.stubGlobal('WebSocket', MockWebSocket)
|
||||
|
||||
// Mock requestFullscreen e speechSynthesis
|
||||
vi.stubGlobal('speechSynthesis', {
|
||||
speak: vi.fn(),
|
||||
cancel: vi.fn(),
|
||||
getVoices: () => []
|
||||
})
|
||||
|
||||
function mountDisplay() {
|
||||
return mount(DisplayPage, {
|
||||
global: {
|
||||
stubs: { 'w-app': true }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
describe('DisplayPage.vue', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// RENDERING PUNTEGGIO
|
||||
// =============================================
|
||||
describe('Rendering punteggio', () => {
|
||||
it('dovrebbe mostrare i nomi dei team', () => {
|
||||
const wrapper = mountDisplay()
|
||||
const text = wrapper.text()
|
||||
expect(text).toContain('Antoniana')
|
||||
expect(text).toContain('Guest')
|
||||
})
|
||||
|
||||
it('dovrebbe mostrare punteggio iniziale 0-0', () => {
|
||||
const wrapper = mountDisplay()
|
||||
const punti = wrapper.findAll('.punt')
|
||||
expect(punti[0].text()).toBe('0')
|
||||
expect(punti[1].text()).toBe('0')
|
||||
})
|
||||
|
||||
it('dovrebbe mostrare i set corretti', () => {
|
||||
const wrapper = mountDisplay()
|
||||
const text = wrapper.text()
|
||||
expect(text).toContain('set 0')
|
||||
})
|
||||
|
||||
it('dovrebbe aggiornare il punteggio quando lo stato cambia', async () => {
|
||||
const wrapper = mountDisplay()
|
||||
wrapper.vm.state.sp.punt.home = 15
|
||||
wrapper.vm.state.sp.punt.guest = 12
|
||||
await wrapper.vm.$nextTick()
|
||||
const punti = wrapper.findAll('.punt')
|
||||
expect(punti[0].text()).toBe('15')
|
||||
expect(punti[1].text()).toBe('12')
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// ORDINE TEAM
|
||||
// =============================================
|
||||
describe('Ordine team', () => {
|
||||
it('order=true → Home prima di Guest', () => {
|
||||
const wrapper = mountDisplay()
|
||||
const headers = wrapper.findAll('.hea')
|
||||
expect(headers[0].classes()).toContain('home')
|
||||
expect(headers[1].classes()).toContain('guest')
|
||||
})
|
||||
|
||||
it('order=false → Guest prima di Home', async () => {
|
||||
const wrapper = mountDisplay()
|
||||
wrapper.vm.state.order = false
|
||||
await wrapper.vm.$nextTick()
|
||||
const headers = wrapper.findAll('.hea')
|
||||
expect(headers[0].classes()).toContain('guest')
|
||||
expect(headers[1].classes()).toContain('home')
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// FORMAZIONE vs PUNTEGGIO
|
||||
// =============================================
|
||||
describe('visuForm toggle', () => {
|
||||
it('visuForm=false → mostra punteggio grande', () => {
|
||||
const wrapper = mountDisplay()
|
||||
expect(wrapper.find('.punteggio-container').exists()).toBe(true)
|
||||
expect(wrapper.find('.form').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('visuForm=true → mostra formazione', async () => {
|
||||
const wrapper = mountDisplay()
|
||||
wrapper.vm.state.visuForm = true
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.findAll('.form').length).toBeGreaterThan(0)
|
||||
expect(wrapper.find('.punteggio-container').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('formazione mostra 6 giocatori per team', async () => {
|
||||
const wrapper = mountDisplay()
|
||||
wrapper.vm.state.visuForm = true
|
||||
await wrapper.vm.$nextTick()
|
||||
const formDivs = wrapper.findAll('.formdiv')
|
||||
// 6 per home + 6 per guest = 12
|
||||
expect(formDivs).toHaveLength(12)
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// STRISCIA
|
||||
// =============================================
|
||||
describe('visuStriscia toggle', () => {
|
||||
it('visuStriscia=true → mostra la striscia', () => {
|
||||
const wrapper = mountDisplay()
|
||||
expect(wrapper.find('.striscia').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('visuStriscia=false → nasconde la striscia', async () => {
|
||||
const wrapper = mountDisplay()
|
||||
wrapper.vm.state.visuStriscia = false
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('.striscia').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// INDICATORE CONNESSIONE
|
||||
// =============================================
|
||||
describe('Indicatore connessione', () => {
|
||||
it('dovrebbe avere classe "disconnected" quando non connesso', () => {
|
||||
const wrapper = mountDisplay()
|
||||
const status = wrapper.find('.connection-status')
|
||||
expect(status.classes()).toContain('disconnected')
|
||||
})
|
||||
|
||||
it('dovrebbe avere classe "connected" quando connesso', async () => {
|
||||
const wrapper = mountDisplay()
|
||||
wrapper.vm.wsConnected = true
|
||||
await wrapper.vm.$nextTick()
|
||||
const status = wrapper.find('.connection-status')
|
||||
expect(status.classes()).toContain('connected')
|
||||
})
|
||||
|
||||
it('dovrebbe mostrare "Disconnesso" quando non connesso', () => {
|
||||
const wrapper = mountDisplay()
|
||||
const status = wrapper.find('.connection-status')
|
||||
expect(status.text()).toContain('Disconnesso')
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// ICONA SERVIZIO
|
||||
// =============================================
|
||||
describe('Icona servizio', () => {
|
||||
it('dovrebbe mostrare l\'icona servizio sul team home quando servHome=true', () => {
|
||||
const wrapper = mountDisplay()
|
||||
// v-show imposta display:none. In happy-dom controlliamo lo style.
|
||||
const imgs = wrapper.findAll('.serv-slot img')
|
||||
// Con state.order=true e servHome=true:
|
||||
// - la prima img (home) è visibile (no display:none)
|
||||
// - la seconda img (guest) ha display:none
|
||||
const homeStyle = imgs[0].attributes('style') || ''
|
||||
const guestStyle = imgs[1].attributes('style') || ''
|
||||
expect(homeStyle).not.toContain('display: none')
|
||||
expect(guestStyle).toContain('display: none')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -16,68 +16,388 @@ class MockWebSocketServer extends EventEmitter {
|
||||
clients = new Set()
|
||||
}
|
||||
|
||||
// Helper: connette e registra un client
|
||||
function connectAndRegister(wss, role) {
|
||||
const ws = new MockWebSocket()
|
||||
wss.emit('connection', ws)
|
||||
wss.clients.add(ws)
|
||||
ws.emit('message', JSON.stringify({ type: 'register', role }))
|
||||
ws.send.mockClear()
|
||||
return ws
|
||||
}
|
||||
|
||||
// Helper: ultimo messaggio inviato a un ws
|
||||
function lastSent(ws) {
|
||||
const calls = ws.send.mock.calls
|
||||
return JSON.parse(calls[calls.length - 1][0])
|
||||
}
|
||||
|
||||
describe('WebSocket Integration (websocket-handler.js)', () => {
|
||||
let wss
|
||||
let handler
|
||||
let ws
|
||||
|
||||
beforeEach(() => {
|
||||
wss = new MockWebSocketServer()
|
||||
handler = setupWebSocketHandler(wss)
|
||||
ws = new MockWebSocket()
|
||||
// Simuliamo la connessione
|
||||
wss.emit('connection', ws)
|
||||
// Aggiungiamo il client al set del server (come farebbe 'ws' realmente)
|
||||
wss.clients.add(ws)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('dovrebbe registrare un client come "display" e inviare lo stato', () => {
|
||||
ws.emit('message', JSON.stringify({ type: 'register', role: 'display' }))
|
||||
// =============================================
|
||||
// REGISTRAZIONE
|
||||
// =============================================
|
||||
describe('Registrazione', () => {
|
||||
it('dovrebbe registrare un client come "display" e inviare lo stato', () => {
|
||||
const ws = new MockWebSocket()
|
||||
wss.emit('connection', ws)
|
||||
wss.clients.add(ws)
|
||||
ws.emit('message', JSON.stringify({ type: 'register', role: 'display' }))
|
||||
|
||||
// Verifica che abbia inviato lo stato iniziale
|
||||
expect(ws.send).toHaveBeenCalled()
|
||||
const sentMsg = JSON.parse(ws.send.mock.calls[0][0])
|
||||
expect(sentMsg.type).toBe('state')
|
||||
expect(sentMsg.state).toBeDefined()
|
||||
expect(ws.send).toHaveBeenCalled()
|
||||
const sentMsg = JSON.parse(ws.send.mock.calls[0][0])
|
||||
expect(sentMsg.type).toBe('state')
|
||||
expect(sentMsg.state).toBeDefined()
|
||||
})
|
||||
|
||||
it('dovrebbe registrare un client come "controller"', () => {
|
||||
connectAndRegister(wss, 'controller')
|
||||
expect(handler.getClients().size).toBe(1)
|
||||
})
|
||||
|
||||
it('dovrebbe rifiutare ruolo non valido', () => {
|
||||
const ws = new MockWebSocket()
|
||||
wss.emit('connection', ws)
|
||||
wss.clients.add(ws)
|
||||
ws.emit('message', JSON.stringify({ type: 'register', role: 'hacker' }))
|
||||
|
||||
const sentMsg = JSON.parse(ws.send.mock.calls[0][0])
|
||||
expect(sentMsg.type).toBe('error')
|
||||
expect(sentMsg.message).toContain('Invalid role')
|
||||
})
|
||||
|
||||
it('dovrebbe usare "display" come ruolo default se mancante', () => {
|
||||
const ws = new MockWebSocket()
|
||||
wss.emit('connection', ws)
|
||||
wss.clients.add(ws)
|
||||
ws.emit('message', JSON.stringify({ type: 'register' }))
|
||||
|
||||
const sentMsg = JSON.parse(ws.send.mock.calls[0][0])
|
||||
expect(sentMsg.type).toBe('state')
|
||||
})
|
||||
})
|
||||
|
||||
it('dovrebbe permettere al controller di cambiare il punteggio', () => {
|
||||
// 1. Registra Controller
|
||||
ws.emit('message', JSON.stringify({ type: 'register', role: 'controller' }))
|
||||
ws.send.mockClear() // pulisco chiamate precedenti
|
||||
// =============================================
|
||||
// AZIONI
|
||||
// =============================================
|
||||
describe('Azioni', () => {
|
||||
it('dovrebbe permettere al controller di cambiare il punteggio', () => {
|
||||
const controller = connectAndRegister(wss, 'controller')
|
||||
|
||||
// 2. Invia Azione
|
||||
ws.emit('message', JSON.stringify({
|
||||
type: 'action',
|
||||
action: { type: 'incPunt', team: 'home' }
|
||||
}))
|
||||
controller.emit('message', JSON.stringify({
|
||||
type: 'action',
|
||||
action: { type: 'incPunt', team: 'home' }
|
||||
}))
|
||||
|
||||
// 3. Verifica Broadcast del nuovo stato
|
||||
expect(ws.send).toHaveBeenCalled()
|
||||
const sentMsg = JSON.parse(ws.send.mock.calls[0][0])
|
||||
expect(sentMsg.type).toBe('state')
|
||||
expect(sentMsg.state.sp.punt.home).toBe(1)
|
||||
expect(controller.send).toHaveBeenCalled()
|
||||
const sentMsg = lastSent(controller)
|
||||
expect(sentMsg.type).toBe('state')
|
||||
expect(sentMsg.state.sp.punt.home).toBe(1)
|
||||
})
|
||||
|
||||
it('dovrebbe impedire al display di inviare azioni', () => {
|
||||
const display = connectAndRegister(wss, 'display')
|
||||
|
||||
display.emit('message', JSON.stringify({
|
||||
type: 'action',
|
||||
action: { type: 'incPunt', team: 'home' }
|
||||
}))
|
||||
|
||||
const sentMsg = lastSent(display)
|
||||
expect(sentMsg.type).toBe('error')
|
||||
expect(sentMsg.message).toContain('Only controllers')
|
||||
})
|
||||
|
||||
it('dovrebbe impedire azioni da client non registrati', () => {
|
||||
const ws = new MockWebSocket()
|
||||
wss.emit('connection', ws)
|
||||
wss.clients.add(ws)
|
||||
|
||||
ws.emit('message', JSON.stringify({
|
||||
type: 'action',
|
||||
action: { type: 'incPunt', team: 'home' }
|
||||
}))
|
||||
|
||||
const sentMsg = JSON.parse(ws.send.mock.calls[0][0])
|
||||
expect(sentMsg.type).toBe('error')
|
||||
expect(sentMsg.message).toContain('Only controllers')
|
||||
})
|
||||
|
||||
it('dovrebbe rifiutare azione con formato invalido (missing action)', () => {
|
||||
const controller = connectAndRegister(wss, 'controller')
|
||||
|
||||
controller.emit('message', JSON.stringify({
|
||||
type: 'action'
|
||||
}))
|
||||
|
||||
const sentMsg = lastSent(controller)
|
||||
expect(sentMsg.type).toBe('error')
|
||||
expect(sentMsg.message).toContain('Invalid action format')
|
||||
})
|
||||
|
||||
it('dovrebbe rifiutare azione con formato invalido (missing action.type)', () => {
|
||||
const controller = connectAndRegister(wss, 'controller')
|
||||
|
||||
controller.emit('message', JSON.stringify({
|
||||
type: 'action',
|
||||
action: { team: 'home' }
|
||||
}))
|
||||
|
||||
const sentMsg = lastSent(controller)
|
||||
expect(sentMsg.type).toBe('error')
|
||||
expect(sentMsg.message).toContain('Invalid action format')
|
||||
})
|
||||
})
|
||||
|
||||
it('dovrebbe impedire al display di inviare azioni', () => {
|
||||
// 1. Registra Display
|
||||
ws.emit('message', JSON.stringify({ type: 'register', role: 'display' }))
|
||||
ws.send.mockClear()
|
||||
// =============================================
|
||||
// BROADCAST MULTI-CLIENT
|
||||
// =============================================
|
||||
describe('Broadcast', () => {
|
||||
it('dovrebbe inviare lo stato a tutti i client dopo un\'azione', () => {
|
||||
const controller = connectAndRegister(wss, 'controller')
|
||||
const display1 = connectAndRegister(wss, 'display')
|
||||
const display2 = connectAndRegister(wss, 'display')
|
||||
|
||||
// 2. Tenta Azione
|
||||
ws.emit('message', JSON.stringify({
|
||||
type: 'action',
|
||||
action: { type: 'incPunt', team: 'home' }
|
||||
}))
|
||||
controller.emit('message', JSON.stringify({
|
||||
type: 'action',
|
||||
action: { type: 'incPunt', team: 'home' }
|
||||
}))
|
||||
|
||||
// 3. Verifica Errore
|
||||
expect(ws.send).toHaveBeenCalled()
|
||||
const sentMsg = JSON.parse(ws.send.mock.calls[0][0])
|
||||
expect(sentMsg.type).toBe('error')
|
||||
expect(sentMsg.message).toContain('Only controllers')
|
||||
// Tutti i client nel set dovrebbero aver ricevuto lo stato
|
||||
expect(controller.send).toHaveBeenCalled()
|
||||
expect(display1.send).toHaveBeenCalled()
|
||||
expect(display2.send).toHaveBeenCalled()
|
||||
|
||||
const msg1 = lastSent(display1)
|
||||
const msg2 = lastSent(display2)
|
||||
expect(msg1.type).toBe('state')
|
||||
expect(msg1.state.sp.punt.home).toBe(1)
|
||||
expect(msg2.state.sp.punt.home).toBe(1)
|
||||
})
|
||||
|
||||
it('non dovrebbe inviare a client con readyState != OPEN', () => {
|
||||
const controller = connectAndRegister(wss, 'controller')
|
||||
const closedClient = connectAndRegister(wss, 'display')
|
||||
closedClient.readyState = 3 // CLOSED
|
||||
|
||||
controller.emit('message', JSON.stringify({
|
||||
type: 'action',
|
||||
action: { type: 'incPunt', team: 'home' }
|
||||
}))
|
||||
|
||||
// closedClient non dovrebbe aver ricevuto il broadcast
|
||||
expect(closedClient.send).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// SPEAK
|
||||
// =============================================
|
||||
describe('Speak', () => {
|
||||
it('dovrebbe inoltrare il messaggio speak solo ai display', () => {
|
||||
const controller = connectAndRegister(wss, 'controller')
|
||||
const display = connectAndRegister(wss, 'display')
|
||||
|
||||
controller.emit('message', JSON.stringify({
|
||||
type: 'speak',
|
||||
text: 'quindici a dieci'
|
||||
}))
|
||||
|
||||
// Il display riceve il messaggio speak
|
||||
expect(display.send).toHaveBeenCalled()
|
||||
const msg = lastSent(display)
|
||||
expect(msg.type).toBe('speak')
|
||||
expect(msg.text).toBe('quindici a dieci')
|
||||
})
|
||||
|
||||
it('non dovrebbe permettere al display di inviare speak', () => {
|
||||
const display = connectAndRegister(wss, 'display')
|
||||
|
||||
display.emit('message', JSON.stringify({
|
||||
type: 'speak',
|
||||
text: 'test'
|
||||
}))
|
||||
|
||||
const msg = lastSent(display)
|
||||
expect(msg.type).toBe('error')
|
||||
expect(msg.message).toContain('Only controllers')
|
||||
})
|
||||
|
||||
it('dovrebbe rifiutare speak con testo vuoto', () => {
|
||||
const controller = connectAndRegister(wss, 'controller')
|
||||
|
||||
controller.emit('message', JSON.stringify({
|
||||
type: 'speak',
|
||||
text: ' '
|
||||
}))
|
||||
|
||||
const msg = lastSent(controller)
|
||||
expect(msg.type).toBe('error')
|
||||
expect(msg.message).toContain('Invalid speak payload')
|
||||
})
|
||||
|
||||
it('dovrebbe rifiutare speak senza testo', () => {
|
||||
const controller = connectAndRegister(wss, 'controller')
|
||||
|
||||
controller.emit('message', JSON.stringify({
|
||||
type: 'speak'
|
||||
}))
|
||||
|
||||
const msg = lastSent(controller)
|
||||
expect(msg.type).toBe('error')
|
||||
})
|
||||
|
||||
it('dovrebbe fare trim del testo speak', () => {
|
||||
const controller = connectAndRegister(wss, 'controller')
|
||||
const display = connectAndRegister(wss, 'display')
|
||||
|
||||
controller.emit('message', JSON.stringify({
|
||||
type: 'speak',
|
||||
text: ' dieci a otto '
|
||||
}))
|
||||
|
||||
const msg = lastSent(display)
|
||||
expect(msg.text).toBe('dieci a otto')
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// MESSAGGI MALFORMATI
|
||||
// =============================================
|
||||
describe('Messaggi malformati', () => {
|
||||
it('dovrebbe gestire JSON non valido senza crash', () => {
|
||||
const ws = new MockWebSocket()
|
||||
wss.emit('connection', ws)
|
||||
wss.clients.add(ws)
|
||||
|
||||
expect(() => {
|
||||
ws.emit('message', 'questo non è JSON {{{')
|
||||
}).not.toThrow()
|
||||
|
||||
const msg = lastSent(ws)
|
||||
expect(msg.type).toBe('error')
|
||||
expect(msg.message).toContain('Invalid message format')
|
||||
})
|
||||
|
||||
it('dovrebbe gestire Buffer come input', () => {
|
||||
const controller = connectAndRegister(wss, 'controller')
|
||||
|
||||
const buf = Buffer.from(JSON.stringify({
|
||||
type: 'action',
|
||||
action: { type: 'incPunt', team: 'home' }
|
||||
}))
|
||||
|
||||
controller.emit('message', buf)
|
||||
|
||||
const msg = lastSent(controller)
|
||||
expect(msg.type).toBe('state')
|
||||
expect(msg.state.sp.punt.home).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// DISCONNESSIONE
|
||||
// =============================================
|
||||
describe('Disconnessione', () => {
|
||||
it('dovrebbe rimuovere il client dalla mappa alla disconnessione', () => {
|
||||
const controller = connectAndRegister(wss, 'controller')
|
||||
expect(handler.getClients().size).toBe(1)
|
||||
|
||||
controller.emit('close')
|
||||
|
||||
expect(handler.getClients().size).toBe(0)
|
||||
})
|
||||
|
||||
it('i client rimanenti non dovrebbero essere affetti dalla disconnessione', () => {
|
||||
const controller = connectAndRegister(wss, 'controller')
|
||||
const display = connectAndRegister(wss, 'display')
|
||||
expect(handler.getClients().size).toBe(2)
|
||||
|
||||
controller.emit('close')
|
||||
expect(handler.getClients().size).toBe(1)
|
||||
|
||||
expect(handler.getClients().has(display)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// ERRORI WEBSOCKET
|
||||
// =============================================
|
||||
describe('Errori WebSocket', () => {
|
||||
it('dovrebbe terminare la connessione per errore UTF8 invalido', () => {
|
||||
const ws = new MockWebSocket()
|
||||
wss.emit('connection', ws)
|
||||
|
||||
const err = new Error('Invalid UTF8')
|
||||
err.code = 'WS_ERR_INVALID_UTF8'
|
||||
ws.emit('error', err)
|
||||
|
||||
expect(ws.terminate).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('dovrebbe terminare la connessione per close code invalido', () => {
|
||||
const ws = new MockWebSocket()
|
||||
wss.emit('connection', ws)
|
||||
|
||||
const err = new Error('Invalid close code')
|
||||
err.code = 'WS_ERR_INVALID_CLOSE_CODE'
|
||||
ws.emit('error', err)
|
||||
|
||||
expect(ws.terminate).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('non dovrebbe terminare per altri errori', () => {
|
||||
const ws = new MockWebSocket()
|
||||
wss.emit('connection', ws)
|
||||
|
||||
const err = new Error('Generic error')
|
||||
ws.emit('error', err)
|
||||
|
||||
expect(ws.terminate).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// API PUBBLICA
|
||||
// =============================================
|
||||
describe('API pubblica', () => {
|
||||
it('getState dovrebbe restituire lo stato corrente', () => {
|
||||
const state = handler.getState()
|
||||
expect(state.sp.punt.home).toBe(0)
|
||||
expect(state.sp.punt.guest).toBe(0)
|
||||
})
|
||||
|
||||
it('setState dovrebbe sovrascrivere lo stato', () => {
|
||||
const newState = handler.getState()
|
||||
newState.sp.punt.home = 99
|
||||
handler.setState(newState)
|
||||
expect(handler.getState().sp.punt.home).toBe(99)
|
||||
})
|
||||
|
||||
it('broadcastState dovrebbe inviare a tutti i client', () => {
|
||||
const display = connectAndRegister(wss, 'display')
|
||||
handler.broadcastState()
|
||||
expect(display.send).toHaveBeenCalled()
|
||||
const msg = lastSent(display)
|
||||
expect(msg.type).toBe('state')
|
||||
})
|
||||
|
||||
it('getClients dovrebbe restituire la mappa dei client', () => {
|
||||
expect(handler.getClients()).toBeInstanceOf(Map)
|
||||
expect(handler.getClients().size).toBe(0)
|
||||
connectAndRegister(wss, 'display')
|
||||
expect(handler.getClients().size).toBe(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
118
tests/stress/websocket-load.test.js
Normal file
118
tests/stress/websocket-load.test.js
Normal file
@@ -0,0 +1,118 @@
|
||||
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
|
||||
import { setupWebSocketHandler } from '../../src/websocket-handler.js'
|
||||
import { EventEmitter } from 'events'
|
||||
|
||||
class MockWebSocket extends EventEmitter {
|
||||
constructor() {
|
||||
super()
|
||||
this.readyState = 1
|
||||
}
|
||||
send = vi.fn()
|
||||
terminate = vi.fn()
|
||||
}
|
||||
|
||||
class MockWebSocketServer extends EventEmitter {
|
||||
clients = new Set()
|
||||
}
|
||||
|
||||
function connectAndRegister(wss, role) {
|
||||
const ws = new MockWebSocket()
|
||||
wss.emit('connection', ws)
|
||||
wss.clients.add(ws)
|
||||
ws.emit('message', JSON.stringify({ type: 'register', role }))
|
||||
ws.send.mockClear()
|
||||
return ws
|
||||
}
|
||||
|
||||
describe('Stress Test WebSocket', () => {
|
||||
let wss
|
||||
let handler
|
||||
|
||||
beforeEach(() => {
|
||||
wss = new MockWebSocketServer()
|
||||
handler = setupWebSocketHandler(wss)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('dovrebbe gestire 50 client display connessi simultaneamente', () => {
|
||||
const displays = []
|
||||
for (let i = 0; i < 50; i++) {
|
||||
displays.push(connectAndRegister(wss, 'display'))
|
||||
}
|
||||
|
||||
expect(handler.getClients().size).toBe(50)
|
||||
|
||||
// Un controller invia un'azione
|
||||
const controller = connectAndRegister(wss, 'controller')
|
||||
controller.emit('message', JSON.stringify({
|
||||
type: 'action',
|
||||
action: { type: 'incPunt', team: 'home' }
|
||||
}))
|
||||
|
||||
// Tutti i display devono aver ricevuto il broadcast
|
||||
for (const display of displays) {
|
||||
expect(display.send).toHaveBeenCalled()
|
||||
const msg = JSON.parse(display.send.mock.calls[display.send.mock.calls.length - 1][0])
|
||||
expect(msg.type).toBe('state')
|
||||
expect(msg.state.sp.punt.home).toBe(1)
|
||||
}
|
||||
})
|
||||
|
||||
it('dovrebbe gestire 100 azioni rapide in sequenza con stato finale corretto', () => {
|
||||
const controller = connectAndRegister(wss, 'controller')
|
||||
|
||||
// 60 punti home, 40 punti guest
|
||||
for (let i = 0; i < 60; i++) {
|
||||
controller.emit('message', JSON.stringify({
|
||||
type: 'action',
|
||||
action: { type: 'incPunt', team: 'home' }
|
||||
}))
|
||||
}
|
||||
for (let i = 0; i < 40; i++) {
|
||||
controller.emit('message', JSON.stringify({
|
||||
type: 'action',
|
||||
action: { type: 'incPunt', team: 'guest' }
|
||||
}))
|
||||
}
|
||||
|
||||
// Lo stato finale dipende da checkVittoria che blocca a 25+2
|
||||
// Home arriva a 25-0 → vittoria → blocca. Quindi punti home = 25
|
||||
const state = handler.getState()
|
||||
expect(state.sp.punt.home).toBe(25)
|
||||
// Guest: non può segnare dopo vittoria? No, checkVittoria blocca solo il team che ha vinto?
|
||||
// Controlliamo: checkVittoria controlla ENTRAMBI i team.
|
||||
// A 25-0 → vittoria=true → incPunt per guest è anche bloccato
|
||||
expect(state.sp.punt.guest).toBe(0)
|
||||
})
|
||||
|
||||
it('dovrebbe garantire che tutti i display ricevano ogni update sotto carico', () => {
|
||||
const displays = []
|
||||
for (let i = 0; i < 10; i++) {
|
||||
displays.push(connectAndRegister(wss, 'display'))
|
||||
}
|
||||
|
||||
const controller = connectAndRegister(wss, 'controller')
|
||||
|
||||
// 5 azioni rapide
|
||||
for (let i = 0; i < 5; i++) {
|
||||
controller.emit('message', JSON.stringify({
|
||||
type: 'action',
|
||||
action: { type: 'incPunt', team: 'home' }
|
||||
}))
|
||||
}
|
||||
|
||||
// Ogni display deve aver ricevuto esattamente 5 broadcast
|
||||
for (const display of displays) {
|
||||
expect(display.send).toHaveBeenCalledTimes(5)
|
||||
}
|
||||
|
||||
// Verifica stato finale su tutti i display
|
||||
for (const display of displays) {
|
||||
const lastMsg = JSON.parse(display.send.mock.calls[4][0])
|
||||
expect(lastMsg.state.sp.punt.home).toBe(5)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -8,7 +8,10 @@ describe('Game Logic (gameState.js)', () => {
|
||||
state = createInitialState()
|
||||
})
|
||||
|
||||
describe('Initial State', () => {
|
||||
// =============================================
|
||||
// STATO INIZIALE
|
||||
// =============================================
|
||||
describe('Stato iniziale', () => {
|
||||
it('dovrebbe iniziare con 0-0', () => {
|
||||
expect(state.sp.punt.home).toBe(0)
|
||||
expect(state.sp.punt.guest).toBe(0)
|
||||
@@ -18,40 +21,451 @@ describe('Game Logic (gameState.js)', () => {
|
||||
expect(state.sp.set.home).toBe(0)
|
||||
expect(state.sp.set.guest).toBe(0)
|
||||
})
|
||||
|
||||
it('dovrebbe avere servizio Home', () => {
|
||||
expect(state.sp.servHome).toBe(true)
|
||||
})
|
||||
|
||||
it('dovrebbe avere formazione di default [1-6]', () => {
|
||||
expect(state.sp.form.home).toEqual(["1", "2", "3", "4", "5", "6"])
|
||||
expect(state.sp.form.guest).toEqual(["1", "2", "3", "4", "5", "6"])
|
||||
})
|
||||
|
||||
it('dovrebbe avere la striscia iniziale a [0]', () => {
|
||||
expect(state.sp.striscia.home).toEqual([0])
|
||||
expect(state.sp.striscia.guest).toEqual([0])
|
||||
})
|
||||
|
||||
it('dovrebbe avere storico servizio vuoto', () => {
|
||||
expect(state.sp.storicoServizio).toEqual([])
|
||||
})
|
||||
|
||||
it('dovrebbe avere modalità 3/5 di default', () => {
|
||||
expect(state.modalitaPartita).toBe("3/5")
|
||||
})
|
||||
|
||||
it('dovrebbe avere visuForm false e visuStriscia true', () => {
|
||||
expect(state.visuForm).toBe(false)
|
||||
expect(state.visuStriscia).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Punteggio', () => {
|
||||
it('dovrebbe incrementare i punti (Home)', () => {
|
||||
// =============================================
|
||||
// IMMUTABILITÀ
|
||||
// =============================================
|
||||
describe('Immutabilità', () => {
|
||||
it('applyAction non dovrebbe mutare lo stato originale', () => {
|
||||
const original = JSON.stringify(state)
|
||||
applyAction(state, { type: 'incPunt', team: 'home' })
|
||||
expect(JSON.stringify(state)).toBe(original)
|
||||
})
|
||||
|
||||
it('dovrebbe restituire un nuovo oggetto', () => {
|
||||
const newState = applyAction(state, { type: 'incPunt', team: 'home' })
|
||||
expect(newState).not.toBe(state)
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// INCREMENTO PUNTI (incPunt)
|
||||
// =============================================
|
||||
describe('incPunt', () => {
|
||||
it('dovrebbe incrementare i punti Home', () => {
|
||||
const newState = applyAction(state, { type: 'incPunt', team: 'home' })
|
||||
expect(newState.sp.punt.home).toBe(1)
|
||||
expect(newState.sp.punt.guest).toBe(0)
|
||||
})
|
||||
|
||||
it('dovrebbe gestire il cambio palla', () => {
|
||||
// Home batte
|
||||
state.sp.servHome = true
|
||||
// Punto Guest -> Cambio palla
|
||||
const s1 = applyAction(state, { type: 'incPunt', team: 'guest' })
|
||||
expect(s1.sp.servHome).toBe(false) // Ora batte Guest
|
||||
|
||||
// Punto Home -> Cambio palla
|
||||
const s2 = applyAction(s1, { type: 'incPunt', team: 'home' })
|
||||
expect(s2.sp.servHome).toBe(true) // Torna a battere Home
|
||||
it('dovrebbe incrementare i punti Guest', () => {
|
||||
const newState = applyAction(state, { type: 'incPunt', team: 'guest' })
|
||||
expect(newState.sp.punt.guest).toBe(1)
|
||||
expect(newState.sp.punt.home).toBe(0)
|
||||
})
|
||||
|
||||
it('dovrebbe gestire la rotazione formazione al cambio palla', () => {
|
||||
state.sp.servHome = true // Batte Home
|
||||
it('dovrebbe gestire il cambio palla (Guest segna, batteva Home)', () => {
|
||||
state.sp.servHome = true
|
||||
const s1 = applyAction(state, { type: 'incPunt', team: 'guest' })
|
||||
expect(s1.sp.servHome).toBe(false)
|
||||
})
|
||||
|
||||
it('dovrebbe gestire il cambio palla (Home segna, batteva Guest)', () => {
|
||||
state.sp.servHome = false
|
||||
const s1 = applyAction(state, { type: 'incPunt', team: 'home' })
|
||||
expect(s1.sp.servHome).toBe(true)
|
||||
})
|
||||
|
||||
it('non dovrebbe cambiare palla se segna chi batte', () => {
|
||||
state.sp.servHome = true
|
||||
const s1 = applyAction(state, { type: 'incPunt', team: 'home' })
|
||||
expect(s1.sp.servHome).toBe(true)
|
||||
})
|
||||
|
||||
it('dovrebbe ruotare la formazione al cambio palla', () => {
|
||||
state.sp.servHome = true
|
||||
state.sp.form.guest = ["1", "2", "3", "4", "5", "6"]
|
||||
|
||||
// Punto Guest -> Cambio palla e rotazione Guest
|
||||
const newState = applyAction(state, { type: 'incPunt', team: 'guest' })
|
||||
|
||||
// Verifica che la formazione sia ruotata (il primo elemento diventa ultimo)
|
||||
expect(newState.sp.form.guest).toEqual(["2", "3", "4", "5", "6", "1"])
|
||||
})
|
||||
|
||||
it('non dovrebbe ruotare la formazione se non c\'è cambio palla', () => {
|
||||
state.sp.servHome = true
|
||||
state.sp.form.home = ["1", "2", "3", "4", "5", "6"]
|
||||
const newState = applyAction(state, { type: 'incPunt', team: 'home' })
|
||||
expect(newState.sp.form.home).toEqual(["1", "2", "3", "4", "5", "6"])
|
||||
})
|
||||
|
||||
it('dovrebbe aggiornare la striscia per punto Home', () => {
|
||||
const s = applyAction(state, { type: 'incPunt', team: 'home' })
|
||||
expect(s.sp.striscia.home).toEqual([0, 1])
|
||||
expect(s.sp.striscia.guest).toEqual([0, " "])
|
||||
})
|
||||
|
||||
it('dovrebbe aggiornare la striscia per punto Guest', () => {
|
||||
const s = applyAction(state, { type: 'incPunt', team: 'guest' })
|
||||
expect(s.sp.striscia.guest).toEqual([0, 1])
|
||||
expect(s.sp.striscia.home).toEqual([0, " "])
|
||||
})
|
||||
|
||||
it('dovrebbe registrare lo storico servizio', () => {
|
||||
const s = applyAction(state, { type: 'incPunt', team: 'home' })
|
||||
expect(s.sp.storicoServizio).toHaveLength(1)
|
||||
expect(s.sp.storicoServizio[0]).toHaveProperty('servHome')
|
||||
expect(s.sp.storicoServizio[0]).toHaveProperty('cambioPalla')
|
||||
})
|
||||
|
||||
it('non dovrebbe incrementare i punti dopo vittoria', () => {
|
||||
state.sp.punt.home = 25
|
||||
state.sp.punt.guest = 23
|
||||
const s = applyAction(state, { type: 'incPunt', team: 'home' })
|
||||
expect(s.sp.punt.home).toBe(25)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vittoria Set', () => {
|
||||
// =============================================
|
||||
// DECREMENTO PUNTI (decPunt)
|
||||
// =============================================
|
||||
describe('decPunt', () => {
|
||||
it('dovrebbe annullare l\'ultimo punto Home', () => {
|
||||
const s1 = applyAction(state, { type: 'incPunt', team: 'home' })
|
||||
const s2 = applyAction(s1, { type: 'decPunt' })
|
||||
expect(s2.sp.punt.home).toBe(0)
|
||||
expect(s2.sp.punt.guest).toBe(0)
|
||||
})
|
||||
|
||||
it('dovrebbe annullare l\'ultimo punto Guest', () => {
|
||||
const s1 = applyAction(state, { type: 'incPunt', team: 'guest' })
|
||||
const s2 = applyAction(s1, { type: 'decPunt' })
|
||||
expect(s2.sp.punt.home).toBe(0)
|
||||
expect(s2.sp.punt.guest).toBe(0)
|
||||
})
|
||||
|
||||
it('non dovrebbe fare nulla sullo stato iniziale', () => {
|
||||
const s = applyAction(state, { type: 'decPunt' })
|
||||
expect(s.sp.punt.home).toBe(0)
|
||||
expect(s.sp.punt.guest).toBe(0)
|
||||
})
|
||||
|
||||
it('dovrebbe ripristinare il servizio dopo undo con cambio palla', () => {
|
||||
state.sp.servHome = true
|
||||
const s1 = applyAction(state, { type: 'incPunt', team: 'guest' })
|
||||
expect(s1.sp.servHome).toBe(false)
|
||||
const s2 = applyAction(s1, { type: 'decPunt' })
|
||||
expect(s2.sp.servHome).toBe(true)
|
||||
})
|
||||
|
||||
it('dovrebbe invertire la rotazione dopo undo con cambio palla', () => {
|
||||
state.sp.servHome = true
|
||||
state.sp.form.guest = ["1", "2", "3", "4", "5", "6"]
|
||||
const s1 = applyAction(state, { type: 'incPunt', team: 'guest' })
|
||||
expect(s1.sp.form.guest).toEqual(["2", "3", "4", "5", "6", "1"])
|
||||
const s2 = applyAction(s1, { type: 'decPunt' })
|
||||
expect(s2.sp.form.guest).toEqual(["1", "2", "3", "4", "5", "6"])
|
||||
})
|
||||
|
||||
it('dovrebbe ripristinare la striscia', () => {
|
||||
const s1 = applyAction(state, { type: 'incPunt', team: 'home' })
|
||||
const s2 = applyAction(s1, { type: 'decPunt' })
|
||||
expect(s2.sp.striscia.home).toEqual([0])
|
||||
})
|
||||
|
||||
it('dovrebbe gestire undo multipli in sequenza', () => {
|
||||
let s = state
|
||||
s = applyAction(s, { type: 'incPunt', team: 'home' })
|
||||
s = applyAction(s, { type: 'incPunt', team: 'guest' })
|
||||
s = applyAction(s, { type: 'incPunt', team: 'home' })
|
||||
expect(s.sp.punt.home).toBe(2)
|
||||
expect(s.sp.punt.guest).toBe(1)
|
||||
s = applyAction(s, { type: 'decPunt' })
|
||||
expect(s.sp.punt.home).toBe(1)
|
||||
s = applyAction(s, { type: 'decPunt' })
|
||||
expect(s.sp.punt.guest).toBe(0)
|
||||
s = applyAction(s, { type: 'decPunt' })
|
||||
expect(s.sp.punt.home).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// INCREMENTO SET (incSet)
|
||||
// =============================================
|
||||
describe('incSet', () => {
|
||||
it('dovrebbe incrementare il set Home', () => {
|
||||
const s = applyAction(state, { type: 'incSet', team: 'home' })
|
||||
expect(s.sp.set.home).toBe(1)
|
||||
})
|
||||
|
||||
it('dovrebbe incrementare il set Guest', () => {
|
||||
const s = applyAction(state, { type: 'incSet', team: 'guest' })
|
||||
expect(s.sp.set.guest).toBe(1)
|
||||
})
|
||||
|
||||
it('dovrebbe fare wrap da 2 a 0', () => {
|
||||
state.sp.set.home = 2
|
||||
const s = applyAction(state, { type: 'incSet', team: 'home' })
|
||||
expect(s.sp.set.home).toBe(0)
|
||||
})
|
||||
|
||||
it('dovrebbe incrementare da 1 a 2', () => {
|
||||
state.sp.set.home = 1
|
||||
const s = applyAction(state, { type: 'incSet', team: 'home' })
|
||||
expect(s.sp.set.home).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// CAMBIO PALLA (cambiaPalla)
|
||||
// =============================================
|
||||
describe('cambiaPalla', () => {
|
||||
it('dovrebbe invertire il servizio a 0-0', () => {
|
||||
expect(state.sp.servHome).toBe(true)
|
||||
const s = applyAction(state, { type: 'cambiaPalla' })
|
||||
expect(s.sp.servHome).toBe(false)
|
||||
})
|
||||
|
||||
it('dovrebbe tornare a Home con doppio toggle', () => {
|
||||
let s = applyAction(state, { type: 'cambiaPalla' })
|
||||
s = applyAction(s, { type: 'cambiaPalla' })
|
||||
expect(s.sp.servHome).toBe(true)
|
||||
})
|
||||
|
||||
it('non dovrebbe cambiare palla se il punteggio non è 0-0', () => {
|
||||
state.sp.punt.home = 1
|
||||
const s = applyAction(state, { type: 'cambiaPalla' })
|
||||
expect(s.sp.servHome).toBe(true)
|
||||
})
|
||||
|
||||
it('non dovrebbe cambiare palla se Guest ha punti', () => {
|
||||
state.sp.punt.guest = 3
|
||||
const s = applyAction(state, { type: 'cambiaPalla' })
|
||||
expect(s.sp.servHome).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// TOGGLE (toggleFormazione, toggleStriscia, toggleOrder)
|
||||
// =============================================
|
||||
describe('Toggle', () => {
|
||||
it('toggleFormazione: false → true', () => {
|
||||
expect(state.visuForm).toBe(false)
|
||||
const s = applyAction(state, { type: 'toggleFormazione' })
|
||||
expect(s.visuForm).toBe(true)
|
||||
})
|
||||
|
||||
it('toggleFormazione: true → false', () => {
|
||||
state.visuForm = true
|
||||
const s = applyAction(state, { type: 'toggleFormazione' })
|
||||
expect(s.visuForm).toBe(false)
|
||||
})
|
||||
|
||||
it('toggleStriscia: true → false', () => {
|
||||
expect(state.visuStriscia).toBe(true)
|
||||
const s = applyAction(state, { type: 'toggleStriscia' })
|
||||
expect(s.visuStriscia).toBe(false)
|
||||
})
|
||||
|
||||
it('toggleOrder: true → false', () => {
|
||||
expect(state.order).toBe(true)
|
||||
const s = applyAction(state, { type: 'toggleOrder' })
|
||||
expect(s.order).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// NOMI (setNomi)
|
||||
// =============================================
|
||||
describe('setNomi', () => {
|
||||
it('dovrebbe aggiornare entrambi i nomi', () => {
|
||||
const s = applyAction(state, { type: 'setNomi', home: 'Volley A', guest: 'Volley B' })
|
||||
expect(s.sp.nomi.home).toBe('Volley A')
|
||||
expect(s.sp.nomi.guest).toBe('Volley B')
|
||||
})
|
||||
|
||||
it('dovrebbe aggiornare solo il nome Home se guest è undefined', () => {
|
||||
const s = applyAction(state, { type: 'setNomi', home: 'Volley A' })
|
||||
expect(s.sp.nomi.home).toBe('Volley A')
|
||||
expect(s.sp.nomi.guest).toBe('Guest')
|
||||
})
|
||||
|
||||
it('dovrebbe aggiornare solo il nome Guest se home è undefined', () => {
|
||||
const s = applyAction(state, { type: 'setNomi', guest: 'Volley B' })
|
||||
expect(s.sp.nomi.home).toBe('Antoniana')
|
||||
expect(s.sp.nomi.guest).toBe('Volley B')
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// MODALITÀ (setModalita)
|
||||
// =============================================
|
||||
describe('setModalita', () => {
|
||||
it('dovrebbe cambiare in 2/3', () => {
|
||||
const s = applyAction(state, { type: 'setModalita', modalita: '2/3' })
|
||||
expect(s.modalitaPartita).toBe('2/3')
|
||||
})
|
||||
|
||||
it('dovrebbe cambiare in 3/5', () => {
|
||||
state.modalitaPartita = '2/3'
|
||||
const s = applyAction(state, { type: 'setModalita', modalita: '3/5' })
|
||||
expect(s.modalitaPartita).toBe('3/5')
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// FORMAZIONE (setFormazione)
|
||||
// =============================================
|
||||
describe('setFormazione', () => {
|
||||
it('dovrebbe sostituire la formazione Home', () => {
|
||||
const nuova = ["10", "11", "12", "13", "14", "15"]
|
||||
const s = applyAction(state, { type: 'setFormazione', team: 'home', form: nuova })
|
||||
expect(s.sp.form.home).toEqual(nuova)
|
||||
})
|
||||
|
||||
it('dovrebbe sostituire la formazione Guest', () => {
|
||||
const nuova = ["7", "8", "9", "10", "11", "12"]
|
||||
const s = applyAction(state, { type: 'setFormazione', team: 'guest', form: nuova })
|
||||
expect(s.sp.form.guest).toEqual(nuova)
|
||||
})
|
||||
|
||||
it('non dovrebbe modificare se manca team', () => {
|
||||
const s = applyAction(state, { type: 'setFormazione', form: ["7", "8", "9", "10", "11", "12"] })
|
||||
expect(s.sp.form.home).toEqual(["1", "2", "3", "4", "5", "6"])
|
||||
})
|
||||
|
||||
it('non dovrebbe modificare se manca form', () => {
|
||||
const s = applyAction(state, { type: 'setFormazione', team: 'home' })
|
||||
expect(s.sp.form.home).toEqual(["1", "2", "3", "4", "5", "6"])
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// CAMBI GIOCATORI (confermaCambi)
|
||||
// =============================================
|
||||
describe('confermaCambi', () => {
|
||||
it('dovrebbe effettuare una sostituzione valida', () => {
|
||||
state.sp.form.home = ["1", "2", "3", "4", "5", "6"]
|
||||
const s = applyAction(state, {
|
||||
type: 'confermaCambi',
|
||||
team: 'home',
|
||||
cambi: [{ in: "10", out: "3" }]
|
||||
})
|
||||
expect(s.sp.form.home).toContain("10")
|
||||
expect(s.sp.form.home).not.toContain("3")
|
||||
})
|
||||
|
||||
it('dovrebbe gestire doppia sostituzione', () => {
|
||||
state.sp.form.home = ["1", "2", "3", "4", "5", "6"]
|
||||
const s = applyAction(state, {
|
||||
type: 'confermaCambi',
|
||||
team: 'home',
|
||||
cambi: [
|
||||
{ in: "10", out: "1" },
|
||||
{ in: "11", out: "2" }
|
||||
]
|
||||
})
|
||||
expect(s.sp.form.home).toContain("10")
|
||||
expect(s.sp.form.home).toContain("11")
|
||||
expect(s.sp.form.home).not.toContain("1")
|
||||
expect(s.sp.form.home).not.toContain("2")
|
||||
})
|
||||
|
||||
it('non dovrebbe accettare input non numerico', () => {
|
||||
state.sp.form.home = ["1", "2", "3", "4", "5", "6"]
|
||||
const s = applyAction(state, {
|
||||
type: 'confermaCambi',
|
||||
team: 'home',
|
||||
cambi: [{ in: "abc", out: "1" }]
|
||||
})
|
||||
expect(s.sp.form.home).toEqual(["1", "2", "3", "4", "5", "6"])
|
||||
})
|
||||
|
||||
it('non dovrebbe accettare in == out', () => {
|
||||
const s = applyAction(state, {
|
||||
type: 'confermaCambi',
|
||||
team: 'home',
|
||||
cambi: [{ in: "1", out: "1" }]
|
||||
})
|
||||
expect(s.sp.form.home).toEqual(["1", "2", "3", "4", "5", "6"])
|
||||
})
|
||||
|
||||
it('non dovrebbe accettare giocatore IN già in formazione', () => {
|
||||
const s = applyAction(state, {
|
||||
type: 'confermaCambi',
|
||||
team: 'home',
|
||||
cambi: [{ in: "2", out: "1" }]
|
||||
})
|
||||
expect(s.sp.form.home).toEqual(["1", "2", "3", "4", "5", "6"])
|
||||
})
|
||||
|
||||
it('non dovrebbe accettare giocatore OUT non in formazione', () => {
|
||||
const s = applyAction(state, {
|
||||
type: 'confermaCambi',
|
||||
team: 'home',
|
||||
cambi: [{ in: "10", out: "99" }]
|
||||
})
|
||||
expect(s.sp.form.home).toEqual(["1", "2", "3", "4", "5", "6"])
|
||||
})
|
||||
|
||||
it('dovrebbe saltare cambi con campo vuoto', () => {
|
||||
const s = applyAction(state, {
|
||||
type: 'confermaCambi',
|
||||
team: 'home',
|
||||
cambi: [
|
||||
{ in: "", out: "" },
|
||||
{ in: "10", out: "1" }
|
||||
]
|
||||
})
|
||||
expect(s.sp.form.home).toContain("10")
|
||||
})
|
||||
|
||||
it('dovrebbe mantenere la posizione del giocatore sostituito', () => {
|
||||
state.sp.form.home = ["1", "2", "3", "4", "5", "6"]
|
||||
const s = applyAction(state, {
|
||||
type: 'confermaCambi',
|
||||
team: 'home',
|
||||
cambi: [{ in: "10", out: "3" }]
|
||||
})
|
||||
expect(s.sp.form.home[2]).toBe("10")
|
||||
})
|
||||
|
||||
it('dovrebbe gestire cambi sequenziali che dipendono l\'uno dall\'altro', () => {
|
||||
// Sostituisci 1→10, poi 10→20 (il secondo dipende dal risultato del primo)
|
||||
state.sp.form.home = ["1", "2", "3", "4", "5", "6"]
|
||||
const s = applyAction(state, {
|
||||
type: 'confermaCambi',
|
||||
team: 'home',
|
||||
cambi: [
|
||||
{ in: "10", out: "1" },
|
||||
{ in: "20", out: "10" }
|
||||
]
|
||||
})
|
||||
expect(s.sp.form.home).toContain("20")
|
||||
expect(s.sp.form.home).not.toContain("1")
|
||||
expect(s.sp.form.home).not.toContain("10")
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// VITTORIA SET (checkVittoria)
|
||||
// =============================================
|
||||
describe('checkVittoria', () => {
|
||||
it('non dovrebbe dare vittoria a 24-24', () => {
|
||||
state.sp.punt.home = 24
|
||||
state.sp.punt.guest = 24
|
||||
@@ -64,28 +478,182 @@ describe('Game Logic (gameState.js)', () => {
|
||||
expect(checkVittoria(state)).toBe(true)
|
||||
})
|
||||
|
||||
it('dovrebbe richiedere 2 punti di scarto (26-24)', () => {
|
||||
it('non dovrebbe dare vittoria a 25-24 (serve 2 punti di scarto)', () => {
|
||||
state.sp.punt.home = 25
|
||||
state.sp.punt.guest = 24
|
||||
expect(checkVittoria(state)).toBe(false)
|
||||
})
|
||||
|
||||
it('dovrebbe dare vittoria a 26-24', () => {
|
||||
state.sp.punt.home = 26
|
||||
state.sp.punt.guest = 24
|
||||
expect(checkVittoria(state)).toBe(true)
|
||||
})
|
||||
|
||||
it('dovrebbe dare vittoria Guest a 25-20', () => {
|
||||
state.sp.punt.home = 20
|
||||
state.sp.punt.guest = 25
|
||||
expect(checkVittoria(state)).toBe(true)
|
||||
})
|
||||
|
||||
it('dovrebbe dare vittoria ai vantaggi (30-28)', () => {
|
||||
state.sp.punt.home = 30
|
||||
state.sp.punt.guest = 28
|
||||
expect(checkVittoria(state)).toBe(true)
|
||||
})
|
||||
|
||||
it('non dovrebbe dare vittoria ai vantaggi senza scarto (28-27)', () => {
|
||||
state.sp.punt.home = 28
|
||||
state.sp.punt.guest = 27
|
||||
expect(checkVittoria(state)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Reset', () => {
|
||||
it('dovrebbe resettare tutto a zero', () => {
|
||||
state.sp.punt.home = 10
|
||||
// =============================================
|
||||
// SET DECISIVO (15 punti)
|
||||
// =============================================
|
||||
describe('Set decisivo', () => {
|
||||
it('modalità 3/5: set decisivo dopo 4 set totali → vittoria a 15', () => {
|
||||
state.modalitaPartita = "3/5"
|
||||
state.sp.set.home = 2
|
||||
state.sp.set.guest = 2
|
||||
state.sp.punt.home = 15
|
||||
state.sp.punt.guest = 10
|
||||
expect(checkVittoria(state)).toBe(true)
|
||||
})
|
||||
|
||||
it('modalità 3/5: non vittoria a 14-10 nel set decisivo', () => {
|
||||
state.modalitaPartita = "3/5"
|
||||
state.sp.set.home = 2
|
||||
state.sp.set.guest = 2
|
||||
state.sp.punt.home = 14
|
||||
state.sp.punt.guest = 10
|
||||
expect(checkVittoria(state)).toBe(false)
|
||||
})
|
||||
|
||||
it('modalità 3/5: vittoria a 15-13 nel set decisivo', () => {
|
||||
state.modalitaPartita = "3/5"
|
||||
state.sp.set.home = 2
|
||||
state.sp.set.guest = 2
|
||||
state.sp.punt.home = 15
|
||||
state.sp.punt.guest = 13
|
||||
expect(checkVittoria(state)).toBe(true)
|
||||
})
|
||||
|
||||
it('modalità 3/5: non vittoria a 15-14 nel set decisivo (serve scarto)', () => {
|
||||
state.modalitaPartita = "3/5"
|
||||
state.sp.set.home = 2
|
||||
state.sp.set.guest = 2
|
||||
state.sp.punt.home = 15
|
||||
state.sp.punt.guest = 14
|
||||
expect(checkVittoria(state)).toBe(false)
|
||||
})
|
||||
|
||||
it('modalità 3/5: vittoria a 16-14 nel set decisivo', () => {
|
||||
state.modalitaPartita = "3/5"
|
||||
state.sp.set.home = 2
|
||||
state.sp.set.guest = 2
|
||||
state.sp.punt.home = 16
|
||||
state.sp.punt.guest = 14
|
||||
expect(checkVittoria(state)).toBe(true)
|
||||
})
|
||||
|
||||
it('modalità 2/3: set decisivo dopo 2 set totali → vittoria a 15', () => {
|
||||
state.modalitaPartita = "2/3"
|
||||
state.sp.set.home = 1
|
||||
state.sp.set.guest = 1
|
||||
state.sp.punt.home = 15
|
||||
state.sp.punt.guest = 10
|
||||
expect(checkVittoria(state)).toBe(true)
|
||||
})
|
||||
|
||||
const newState = applyAction(state, { type: 'resetta' })
|
||||
it('modalità 2/3: non vittoria a 24-20 nel set decisivo (soglia 15)', () => {
|
||||
state.modalitaPartita = "2/3"
|
||||
state.sp.set.home = 1
|
||||
state.sp.set.guest = 1
|
||||
state.sp.punt.home = 14
|
||||
state.sp.punt.guest = 10
|
||||
expect(checkVittoria(state)).toBe(false)
|
||||
})
|
||||
|
||||
expect(newState.sp.punt.home).toBe(0)
|
||||
expect(newState.sp.set.home).toBe(0) // Nota: il reset attuale resetta solo i punti o tutto?
|
||||
// Controllo il codice: "s.sp.punt.home = 0... s.sp.storicoServizio = []"
|
||||
// Attenzione: nel codice originale `resetta` NON sembra resettare i set!
|
||||
// Verifichiamo il comportamento attuale del codice.
|
||||
it('modalità 2/3: set non decisivo (1-0) → soglia 25', () => {
|
||||
state.modalitaPartita = "2/3"
|
||||
state.sp.set.home = 1
|
||||
state.sp.set.guest = 0
|
||||
state.sp.punt.home = 15
|
||||
state.sp.punt.guest = 10
|
||||
expect(checkVittoria(state)).toBe(false)
|
||||
})
|
||||
|
||||
it('modalità 3/5: set non decisivo (2-1) → soglia 25', () => {
|
||||
state.modalitaPartita = "3/5"
|
||||
state.sp.set.home = 2
|
||||
state.sp.set.guest = 1
|
||||
state.sp.punt.home = 15
|
||||
state.sp.punt.guest = 10
|
||||
expect(checkVittoria(state)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// RESET
|
||||
// =============================================
|
||||
describe('Reset', () => {
|
||||
it('dovrebbe resettare punti e set a zero', () => {
|
||||
state.sp.punt.home = 10
|
||||
state.sp.punt.guest = 8
|
||||
state.sp.set.home = 1
|
||||
state.sp.set.guest = 1
|
||||
const s = applyAction(state, { type: 'resetta' })
|
||||
expect(s.sp.punt.home).toBe(0)
|
||||
expect(s.sp.punt.guest).toBe(0)
|
||||
expect(s.sp.set.home).toBe(0)
|
||||
expect(s.sp.set.guest).toBe(0)
|
||||
})
|
||||
|
||||
it('dovrebbe resettare formazioni a default', () => {
|
||||
state.sp.form.home = ["10", "11", "12", "13", "14", "15"]
|
||||
const s = applyAction(state, { type: 'resetta' })
|
||||
expect(s.sp.form.home).toEqual(["1", "2", "3", "4", "5", "6"])
|
||||
expect(s.sp.form.guest).toEqual(["1", "2", "3", "4", "5", "6"])
|
||||
})
|
||||
|
||||
it('dovrebbe resettare la striscia', () => {
|
||||
state.sp.striscia = { home: [0, 1, 2, 3], guest: [0, " ", " ", 1] }
|
||||
const s = applyAction(state, { type: 'resetta' })
|
||||
expect(s.sp.striscia.home).toEqual([0])
|
||||
expect(s.sp.striscia.guest).toEqual([0])
|
||||
})
|
||||
|
||||
it('dovrebbe resettare lo storico servizio', () => {
|
||||
state.sp.storicoServizio = [{ servHome: true, cambioPalla: false }]
|
||||
const s = applyAction(state, { type: 'resetta' })
|
||||
expect(s.sp.storicoServizio).toEqual([])
|
||||
})
|
||||
|
||||
it('dovrebbe impostare visuForm a false', () => {
|
||||
state.visuForm = true
|
||||
const s = applyAction(state, { type: 'resetta' })
|
||||
expect(s.visuForm).toBe(false)
|
||||
})
|
||||
|
||||
it('dovrebbe mantenere nomi e modalità', () => {
|
||||
state.sp.nomi.home = "Squadra A"
|
||||
state.modalitaPartita = "2/3"
|
||||
const s = applyAction(state, { type: 'resetta' })
|
||||
expect(s.sp.nomi.home).toBe("Squadra A")
|
||||
expect(s.modalitaPartita).toBe("2/3")
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// AZIONE SCONOSCIUTA
|
||||
// =============================================
|
||||
describe('Azione sconosciuta', () => {
|
||||
it('dovrebbe restituire lo stato invariato con azione non riconosciuta', () => {
|
||||
const s = applyAction(state, { type: 'azioneInesistente' })
|
||||
expect(s.sp.punt.home).toBe(0)
|
||||
expect(s.sp.punt.guest).toBe(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,22 +1,148 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { printServerInfo } from '../../src/server-utils.js'
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest'
|
||||
import * as os from 'os'
|
||||
|
||||
// Mocking console.log per evitare output sporchi durante i test
|
||||
import { vi } from 'vitest'
|
||||
vi.mock('os', async (importOriginal) => {
|
||||
return {
|
||||
...await importOriginal(),
|
||||
networkInterfaces: vi.fn(() => ({}))
|
||||
}
|
||||
})
|
||||
|
||||
import { getNetworkIPs, printServerInfo } from '../../src/server-utils.js'
|
||||
|
||||
describe('Server Utils', () => {
|
||||
it('printServerInfo dovrebbe stampare le porte corrette', () => {
|
||||
const consoleSpy = vi.spyOn(console, 'log')
|
||||
printServerInfo(3000, 3001)
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalled()
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
// Unisce tutti i messaggi loggati in un'unica stringa per facilitare la ricerca
|
||||
const allLogs = consoleSpy.mock.calls.map(args => args[0]).join('\n')
|
||||
// =============================================
|
||||
// getNetworkIPs
|
||||
// =============================================
|
||||
describe('getNetworkIPs', () => {
|
||||
it('dovrebbe restituire indirizzi IPv4 non-loopback', () => {
|
||||
os.networkInterfaces.mockReturnValue({
|
||||
eth0: [
|
||||
{ family: 'IPv4', internal: false, address: '192.168.1.100' }
|
||||
]
|
||||
})
|
||||
expect(getNetworkIPs()).toEqual(['192.168.1.100'])
|
||||
})
|
||||
|
||||
expect(allLogs).toContain('3000')
|
||||
expect(allLogs).toContain('3001')
|
||||
it('dovrebbe escludere indirizzi loopback (internal)', () => {
|
||||
os.networkInterfaces.mockReturnValue({
|
||||
lo: [
|
||||
{ family: 'IPv4', internal: true, address: '127.0.0.1' }
|
||||
],
|
||||
eth0: [
|
||||
{ family: 'IPv4', internal: false, address: '192.168.1.100' }
|
||||
]
|
||||
})
|
||||
const ips = getNetworkIPs()
|
||||
expect(ips).not.toContain('127.0.0.1')
|
||||
expect(ips).toContain('192.168.1.100')
|
||||
})
|
||||
|
||||
consoleSpy.mockRestore()
|
||||
it('dovrebbe escludere indirizzi IPv6', () => {
|
||||
os.networkInterfaces.mockReturnValue({
|
||||
eth0: [
|
||||
{ family: 'IPv6', internal: false, address: 'fe80::1' },
|
||||
{ family: 'IPv4', internal: false, address: '192.168.1.100' }
|
||||
]
|
||||
})
|
||||
const ips = getNetworkIPs()
|
||||
expect(ips).toEqual(['192.168.1.100'])
|
||||
})
|
||||
|
||||
it('dovrebbe escludere bridge Docker 172.17.x.x', () => {
|
||||
os.networkInterfaces.mockReturnValue({
|
||||
docker0: [
|
||||
{ family: 'IPv4', internal: false, address: '172.17.0.1' }
|
||||
],
|
||||
eth0: [
|
||||
{ family: 'IPv4', internal: false, address: '10.0.0.5' }
|
||||
]
|
||||
})
|
||||
const ips = getNetworkIPs()
|
||||
expect(ips).not.toContain('172.17.0.1')
|
||||
expect(ips).toContain('10.0.0.5')
|
||||
})
|
||||
|
||||
it('dovrebbe escludere bridge Docker 172.18.x.x', () => {
|
||||
os.networkInterfaces.mockReturnValue({
|
||||
br0: [
|
||||
{ family: 'IPv4', internal: false, address: '172.18.0.1' }
|
||||
]
|
||||
})
|
||||
expect(getNetworkIPs()).toEqual([])
|
||||
})
|
||||
|
||||
it('dovrebbe restituire array vuoto se nessuna interfaccia disponibile', () => {
|
||||
os.networkInterfaces.mockReturnValue({})
|
||||
expect(getNetworkIPs()).toEqual([])
|
||||
})
|
||||
|
||||
it('dovrebbe restituire più indirizzi da interfacce diverse', () => {
|
||||
os.networkInterfaces.mockReturnValue({
|
||||
eth0: [
|
||||
{ family: 'IPv4', internal: false, address: '192.168.1.100' }
|
||||
],
|
||||
wlan0: [
|
||||
{ family: 'IPv4', internal: false, address: '192.168.1.101' }
|
||||
]
|
||||
})
|
||||
const ips = getNetworkIPs()
|
||||
expect(ips).toHaveLength(2)
|
||||
expect(ips).toContain('192.168.1.100')
|
||||
expect(ips).toContain('192.168.1.101')
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// printServerInfo
|
||||
// =============================================
|
||||
describe('printServerInfo', () => {
|
||||
it('dovrebbe stampare le porte corrette (default)', () => {
|
||||
os.networkInterfaces.mockReturnValue({})
|
||||
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
printServerInfo()
|
||||
const allLogs = consoleSpy.mock.calls.map(c => c[0]).join('\n')
|
||||
expect(allLogs).toContain('5173')
|
||||
expect(allLogs).toContain('3001')
|
||||
consoleSpy.mockRestore()
|
||||
})
|
||||
|
||||
it('dovrebbe stampare le porte personalizzate', () => {
|
||||
os.networkInterfaces.mockReturnValue({})
|
||||
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
printServerInfo(3000, 4000)
|
||||
const allLogs = consoleSpy.mock.calls.map(c => c[0]).join('\n')
|
||||
expect(allLogs).toContain('3000')
|
||||
expect(allLogs).toContain('4000')
|
||||
consoleSpy.mockRestore()
|
||||
})
|
||||
|
||||
it('dovrebbe mostrare gli URL remoti se ci sono IP di rete', () => {
|
||||
os.networkInterfaces.mockReturnValue({
|
||||
eth0: [
|
||||
{ family: 'IPv4', internal: false, address: '192.168.1.50' }
|
||||
]
|
||||
})
|
||||
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
printServerInfo(3000, 3001)
|
||||
const allLogs = consoleSpy.mock.calls.map(c => c[0]).join('\n')
|
||||
expect(allLogs).toContain('192.168.1.50')
|
||||
expect(allLogs).toContain('remoti')
|
||||
consoleSpy.mockRestore()
|
||||
})
|
||||
|
||||
it('non dovrebbe mostrare sezione remoti se nessun IP di rete', () => {
|
||||
os.networkInterfaces.mockReturnValue({})
|
||||
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
printServerInfo(3000, 3001)
|
||||
const allLogs = consoleSpy.mock.calls.map(c => c[0]).join('\n')
|
||||
expect(allLogs).not.toContain('remoti')
|
||||
consoleSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user