From 7a39259507eb0b65fe947b6487847c28fe5460a2 Mon Sep 17 00:00:00 2001 From: Wolfram Hofmeister Date: Thu, 5 Sep 2024 13:27:03 +0200 Subject: [PATCH] Add unit tests for SelectBox --- spec/components/selectbox.spec.ts | 325 ++++++++++++++++++++++++++++++ spec/helper/MockHelper.ts | 9 + 2 files changed, 334 insertions(+) create mode 100644 spec/components/selectbox.spec.ts diff --git a/spec/components/selectbox.spec.ts b/spec/components/selectbox.spec.ts new file mode 100644 index 000000000..cbc43941f --- /dev/null +++ b/spec/components/selectbox.spec.ts @@ -0,0 +1,325 @@ +import type { PlayerAPI } from 'bitmovin-player'; + +import type { Component, ViewModeChangedEventArgs } from '../../src/ts/components/component'; +import { ViewMode } from '../../src/ts/components/component'; +import type { ListSelectorConfig } from '../../src/ts/components/listselector'; +import { SelectBox } from '../../src/ts/components/selectbox'; +import type { Event } from '../../src/ts/eventdispatcher'; +import { PlayerUtils } from '../../src/ts/playerutils'; +import type { UIInstanceManager } from '../../src/ts/uimanager'; +import { MockHelper } from '../helper/MockHelper'; +import getUiInstanceManagerMock = MockHelper.getUiInstanceManagerMock; +import getPlayerMock = MockHelper.getPlayerMock; +import generateDOMMock = MockHelper.generateDOMMock; +import PlayerState = PlayerUtils.PlayerState; +import type { DOM } from '../../src/ts/dom'; + +jest.mock('../../src/ts/dom', generateDOMMock); + +describe('SelectBox', () => { + let selectBox: SelectBox; + let playerMock: PlayerAPI; + let uiManagerMock: UIInstanceManager; + + beforeEach(() => { + selectBox = new SelectBox(); + playerMock = getPlayerMock(); + uiManagerMock = getUiInstanceManagerMock(); + }); + + describe('viewMode', () => { + it('should initialize the `ViewMode` to `Temporary`', () => { + expect(selectBox['viewMode']).toEqual(ViewMode.Temporary); + }); + }); + + describe('configure', () => { + test.each` + event + ${'onShow'} + ${'onHide'} + ${'onViewModeChanged'} + `('should subscribe to "$event"', ({ event }) => { + const subscribeSpy = jest.spyOn(selectBox[event as keyof SelectBox] as Event, 'subscribe'); + + selectBox.configure(playerMock, uiManagerMock); + + expect(subscribeSpy).toHaveBeenCalled(); + }); + + test.each` + event + ${'mouseenter'} + ${'mouseleave'} + `('should add a "$event" listener to the DOM element', ({ event }) => { + const onSpy = jest.spyOn(selectBox.getDomElement(), 'on'); + + selectBox.configure(playerMock, uiManagerMock); + + expect(onSpy).toHaveBeenCalledWith(event, expect.any(Function)); + }); + }); + + describe('onViewModeChangedEvent', () => { + let viewModeChangedSpy: jest.Mock, ViewModeChangedEventArgs]>; + + beforeEach(() => { + viewModeChangedSpy = jest.fn(); + selectBox.onViewModeChanged.subscribe(viewModeChangedSpy); + }); + + it('should dispatch the onViewModeChanged event', () => { + selectBox['onViewModeChangedEvent'](ViewMode.Persistent); + + expect(viewModeChangedSpy).toHaveBeenCalledWith(expect.any(SelectBox), { mode: ViewMode.Persistent }); + }); + + it('should not dispatch the onViewModeChanged event if the view mode did not change', () => { + selectBox['onViewModeChangedEvent'](ViewMode.Temporary); + + expect(viewModeChangedSpy).not.toHaveBeenCalled(); + }); + }); + + describe('toDomElement', () => { + test.each` + event + ${'onDisabled'} + ${'onHide'} + `('should subscribe to "$event" to close the dropdown', ({ event }) => { + const subscribeSpy = jest.spyOn(selectBox[event as keyof SelectBox] as Event, 'subscribe'); + + selectBox.getDomElement(); + + expect(subscribeSpy).toHaveBeenCalledWith(selectBox.closeDropdown); + }); + + it('should add event dropdown open event listeners', () => { + const domElement = selectBox.getDomElement(); + + expect(domElement.on).toHaveBeenCalled(); + }); + }); + + describe('configure', () => { + it('should subscribe to player state changes', () => { + const uiContainer = uiManagerMock.getUI(); + + selectBox.configure(playerMock, uiManagerMock); + + expect(uiContainer.onPlayerStateChange().subscribe).toHaveBeenCalledWith(selectBox['onPlayerStateChange']); + }); + }); + + describe('closeDropdown', () => { + it('should call blur on the DOM select element', () => { + const element = document.createElement('select'); + const blurSpy = jest.spyOn(element, 'blur'); + + jest.spyOn(selectBox as any, 'getSelectElement').mockReturnValue(element); + selectBox.closeDropdown(); + + expect(blurSpy).toHaveBeenCalled(); + }); + }); + + describe('onPlayerStateChange', () => { + test.each` + playerState | shouldClose + ${PlayerState.Idle} | ${true} + ${PlayerState.Finished} | ${true} + ${PlayerState.Paused} | ${false} + ${PlayerState.Playing} | ${false} + ${PlayerState.Prepared} | ${false} + `( + `should close the dropdown=$shouldClose when the player state changes to $playerState`, + ({ playerState, shouldClose }) => { + const closeDropdownSpy = jest.spyOn(selectBox, 'closeDropdown'); + + selectBox['onPlayerStateChange'](uiManagerMock.getUI(), playerState); + + if (shouldClose) { + expect(closeDropdownSpy).toHaveBeenCalled(); + } else { + expect(closeDropdownSpy).not.toHaveBeenCalled(); + } + }, + ); + }); + + describe('onDropdownOpened', () => { + it('should clear the existing closed event listener addition timeout', () => { + const clearTimeoutSpy = jest.spyOn(window, 'clearTimeout'); + + selectBox['onDropdownOpened'](); + + expect(clearTimeoutSpy).toHaveBeenCalled(); + }); + + it('should start a timeout to add closed event listeners', () => { + const setTimeoutSpy = jest.spyOn(window, 'setTimeout'); + + selectBox['onDropdownOpened'](); + + expect(setTimeoutSpy).toHaveBeenCalled(); + }); + + it('should change the view mode to `Persistent`', () => { + const onViewModeChangedSpy = jest.fn(); + + selectBox.onViewModeChanged.subscribe(onViewModeChangedSpy); + selectBox['onDropdownOpened'](); + + expect(onViewModeChangedSpy).toHaveBeenCalledWith(expect.any(SelectBox), { mode: ViewMode.Persistent }); + }); + }); + + describe('onDropdownClosed', () => { + it('should clear the closed event listener addition timeout', () => { + const clearTimeoutSpy = jest.spyOn(window, 'clearTimeout'); + + selectBox['onDropdownClosed'](); + + expect(clearTimeoutSpy).toHaveBeenCalled(); + }); + + it('should remove the close event listeners', () => { + const removeDropdownCloseListenersSpy = jest.fn(); + + selectBox['removeDropdownCloseListeners'] = removeDropdownCloseListenersSpy; + selectBox['onDropdownClosed'](); + + expect(removeDropdownCloseListenersSpy).toHaveBeenCalled(); + }); + + it('should change the view mode to `Temporary`', () => { + const onViewModeChangedSpy = jest.fn(); + + selectBox['viewMode'] = ViewMode.Persistent; + selectBox.onViewModeChanged.subscribe(onViewModeChangedSpy); + selectBox['onDropdownClosed'](); + + expect(onViewModeChangedSpy).toHaveBeenCalledWith(expect.any(SelectBox), { mode: ViewMode.Temporary }); + }); + }); + + describe('addDropdownCloseListeners', () => { + let selectElement: DOM; + + beforeEach(() => { + selectElement = { on: jest.fn(), off: jest.fn() } as unknown as DOM; + selectBox['selectElement'] = selectElement; + }); + + it('should remove existing close listeners', () => { + const removeDropdownCloseListenersSpy = jest.fn(); + + selectBox['removeDropdownCloseListeners'] = removeDropdownCloseListenersSpy; + selectBox['addDropdownCloseListeners'](); + + expect(removeDropdownCloseListenersSpy).toHaveBeenCalled(); + }); + + it('should clear the closed event listener addition timeout', () => { + const clearTimeoutSpy = jest.spyOn(window, 'clearTimeout'); + + selectBox['addDropdownCloseListeners'](); + + expect(clearTimeoutSpy).toHaveBeenCalled(); + }); + + it('should add close event listeners to the document', () => { + const addEventListenerSpy = jest.spyOn(document, 'addEventListener'); + + selectBox['addDropdownCloseListeners'](); + + expect(addEventListenerSpy).toHaveBeenCalled(); + }); + + it('should add close event listeners to the select element', () => { + selectBox['addDropdownCloseListeners'](); + + expect(selectElement.on).toHaveBeenCalled(); + }); + + it('should set the removeDropdownCloseListeners', () => { + const initialRemoveDropdownCloseListenersFunction = selectBox['removeDropdownCloseListeners']; + + selectBox['addDropdownCloseListeners'](); + + const newRemoveDropdownCloseListenersFunction = selectBox['removeDropdownCloseListeners']; + + expect(initialRemoveDropdownCloseListenersFunction).not.toEqual(newRemoveDropdownCloseListenersFunction); + }); + }); + + describe('removeDropdownCloseListeners', () => { + let selectElement: DOM; + + beforeEach(() => { + selectElement = { on: jest.fn(), off: jest.fn() } as unknown as DOM; + selectBox['selectElement'] = selectElement; + selectBox['addDropdownCloseListeners'](); + }); + + it('should remove close event listeners to the document', () => { + const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener'); + + selectBox['removeDropdownCloseListeners'](); + + expect(removeEventListenerSpy).toHaveBeenCalled(); + }); + + it('should remove close event listeners from the select element', () => { + selectBox['removeDropdownCloseListeners'](); + + expect(selectElement.off).toHaveBeenCalled(); + }); + }); + + describe('addDropdownOpenedListeners', () => { + let selectElement: DOM; + + beforeEach(() => { + selectElement = { on: jest.fn(), off: jest.fn() } as unknown as DOM; + selectBox['selectElement'] = selectElement; + }); + + it('should remove existing open listeners', () => { + const removeDropdownOpenedListenersSpy = jest.fn(); + + selectBox['removeDropdownOpenedListeners'] = removeDropdownOpenedListenersSpy; + selectBox['addDropdownOpenedListeners'](); + + expect(removeDropdownOpenedListenersSpy).toHaveBeenCalled(); + }); + + it('should add event listener on the select element', () => { + selectBox['addDropdownOpenedListeners'](); + + expect(selectElement.on).toHaveBeenCalled(); + }); + + it('should set the removeDropdownOpenedListeners', () => { + const initialRemoveDropdownOpenedListenersFunction = selectBox['removeDropdownOpenedListeners']; + + selectBox['addDropdownOpenedListeners'](); + + const newRemoveDropdownOpenedListenersFunction = selectBox['removeDropdownOpenedListeners']; + + expect(initialRemoveDropdownOpenedListenersFunction).not.toEqual(newRemoveDropdownOpenedListenersFunction); + }); + }); + + describe('removeDropdownOpenedListeners', () => { + it('should remove opened event listeners from the select element', () => { + const selectElement = { on: jest.fn(), off: jest.fn() } as unknown as DOM; + selectBox['selectElement'] = selectElement; + selectBox['addDropdownOpenedListeners'](); + + selectBox['removeDropdownOpenedListeners'](); + + expect(selectElement.off).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/helper/MockHelper.ts b/spec/helper/MockHelper.ts index 62fd341d3..5db46f8a0 100644 --- a/spec/helper/MockHelper.ts +++ b/spec/helper/MockHelper.ts @@ -2,6 +2,7 @@ import { PlayerAPI, PlayerEvent } from 'bitmovin-player'; import { UIInstanceManager } from '../../src/ts/uimanager'; import { DOM } from '../../src/ts/dom'; import { PlayerEventEmitter } from './PlayerEventEmitter'; +import { UIContainer } from '../../src/ts/components/uicontainer'; jest.mock('../../src/ts/dom'); @@ -18,7 +19,14 @@ export namespace MockHelper { }; } + export function getUiMock(): UIContainer { + return { + onPlayerStateChange: jest.fn().mockReturnValue({ subscribe: jest.fn() }), + } as unknown as UIContainer; + } + export function getUiInstanceManagerMock(): UIInstanceManager { + const uiMock = getUiMock(); const UiInstanceManagerMockClass: jest.Mock = jest.fn().mockImplementation(() => ({ onConfigured: getEventDispatcherMock(), getConfig: jest.fn().mockReturnValue({ @@ -29,6 +37,7 @@ export namespace MockHelper { markers: [], }, }), + getUI: () => uiMock, onControlsShow: getEventDispatcherMock(), onControlsHide: getEventDispatcherMock(), onComponentHide: getEventDispatcherMock(),