From 28ac4b0d8cd296c00e1480884550fec3480048bc Mon Sep 17 00:00:00 2001 From: juancarmore Date: Tue, 8 Jul 2025 23:58:37 +0200 Subject: [PATCH] tests: update WebComponent tests for improved attribute handling and event management --- .../tests/unit/attributes.test.ts | 22 +-- .../webcomponent/tests/unit/commands.test.ts | 136 ++++++++++++++---- .../webcomponent/tests/unit/events.test.ts | 68 +++++++-- .../webcomponent/tests/unit/lifecycle.test.ts | 39 +++-- 4 files changed, 200 insertions(+), 65 deletions(-) diff --git a/frontend/webcomponent/tests/unit/attributes.test.ts b/frontend/webcomponent/tests/unit/attributes.test.ts index e0c05f0..dc39970 100644 --- a/frontend/webcomponent/tests/unit/attributes.test.ts +++ b/frontend/webcomponent/tests/unit/attributes.test.ts @@ -1,9 +1,8 @@ -import { describe, it, expect, jest } from '@jest/globals'; -import { WEBCOMPONENT_ROOM_URL } from '../config'; +import { describe, expect, it, jest } from '@jest/globals'; import { OpenViduMeet } from '../../src/components/OpenViduMeet'; import '../../src/index'; -describe('Web Component Attributes', () => { +describe('OpenViduMeet WebComponent Attributes', () => { let component: OpenViduMeet; beforeEach(() => { @@ -21,8 +20,10 @@ describe('Web Component Attributes', () => { expect(iframe).not.toBeNull(); expect(iframe?.getAttribute('allow')).toContain('camera'); expect(iframe?.getAttribute('allow')).toContain('microphone'); - expect(iframe?.getAttribute('allow')).toContain('fullscreen'); expect(iframe?.getAttribute('allow')).toContain('display-capture'); + expect(iframe?.getAttribute('allow')).toContain('fullscreen'); + expect(iframe?.getAttribute('allow')).toContain('autoplay'); + expect(iframe?.getAttribute('allow')).toContain('compute-pressure'); }); it('should reject rendering iframe when "room-url" attribute is missing', () => { @@ -41,25 +42,28 @@ describe('Web Component Attributes', () => { }); it('should update iframe src when "room-url" attribute changes', () => { - component.setAttribute('room-url', WEBCOMPONENT_ROOM_URL); + const roomUrl = 'https://example.com/room/testRoom-123?secret=123456'; + component.setAttribute('room-url', roomUrl); component.setAttribute('user', 'testUser'); // Manually trigger the update (MutationObserver doesn't always trigger in tests) (component as any).updateIframeSrc(); const iframe = component.shadowRoot?.querySelector('iframe'); - expect(iframe?.src).toEqual(`${WEBCOMPONENT_ROOM_URL}?user=testUser`); + expect(iframe?.src).toEqual(`${roomUrl}&user=testUser`); }); it('should extract origin from room-url and set as allowed origin', () => { - const roomUrl = 'https://example.com/room/123'; + const domain = 'https://example.com'; + const roomUrl = `${domain}/room/testRoom-123?secret=123456`; component.setAttribute('room-url', roomUrl); // Trigger update (component as any).updateIframeSrc(); // Check if origin was extracted and set - expect((component as any).targetIframeOrigin).toBe('https://example.com'); - expect((component as any).commandsManager.targetIframeOrigin).toBe('https://example.com'); + expect((component as any).targetIframeOrigin).toBe(domain); + expect((component as any).commandsManager.targetIframeOrigin).toBe(domain); + expect((component as any).eventsManager.targetIframeOrigin).toBe(domain); }); }); diff --git a/frontend/webcomponent/tests/unit/commands.test.ts b/frontend/webcomponent/tests/unit/commands.test.ts index 08769eb..1f61557 100644 --- a/frontend/webcomponent/tests/unit/commands.test.ts +++ b/frontend/webcomponent/tests/unit/commands.test.ts @@ -1,10 +1,11 @@ -import { describe, it, expect, jest } from '@jest/globals'; +import { describe, expect, it, jest } from '@jest/globals'; +import { CommandsManager } from '../../src/components/CommandsManager'; import { OpenViduMeet } from '../../src/components/OpenViduMeet'; import '../../src/index'; import { WebComponentCommand } from '../../src/typings/ce/command.model'; -import { CommandsManager } from '../../src/components/CommandsManager'; +import { WebComponentEvent } from '../../src/typings/ce/event.model'; -describe('OpenViduMeet Web Component Commands', () => { +describe('OpenViduMeet WebComponent Commands', () => { let component: OpenViduMeet; let commandsManager: CommandsManager; @@ -21,31 +22,121 @@ describe('OpenViduMeet Web Component Commands', () => { }); it('should update allowedOrigin when setAllowedOrigin is called', () => { - const testOrigin = 'https://test-origin.com'; - - + const testOrigin = 'https://example.com'; commandsManager.setTargetOrigin(testOrigin); - expect(commandsManager['targetIframeOrigin']).toBe(testOrigin); // Check if it was updated + expect(commandsManager['targetIframeOrigin']).toBe(testOrigin); expect((component as any).commandsManager.targetIframeOrigin).toBe(testOrigin); }); - it('should call commandsManager.leaveRoom when leaveRoom is called', () => { - const meetSpy = jest.spyOn(component['commandsManager'], 'leaveRoom'); + it('should send initialize command with initialize()', () => { + const spy = jest.spyOn(commandsManager as any, 'sendMessage'); - const spy = jest.spyOn(commandsManager, 'sendMessage' as keyof CommandsManager); - component.leaveRoom(); + const testOrigin = 'https://example.com'; + Object.defineProperty(window, 'location', { + value: { origin: testOrigin }, + writable: true + }); - expect(meetSpy).toHaveBeenCalled(); - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledWith({ command: WebComponentCommand.LEAVE_ROOM }); + commandsManager.initialize(); + expect(spy).toHaveBeenCalledWith({ + command: WebComponentCommand.INITIALIZE, + payload: { domain: testOrigin } + }); }); - it('should call commandsManager.sendMessage when endMeeting is called', () => { - const spy = jest.spyOn(commandsManager, 'sendMessage' as keyof CommandsManager); + it('should subscribe and trigger event with on()', () => { + const callback = jest.fn(); + commandsManager.on(component, WebComponentEvent.READY, callback); + + const event = new CustomEvent(WebComponentEvent.READY, { detail: { foo: 'bar' } }); + component.dispatchEvent(event); + + expect(callback).toHaveBeenCalledWith({ foo: 'bar' }); + }); + + it('should not subscribe unsupported events with on()', () => { + const callback = jest.fn(); + commandsManager.on(component, 'not-supported' as any, callback); + + const event = new CustomEvent('not-supported', { detail: { foo: 'bar' } }); + component.dispatchEvent(event); + + expect(callback).not.toHaveBeenCalled(); + }); + + it('should subscribe and trigger event only once with once()', () => { + const callback = jest.fn(); + commandsManager.once(component, WebComponentEvent.READY, callback); + + const event = new CustomEvent(WebComponentEvent.READY, { detail: 123 }); + component.dispatchEvent(event); + component.dispatchEvent(event); + + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith(123); + }); + + it('should unsubscribe a specific handler with off()', () => { + const callback = jest.fn(); + commandsManager.on(component, WebComponentEvent.READY, callback); + + commandsManager.off(component, WebComponentEvent.READY, callback); + + const event = new CustomEvent(WebComponentEvent.READY, {}); + component.dispatchEvent(event); + + expect(callback).not.toHaveBeenCalled(); + }); + + it('should unsubscribe all handlers for an event with off()', () => { + const cb1 = jest.fn(); + const cb2 = jest.fn(); + commandsManager.on(component, WebComponentEvent.READY, cb1); + commandsManager.on(component, WebComponentEvent.READY, cb2); + + commandsManager.off(component, WebComponentEvent.READY); + + const event = new CustomEvent(WebComponentEvent.READY, {}); + component.dispatchEvent(event); + + expect(cb1).not.toHaveBeenCalled(); + expect(cb2).not.toHaveBeenCalled(); + }); + + it('should call commandsManager.leaveRoom when leaveRoom is called', () => { + const leaveRoomSpy = jest.spyOn(component['commandsManager'], 'leaveRoom'); + const sendMessageSpy = jest.spyOn(commandsManager, 'sendMessage' as keyof CommandsManager); + component.leaveRoom(); + + expect(leaveRoomSpy).toHaveBeenCalled(); + expect(sendMessageSpy).toHaveBeenCalledTimes(1); + expect(sendMessageSpy).toHaveBeenCalledWith({ command: WebComponentCommand.LEAVE_ROOM }); + }); + + it('should call commandsManager.endMeeting when endMeeting is called', () => { + const endMeetingSpy = jest.spyOn(component['commandsManager'], 'endMeeting'); + const sendMessageSpy = jest.spyOn(commandsManager, 'sendMessage' as keyof CommandsManager); component.endMeeting(); - expect(spy).toHaveBeenCalledWith({ command: WebComponentCommand.END_MEETING }); + + expect(endMeetingSpy).toHaveBeenCalled(); + expect(sendMessageSpy).toHaveBeenCalledTimes(1); + expect(sendMessageSpy).toHaveBeenCalledWith({ command: WebComponentCommand.END_MEETING }); + }); + + it('should call commandsManager.kickParticipant when kickParticipant is called', () => { + const participantIdentity = 'test-participant'; + const kickParticipantSpy = jest.spyOn(component['commandsManager'], 'kickParticipant'); + const sendMessageSpy = jest.spyOn(commandsManager, 'sendMessage' as keyof CommandsManager); + component.kickParticipant(participantIdentity); + + expect(kickParticipantSpy).toHaveBeenCalledWith(participantIdentity); + expect(sendMessageSpy).toHaveBeenCalledTimes(1); + expect(sendMessageSpy).toHaveBeenCalledWith({ + command: WebComponentCommand.KICK_PARTICIPANT, + payload: { participantIdentity } + }); }); it('should send message to iframe with correct origin', () => { @@ -60,18 +151,13 @@ describe('OpenViduMeet Web Component Commands', () => { }); // Set allowed origin - (component as any).commandsManager.setTargetOrigin('https://test.com'); + const testOrigin = 'https://example.com'; + (component as any).commandsManager.setTargetOrigin(testOrigin); // Send a message (component as any).commandsManager.sendMessage({ command: 'TEST' }); // Check if postMessage was called correctly - expect(mockPostMessage).toHaveBeenCalledWith({ command: 'TEST' }, 'https://test.com'); + expect(mockPostMessage).toHaveBeenCalledWith({ command: 'TEST' }, testOrigin); }); - - // it('should call commandsManager.sendMessage when toggleChat is called', () => { - // const spy = jest.spyOn(component['commandsManager'], 'sendMessage'); - // component.toggleChat(); - // expect(spy).toHaveBeenCalledWith({ action: WebComponentCommand.TOGGLE_CHAT }); - // }); }); diff --git a/frontend/webcomponent/tests/unit/events.test.ts b/frontend/webcomponent/tests/unit/events.test.ts index 0bbc1fa..fad90d9 100644 --- a/frontend/webcomponent/tests/unit/events.test.ts +++ b/frontend/webcomponent/tests/unit/events.test.ts @@ -1,12 +1,13 @@ -import { describe, it, expect, jest } from '@jest/globals'; -import { OpenViduMeet } from '../../src/components/OpenViduMeet'; +import { describe, expect, it, jest } from '@jest/globals'; import { EventsManager } from '../../src/components/EventsManager'; +import { OpenViduMeet } from '../../src/components/OpenViduMeet'; import '../../src/index'; -describe('Web Component Events', () => { +describe('OpenViduMeet WebComponent Events', () => { + const testOrigin = 'http://example.com'; + let component: OpenViduMeet; let eventsManager: EventsManager; - const allowedOrigin = 'http://example.com'; beforeEach(() => { component = document.createElement('openvidu-meet') as OpenViduMeet; @@ -19,26 +20,77 @@ describe('Web Component Events', () => { document.body.innerHTML = ''; }); + it('should update allowedOrigin when setAllowedOrigin is called', () => { + eventsManager.setTargetOrigin(testOrigin); + + // Check if it was updated + expect((eventsManager as any).targetIframeOrigin).toBe(testOrigin); + expect((component as any).eventsManager.targetIframeOrigin).toBe(testOrigin); + }); + it('should register message event listener on connection', () => { const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); // Call connectedCallback again (even though it was called when created) (component as any).connectedCallback(); - expect(addEventListenerSpy).toHaveBeenCalledWith('message', expect.any(Function)); }); it('should ignore invalid messages', () => { + eventsManager.setTargetOrigin(testOrigin); const dispatchEventSpy = jest.spyOn(component, 'dispatchEvent'); const event = new MessageEvent('message', { - origin: allowedOrigin, + origin: testOrigin, data: { invalid: 'data' } }); (eventsManager as any).handleMessage(event); - expect(dispatchEventSpy).not.toHaveBeenCalled(); }); - // TODO: Add test for leave room event + it('should ignore messages from unknown origins', () => { + const dispatchEventSpy = jest.spyOn(component, 'dispatchEvent'); + const event = new MessageEvent('message', { + origin: 'https://not-allowed.com', + data: { event: 'READY', payload: {} } + }); + + (eventsManager as any).handleMessage(event); + expect(dispatchEventSpy).not.toHaveBeenCalled(); + }); + + it('should dispatch event for valid messages from allowed origin', () => { + eventsManager.setTargetOrigin(testOrigin); + const dispatchEventSpy = jest.spyOn(component, 'dispatchEvent'); + const event = new MessageEvent('message', { + origin: testOrigin, + data: { event: 'READY', payload: { foo: 'bar' } } + }); + + (eventsManager as any).handleMessage(event); + expect(dispatchEventSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'READY', + detail: { foo: 'bar' } + }) + ); + }); + + it('should ignore messages without an event property', () => { + eventsManager.setTargetOrigin(testOrigin); + const dispatchEventSpy = jest.spyOn(component, 'dispatchEvent'); + const event = new MessageEvent('message', { + origin: testOrigin, + data: { payload: {} } + }); + + (eventsManager as any).handleMessage(event); + expect(dispatchEventSpy).not.toHaveBeenCalled(); + }); + + it('should remove message event listener on cleanup', () => { + const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); + eventsManager.cleanup(); + expect(removeEventListenerSpy).toHaveBeenCalledWith('message', expect.any(Function)); + }); }); diff --git a/frontend/webcomponent/tests/unit/lifecycle.test.ts b/frontend/webcomponent/tests/unit/lifecycle.test.ts index 23097d4..eead60d 100644 --- a/frontend/webcomponent/tests/unit/lifecycle.test.ts +++ b/frontend/webcomponent/tests/unit/lifecycle.test.ts @@ -1,11 +1,12 @@ -import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals'; +import { afterEach, beforeEach, describe, expect, it, jest } from '@jest/globals'; +import { CommandsManager } from '../../src/components/CommandsManager'; import { OpenViduMeet } from '../../src/components/OpenViduMeet'; import '../../src/index'; import { WebComponentCommand } from '../../src/typings/ce/command.model'; -import { WEBCOMPONENT_ROOM_URL } from '../config'; -import { CommandsManager } from '../../src/components/CommandsManager'; describe('OpenViduMeet Event Handling', () => { + const testOrigin = window.location.origin; + let component: OpenViduMeet; let commandsManager: CommandsManager; @@ -21,33 +22,22 @@ describe('OpenViduMeet Event Handling', () => { document.body.innerHTML = ''; }); - it('should be created correctly', () => { - expect(component).toBeDefined(); - expect(component.shadowRoot).not.toBeNull(); - }); - - it('should remove message event listener on disconnection', () => { - const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); - - // Call disconnectedCallback - (component as any).disconnectedCallback(); - - expect(removeEventListenerSpy).toHaveBeenCalledWith('message', expect.any(Function)); - }); - it('should call sendMessage when READY event is received', () => { const sendMessageSpy = jest.spyOn(commandsManager, 'sendMessage' as keyof CommandsManager); + (component as any).eventsManager.setTargetOrigin(testOrigin); + // Mock a message event const readyEvent = new MessageEvent('message', { - data: { event: 'READY' } + data: { event: 'READY' }, + origin: testOrigin }); window.dispatchEvent(readyEvent); expect(sendMessageSpy).toHaveBeenCalledTimes(1); expect(sendMessageSpy).toHaveBeenCalledWith({ command: WebComponentCommand.INITIALIZE, - payload: { domain: window.location.origin } + payload: { domain: testOrigin } }); // Check if sendMessage was not called again @@ -58,12 +48,15 @@ describe('OpenViduMeet Event Handling', () => { // Create a spy for dispatchEvent const dispatchEventSpy = jest.spyOn(component, 'dispatchEvent'); + (component as any).eventsManager.setTargetOrigin(testOrigin); + // Mock a message event const messageEvent = new MessageEvent('message', { data: { event: 'test-event', payload: { foo: 'bar' } - } + }, + origin: testOrigin }); // Manually call the handler @@ -88,7 +81,6 @@ describe('OpenViduMeet Event Handling', () => { (component as any).loadTimeout = setTimeout(() => {}, 1000); // Remove from DOM - (component as any).disconnectedCallback(); // Check if cleanup was called @@ -119,7 +111,8 @@ describe('OpenViduMeet Event Handling', () => { document.body.appendChild(component); // Set attributes - component.setAttribute('room-url', WEBCOMPONENT_ROOM_URL); + const roomUrl = 'https://example.com/room/testRoom-123?secret=123456'; + component.setAttribute('room-url', roomUrl); component.setAttribute('user', 'testUser'); component.setAttribute('role', 'publisher'); component.setAttribute('token', 'test-token'); @@ -131,7 +124,7 @@ describe('OpenViduMeet Event Handling', () => { const iframe = component.shadowRoot?.querySelector('iframe'); const src = iframe?.src; - expect(src).toContain(WEBCOMPONENT_ROOM_URL); + expect(src).toContain(roomUrl); expect(src).toContain('user=testUser'); expect(src).toContain('role=publisher'); expect(src).toContain('token=test-token');