fix: Restore all files lost during destructive rebase
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>
This commit is contained in:
849
backend/frontend/tests/studio-panels.test.js
Normal file
849
backend/frontend/tests/studio-panels.test.js
Normal file
@@ -0,0 +1,849 @@
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user