This commit is contained in:
+41
-3
@@ -14,6 +14,7 @@ function makeHarness({supported = true, tts = true, onTranscript = () => Promise
|
||||
const utterances = [];
|
||||
const states = [];
|
||||
const timers = new Map();
|
||||
const timerDelays = new Map();
|
||||
let timerId = 0;
|
||||
class Recognition {
|
||||
constructor() { recognitions.push(this); }
|
||||
@@ -31,8 +32,8 @@ function makeHarness({supported = true, tts = true, onTranscript = () => Promise
|
||||
cancel() { this.cancelled += 1; },
|
||||
} : undefined;
|
||||
const window = {
|
||||
setTimeout(callback) { const id = ++timerId; timers.set(id, callback); return id; },
|
||||
clearTimeout(id) { timers.delete(id); },
|
||||
setTimeout(callback, delay = 0) { const id = ++timerId; timers.set(id, callback); timerDelays.set(id, delay); return id; },
|
||||
clearTimeout(id) { timers.delete(id); timerDelays.delete(id); },
|
||||
speechSynthesis: synthesis,
|
||||
SpeechSynthesisUtterance: tts ? Utterance : undefined,
|
||||
SpeechRecognition: supported ? Recognition : undefined,
|
||||
@@ -43,7 +44,14 @@ function makeHarness({supported = true, tts = true, onTranscript = () => Promise
|
||||
onState: (state, message) => states.push({state, message}),
|
||||
onTranscript,
|
||||
});
|
||||
return {controller, recognitions, utterances, synthesis, states, timers};
|
||||
return {
|
||||
controller, recognitions, utterances, synthesis, states, timers,
|
||||
runTimer(delay) {
|
||||
const match = [...timerDelays.entries()].find(([, value]) => value === delay);
|
||||
if (!match) throw new Error(`No timer found with delay ${delay}`);
|
||||
timers.get(match[0])();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('recognition is single-shot Swedish and sends only final transcript once', async () => {
|
||||
@@ -122,6 +130,35 @@ test('TTS selects Swedish fallback, applies rate, and cancellation invalidates c
|
||||
assert.equal(h.controller.getState(), 'IDLE');
|
||||
});
|
||||
|
||||
test('iOS user gesture primes speech silently and idle speech is not cancelled before speak', () => {
|
||||
const h = makeHarness();
|
||||
assert.equal(h.controller.activateFromUserGesture(), true);
|
||||
assert.equal(h.synthesis.spoken[0].text, ' ');
|
||||
assert.equal(h.synthesis.spoken[0].volume, 0);
|
||||
const cancelledBeforeReply = h.synthesis.cancelled;
|
||||
assert.equal(h.controller.speak('Ett svar efter nätverksanropet'), true);
|
||||
assert.equal(h.synthesis.spoken.at(-1).text, 'Ett svar efter nätverksanropet');
|
||||
assert.equal(h.synthesis.cancelled, cancelledBeforeReply);
|
||||
});
|
||||
|
||||
test('replacing active speech waits one event-loop turn after WebKit cancel', () => {
|
||||
const h = makeHarness();
|
||||
h.controller.speak('Första repliken');
|
||||
const cancelledBeforeReplace = h.synthesis.cancelled;
|
||||
h.controller.speak('Nästa replik');
|
||||
assert.equal(h.synthesis.cancelled, cancelledBeforeReplace + 1);
|
||||
assert.notEqual(h.synthesis.spoken.at(-1).text, 'Nästa replik');
|
||||
h.runTimer(100);
|
||||
assert.equal(h.synthesis.spoken.at(-1).text, 'Nästa replik');
|
||||
});
|
||||
|
||||
test('speech start timeout tells the player how to recover when mobile playback is blocked', () => {
|
||||
const h = makeHarness();
|
||||
h.controller.speak('En replik');
|
||||
h.runTimer(4000);
|
||||
assert.match(h.states.at(-1).message, /Tryck på Läs senaste repliken/);
|
||||
});
|
||||
|
||||
test('character voice identity gives stable distinct masculine, feminine, and neutral timbres', () => {
|
||||
const profile = GameVoice.characterVoiceProfile;
|
||||
const masculine = profile('masculine', 'character-1');
|
||||
@@ -138,6 +175,7 @@ test('narrator uses neutral pitch while character replies use their saved voice
|
||||
h.controller.update({mode: 'game-voice'});
|
||||
h.controller.speak('Berättarröst', {role: 'narrator', voiceIdentity: 'feminine', characterId: 'narrator'});
|
||||
h.controller.speak('NPC-replik', {voiceIdentity: 'masculine', characterId: 'npc-1'});
|
||||
h.runTimer(100);
|
||||
assert.equal(h.utterances[0].pitch, 1);
|
||||
assert.ok(h.utterances[1].pitch < 1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user