A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
850 lines
25 KiB
JavaScript
850 lines
25 KiB
JavaScript
/**
|
|
* Unit Tests for Studio Panel Navigation
|
|
*
|
|
* These tests verify the panel navigation functions in studio.js
|
|
* Run with: npm test (requires Jest and jsdom)
|
|
*/
|
|
|
|
// Mock DOM elements
|
|
const mockElements = {};
|
|
|
|
function createMockElement(id, display = 'none') {
|
|
return {
|
|
id,
|
|
style: { display },
|
|
classList: {
|
|
_classes: new Set(),
|
|
add(cls) { this._classes.add(cls); },
|
|
remove(cls) { this._classes.delete(cls); },
|
|
contains(cls) { return this._classes.has(cls); }
|
|
}
|
|
};
|
|
}
|
|
|
|
// Setup mock DOM before tests
|
|
function setupMockDOM() {
|
|
mockElements['panel-compare'] = createMockElement('panel-compare', 'flex');
|
|
mockElements['panel-tiles'] = createMockElement('panel-tiles');
|
|
mockElements['panel-messenger'] = createMockElement('panel-messenger');
|
|
mockElements['panel-video'] = createMockElement('panel-video');
|
|
mockElements['panel-correction'] = createMockElement('panel-correction');
|
|
mockElements['panel-letters'] = createMockElement('panel-letters');
|
|
mockElements['studio-sub-menu'] = createMockElement('studio-sub-menu', 'flex');
|
|
mockElements['sub-worksheets'] = createMockElement('sub-worksheets');
|
|
mockElements['sub-tiles'] = createMockElement('sub-tiles');
|
|
mockElements['sidebar-studio'] = createMockElement('sidebar-studio');
|
|
mockElements['sidebar-correction'] = createMockElement('sidebar-correction');
|
|
mockElements['sidebar-messenger'] = createMockElement('sidebar-messenger');
|
|
mockElements['sidebar-video'] = createMockElement('sidebar-video');
|
|
mockElements['sidebar-letters'] = createMockElement('sidebar-letters');
|
|
|
|
global.document = {
|
|
getElementById: (id) => mockElements[id] || null,
|
|
querySelectorAll: (selector) => {
|
|
if (selector === '.sidebar-item') {
|
|
return Object.values(mockElements).filter(el => el.id.startsWith('sidebar-'));
|
|
}
|
|
if (selector === '.sidebar-sub-item') {
|
|
return Object.values(mockElements).filter(el => el.id.startsWith('sub-'));
|
|
}
|
|
return [];
|
|
}
|
|
};
|
|
|
|
global.console = { log: jest.fn(), error: jest.fn() };
|
|
}
|
|
|
|
// Import functions (in real setup, these would be imported from studio.js)
|
|
function hideAllPanels() {
|
|
const panels = [
|
|
'panel-compare',
|
|
'panel-tiles',
|
|
'panel-correction',
|
|
'panel-letters',
|
|
'panel-messenger',
|
|
'panel-video'
|
|
];
|
|
panels.forEach(panelId => {
|
|
const panel = document.getElementById(panelId);
|
|
if (panel) {
|
|
panel.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
function hideStudioSubMenu() {
|
|
const subMenu = document.getElementById('studio-sub-menu');
|
|
if (subMenu) {
|
|
subMenu.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function updateSidebarActive(activeSidebarId) {
|
|
document.querySelectorAll('.sidebar-item').forEach(item => {
|
|
item.classList.remove('active');
|
|
});
|
|
const activeItem = document.getElementById(activeSidebarId);
|
|
if (activeItem) {
|
|
activeItem.classList.add('active');
|
|
}
|
|
}
|
|
|
|
function updateSubNavActive(activeSubId) {
|
|
document.querySelectorAll('.sidebar-sub-item').forEach(item => {
|
|
item.classList.remove('active');
|
|
});
|
|
const activeItem = document.getElementById(activeSubId);
|
|
if (activeItem) {
|
|
activeItem.classList.add('active');
|
|
}
|
|
}
|
|
|
|
function showWorksheetTab() {
|
|
const panelCompare = document.getElementById('panel-compare');
|
|
const panelTiles = document.getElementById('panel-tiles');
|
|
|
|
if (panelCompare) panelCompare.style.display = 'flex';
|
|
if (panelTiles) panelTiles.style.display = 'none';
|
|
|
|
updateSubNavActive('sub-worksheets');
|
|
}
|
|
|
|
function showTilesTab() {
|
|
const panelCompare = document.getElementById('panel-compare');
|
|
const panelTiles = document.getElementById('panel-tiles');
|
|
|
|
if (panelCompare) panelCompare.style.display = 'none';
|
|
if (panelTiles) panelTiles.style.display = 'flex';
|
|
|
|
updateSubNavActive('sub-tiles');
|
|
}
|
|
|
|
function showStudioPanel() {
|
|
hideAllPanels();
|
|
const subMenu = document.getElementById('studio-sub-menu');
|
|
if (subMenu) {
|
|
subMenu.style.display = 'flex';
|
|
}
|
|
showWorksheetTab();
|
|
updateSidebarActive('sidebar-studio');
|
|
}
|
|
|
|
function showCorrectionPanel() {
|
|
hideAllPanels();
|
|
hideStudioSubMenu();
|
|
const correctionPanel = document.getElementById('panel-correction');
|
|
if (correctionPanel) {
|
|
correctionPanel.style.display = 'flex';
|
|
}
|
|
updateSidebarActive('sidebar-correction');
|
|
}
|
|
|
|
function showMessengerPanel() {
|
|
hideAllPanels();
|
|
hideStudioSubMenu();
|
|
const messengerPanel = document.getElementById('panel-messenger');
|
|
if (messengerPanel) {
|
|
messengerPanel.style.display = 'flex';
|
|
}
|
|
updateSidebarActive('sidebar-messenger');
|
|
}
|
|
|
|
function showVideoPanel() {
|
|
hideAllPanels();
|
|
hideStudioSubMenu();
|
|
const videoPanel = document.getElementById('panel-video');
|
|
if (videoPanel) {
|
|
videoPanel.style.display = 'flex';
|
|
}
|
|
updateSidebarActive('sidebar-video');
|
|
}
|
|
|
|
// Legacy alias
|
|
function showCommunicationPanel() {
|
|
showMessengerPanel();
|
|
}
|
|
|
|
function showLettersPanel() {
|
|
hideAllPanels();
|
|
hideStudioSubMenu();
|
|
const lettersPanel = document.getElementById('panel-letters');
|
|
if (lettersPanel) {
|
|
lettersPanel.style.display = 'flex';
|
|
}
|
|
updateSidebarActive('sidebar-letters');
|
|
}
|
|
|
|
// Tests
|
|
describe('Panel Navigation Functions', () => {
|
|
beforeEach(() => {
|
|
setupMockDOM();
|
|
});
|
|
|
|
describe('hideAllPanels', () => {
|
|
test('should hide all panels', () => {
|
|
// Set some panels to visible
|
|
mockElements['panel-compare'].style.display = 'flex';
|
|
mockElements['panel-tiles'].style.display = 'flex';
|
|
|
|
hideAllPanels();
|
|
|
|
expect(mockElements['panel-compare'].style.display).toBe('none');
|
|
expect(mockElements['panel-tiles'].style.display).toBe('none');
|
|
expect(mockElements['panel-messenger'].style.display).toBe('none');
|
|
expect(mockElements['panel-video'].style.display).toBe('none');
|
|
expect(mockElements['panel-correction'].style.display).toBe('none');
|
|
expect(mockElements['panel-letters'].style.display).toBe('none');
|
|
});
|
|
});
|
|
|
|
describe('hideStudioSubMenu', () => {
|
|
test('should hide studio sub-menu', () => {
|
|
mockElements['studio-sub-menu'].style.display = 'flex';
|
|
|
|
hideStudioSubMenu();
|
|
|
|
expect(mockElements['studio-sub-menu'].style.display).toBe('none');
|
|
});
|
|
});
|
|
|
|
describe('showWorksheetTab', () => {
|
|
test('should show panel-compare and hide panel-tiles', () => {
|
|
mockElements['panel-tiles'].style.display = 'flex';
|
|
|
|
showWorksheetTab();
|
|
|
|
expect(mockElements['panel-compare'].style.display).toBe('flex');
|
|
expect(mockElements['panel-tiles'].style.display).toBe('none');
|
|
});
|
|
|
|
test('should activate sub-worksheets in sub-navigation', () => {
|
|
showWorksheetTab();
|
|
|
|
expect(mockElements['sub-worksheets'].classList.contains('active')).toBe(true);
|
|
expect(mockElements['sub-tiles'].classList.contains('active')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('showTilesTab', () => {
|
|
test('should show panel-tiles and hide panel-compare', () => {
|
|
mockElements['panel-compare'].style.display = 'flex';
|
|
|
|
showTilesTab();
|
|
|
|
expect(mockElements['panel-compare'].style.display).toBe('none');
|
|
expect(mockElements['panel-tiles'].style.display).toBe('flex');
|
|
});
|
|
|
|
test('should activate sub-tiles in sub-navigation', () => {
|
|
showTilesTab();
|
|
|
|
expect(mockElements['sub-tiles'].classList.contains('active')).toBe(true);
|
|
expect(mockElements['sub-worksheets'].classList.contains('active')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('showStudioPanel', () => {
|
|
test('should hide all panels first', () => {
|
|
mockElements['panel-correction'].style.display = 'flex';
|
|
|
|
showStudioPanel();
|
|
|
|
expect(mockElements['panel-correction'].style.display).toBe('none');
|
|
});
|
|
|
|
test('should show sub-menu', () => {
|
|
mockElements['studio-sub-menu'].style.display = 'none';
|
|
|
|
showStudioPanel();
|
|
|
|
expect(mockElements['studio-sub-menu'].style.display).toBe('flex');
|
|
});
|
|
|
|
test('should show worksheet tab by default', () => {
|
|
showStudioPanel();
|
|
|
|
expect(mockElements['panel-compare'].style.display).toBe('flex');
|
|
expect(mockElements['panel-tiles'].style.display).toBe('none');
|
|
});
|
|
|
|
test('should activate sidebar-studio', () => {
|
|
showStudioPanel();
|
|
|
|
expect(mockElements['sidebar-studio'].classList.contains('active')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('showCorrectionPanel', () => {
|
|
test('should hide all panels and show correction panel', () => {
|
|
mockElements['panel-compare'].style.display = 'flex';
|
|
|
|
showCorrectionPanel();
|
|
|
|
expect(mockElements['panel-compare'].style.display).toBe('none');
|
|
expect(mockElements['panel-correction'].style.display).toBe('flex');
|
|
});
|
|
|
|
test('should hide studio sub-menu', () => {
|
|
mockElements['studio-sub-menu'].style.display = 'flex';
|
|
|
|
showCorrectionPanel();
|
|
|
|
expect(mockElements['studio-sub-menu'].style.display).toBe('none');
|
|
});
|
|
|
|
test('should activate sidebar-correction', () => {
|
|
showCorrectionPanel();
|
|
|
|
expect(mockElements['sidebar-correction'].classList.contains('active')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('showMessengerPanel', () => {
|
|
test('should hide all panels and show messenger panel', () => {
|
|
mockElements['panel-compare'].style.display = 'flex';
|
|
|
|
showMessengerPanel();
|
|
|
|
expect(mockElements['panel-compare'].style.display).toBe('none');
|
|
expect(mockElements['panel-messenger'].style.display).toBe('flex');
|
|
});
|
|
|
|
test('should hide studio sub-menu', () => {
|
|
mockElements['studio-sub-menu'].style.display = 'flex';
|
|
|
|
showMessengerPanel();
|
|
|
|
expect(mockElements['studio-sub-menu'].style.display).toBe('none');
|
|
});
|
|
|
|
test('should activate sidebar-messenger', () => {
|
|
showMessengerPanel();
|
|
|
|
expect(mockElements['sidebar-messenger'].classList.contains('active')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('showVideoPanel', () => {
|
|
test('should hide all panels and show video panel', () => {
|
|
mockElements['panel-compare'].style.display = 'flex';
|
|
|
|
showVideoPanel();
|
|
|
|
expect(mockElements['panel-compare'].style.display).toBe('none');
|
|
expect(mockElements['panel-video'].style.display).toBe('flex');
|
|
});
|
|
|
|
test('should hide studio sub-menu', () => {
|
|
mockElements['studio-sub-menu'].style.display = 'flex';
|
|
|
|
showVideoPanel();
|
|
|
|
expect(mockElements['studio-sub-menu'].style.display).toBe('none');
|
|
});
|
|
|
|
test('should activate sidebar-video', () => {
|
|
showVideoPanel();
|
|
|
|
expect(mockElements['sidebar-video'].classList.contains('active')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('showLettersPanel', () => {
|
|
test('should hide all panels and show letters panel', () => {
|
|
mockElements['panel-compare'].style.display = 'flex';
|
|
|
|
showLettersPanel();
|
|
|
|
expect(mockElements['panel-compare'].style.display).toBe('none');
|
|
expect(mockElements['panel-letters'].style.display).toBe('flex');
|
|
});
|
|
|
|
test('should hide studio sub-menu', () => {
|
|
mockElements['studio-sub-menu'].style.display = 'flex';
|
|
|
|
showLettersPanel();
|
|
|
|
expect(mockElements['studio-sub-menu'].style.display).toBe('none');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Sidebar Active State', () => {
|
|
beforeEach(() => {
|
|
setupMockDOM();
|
|
});
|
|
|
|
test('updateSidebarActive should remove active from all items', () => {
|
|
mockElements['sidebar-studio'].classList.add('active');
|
|
mockElements['sidebar-correction'].classList.add('active');
|
|
|
|
updateSidebarActive('sidebar-letters');
|
|
|
|
expect(mockElements['sidebar-studio'].classList.contains('active')).toBe(false);
|
|
expect(mockElements['sidebar-correction'].classList.contains('active')).toBe(false);
|
|
expect(mockElements['sidebar-letters'].classList.contains('active')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Sub-Navigation Active State', () => {
|
|
beforeEach(() => {
|
|
setupMockDOM();
|
|
});
|
|
|
|
test('updateSubNavActive should toggle active state correctly', () => {
|
|
mockElements['sub-worksheets'].classList.add('active');
|
|
|
|
updateSubNavActive('sub-tiles');
|
|
|
|
expect(mockElements['sub-worksheets'].classList.contains('active')).toBe(false);
|
|
expect(mockElements['sub-tiles'].classList.contains('active')).toBe(true);
|
|
});
|
|
});
|
|
|
|
// ============================================
|
|
// JITSI VIDEOKONFERENZ MODULE TESTS
|
|
// ============================================
|
|
|
|
// Mock additional elements for Jitsi tests
|
|
function setupJitsiMockDOM() {
|
|
setupMockDOM();
|
|
|
|
// Jitsi-specific elements
|
|
mockElements['meeting-name'] = {
|
|
id: 'meeting-name',
|
|
value: 'Test Meeting',
|
|
style: { display: 'block' }
|
|
};
|
|
mockElements['jitsi-container'] = createMockElement('jitsi-container');
|
|
mockElements['jitsi-container'].innerHTML = '';
|
|
mockElements['jitsi-placeholder'] = createMockElement('jitsi-placeholder', 'flex');
|
|
mockElements['jitsi-controls'] = createMockElement('jitsi-controls');
|
|
mockElements['btn-mute'] = createMockElement('btn-mute');
|
|
mockElements['btn-mute'].textContent = '🎤 Stumm';
|
|
mockElements['btn-video'] = createMockElement('btn-video');
|
|
mockElements['btn-video'].textContent = '📹 Video aus';
|
|
mockElements['meeting-link-display'] = createMockElement('meeting-link-display');
|
|
mockElements['meeting-url'] = {
|
|
id: 'meeting-url',
|
|
textContent: '',
|
|
style: { display: 'block' }
|
|
};
|
|
|
|
// Override document methods for extended elements
|
|
global.document.getElementById = (id) => mockElements[id] || null;
|
|
global.document.createElement = (tag) => {
|
|
return {
|
|
tagName: tag.toUpperCase(),
|
|
style: {},
|
|
setAttribute: jest.fn(),
|
|
appendChild: jest.fn()
|
|
};
|
|
};
|
|
|
|
// Mock clipboard API
|
|
global.navigator = {
|
|
clipboard: {
|
|
writeText: jest.fn().mockResolvedValue(undefined)
|
|
}
|
|
};
|
|
|
|
// Mock alert
|
|
global.alert = jest.fn();
|
|
}
|
|
|
|
// Jitsi module state
|
|
let currentJitsiMeetingUrl = null;
|
|
let jitsiMicMuted = false;
|
|
let jitsiVideoOff = false;
|
|
|
|
// Jitsi functions (copied from studio.js for testing)
|
|
async function startInstantMeeting() {
|
|
console.log('Starting instant meeting...');
|
|
const meetingNameEl = document.getElementById('meeting-name');
|
|
const meetingName = meetingNameEl?.value || '';
|
|
|
|
try {
|
|
const roomId = 'bp-' + Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
|
|
const jitsiDomain = 'meet.jit.si';
|
|
currentJitsiMeetingUrl = `https://${jitsiDomain}/${roomId}`;
|
|
|
|
const container = document.getElementById('jitsi-container');
|
|
const placeholder = document.getElementById('jitsi-placeholder');
|
|
const controls = document.getElementById('jitsi-controls');
|
|
const linkDisplay = document.getElementById('meeting-link-display');
|
|
const urlDisplay = document.getElementById('meeting-url');
|
|
|
|
if (placeholder) placeholder.style.display = 'none';
|
|
if (controls) controls.style.display = 'flex';
|
|
if (linkDisplay) linkDisplay.style.display = 'flex';
|
|
if (urlDisplay) urlDisplay.textContent = currentJitsiMeetingUrl;
|
|
|
|
if (container) {
|
|
const iframe = document.createElement('iframe');
|
|
iframe.setAttribute('src', `${currentJitsiMeetingUrl}#config.prejoinPageEnabled=false`);
|
|
iframe.setAttribute('allow', 'camera; microphone; fullscreen; display-capture');
|
|
container.appendChild(iframe);
|
|
}
|
|
|
|
return { success: true, url: currentJitsiMeetingUrl };
|
|
} catch (error) {
|
|
console.error('Error starting meeting:', error);
|
|
return { success: false, error };
|
|
}
|
|
}
|
|
|
|
function leaveJitsiMeeting() {
|
|
const container = document.getElementById('jitsi-container');
|
|
const placeholder = document.getElementById('jitsi-placeholder');
|
|
const controls = document.getElementById('jitsi-controls');
|
|
const linkDisplay = document.getElementById('meeting-link-display');
|
|
|
|
if (container) container.innerHTML = '';
|
|
if (placeholder) placeholder.style.display = 'flex';
|
|
if (controls) controls.style.display = 'none';
|
|
if (linkDisplay) linkDisplay.style.display = 'none';
|
|
|
|
currentJitsiMeetingUrl = null;
|
|
jitsiMicMuted = false;
|
|
jitsiVideoOff = false;
|
|
}
|
|
|
|
function toggleJitsiMute() {
|
|
jitsiMicMuted = !jitsiMicMuted;
|
|
const btn = document.getElementById('btn-mute');
|
|
if (btn) {
|
|
btn.textContent = jitsiMicMuted ? '🔇 Unmute' : '🎤 Stumm';
|
|
}
|
|
return jitsiMicMuted;
|
|
}
|
|
|
|
function toggleJitsiVideo() {
|
|
jitsiVideoOff = !jitsiVideoOff;
|
|
const btn = document.getElementById('btn-video');
|
|
if (btn) {
|
|
btn.textContent = jitsiVideoOff ? '📷 Video an' : '📹 Video aus';
|
|
}
|
|
return jitsiVideoOff;
|
|
}
|
|
|
|
async function copyMeetingLink() {
|
|
if (currentJitsiMeetingUrl) {
|
|
await navigator.clipboard.writeText(currentJitsiMeetingUrl);
|
|
alert('Meeting-Link wurde kopiert!');
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function joinScheduledMeeting(meetingId) {
|
|
console.log('Joining scheduled meeting:', meetingId);
|
|
const jitsiDomain = 'meet.jit.si';
|
|
currentJitsiMeetingUrl = `https://${jitsiDomain}/${meetingId}`;
|
|
|
|
const container = document.getElementById('jitsi-container');
|
|
const placeholder = document.getElementById('jitsi-placeholder');
|
|
const controls = document.getElementById('jitsi-controls');
|
|
|
|
if (placeholder) placeholder.style.display = 'none';
|
|
if (controls) controls.style.display = 'flex';
|
|
|
|
if (container) {
|
|
const iframe = document.createElement('iframe');
|
|
iframe.setAttribute('src', `${currentJitsiMeetingUrl}#config.prejoinPageEnabled=false`);
|
|
container.appendChild(iframe);
|
|
}
|
|
|
|
return currentJitsiMeetingUrl;
|
|
}
|
|
|
|
describe('Jitsi Videokonferenz Module', () => {
|
|
beforeEach(() => {
|
|
setupJitsiMockDOM();
|
|
currentJitsiMeetingUrl = null;
|
|
jitsiMicMuted = false;
|
|
jitsiVideoOff = false;
|
|
});
|
|
|
|
describe('startInstantMeeting', () => {
|
|
test('should create a meeting URL with bp- prefix', async () => {
|
|
const result = await startInstantMeeting();
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.url).toMatch(/^https:\/\/meet\.jit\.si\/bp-/);
|
|
expect(currentJitsiMeetingUrl).toBe(result.url);
|
|
});
|
|
|
|
test('should hide placeholder and show controls', async () => {
|
|
await startInstantMeeting();
|
|
|
|
expect(mockElements['jitsi-placeholder'].style.display).toBe('none');
|
|
expect(mockElements['jitsi-controls'].style.display).toBe('flex');
|
|
expect(mockElements['meeting-link-display'].style.display).toBe('flex');
|
|
});
|
|
|
|
test('should update meeting URL display', async () => {
|
|
await startInstantMeeting();
|
|
|
|
expect(mockElements['meeting-url'].textContent).toMatch(/^https:\/\/meet\.jit\.si\/bp-/);
|
|
});
|
|
});
|
|
|
|
describe('leaveJitsiMeeting', () => {
|
|
test('should reset all meeting state', async () => {
|
|
await startInstantMeeting();
|
|
expect(currentJitsiMeetingUrl).not.toBeNull();
|
|
|
|
leaveJitsiMeeting();
|
|
|
|
expect(currentJitsiMeetingUrl).toBeNull();
|
|
expect(jitsiMicMuted).toBe(false);
|
|
expect(jitsiVideoOff).toBe(false);
|
|
});
|
|
|
|
test('should show placeholder and hide controls', async () => {
|
|
await startInstantMeeting();
|
|
|
|
leaveJitsiMeeting();
|
|
|
|
expect(mockElements['jitsi-placeholder'].style.display).toBe('flex');
|
|
expect(mockElements['jitsi-controls'].style.display).toBe('none');
|
|
expect(mockElements['meeting-link-display'].style.display).toBe('none');
|
|
});
|
|
});
|
|
|
|
describe('toggleJitsiMute', () => {
|
|
test('should toggle mute state', () => {
|
|
expect(jitsiMicMuted).toBe(false);
|
|
|
|
const result1 = toggleJitsiMute();
|
|
expect(result1).toBe(true);
|
|
expect(jitsiMicMuted).toBe(true);
|
|
|
|
const result2 = toggleJitsiMute();
|
|
expect(result2).toBe(false);
|
|
expect(jitsiMicMuted).toBe(false);
|
|
});
|
|
|
|
test('should update button text', () => {
|
|
toggleJitsiMute();
|
|
expect(mockElements['btn-mute'].textContent).toBe('🔇 Unmute');
|
|
|
|
toggleJitsiMute();
|
|
expect(mockElements['btn-mute'].textContent).toBe('🎤 Stumm');
|
|
});
|
|
});
|
|
|
|
describe('toggleJitsiVideo', () => {
|
|
test('should toggle video state', () => {
|
|
expect(jitsiVideoOff).toBe(false);
|
|
|
|
const result1 = toggleJitsiVideo();
|
|
expect(result1).toBe(true);
|
|
expect(jitsiVideoOff).toBe(true);
|
|
|
|
const result2 = toggleJitsiVideo();
|
|
expect(result2).toBe(false);
|
|
expect(jitsiVideoOff).toBe(false);
|
|
});
|
|
|
|
test('should update button text', () => {
|
|
toggleJitsiVideo();
|
|
expect(mockElements['btn-video'].textContent).toBe('📷 Video an');
|
|
|
|
toggleJitsiVideo();
|
|
expect(mockElements['btn-video'].textContent).toBe('📹 Video aus');
|
|
});
|
|
});
|
|
|
|
describe('copyMeetingLink', () => {
|
|
test('should copy meeting URL to clipboard when active', async () => {
|
|
await startInstantMeeting();
|
|
|
|
const result = await copyMeetingLink();
|
|
|
|
expect(result).toBe(true);
|
|
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(currentJitsiMeetingUrl);
|
|
expect(alert).toHaveBeenCalledWith('Meeting-Link wurde kopiert!');
|
|
});
|
|
|
|
test('should return false when no active meeting', async () => {
|
|
const result = await copyMeetingLink();
|
|
|
|
expect(result).toBe(false);
|
|
expect(navigator.clipboard.writeText).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('joinScheduledMeeting', () => {
|
|
test('should create meeting URL with provided ID', () => {
|
|
const meetingId = 'scheduled-meeting-123';
|
|
|
|
const url = joinScheduledMeeting(meetingId);
|
|
|
|
expect(url).toBe(`https://meet.jit.si/${meetingId}`);
|
|
expect(currentJitsiMeetingUrl).toBe(url);
|
|
});
|
|
|
|
test('should hide placeholder and show controls', () => {
|
|
joinScheduledMeeting('test-meeting');
|
|
|
|
expect(mockElements['jitsi-placeholder'].style.display).toBe('none');
|
|
expect(mockElements['jitsi-controls'].style.display).toBe('flex');
|
|
});
|
|
});
|
|
});
|
|
|
|
// ============================================
|
|
// MATRIX MESSENGER MODULE TESTS
|
|
// ============================================
|
|
|
|
// Messenger stub functions (copied from studio.js)
|
|
function startQuickMeeting() {
|
|
showVideoPanel();
|
|
// In real implementation: setTimeout(() => startInstantMeeting(), 100);
|
|
return 'video-panel-shown';
|
|
}
|
|
|
|
function createClassRoom() {
|
|
console.log('Creating class room...');
|
|
alert('Klassenraum-Erstellung wird in Phase 2 implementiert.');
|
|
return 'stub';
|
|
}
|
|
|
|
function scheduleParentMeeting() {
|
|
console.log('Scheduling parent meeting...');
|
|
alert('Elterngespräch-Planung wird in Phase 2 implementiert.');
|
|
return 'stub';
|
|
}
|
|
|
|
function selectRoom(roomId) {
|
|
console.log('Selecting room:', roomId);
|
|
// Stub: Would load room messages
|
|
return roomId;
|
|
}
|
|
|
|
function sendMessage() {
|
|
const input = document.getElementById('messenger-input');
|
|
const message = input?.value?.trim();
|
|
if (!message) {
|
|
console.log('Empty message, not sending');
|
|
return false;
|
|
}
|
|
console.log('Sending message:', message);
|
|
// Stub: Would send via Matrix API
|
|
if (input) input.value = '';
|
|
return true;
|
|
}
|
|
|
|
// Setup Messenger mock DOM
|
|
function setupMessengerMockDOM() {
|
|
setupMockDOM();
|
|
|
|
mockElements['messenger-input'] = {
|
|
id: 'messenger-input',
|
|
value: '',
|
|
style: { display: 'block' }
|
|
};
|
|
}
|
|
|
|
describe('Matrix Messenger Module', () => {
|
|
beforeEach(() => {
|
|
setupMessengerMockDOM();
|
|
});
|
|
|
|
describe('startQuickMeeting', () => {
|
|
test('should show video panel', () => {
|
|
const result = startQuickMeeting();
|
|
|
|
expect(result).toBe('video-panel-shown');
|
|
expect(mockElements['panel-video'].style.display).toBe('flex');
|
|
});
|
|
});
|
|
|
|
describe('createClassRoom', () => {
|
|
test('should return stub indicator', () => {
|
|
const result = createClassRoom();
|
|
|
|
expect(result).toBe('stub');
|
|
expect(alert).toHaveBeenCalledWith('Klassenraum-Erstellung wird in Phase 2 implementiert.');
|
|
});
|
|
});
|
|
|
|
describe('scheduleParentMeeting', () => {
|
|
test('should return stub indicator', () => {
|
|
const result = scheduleParentMeeting();
|
|
|
|
expect(result).toBe('stub');
|
|
expect(alert).toHaveBeenCalledWith('Elterngespräch-Planung wird in Phase 2 implementiert.');
|
|
});
|
|
});
|
|
|
|
describe('selectRoom', () => {
|
|
test('should return room ID', () => {
|
|
const roomId = 'room-123';
|
|
|
|
const result = selectRoom(roomId);
|
|
|
|
expect(result).toBe(roomId);
|
|
});
|
|
});
|
|
|
|
describe('sendMessage', () => {
|
|
test('should return false for empty message', () => {
|
|
mockElements['messenger-input'].value = '';
|
|
|
|
const result = sendMessage();
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
test('should return false for whitespace-only message', () => {
|
|
mockElements['messenger-input'].value = ' ';
|
|
|
|
const result = sendMessage();
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
test('should return true and clear input for valid message', () => {
|
|
mockElements['messenger-input'].value = 'Hello World';
|
|
|
|
const result = sendMessage();
|
|
|
|
expect(result).toBe(true);
|
|
expect(mockElements['messenger-input'].value).toBe('');
|
|
});
|
|
});
|
|
});
|
|
|
|
// ============================================
|
|
// INTEGRATION TESTS
|
|
// ============================================
|
|
|
|
describe('Panel Integration', () => {
|
|
beforeEach(() => {
|
|
setupJitsiMockDOM();
|
|
});
|
|
|
|
test('switching from messenger to video should preserve panel state', () => {
|
|
showMessengerPanel();
|
|
expect(mockElements['panel-messenger'].style.display).toBe('flex');
|
|
expect(mockElements['sidebar-messenger'].classList.contains('active')).toBe(true);
|
|
|
|
showVideoPanel();
|
|
expect(mockElements['panel-messenger'].style.display).toBe('none');
|
|
expect(mockElements['panel-video'].style.display).toBe('flex');
|
|
expect(mockElements['sidebar-messenger'].classList.contains('active')).toBe(false);
|
|
expect(mockElements['sidebar-video'].classList.contains('active')).toBe(true);
|
|
});
|
|
|
|
test('video panel should be accessible via startQuickMeeting', () => {
|
|
showMessengerPanel();
|
|
|
|
startQuickMeeting();
|
|
|
|
expect(mockElements['panel-video'].style.display).toBe('flex');
|
|
expect(mockElements['sidebar-video'].classList.contains('active')).toBe(true);
|
|
});
|
|
});
|