mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
feat(voice): render the voice control from a presenter
createVoiceControl now returns a presenter instead of building the control with innerHTML: the session, cost, input, card and panel owners write its state by part and listen for interactions, and VoiceControl.vue renders it in the command dock, between the location bar and the visual presets as before. Focus moves wait for the render; the waveform's bars publish apart from the control and only when they change; the minutes field keeps a draft until its owner writes it. Owner tests use a real presenter on their own store, and view tests render the control.
This commit is contained in:
1 parent
2ffd7a4afd
commit
1de4f635ca
47 files changed
+1691
-1645
No files matched your search
@@ -1,6 +1,9 @@
|
||||
import '../testSupport/registerDom.mjs';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import test from 'node:test';
|
||||
import { nextTick } from 'vue';
|
||||
import { createTestVoiceUi } from '../testSupport/voiceUi.mjs';
|
||||
import { mountVoiceView } from '../testSupport/voiceView.mjs';
|
||||
import { GevRealtimeController } from './realtimeController.js';
|
||||
import {
|
||||
POINTER_OPEN_MIC_RECENT_MS,
|
||||
@@ -63,16 +66,7 @@ function controllerFixture(pointer, { pushToTalk = false } = {}) {
|
||||
const sent = [];
|
||||
const events = [];
|
||||
const controller = new GevRealtimeController({
|
||||
ui: {
|
||||
root: {
|
||||
dataset: {},
|
||||
classList: { remove() {} },
|
||||
querySelectorAll: () => [],
|
||||
},
|
||||
status: { textContent: '' },
|
||||
detail: { textContent: '', title: '' },
|
||||
errorDetail: { textContent: '' },
|
||||
},
|
||||
ui: createTestVoiceUi(),
|
||||
runner: async () => ({ ok: true }),
|
||||
debugSink: null,
|
||||
pointer,
|
||||
@@ -299,20 +293,23 @@ test('card: the chip says what "this" or "here" resolved to and resets per turn'
|
||||
assert.equal(unchanged, state, 'a stale pointer on an empty chip is a no-op');
|
||||
});
|
||||
|
||||
test('markup: the chip sits under the user caption with a screen-reader separator', () => {
|
||||
const control = readFileSync(
|
||||
new URL('./control.js', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
const user = control.indexOf('id="gev-voice-card-user"');
|
||||
const chip = control.indexOf('id="gev-voice-card-pointer"');
|
||||
const assistant = control.indexOf('id="gev-voice-card-assistant"');
|
||||
assert.ok(user < chip && chip < assistant);
|
||||
assert.match(
|
||||
control,
|
||||
/id="gev-voice-card-pointer" class="gev-voice-card-pointer" hidden/,
|
||||
);
|
||||
assert.match(control, /<span class="gev-voice-card-sr">: <\/span>/);
|
||||
test('markup: the chip sits under the user caption with a screen-reader separator', async (t) => {
|
||||
const { pinia } = mountVoiceView(t);
|
||||
const ui = createTestVoiceUi({ pinia });
|
||||
t.after(() => ui.remove());
|
||||
await nextTick();
|
||||
const card = document.getElementById('gev-voice-card');
|
||||
const order = [...card.children].map((node) => node.id);
|
||||
const user = order.indexOf('gev-voice-card-user');
|
||||
const chip = order.indexOf('gev-voice-card-pointer');
|
||||
const assistant = order.indexOf('gev-voice-card-assistant');
|
||||
assert.ok(user >= 0 && user < chip && chip < assistant);
|
||||
const pointer = document.getElementById('gev-voice-card-pointer');
|
||||
assert.equal(pointer.className, 'gev-voice-card-pointer');
|
||||
assert.equal(pointer.hidden, true);
|
||||
const separator = pointer.querySelector('.gev-voice-card-sr');
|
||||
assert.equal(separator.textContent, ': ');
|
||||
assert.equal(separator.previousElementSibling.id, 'gev-voice-card-pointer-kind');
|
||||
});
|
||||
|
||||
test('deixis prompt lint: one short rule, and every sentinel it names exists in the schema', () => {
|
||||
|
||||
Reference in new issue
Block a user