Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/6048-fix-test-speech-helper-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- 👷🏻 Fixed speech test helpers not failing the test when its callback throws, in PR [#6048](https://github.com/microsoft/BotFramework-WebChat/pull/6048), by [@OEvgeny](https://github.com/OEvgeny)
40 changes: 28 additions & 12 deletions __tests__/assets/esm/speech/speechPageObjects.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,32 @@ function createWebSpeechPonyfill() {
*/
async function actSpeakOnce({ speechSynthesis }, actor, speakActor) {
let lastUtterance;
let rejectSpeak;
let spoken;

spyOn(speechSynthesis, 'speak').mockImplementationOnce(async utterance => {
// Rejects as soon as speakActor throws, so an actor waiting for the utterance's "end" event does not hang.
const speakFailed = new Promise((_, reject) => {
rejectSpeak = reject;
});

spyOn(speechSynthesis, 'speak').mockImplementationOnce(utterance => {
lastUtterance = utterance;

utterance.dispatchEvent(new SpeechSynthesisEvent('start', { utterance }));
spoken = (async () => {
utterance.dispatchEvent(new SpeechSynthesisEvent('start', { utterance }));

await speakActor?.(utterance);
await speakActor?.(utterance);

utterance.dispatchEvent(new SpeechSynthesisEvent('end', { utterance }));
utterance.dispatchEvent(new SpeechSynthesisEvent('end', { utterance }));
})();

spoken.catch(rejectSpeak);
});

await actor();
await Promise.race([actor(), speakFailed]);

// Waits for speakActor, if speak() was called.
await spoken;
Comment thread
compulim marked this conversation as resolved.
Resolved

return lastUtterance;
}
Expand Down Expand Up @@ -83,9 +97,8 @@ async function actSpeak({ speechSynthesis }, actor, speakActor) {
return Object.freeze(utterances);
}

function actRecognizeOnce(ponyfill, actor, recognizeActor) {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async resolve => {
async function actRecognizeOnce(ponyfill, actor, recognizeActor) {
const recognized = new Promise((resolve, reject) => {
const OriginalSpeechRecognition = ponyfill.SpeechRecognition;

spyOn(ponyfill, 'SpeechRecognition').mockImplementationOnce(() => {
Expand Down Expand Up @@ -169,7 +182,7 @@ function actRecognizeOnce(ponyfill, actor, recognizeActor) {
end();
});

spyOn(speechRecognition, 'start').mockImplementationOnce(async () => {
const recognize = async () => {
// Like a browser, events come after start() returned and "starting" is painted: start() is called from an effect,
// events inside it would render "starting" and "dictating" in the same frame on React 18 and later.
await new Promise(resolve => requestAnimationFrame(() => setTimeout(resolve, 0)));
Expand Down Expand Up @@ -199,15 +212,18 @@ function actRecognizeOnce(ponyfill, actor, recognizeActor) {
finalizedResult
});
}
};

resolve();
spyOn(speechRecognition, 'start').mockImplementationOnce(() => {
recognize().then(resolve, reject);
});

return speechRecognition;
});

await actor();
});

// Waits for both actors, so an error thrown by either one rejects.
await Promise.all([actor(), recognized]);
}

async function sendMessageViaMicrophone(speechPonyfill, text) {
Expand Down
2 changes: 1 addition & 1 deletion __tests__/html2/hooks/useStartDictate.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
`[data-testid="${testIds.sendBoxMicrophoneButton}"]`
)?.parentElement;

actRecognizeOnce(
await actRecognizeOnce(
ponyfill,
async () => {
startDictate();
Expand Down
2 changes: 1 addition & 1 deletion __tests__/html2/hooks/useStopDictate.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@

await pageConditions.uiConnected();

actRecognizeOnce(
await actRecognizeOnce(
ponyfill,
async () => {
await host.click(document.querySelector(`[data-testid="${testIds.sendBoxMicrophoneButton}"]`));
Expand Down
Loading