@@ -16,6 +16,8 @@ function harness() {
|
||||
}
|
||||
add_child(child) { this.children.push(child); }
|
||||
destroy_all_children() { this.children = []; }
|
||||
get_n_children() { return this.children.length; }
|
||||
contains() { return false; }
|
||||
get_last_child() { return this.children[this.children.length - 1] || null; }
|
||||
connect() { return 1; }
|
||||
hide() { this.visible = false; }
|
||||
@@ -28,9 +30,10 @@ function harness() {
|
||||
}
|
||||
const context = vm.createContext({
|
||||
Extension: class {},
|
||||
global: { stage: { get_key_focus() { return null; } } },
|
||||
St: { BoxLayout: Actor, Label: Actor, Widget: Actor, Button: Actor, Entry: Actor, ScrollView: Actor, PolicyType: { NEVER: 0, AUTOMATIC: 1 } },
|
||||
Clutter: { ActorAlign: { CENTER: 0, START: 1 }, EVENT_STOP: 1, EVENT_PROPAGATE: 0 },
|
||||
Pango: { EllipsizeMode: { NONE: 0, END: 3 } },
|
||||
Pango: { WrapMode: { WORD_CHAR: 2 }, EllipsizeMode: { NONE: 0, END: 3 } },
|
||||
Main: { layoutManager: { addChrome() {} } },
|
||||
GLib: {
|
||||
PRIORITY_DEFAULT: 0, SOURCE_REMOVE: false,
|
||||
|
||||
@@ -51,7 +51,7 @@ test('Phase 4 feedback gate drops capture while speaking and during cooldown', (
|
||||
test('Phase 4 PipeWire capture uses a named 16 kHz mono node', () => {
|
||||
let args; const child = new EventEmitter(); child.stdout = new EventEmitter(); child.stderr = new EventEmitter(); child.kill = () => {};
|
||||
const capture = new PipeWireCapture({ spawnImpl: (_cmd, received) => { args = received; return child; } }); capture.start();
|
||||
assert.deepEqual(args, ['--record', '--raw', '--format', 's16', '--rate', '16000', '--channels', '1', '--name', 'Jarvis']);
|
||||
assert.deepEqual(args, ['--record', '--raw', '--format', 's16', '--rate', '16000', '--channels', '1', '--properties', 'node.name=Jarvis', '-']);
|
||||
capture.stop();
|
||||
});
|
||||
|
||||
@@ -80,3 +80,34 @@ test('PCM packing uses even s16le sample pairs', () => {
|
||||
assert.equal(fromBytes.samples.length, 2);
|
||||
assert.equal(fromBytes.samples[0], 256);
|
||||
});
|
||||
|
||||
test('ASR failure preserves speech output and reports unavailable microphone', async () => {
|
||||
const capture = new EventEmitter(); let captured = false;
|
||||
capture.start = () => { captured = true; }; capture.stop = () => {};
|
||||
let spoken = '';
|
||||
const loop = new VoiceLoop({ capture, wake: new WakeEngine(),
|
||||
asr: { start: async () => { throw new Error('Whisper plugin missing'); } },
|
||||
tts: { start: async () => {}, speak: async (text) => { spoken = text; return { samples: new Int16Array(8) }; } },
|
||||
playback: { play: async () => {}, stop() {} },
|
||||
});
|
||||
loop.on('error', () => {});
|
||||
await loop.start(); await loop.speak('Speech still works.');
|
||||
assert.equal(spoken, 'Speech still works.'); assert.equal(captured, false);
|
||||
assert.equal(loop.status.tts, true); assert.equal(loop.status.asr, false);
|
||||
assert.match(loop.status.errors.asr, /Whisper/); await loop.stop();
|
||||
});
|
||||
|
||||
test('TTS failure preserves ASR and push to talk without wake command', async () => {
|
||||
const capture = new EventEmitter(); capture.start = () => {}; capture.stop = () => {};
|
||||
const loop = new VoiceLoop({ capture, wake: new WakeEngine(),
|
||||
asr: { start: async () => {} }, tts: { start: async () => { throw new Error('TTS failed'); } },
|
||||
playback: { stop() {} },
|
||||
});
|
||||
loop.on('error', () => {}); await loop.start(); loop.setPushToTalk(true);
|
||||
assert.equal(loop.status.asr, true); assert.equal(loop.ptt, true);
|
||||
assert.equal(loop.status.tts, false); assert.equal(loop.status.wake, false); await loop.stop();
|
||||
});
|
||||
|
||||
test('QVAC numeric PCM arrays preserve signed 16-bit samples', () => {
|
||||
assert.deepEqual([...pcmS16le([256, -32768, 32767]).samples], [256, -32768, 32767]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user