// @vitest-environment happy-dom // Settings' "Read aloud" row (public/modules/settings.js renderVoiceSettings): // the voice the Billion tab reads in, picked here and remembered by the browser. import { describe, it, expect, vi, beforeEach } from 'vitest'; import { readFileSync } from 'node:fs'; vi.mock('../public/modules/ws.js', () => ({ send: vi.fn(() => true) })); import { renderVoiceSettings } from '../public/modules/settings.js'; import { savedVoiceURI } from '../public/modules/readaloud.js'; const html = readFileSync('public/index.html', 'utf8'); const section = html.match(/
\s*<\/div>/)[0]; const voices = (...names) => names.map((name, i) => ({ name, lang: 'en-US', voiceURI: `v${i}`, localService: true })); const box = () => document.getElementById('voice-settings'); const pick = () => document.getElementById('voice-pick'); beforeEach(() => { localStorage.clear(); document.body.innerHTML = `
${section}
`; window.SpeechSynthesisUtterance = class {}; window.speechSynthesis = { speak() {}, cancel() {}, getVoices: () => [] }; }); describe('the Settings voice row', () => { it('sits in the Settings window under "Read aloud", a select labelled "Voice for reading messages aloud" that is no login field', () => { expect(html.indexOf('id="voice-settings"')).toBeGreaterThan(html.indexOf('id="settings-panel"')); expect(html.indexOf('id="voice-settings"')).toBeLessThan(html.indexOf('id="theme-toggle"')); expect(box().querySelector('h2').textContent).toBe('Read aloud'); expect(pick().closest('label').textContent).toBe('Voice for reading messages aloud'); expect(pick().getAttribute('autocomplete')).toBe('off'); const css = readFileSync('public/style.css', 'utf8'); expect(css).toMatch(/@media \(max-width: 700px\) \{ \.settings-voice-pick \{ font-size: 16px;/); }); it('shows only when the browser offers a choice of voices', () => { renderVoiceSettings(); expect(box().hidden).toBe(true); window.speechSynthesis.getVoices = () => voices('Albert'); renderVoiceSettings(); expect(box().hidden).toBe(true); window.speechSynthesis.getVoices = () => voices('Albert', 'Samantha'); renderVoiceSettings(); expect(box().hidden).toBe(false); delete window.speechSynthesis; renderVoiceSettings(); expect(box().hidden).toBe(true); }); it('offers "Auto · " then each voice, and remembers the pick across a reload', () => { window.speechSynthesis.getVoices = () => voices('Albert', 'Samantha'); renderVoiceSettings(); expect([...pick().options].map(o => [o.value, o.textContent])).toEqual([['', expect.stringMatching(/^Auto · /)], ['v0', 'Albert'], ['v1', 'Samantha']]); expect(pick().value).toBe(''); pick().value = 'v1'; pick().onchange(); expect(savedVoiceURI()).toBe('v1'); document.body.innerHTML = `
${section}
`; renderVoiceSettings(); expect(pick().value).toBe('v1'); pick().value = ''; pick().onchange(); expect(savedVoiceURI()).toBeNull(); }); });