import { render, screen, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { vi, test, expect, afterEach } from 'vitest'; import DictateButton, { appendSpoken } from './DictateButton.jsx'; afterEach(() => { delete window.SpeechRecognition; delete window.webkitSpeechRecognition; }); // A stand-in for the browser's recogniser that we can make "hear" things function fakeRecognition() { const instances = []; class Fake { constructor() { instances.push(this); this.started = false; } start() { this.started = true; } stop() { this.started = false; this.onend?.(); } abort() { this.started = false; } } window.webkitSpeechRecognition = Fake; return instances; } const hear = (rec, text, isFinal = true) => rec.onresult({ resultIndex: 0, results: [Object.assign([{ transcript: text }], { isFinal })] }); test('spoken words are added to what is already written', () => { expect(appendSpoken('', ' grace is a gift ')).toBe('Grace is a gift'); expect(appendSpoken('Paul writes', 'to Titus')).toBe('Paul writes to Titus'); expect(appendSpoken('He saved us.', 'not by works')).toBe('He saved us. Not by works'); expect(appendSpoken('Line one\n', 'line two')).toBe('Line one\nline two'); expect(appendSpoken('Keep this', ' ')).toBe('Keep this'); }); test('with no speech recognition in the browser, the button is not shown', () => { const { container } = render( {}} />); expect(container).toBeEmptyDOMElement(); }); test('tap to listen, final phrases are passed on, tap again to stop', async () => { const instances = fakeRecognition(); const onText = vi.fn(); const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Dictate script' })); expect(instances[0].started).toBe(true); expect(screen.getByRole('button', { name: 'Stop dictating' })).toHaveAttribute('aria-pressed', 'true'); act(() => hear(instances[0], 'the Lord is my shepherd', false)); // still being worked out: ignored act(() => hear(instances[0], 'the Lord is my shepherd')); expect(onText).toHaveBeenCalledTimes(1); expect(onText).toHaveBeenCalledWith('the Lord is my shepherd'); await user.click(screen.getByRole('button', { name: 'Stop dictating' })); expect(screen.getByRole('button', { name: 'Dictate script' })).toHaveAttribute('aria-pressed', 'false'); }); test('a blocked microphone is explained', async () => { const instances = fakeRecognition(); const user = userEvent.setup(); render( {}} />); await user.click(screen.getByRole('button', { name: 'Dictate' })); act(() => instances[0].onerror({ error: 'not-allowed' })); expect(await screen.findByRole('alert')).toHaveTextContent(/microphone is blocked/i); act(() => instances[0].onerror({ error: 'no-speech' })); // silence is not an error worth showing });