ASR-demo/realtime_asr_optimization_demo/tests/test_frontend.cjs

103 lines
4.9 KiB
JavaScript
Raw Normal View History

2026-09-10 05:47:09 +00:00
// 使用 Node 内置测试器和最小 DOM 验证真实页面脚本,无需安装浏览器或 GPU。
const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
class Element {
constructor() {
this.childNodes = [];
this.className = '';
this.style = {};
this.textContent = '';
this.classList = { add() {}, remove() {}, toggle() {} };
}
append(...nodes) { nodes.forEach(node => this.appendChild(node)); }
appendChild(node) { node.parent = this; this.childNodes.push(node); }
replaceChildren() { this.childNodes = []; }
addEventListener() {}
remove() { if (this.parent) this.parent.childNodes = this.parent.childNodes.filter(n => n !== this); }
querySelector(selector) {
const name = selector.slice(1);
for (const child of this.childNodes) {
if (child.className.split(' ').includes(name)) return child;
const nested = child.querySelector(selector);
if (nested) return nested;
}
return null;
}
get firstChild() { return this.childNodes[0]; }
}
// 每个测试使用独立页面上下文,不共享会话状态。
function page() {
const elements = new Map();
const timers = [];
const context = vm.createContext({
document: {
getElementById(id) { if (!elements.has(id)) elements.set(id, new Element()); return elements.get(id); },
createElement() { return new Element(); },
body: new Element(),
},
console,
setTimeout(fn, ms) { timers.push({ fn, ms }); },
clearInterval() {},
WebSocket: { OPEN: 1 },
fetch: async () => ({ json: async () => ({ model: 'test', model_service_url: 'http://fake/v1' }) }),
});
vm.runInContext(fs.readFileSync(path.join(__dirname, '../static/app.js'), 'utf8'), context);
return { context, elements, timers };
}
const block = (id, speaker, text = 'text', reason = '') => ({
block_id: `block-${id}`, sentence: text, sentence_type: 1, start_time: id * 1000, end_time: (id + 1) * 1000,
speaker_id: speaker, speaker_name: speaker < 0 ? '' : `Person ${speaker}`,
speaker_evidence: speaker < 0 ? 'pending' : 'confirmed', speaker_reason: reason,
});
test('display snapshots update and merge pending rows; stale snapshots are ignored', () => {
const { context, elements } = page();
context.renderDisplayState({ revision: 1, display_blocks: [block(0, -1), block(1, -1)] }, true);
assert.equal(elements.get('resultArea').childNodes.length, 2);
context.renderDisplayState({ revision: 2, display_blocks: [block(0, 0, 'A B')] }, true);
assert.equal(elements.get('resultArea').childNodes.length, 1);
assert.equal(elements.get('resultArea').querySelector('.speaker-name').textContent, 'Person 0');
context.renderDisplayState({ revision: 1, display_blocks: [block(0, -1), block(1, -1)] }, true);
assert.equal(elements.get('resultArea').childNodes.length, 1);
});
test('A B A retains time order and an unknown interruption has its own bubble', () => {
const { context, elements } = page();
context.renderDisplayState({ revision: 1, display_blocks: [block(0, 0), block(1, 1), block(2, 0), block(3, -1, 'short', '音频不足')] }, true);
const rows = elements.get('resultArea').childNodes;
assert.deepEqual(rows.map(row => row.querySelector('.speaker-name').textContent), ['Person 0', 'Person 1', 'Person 0', '未知说话人']);
assert.equal(rows[3].querySelector('.speaker-name').title, '音频不足');
});
test('sentences do not duplicate display_state and end uses the final snapshot', () => {
const { context, elements } = page();
vm.runInContext('displayStateSupported = true', context);
context.handleServerMessage({ type: 'sentences', sentences: [{ ...block(0, -1), sentence_id: 0 }] }, true);
assert.equal(elements.get('resultArea').childNodes.length, 0);
context.handleServerMessage({ type: 'end', display_blocks: [block(0, 0)], sentences: [] }, true);
assert.equal(elements.get('resultArea').childNodes.length, 1);
});
test('stop sends a control message without scheduling a forced close', () => {
const { context, timers } = page();
vm.runInContext('var sent = []; ws = { readyState: 1, send(message) { sent.push(JSON.parse(message)); } }; sending = true', context);
context.stopRecognition();
assert.equal(vm.runInContext('sent[0].type', context), 'stop');
assert.equal(timers.length, 0);
assert.equal(vm.runInContext('ws !== null', context), true);
});
test('raw logs render untrusted transcript as text', () => {
const { context, elements } = page();
context.appendLog({ type: 'sentences', text: '<img src=x onerror=alert(1)>' });
const entry = elements.get('logArea').childNodes[0];
assert.equal(entry.innerHTML, undefined);
assert.ok(entry.childNodes[1].textContent.includes('<img'));
});