eb37f8319f
La suite era allineata a una vecchia forma dello stato (sp.punt/sp.set/ sp.servHome) e a una vecchia architettura e2e (controller su :3001). Baseline iniziale: 77/170 test Vitest falliti. Vitest (ora 212/212 verdi): - gameState.test.js: riscritto con helper che derivano punteggio/set/ servizio dalla striscia; aggiunto blocco formInizio - server-utils.js: getNetworkIPs accetta interfacce iniettabili e printServerInfo accetta gli IP iniettabili (deterministico anche su WSL); filtro LAN unificato (esclude 127./169.254./172.) - websocket + stress: punteggio letto via punteggio(striscia) - ControllerPage/DisplayPage: forzato layout mobile (viewport portrait), punteggi impostati via striscia; aggiunto test bottone REFERTO - nuovi: referto.test.js, persist.test.js (mock fs), wsMixin.test.js, integration/server.test.js (routing) Refactor di supporto: - referto.js: estratta buildRefertoHtml(state, now) pura; generaReferto resta wrapper con window.open/print - server.js: estratti createApp()/startServer(); avvio solo se entrypoint e2e (migrazione parziale, NON ancora verificata verde): - tutti i riferimenti controller :3001 -> :3000/controller - forzato viewport portrait sul controller prima del goto - reset helper: chiude il dialog di configurazione che doReset apre - game-simulation: gestione del dialog SET VINTO automatico a 25
175 lines
6.2 KiB
JavaScript
175 lines
6.2 KiB
JavaScript
// @vitest-environment happy-dom
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
import { mount } from '@vue/test-utils'
|
|
import { createWsMixin } from '../../src/wsMixin.js'
|
|
import { createInitialState } from '../../src/gameState.js'
|
|
|
|
// WebSocket mock controllabile: i gestori (onopen/onmessage/onclose) vengono
|
|
// assegnati dal mixin e li invochiamo manualmente nei test.
|
|
class MockWebSocket {
|
|
static CONNECTING = 0
|
|
static OPEN = 1
|
|
static CLOSING = 2
|
|
static CLOSED = 3
|
|
constructor(url) {
|
|
this.url = url
|
|
this.readyState = MockWebSocket.CONNECTING
|
|
this.send = vi.fn()
|
|
this.close = vi.fn()
|
|
MockWebSocket.instances.push(this)
|
|
}
|
|
}
|
|
MockWebSocket.instances = []
|
|
|
|
vi.stubGlobal('WebSocket', MockWebSocket)
|
|
|
|
// Monta un componente che usa il mixin. `extra` permette di aggiungere hook.
|
|
function mountWith(role = 'controller', extra = {}) {
|
|
const Comp = {
|
|
mixins: [createWsMixin(role)],
|
|
template: '<div></div>',
|
|
...extra,
|
|
}
|
|
return mount(Comp)
|
|
}
|
|
|
|
function ultimaWs() {
|
|
return MockWebSocket.instances.at(-1)
|
|
}
|
|
|
|
describe('createWsMixin (wsMixin.js)', () => {
|
|
beforeEach(() => {
|
|
MockWebSocket.instances = []
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
describe('computed derivati', () => {
|
|
it('punt/servHome/set delegano alle funzioni pure sulla striscia', async () => {
|
|
const wrapper = mountWith()
|
|
wrapper.vm.state.sp.striscia = [
|
|
{ serv: 'h', ris: 'h', vinc: 'h' },
|
|
{ serv: 'h', ris: 'h'.repeat(10) + 'g'.repeat(8), vinc: null },
|
|
]
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.vm.punt).toEqual({ home: 10, guest: 8 })
|
|
expect(wrapper.vm.set).toEqual({ home: 1, guest: 0 })
|
|
// ultimo punto 'g' → serve guest
|
|
expect(wrapper.vm.servHome).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('connessione', () => {
|
|
it('apre una WebSocket verso /ws al mount', () => {
|
|
mountWith()
|
|
const ws = ultimaWs()
|
|
expect(ws).toBeDefined()
|
|
expect(ws.url).toMatch(/^ws:\/\/.+\/ws$/)
|
|
})
|
|
|
|
it('invia il messaggio register all\'apertura', () => {
|
|
const wrapper = mountWith('controller')
|
|
const ws = ultimaWs()
|
|
ws.readyState = MockWebSocket.OPEN
|
|
ws.onopen()
|
|
expect(ws.send).toHaveBeenCalledTimes(1)
|
|
const msg = JSON.parse(ws.send.mock.calls[0][0])
|
|
expect(msg).toEqual({ type: 'register', role: 'controller' })
|
|
expect(wrapper.vm.wsConnected).toBe(true)
|
|
})
|
|
|
|
it('un messaggio "state" aggiorna lo stato locale', () => {
|
|
const wrapper = mountWith()
|
|
const ws = ultimaWs()
|
|
const nuovo = createInitialState()
|
|
nuovo.sp.nomi.home = 'Nuova Squadra'
|
|
ws.onmessage({ data: JSON.stringify({ type: 'state', state: nuovo }) })
|
|
expect(wrapper.vm.state.sp.nomi.home).toBe('Nuova Squadra')
|
|
})
|
|
|
|
it('un messaggio non-state invoca l\'hook onWsMessage', () => {
|
|
const onWsMessage = vi.fn()
|
|
const wrapper = mountWith('display', { methods: { onWsMessage } })
|
|
const ws = ultimaWs()
|
|
ws.onmessage({ data: JSON.stringify({ type: 'speak', text: 'ciao' }) })
|
|
expect(onWsMessage).toHaveBeenCalledWith({ type: 'speak', text: 'ciao' })
|
|
})
|
|
})
|
|
|
|
describe('riconnessione', () => {
|
|
it('scheduleReconnect usa backoff esponenziale con cap a 30s', () => {
|
|
const wrapper = mountWith()
|
|
const delays = []
|
|
const spy = vi.spyOn(globalThis, 'setTimeout').mockImplementation(() => 123)
|
|
// intercetta i delay leggendoli dalle chiamate
|
|
spy.mockImplementation((_fn, d) => { delays.push(d); return 123 })
|
|
|
|
wrapper.vm.reconnectAttempts = 0
|
|
wrapper.vm.reconnectTimeout = null
|
|
wrapper.vm.scheduleReconnect() // 1000
|
|
wrapper.vm.reconnectTimeout = null
|
|
wrapper.vm.scheduleReconnect() // 2000
|
|
wrapper.vm.reconnectTimeout = null
|
|
wrapper.vm.scheduleReconnect() // 4000
|
|
expect(delays).toEqual([1000, 2000, 4000])
|
|
|
|
// attempts alto → cap a 30000
|
|
delays.length = 0
|
|
wrapper.vm.reconnectAttempts = 20
|
|
wrapper.vm.reconnectTimeout = null
|
|
wrapper.vm.scheduleReconnect()
|
|
expect(delays[0]).toBe(30000)
|
|
|
|
spy.mockRestore()
|
|
})
|
|
|
|
it('non riconnette su chiusura pulita (1000/1001)', () => {
|
|
const wrapper = mountWith()
|
|
const ws = ultimaWs()
|
|
const spy = vi.spyOn(wrapper.vm, 'scheduleReconnect')
|
|
ws.onclose({ code: 1000 })
|
|
ws.onclose({ code: 1001 })
|
|
expect(spy).not.toHaveBeenCalled()
|
|
expect(wrapper.vm.wsConnected).toBe(false)
|
|
})
|
|
|
|
it('riconnette su chiusura anomala (es. 1006)', () => {
|
|
const wrapper = mountWith()
|
|
const ws = ultimaWs()
|
|
const spy = vi.spyOn(wrapper.vm, 'scheduleReconnect')
|
|
ws.onclose({ code: 1006 })
|
|
expect(spy).toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
describe('sendWs', () => {
|
|
it('ritorna false se non connesso', () => {
|
|
const wrapper = mountWith()
|
|
wrapper.vm.wsConnected = false
|
|
expect(wrapper.vm.sendWs({ type: 'action' })).toBe(false)
|
|
})
|
|
|
|
it('serializza e invia se connesso e aperto', () => {
|
|
const wrapper = mountWith()
|
|
const ws = ultimaWs()
|
|
ws.readyState = MockWebSocket.OPEN
|
|
wrapper.vm.wsConnected = true
|
|
const ok = wrapper.vm.sendWs({ type: 'action', action: { type: 'incPunt' } })
|
|
expect(ok).toBe(true)
|
|
const inviato = JSON.parse(ws.send.mock.calls.at(-1)[0])
|
|
expect(inviato.type).toBe('action')
|
|
})
|
|
})
|
|
|
|
describe('cleanup', () => {
|
|
it('beforeUnmount chiude la WebSocket', () => {
|
|
const wrapper = mountWith()
|
|
const ws = ultimaWs()
|
|
wrapper.unmount()
|
|
expect(ws.close).toHaveBeenCalled()
|
|
})
|
|
})
|
|
})
|