0b7cbd00f6
Aggiunge 65 test per ControllerPage.vue (layout esteso/landscape, popup automatico di set vinto con soglia decisiva, modal di configurazione, validazione completa dei cambi giocatore, modal time out con selezione video/ticker/countdown, cleanup dei listener) e 35 per DisplayPage.vue (countdown overlay, riallineamento video su riconnessione, fallback muto per autoplay bloccato, selezione voce per la sintesi vocale). Nessun bug riscontrato nei componenti.
537 lines
23 KiB
JavaScript
537 lines
23 KiB
JavaScript
// @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: () => []
|
|
})
|
|
|
|
// happy-dom non implementa la Web Speech API: stub minimo dell'utterance.
|
|
vi.stubGlobal('SpeechSynthesisUtterance', class {
|
|
constructor(text) {
|
|
this.text = text
|
|
this.voice = null
|
|
this.lang = ''
|
|
}
|
|
})
|
|
|
|
// happy-dom non implementa la riproduzione media: stub di play/pause/load
|
|
// così l'overlay time out può montare il <video> senza errori.
|
|
Object.defineProperty(HTMLMediaElement.prototype, 'play', { value: vi.fn().mockResolvedValue(undefined), writable: true })
|
|
Object.defineProperty(HTMLMediaElement.prototype, 'pause', { value: vi.fn(), writable: true })
|
|
Object.defineProperty(HTMLMediaElement.prototype, 'load', { value: vi.fn(), writable: true })
|
|
|
|
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()
|
|
// il punteggio si ricava dalla striscia: 15 punti home + 12 guest
|
|
wrapper.vm.state.sp.striscia.at(-1).ris = 'h'.repeat(15) + 'g'.repeat(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')
|
|
})
|
|
})
|
|
|
|
// =============================================
|
|
// OVERLAY TIME OUT
|
|
// =============================================
|
|
describe('Overlay time out', () => {
|
|
// Attiva il time out impostando lo stato come farebbe il server.
|
|
async function attivaTimeout(wrapper, { team = 'home', video = null, startedAt = Date.now() } = {}) {
|
|
wrapper.vm.state.timeoutTeam = team
|
|
wrapper.vm.state.timeoutVideo = video
|
|
wrapper.vm.state.timeoutStartedAt = startedAt
|
|
wrapper.vm.state.timeout = true
|
|
await wrapper.vm.$nextTick()
|
|
await wrapper.vm.$nextTick()
|
|
}
|
|
|
|
it('non mostra l\'overlay quando il time out non è attivo', () => {
|
|
const wrapper = mountDisplay()
|
|
expect(wrapper.find('.timeout-overlay').exists()).toBe(false)
|
|
})
|
|
|
|
it('senza video mostra il placeholder con il nome della squadra', async () => {
|
|
const wrapper = mountDisplay()
|
|
await attivaTimeout(wrapper, { team: 'home', video: null })
|
|
expect(wrapper.find('.timeout-overlay').exists()).toBe(true)
|
|
const placeholder = wrapper.find('.timeout-placeholder')
|
|
expect(placeholder.text()).toContain('TIME OUT')
|
|
expect(placeholder.text()).toContain('Antoniana')
|
|
expect(wrapper.find('.timeout-video').exists()).toBe(false)
|
|
})
|
|
|
|
it('con un video lo riproduce nell\'overlay', async () => {
|
|
const wrapper = mountDisplay()
|
|
await attivaTimeout(wrapper, { team: 'guest', video: 'a.mp4' })
|
|
const video = wrapper.find('.timeout-video')
|
|
expect(video.exists()).toBe(true)
|
|
expect(video.element.src).toContain('/spot/a.mp4')
|
|
expect(wrapper.find('.timeout-placeholder').exists()).toBe(false)
|
|
})
|
|
|
|
it('a fine video mostra il placeholder fino allo stop del server', async () => {
|
|
const wrapper = mountDisplay()
|
|
await attivaTimeout(wrapper, { video: 'a.mp4' })
|
|
const video = wrapper.find('.timeout-video')
|
|
video.element.onended()
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.find('.timeout-video').exists()).toBe(false)
|
|
expect(wrapper.find('.timeout-placeholder').exists()).toBe(true)
|
|
})
|
|
|
|
it('il countdown parte da timeoutStartedAt', async () => {
|
|
const wrapper = mountDisplay()
|
|
await attivaTimeout(wrapper, { startedAt: Date.now() - 5000 })
|
|
expect(wrapper.vm.timeoutRemaining).toBe(25)
|
|
expect(wrapper.find('.timeout-countdown').text()).toBe('00:25')
|
|
})
|
|
|
|
it('timeoutFading è true solo con video oltre i 30s negli ultimi istanti', async () => {
|
|
const wrapper = mountDisplay()
|
|
await attivaTimeout(wrapper, { video: 'a.mp4', startedAt: Date.now() - 29500 })
|
|
// video corto: mai dissolvenza
|
|
wrapper.vm.spotVideoDuration = 20
|
|
expect(wrapper.vm.timeoutFading).toBe(false)
|
|
// video più lungo dei 30s: dissolvenza nell'ultimo secondo
|
|
wrapper.vm.spotVideoDuration = 45
|
|
expect(wrapper.vm.timeoutRemaining).toBe(1)
|
|
expect(wrapper.vm.timeoutFading).toBe(true)
|
|
})
|
|
|
|
it('alla fine del time out l\'overlay scompare', async () => {
|
|
const wrapper = mountDisplay()
|
|
await attivaTimeout(wrapper)
|
|
wrapper.vm.state.timeout = false
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.find('.timeout-overlay').exists()).toBe(false)
|
|
})
|
|
})
|
|
|
|
// =============================================
|
|
// ORDINE GUEST/HOME CON FORMAZIONE
|
|
// =============================================
|
|
describe('Ordine guest/home con formazione visibile', () => {
|
|
it('order=false e visuForm=true mostra punteggio inline e formazioni nell\'ordine guest poi home', async () => {
|
|
const wrapper = mountDisplay()
|
|
wrapper.vm.state.order = false
|
|
wrapper.vm.state.visuForm = true
|
|
wrapper.vm.state.sp.striscia.at(-1).ris = 'h'.repeat(3) + 'g'.repeat(2)
|
|
await wrapper.vm.$nextTick()
|
|
const scores = wrapper.findAll('.score-inline')
|
|
expect(scores[0].text()).toBe('2') // guest per primo
|
|
expect(scores[1].text()).toBe('3') // home per secondo
|
|
const formDivs = wrapper.findAll('.formdiv')
|
|
expect(formDivs).toHaveLength(12)
|
|
})
|
|
})
|
|
|
|
// =============================================
|
|
// MONTAGGIO: FULLSCREEN E TIME OUT GIÀ ATTIVO
|
|
// =============================================
|
|
describe('Comportamento al mount', () => {
|
|
it('su dispositivo mobile richiede la fullscreen', () => {
|
|
const uaSpy = vi.spyOn(navigator, 'userAgent', 'get').mockReturnValue('Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)')
|
|
const fsSpy = vi.fn()
|
|
document.documentElement.requestFullscreen = fsSpy
|
|
const wrapper = mountDisplay()
|
|
expect(fsSpy).toHaveBeenCalled()
|
|
wrapper.unmount()
|
|
uaSpy.mockRestore()
|
|
})
|
|
|
|
it('non richiede la fullscreen su desktop', () => {
|
|
const fsSpy = vi.fn()
|
|
document.documentElement.requestFullscreen = fsSpy
|
|
const wrapper = mountDisplay()
|
|
expect(fsSpy).not.toHaveBeenCalled()
|
|
wrapper.unmount()
|
|
})
|
|
|
|
it('ignora l\'errore se requestFullscreen lancia un\'eccezione', () => {
|
|
const uaSpy = vi.spyOn(navigator, 'userAgent', 'get').mockReturnValue('Mozilla/5.0 (Linux; Android 10)')
|
|
document.documentElement.requestFullscreen = () => { throw new Error('non consentito') }
|
|
expect(() => mountDisplay()).not.toThrow()
|
|
uaSpy.mockRestore()
|
|
})
|
|
|
|
it('se il time out è già attivo al mount avvia subito la modalità time out', async () => {
|
|
const wrapper = mount(DisplayPage, {
|
|
global: { stubs: { 'w-app': true } },
|
|
data() {
|
|
const state = {
|
|
order: true, visuForm: false, visuStriscia: true,
|
|
timeout: true, timeoutTeam: 'home', timeoutVideo: null,
|
|
timeoutStartedAt: Date.now(), modalitaPartita: '3/5',
|
|
sp: {
|
|
striscia: [{ serv: 'h', ris: '', vinc: null }],
|
|
nomi: { home: 'Antoniana', guest: 'Guest' },
|
|
form: { home: ['1', '2', '3', '4', '5', '6'], guest: ['1', '2', '3', '4', '5', '6'] },
|
|
},
|
|
}
|
|
return { state }
|
|
}
|
|
})
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.vm.mostraPlaceholder).toBe(true)
|
|
expect(wrapper.find('.timeout-overlay').exists()).toBe(true)
|
|
})
|
|
})
|
|
|
|
// =============================================
|
|
// TICKER COUNTDOWN E CASI LIMITE
|
|
// =============================================
|
|
describe('Ticker del countdown', () => {
|
|
it('il ticker aggiorna nowTick periodicamente durante il time out', async () => {
|
|
const wrapper = mountDisplay()
|
|
wrapper.vm.state.timeoutTeam = 'home'
|
|
wrapper.vm.state.timeoutStartedAt = Date.now()
|
|
wrapper.vm.state.timeout = true
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.vm.timeoutRemaining).toBe(30)
|
|
vi.advanceTimersByTime(1000)
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.vm.timeoutRemaining).toBeLessThan(30)
|
|
})
|
|
|
|
it('timeoutRemaining resta 30 se manca timeoutStartedAt', async () => {
|
|
const wrapper = mountDisplay()
|
|
wrapper.vm.state.timeoutTeam = 'home'
|
|
wrapper.vm.state.timeoutStartedAt = null
|
|
wrapper.vm.state.timeout = true
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.vm.timeoutRemaining).toBe(30)
|
|
})
|
|
|
|
it('smontare il componente durante il time out ferma il ticker senza errori', async () => {
|
|
const wrapper = mountDisplay()
|
|
wrapper.vm.state.timeoutTeam = 'home'
|
|
wrapper.vm.state.timeoutStartedAt = Date.now()
|
|
wrapper.vm.state.timeout = true
|
|
await wrapper.vm.$nextTick()
|
|
expect(() => wrapper.unmount()).not.toThrow()
|
|
})
|
|
|
|
it('stricciaStrip non fallisce se la striscia del set corrente è assente', async () => {
|
|
const wrapper = mountDisplay()
|
|
wrapper.vm.state.sp.striscia = []
|
|
expect(wrapper.vm.stricciaStrip).toEqual({ home: [], guest: [] })
|
|
// ripristina uno stato valido prima che Vue ricalcoli gli altri computed (punt/servHome)
|
|
wrapper.vm.state.sp.striscia = [{ serv: 'h', ris: '', vinc: null }]
|
|
await wrapper.vm.$nextTick()
|
|
})
|
|
})
|
|
|
|
// =============================================
|
|
// FINE TIME OUT: STOP VIDEO E FALLBACK MUTO
|
|
// =============================================
|
|
describe('Stop del time out e riproduzione video', () => {
|
|
async function attivaTimeout(wrapper, { team = 'home', video = null, startedAt = Date.now() } = {}) {
|
|
wrapper.vm.state.timeoutTeam = team
|
|
wrapper.vm.state.timeoutVideo = video
|
|
wrapper.vm.state.timeoutStartedAt = startedAt
|
|
wrapper.vm.state.timeout = true
|
|
await wrapper.vm.$nextTick()
|
|
await wrapper.vm.$nextTick()
|
|
}
|
|
|
|
it('allo stop del time out con video in corso mette in pausa e resetta il video', async () => {
|
|
const wrapper = mountDisplay()
|
|
await attivaTimeout(wrapper, { video: 'a.mp4' })
|
|
const video = wrapper.find('.timeout-video').element
|
|
const pauseSpy = vi.spyOn(video, 'pause')
|
|
wrapper.vm.state.timeout = false
|
|
await wrapper.vm.$nextTick()
|
|
expect(pauseSpy).toHaveBeenCalled()
|
|
})
|
|
|
|
it('a metà riproduzione si riallinea al punto corretto (riconnessione)', async () => {
|
|
const wrapper = mountDisplay()
|
|
await attivaTimeout(wrapper, { video: 'a.mp4', startedAt: Date.now() - 10000 })
|
|
const video = wrapper.find('.timeout-video').element
|
|
Object.defineProperty(video, 'duration', { value: 60, configurable: true })
|
|
video.onloadedmetadata()
|
|
expect(video.currentTime).toBeCloseTo(10, 0)
|
|
})
|
|
|
|
it('senza timeoutStartedAt non tenta alcun riallineamento (trascorsi=0)', async () => {
|
|
const wrapper = mountDisplay()
|
|
await attivaTimeout(wrapper, { video: 'a.mp4', startedAt: null })
|
|
const video = wrapper.find('.timeout-video').element
|
|
Object.defineProperty(video, 'duration', { value: 60, configurable: true })
|
|
video.currentTime = 0
|
|
video.onloadedmetadata()
|
|
expect(video.currentTime).toBe(0)
|
|
})
|
|
|
|
it('non si riallinea se il tempo trascorso supera la durata del video', async () => {
|
|
const wrapper = mountDisplay()
|
|
await attivaTimeout(wrapper, { video: 'a.mp4', startedAt: Date.now() - 10000 })
|
|
const video = wrapper.find('.timeout-video').element
|
|
Object.defineProperty(video, 'duration', { value: 5, configurable: true })
|
|
video.currentTime = 0
|
|
video.onloadedmetadata()
|
|
expect(video.currentTime).toBe(0)
|
|
})
|
|
|
|
it('caricaSpotCorrente non fa nulla se manca il ref del video o il video del time out', () => {
|
|
const wrapper = mountDisplay()
|
|
// nessun overlay attivo: $refs.spotVideo non esiste
|
|
expect(() => wrapper.vm.caricaSpotCorrente()).not.toThrow()
|
|
expect(wrapper.vm.spotVideoDuration).toBe(null)
|
|
})
|
|
|
|
it('playSpot non fa nulla se manca il ref del video', () => {
|
|
const wrapper = mountDisplay()
|
|
expect(() => wrapper.vm.playSpot()).not.toThrow()
|
|
})
|
|
|
|
it('se l\'autoplay con audio viene bloccato ripiega sul muto', async () => {
|
|
const wrapper = mountDisplay()
|
|
HTMLMediaElement.prototype.play = vi.fn()
|
|
.mockRejectedValueOnce(new Error('autoplay bloccato'))
|
|
.mockResolvedValueOnce(undefined)
|
|
await attivaTimeout(wrapper, { video: 'a.mp4' })
|
|
const video = wrapper.find('.timeout-video').element
|
|
await Promise.resolve()
|
|
await Promise.resolve()
|
|
expect(video.muted).toBe(true)
|
|
})
|
|
})
|
|
|
|
// =============================================
|
|
// MESSAGGI WEBSOCKET E SINTESI VOCALE
|
|
// =============================================
|
|
describe('onWsMessage e sintesi vocale', () => {
|
|
it('un messaggio "speak" avvia la sintesi vocale con il testo ricevuto', () => {
|
|
const wrapper = mountDisplay()
|
|
wrapper.vm.onWsMessage({ type: 'speak', text: '15 a 10' })
|
|
expect(window.speechSynthesis.cancel).toHaveBeenCalled()
|
|
expect(window.speechSynthesis.speak).toHaveBeenCalled()
|
|
const utterance = window.speechSynthesis.speak.mock.calls.at(-1)[0]
|
|
expect(utterance.text).toBe('15 a 10')
|
|
expect(utterance.lang).toBe('it-IT')
|
|
})
|
|
|
|
it('un messaggio "error" viene loggato in console', () => {
|
|
const wrapper = mountDisplay()
|
|
const spy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
wrapper.vm.onWsMessage({ type: 'error', message: 'boom' })
|
|
expect(spy).toHaveBeenCalledWith('[Display] Server error:', 'boom')
|
|
spy.mockRestore()
|
|
})
|
|
|
|
it('ignora testo vuoto o non stringa nella sintesi vocale', () => {
|
|
const wrapper = mountDisplay()
|
|
window.speechSynthesis.speak.mockClear()
|
|
wrapper.vm.speakOnDisplay(' ')
|
|
wrapper.vm.speakOnDisplay(42)
|
|
expect(window.speechSynthesis.speak).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('sceglie la voce "Google italiano" quando disponibile', () => {
|
|
const wrapper = mountDisplay()
|
|
const vIt = { name: 'voce it generica', lang: 'it-IT' }
|
|
const vGoogle = { name: 'Google italiano', lang: 'it-IT' }
|
|
window.speechSynthesis.getVoices = () => [vIt, vGoogle]
|
|
wrapper.vm.speakOnDisplay('prova')
|
|
const utterance = window.speechSynthesis.speak.mock.calls.at(-1)[0]
|
|
expect(utterance.voice).toBe(vGoogle)
|
|
})
|
|
|
|
it('ripiega su una voce con lingua "it" se manca "Google italiano"', () => {
|
|
const wrapper = mountDisplay()
|
|
const vIt = { name: 'voce italiana', lang: 'it-IT' }
|
|
const vEn = { name: 'english voice', lang: 'en-US' }
|
|
window.speechSynthesis.getVoices = () => [vEn, vIt]
|
|
wrapper.vm.speakOnDisplay('prova')
|
|
const utterance = window.speechSynthesis.speak.mock.calls.at(-1)[0]
|
|
expect(utterance.voice).toBe(vIt)
|
|
})
|
|
|
|
it('usa voice=null se nessuna voce italiana è disponibile', () => {
|
|
const wrapper = mountDisplay()
|
|
window.speechSynthesis.getVoices = () => [{ name: 'english voice', lang: 'en-US' }]
|
|
wrapper.vm.speakOnDisplay('prova')
|
|
const utterance = window.speechSynthesis.speak.mock.calls.at(-1)[0]
|
|
expect(utterance.voice).toBe(null)
|
|
})
|
|
})
|
|
|
|
// =============================================
|
|
// 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')
|
|
})
|
|
})
|
|
})
|