chrome extension: point to Modal transcription service

- add unit test
This commit is contained in:
Aaron Boxer
2026-06-04 16:11:11 -04:00
committed by Aaron Boxer
parent 44940e2834
commit d0ad362b20
5 changed files with 269 additions and 3 deletions
+2
View File
@@ -21,3 +21,5 @@ transcript*.srt
translation*.srt
*.wav
docs/site/
Audio-Transcription-Chrome/node_modules/
Audio-Transcription-Chrome/package-lock.json
@@ -0,0 +1,242 @@
'use strict';
// ---------------------------------------------------------------------------
// Chrome API mock — defined before any extension script is loaded
// ---------------------------------------------------------------------------
const storageData = {};
global.chrome = {
storage: {
local: {
get: jest.fn((keys, cb) => {
if (typeof keys === 'string') {
cb({ [keys]: storageData[keys] });
} else if (Array.isArray(keys)) {
const result = {};
keys.forEach(k => { result[k] = storageData[k]; });
cb(result);
} else {
const result = {};
Object.keys(keys).forEach(k => {
result[k] = k in storageData ? storageData[k] : keys[k];
});
cb(result);
}
}),
set: jest.fn((obj, cb) => {
Object.assign(storageData, obj);
if (cb) cb();
}),
},
},
runtime: {
sendMessage: jest.fn(),
onMessage: { addListener: jest.fn() },
getURL: jest.fn(path => `chrome-extension://fake-id/${path}`),
id: 'fake-extension-id',
},
tabs: {
query: jest.fn(),
get: jest.fn(),
create: jest.fn(),
remove: jest.fn(),
sendMessage: jest.fn(),
},
tabCapture: { capture: jest.fn() },
scripting: { executeScript: jest.fn() },
};
// Flush all pending microtasks and one macrotask round so async click
// handlers (which await at least one Promise inside) complete fully.
const flushPromises = () => new Promise(resolve => setTimeout(resolve, 0));
function resetStorage() {
Object.keys(storageData).forEach(k => delete storageData[k]);
}
function buildPopupDOM() {
document.body.innerHTML = `
<div id="startCapture"></div>
<div id="stopCapture"></div>
<input type="checkbox" id="useServerCheckbox">
<input type="checkbox" id="useVadCheckbox">
<input type="checkbox" id="saveCaptionsCheckbox">
<select id="languageDropdown">
<option value="" selected></option>
<option value="en">English</option>
</select>
<select id="taskDropdown">
<option value="transcribe" selected>Transcribe</option>
</select>
<select id="modelSizeDropdown">
<option value="small" selected>Small</option>
<option value="large-v3">Large-v3</option>
</select>
<select id="captionLinesDropdown">
<option value="3" selected>3</option>
</select>
`;
}
function loadPopup() {
jest.resetModules();
require('../popup.js');
document.dispatchEvent(new Event('DOMContentLoaded'));
}
function clickStart(tabId = 1) {
chrome.tabs.query.mockImplementation((_, cb) => cb([{ id: tabId }]));
document.getElementById('startCapture').click();
}
// ---------------------------------------------------------------------------
// 1. WebSocket URL construction — pure logic extracted from options.js
// ---------------------------------------------------------------------------
describe('WebSocket URL construction', () => {
function buildWsUrl(host, port) {
return port
? `ws://${host}:${port}/`
: `wss://${host}/ws`;
}
test('local server: ws:// with port and trailing slash', () => {
expect(buildWsUrl('localhost', '9090')).toBe('ws://localhost:9090/');
});
test('Modal service (empty port): wss:// with /ws path', () => {
expect(buildWsUrl('boxerab--aavaaz-live-livetranscriber-web.modal.run', '')).toBe(
'wss://boxerab--aavaaz-live-livetranscriber-web.modal.run/ws'
);
});
test('custom host+port stays on ws://', () => {
expect(buildWsUrl('my-server.example.com', '7090')).toBe(
'ws://my-server.example.com:7090/'
);
});
});
// ---------------------------------------------------------------------------
// 2. popup.js — host/port selection based on checkbox
// ---------------------------------------------------------------------------
describe('popup.js host/port selection', () => {
beforeEach(() => {
resetStorage();
buildPopupDOM();
jest.clearAllMocks();
});
test('checkbox unchecked → localhost:9090', async () => {
document.getElementById('useServerCheckbox').checked = false;
loadPopup();
clickStart();
await flushPromises();
const call = chrome.runtime.sendMessage.mock.calls.find(
c => c[0] && c[0].action === 'startCapture'
);
expect(call).toBeDefined();
expect(call[0].host).toBe('localhost');
expect(call[0].port).toBe('9090');
});
test('checkbox checked → Modal host with empty port', async () => {
document.getElementById('useServerCheckbox').checked = true;
loadPopup();
clickStart();
await flushPromises();
const call = chrome.runtime.sendMessage.mock.calls.find(
c => c[0] && c[0].action === 'startCapture'
);
expect(call).toBeDefined();
expect(call[0].host).toBe('boxerab--aavaaz-live-livetranscriber-web.modal.run');
expect(call[0].port).toBe('');
});
test('startCapture message carries language, task, and modelSize', async () => {
document.getElementById('languageDropdown').value = 'en';
document.getElementById('taskDropdown').value = 'transcribe';
document.getElementById('modelSizeDropdown').value = 'large-v3';
loadPopup();
clickStart();
await flushPromises();
const call = chrome.runtime.sendMessage.mock.calls.find(
c => c[0] && c[0].action === 'startCapture'
);
expect(call).toBeDefined();
expect(call[0].task).toBe('transcribe');
expect(call[0].modelSize).toBe('large-v3');
});
});
// ---------------------------------------------------------------------------
// 3. popup.js — button state management
// ---------------------------------------------------------------------------
describe('popup.js button state', () => {
beforeEach(() => {
resetStorage();
buildPopupDOM();
jest.clearAllMocks();
});
test('start button enabled and stop button disabled on load (not capturing)', () => {
storageData.capturingState = { isCapturing: false };
loadPopup();
expect(document.getElementById('startCapture').disabled).toBeFalsy();
expect(document.getElementById('stopCapture').disabled).toBe(true);
});
test('stop button enabled on load when already capturing', () => {
storageData.capturingState = { isCapturing: true };
loadPopup();
expect(document.getElementById('stopCapture').disabled).toBe(false);
});
test('clicking stop sends stopCapture action to runtime', () => {
storageData.capturingState = { isCapturing: true };
loadPopup();
// toggleCaptureButtons(true) disables startCapture and enables stopCapture
document.getElementById('stopCapture').click();
expect(chrome.runtime.sendMessage).toHaveBeenCalledWith(
expect.objectContaining({ action: 'stopCapture' }),
expect.any(Function)
);
});
});
// ---------------------------------------------------------------------------
// 4. popup.js — storage state restoration on open
// ---------------------------------------------------------------------------
describe('popup.js storage restoration', () => {
beforeEach(() => {
resetStorage();
buildPopupDOM();
jest.clearAllMocks();
});
test('restores useServerCheckbox from storage', () => {
storageData.useServerState = true;
loadPopup();
expect(document.getElementById('useServerCheckbox').checked).toBe(true);
});
test('restores language selection from storage', () => {
storageData.selectedLanguage = 'en';
loadPopup();
expect(document.getElementById('languageDropdown').value).toBe('en');
});
test('restores model size from storage', () => {
storageData.selectedModelSize = 'large-v3';
loadPopup();
expect(document.getElementById('modelSizeDropdown').value).toBe('large-v3');
});
});
+4 -1
View File
@@ -129,7 +129,10 @@ async function startRecord(option) {
return;
}
socket = new WebSocket(`ws://${option.host}:${option.port}/`);
const wsUrl = option.port
? `ws://${option.host}:${option.port}/`
: `wss://${option.host}/ws`;
socket = new WebSocket(wsUrl);
isServerReady = false;
let language = option.language;
+19
View File
@@ -0,0 +1,19 @@
{
"name": "audio-transcription-chrome",
"version": "1.0.0",
"description": "Audio Transcription is a Chrome extension that allows users to capture any audio playing on the current tab and transcribe it using OpenAI-whisper in real time. Users will have the option to do voice activity detection as well to not send audio to server when there is no speech.",
"main": "audiopreprocessor.js",
"scripts": {
"test": "jest"
},
"jest": {
"testEnvironment": "jsdom"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"jest": "^30.4.2",
"jest-environment-jsdom": "^30.4.1"
}
}
+2 -2
View File
@@ -90,8 +90,8 @@ document.addEventListener("DOMContentLoaded", function () {
let port = "9090";
const useCollaboraServer = useServerCheckbox.checked;
if (useCollaboraServer){
host = "transcription.kurg.org"
port = "7090"
host = "boxerab--aavaaz-live-livetranscriber-web.modal.run"
port = ""
}
chrome.runtime.sendMessage(