1
0
mirror of https://github.com/videojs/video.js.git synced 2024-12-23 02:04:34 +02:00
video.js/test/unit/spatial-navigation.test.js

494 lines
18 KiB
JavaScript
Raw Permalink Normal View History

feat: implement spatial navigation (#8570) * feat(player): add spatialNavigation feature Adds spatialNavigation feature to enhance user experience - Implemented spatial navigation in slider component - Enhanced player functionality for improved navigation * feat(player): add spatialNavigation class Adds spatialNavigation class to manage spatial-navigation-polyfill - Set class SpatialNavigation on its own file - Imported SpatialNavigation class on component class * feat(player): update spatialNavigation class Adds 3 methods to spatialNavigation class to manage spatial-navigation-polyfill - Added start() to: Start listen of keydown events - Added stop() to: Stop listen key down events - Added getComponents() to: Get current focusable components * feat(player): modify spatialNavigation class & modify component class Modify spatialNavigation class: -Remove unrequired version of function ‘getComponents’ Modify component class: -Add function ‘getIsFocusable’ * Added methods getPositions, handleFocus and handleBLur for spatial navigation needs * feat(player): modify Component class, BigPlayButton class & ClickableComponent class Modify Component class: -Add method getIsAvailableToBeFocused -Modify method getIsFocusable to only focus on finding focusable candidates Modify spatialNavigation class: -Remove unrequired method ‘getIsFocusable’ Modify component class: -Remove unrequired method ‘getIsFocusable’ * Added import in player.js, Created base methods inside spatial-navigation.js * feat(player): modify Component class & SpatialNavigation class Modify Component class: -Modify method getIsAvailableToBeFocused to be more strict on candidates Modify spatialNavigation class: -Modify method getComponents to get all focusable components * feat(player): modify Component class Modify Component class: -Add documentation to ‘isVisible’ function * added keydown event logic for spatial-navigation * feat(player): modify SpatialNavigation class Modify SpatialNavigation class: -Modify documentation of functions * feat(player): modify SpatialNavigation class Modify SpatialNavigation class: -Add ‘clear’ & ‘remove’ methods * feat(player): modify SpatialNavigation class Modify SpatialNavigation class: -Add documentation of functions * feat(player): modify SpatialNavigation class Modify SpatialNavigation class: -Add function ‘getCurretComponent’‘’ * feat(player): modify SpatialNavigation class Modify SpatialNavigation class: -Add documentation for ‘findBestCandidate’ method * Added logic for moving focus to the best candidate * Implemented move, findBestCandidate, isInDirection, and calculateDistance methods for spatial navigation logic * Added a new player option enableKeydownListener, Added gap: 1px to control-bar for spatial-navigation-polyfill needs * feat(player): modify SpatialNavigation class & Component class Modify SpatialNavigation class: -Add function ‘handlePlayerBlur’ -Add function ‘handlePlayerFocus’ Modify Component class: -Modify ‘handleBlur’ -Modify ‘handleFocus’ * Removed enableKeydownListener flag, as user should start the SpatialNavigation manually * Added functionality to track changes in the focusableComponents list (custom event focusableComponentsChanged) * feat(player): modify SpatialNavigation class, ModalDialog & Component class Modify SpatialNavigation class: -Add ‘lastFocusedComponent’ -Add function ‘refocusComponent’ Modify ModalDialog class: -Add condition on ‘close’ function Modify Component class: -Modify ‘handleBlur’ to store blurred component * feat(player): modify ModalDialog Modify ModalDialog: -Add condition to close Modal on Backspace * Refactor SpatialNavigation to use player.spatialNavigation * Added a new custom event endOfFocusableComponents * Added new styles for focused elements in case spatial navigation is enabled * feat(player): modify SpatialNavigation class: -Add condition so getComponents can get as candidates the UI elements from the playlist-ui * Changed to window.SpatialNabigation to this.player_.spatialNavigation * feat(player): modify text-track-settings, created test-track-settings-colors.js, text-track-settings-font.js,text-track-fieldset.js & text-track-select.js: Modify text-track-settings class: - Add changes so newly created components can work as content of the modal. - Create new components as a refactor of the contents of text-track-settings * changed handleKeyDown inside component.js, getComponents method is now iterating player.children * feat(player): create TrackSettingsControls Component & Modify TextTrackSettings Create TrackSettingsControls Component: -Create Component to show buttons reset & done as components. Modify TextTrackSettings: -Add Component TrackSettingsControls in TextTrackSettings * feat(player): Modify ModalDialog Modify ModalDialog: -Add condition for stop propagation of event inside of ModalDialog when spatialNavigation is enabled * getIsFocusable and getIsAvailableToBeFocused methods are now accepting el as a parameter, added a new methods findSuitableDOMChild and focus for spatialNavigation class * feat(player): Modify TextTrackSettings: Modify TextTrackSettings: -Remove unrequired methods to create DOM elements since now those are created by Components. * feat(player): Modify CaptionSettingsMenuItem: Modify CaptionSettingsMenuItem: -Add condition to focus component of TextTrackSelect when modal is open * feat(player): Modify TextTrackSelect & TextTrackFieldset: Modify TextTrackSelect : Modify TextTrackFieldset: -Add comments to certain functions to explain the code * feat(player): Modify TrackSettingsControls: Modify TrackSettingsControls: -Remove unrequired comments & add comments to certain functions to explain the code * feat(player): Modify SpatialNavigation, Component & ModalDialog: Modify SpatialNavigation: Modify Component: Modify ModalDialog: -Add & update comments of documentation. * Handle ENTER keydown in Modals when spatial navigation is enabled * feat(player): Modify ModalDialog, spatialNavigation, TrackSettingsControls, TextTrackFieldset, TextTrackSelect, TrackSettingsColors, TrackSettingsFont: Modify ModalDialog: Modify spatialNavigation: Modify TrackSettingsControls: Modify TextTrackFieldset: Modify TextTrackSelect: Modify TrackSettingsColors: Modify TrackSettingsFont: -Add & update comments of documentation. * Implement additional RCU controls * feat(player): Modify Component class: Modify Component : -Remove unrequired condition inside of handleFocus method. * feat(player): Modify ModalDialog & CaptionSettingsMenuItem Modify ModalDialog: Modify CaptionSettingsMenuItem: -Modify spatialNavigation condition to be more specific regarding spatialNavigation implementation. * feat(player): Modify SpatialNavigation class: Modify SpatialNavigation : -Fix bug where ‘enter’ press was not working properly on select component inside of the ‘vjs-text-track-settings’ modal. * feat(player): Modify SpatialNavigation class: Modify SpatialNavigation : -Minor improvements on the loops of certain functions to stop when they have found the element they are looking for. -Implement minor spacing formatting on switch statement. * Update src/js/component.js More understandable documentation. Co-authored-by: Dzianis Dashkevich <98566601+dzianis-dashkevich@users.noreply.github.com> * Update src/js/component.js More understandable documentation. Co-authored-by: Dzianis Dashkevich <98566601+dzianis-dashkevich@users.noreply.github.com> * feat(player): Modify SpatialNavigation & Component class: Modify Component class : Modify SpatialNavigation class : -Modify ‘getIsFocusable’ function to use ‘this.el_’ instead of ‘el’ parameter * feat(player): Modify SpatialNavigation class: Modify SpatialNavigation class : -Refactor onKeyDown function to use static data & return when pause is true. * feat(player): Modify SpatialNavigation class: Modify SpatialNavigation class : -Refactor to use ‘.el()’ instead of ‘.el_’ * Update src/js/spatial-navigation.js Co-authored-by: Walter Seymour <walterseymour15@gmail.com> * feat(player): Modify ModalDialog class & MenuItem class: Modify ModalDialog class : Modify MenuItem class : -Correct typo of ‘isSpatialNavlistening’ to ‘isSpatialNavListening’. * removed unused property, remove this.focus, which was added for testing purposes * Changed parameters to private, removed redundant code, removed initialFocusedComponent parameter, change STEP_SECONDS to static * feat(player): solve remaining conflict: Modify Spatial Navigation class : - Solve conflict * feat(player): Rename TrackSettingsColors & TrackSettingsFont * feat(player): Remove unrequired functions calls from components TextTrackSettingsColors & TextTrackSettingsFont. * feat(player): Update spatial-navigation.js's keypress return keyword. * bind focus and blur just if spatial navigation is enabled, add 1px gap if spatial navigation is enabled * feat(player): Modify calls on 'isListening' & 'isPaused' for ModalDialog & TextTrackMenuItem * feat(player): remove unrequired object on component 'TrackSettingsControls' * Removed 1px gap * feat(player): Rename function ‘getComponents’ to ‘updateFocusableComponents’ * Changed SpatialNavigation class to extend EventTarget, removed redundant methods for events * fix(player): fix call of 'getIsAvailableToBeFocused' that was throwing an error. * removed Static maps for key presses and extended keycode with the missing keys * refactor(player): Modify functions of 'getIsDisabled', 'getIsExpresslyInert' & 'getIsFocusable' to be more in pair when stablished code of the player. * Conditional assignment for keycode.codes.back based on platform, changed Backspace to Back key for Modal closing * Extend the object for reverse lookup, prenet Up/down keys to open a menu if spatial navigation is anabled * refactor(player): Refactor 'SpatialNavKeycodes' file to not patch 'keycode' dependency * fix(pllayer): fix issue related to 'back' not being used properly in function 'isEventKey' * feat(player): Rename imports of 'spatial-navigation-keycode' to have their extension * feat(player): Add example of use of 'Client app uses a global spatial-navigation solution' * feat(player): rename 'spatial-navigation-keycode.js' filename * Fix on src chnage issue, ESC button closing modal, expand vjs-modal-dialog * change file name and object name * fix: Update ids of labels to use 'guid' so unit test works properly * fix: update localized text in text-track-settings-font & text-track-settings * Mark some methods as private * fix: modify content of modal 'text-track-settings' to change language properly * fix: add missing '.' in jsdoc of text-track components * feature: add unit test for 'text-track-select' component * Add test for Spatial Navigation * test(player): Add minor test related to 'handleBlur' & 'handleFocus' * feat(player): Remove unrequired files from 'react-video-nav-app' * test(player): Add small test to check if 'getPositions' returns required properties * test(player): add test to verify 'getPositions()' properties are not empty * Add missing tests for performMediaAction_ and move * test(player): add test to for 'component.js' related to 'handleBlur' * test(player): add minor test in component related to test keypress propagation event * test(player): add test for component related to 'getIsAvailableToBeFocused' function * test(player): add test for Modal Dialog related to call function of spatial navigation * test(player): add tests for 'spatial-navigation-key-codes' * test(player): add tests for keycodes related to 'should return event name if keyCode is not available' * test(player): add minor test for case when not required parametters are passed * test(player): add test for 'caption-settings-menu-item' * feat(player): remove 'react-video-nav-app' * Move handleFocus and handleBlur from components.js to spatial-navigation.js * refactor(player): refactor 'searchForTrackSelect' to be handled in the spatial navigation * remove unrequired code in function 'searchForTrackSelect' * update documentation comment to be in pair to its current use * remove spatial navigation keydown from modal dialog and move it to spatial navigation class, modify the modal-dialog test accordingly * remove useless tests * Remove caption-settings-menu-item.test.js * Add minor test to 'searchForTrackSelect' in spatial-navigation.test.js * Add unit test for back key and listening to events --------- Co-authored-by: CarlosVillasenor <carlosdeveloper9@gmail.com> Co-authored-by: Dzianis Dashkevich <98566601+dzianis-dashkevich@users.noreply.github.com> Co-authored-by: Walter Seymour <walterseymour15@gmail.com> Co-authored-by: Carlos Villasenor Castillo <cvillasenor@Carloss-MacBook-Pro.local>
2024-04-18 03:34:52 +02:00
/* eslint-env qunit */
import SpatialNavigation from '../../src/js/spatial-navigation.js';
import SpatialNavigationKeyCodes from '../../src/js/utils/spatial-navigation-key-codes';
import TestHelpers from './test-helpers.js';
import sinon from 'sinon';
import document from 'global/document';
import TextTrackSelect from '../../src/js/tracks/text-track-select';
QUnit.module('SpatialNavigation', {
beforeEach() {
this.clock = sinon.useFakeTimers();
// Ensure each test starts with a player that has spatial navigation enabled
this.player = TestHelpers.makePlayer({
controls: true,
bigPlayButton: true,
spatialNavigation: { enabled: true }
});
// Directly reference the instantiated SpatialNavigation from the player
this.spatialNav = this.player.spatialNavigation;
},
afterEach() {
if (this.spatialNav && this.spatialNav.isListening_) {
this.spatialNav.stop();
}
this.player.dispose();
this.clock.restore();
}
});
QUnit.test('Initialization sets up initial properties', function(assert) {
assert.ok(this.spatialNav instanceof SpatialNavigation, 'Instance of SpatialNavigation');
assert.deepEqual(this.spatialNav.focusableComponents, [], 'Initial focusableComponents is an empty array');
assert.notOk(this.spatialNav.isListening_, 'isListening_ is initially false');
assert.notOk(this.spatialNav.isPaused_, 'isPaused_ is initially false');
});
QUnit.test('start method initializes event listeners', function(assert) {
const onSpy = sinon.spy(this.player, 'on');
this.spatialNav.start();
// Check if event listeners are added
assert.ok(onSpy.calledWith('keydown'), 'keydown event listener added');
assert.ok(onSpy.calledWith('loadedmetadata'), 'loadedmetadata event listener added');
assert.ok(onSpy.calledWith('modalKeydown'), 'modalKeydown event listener added');
assert.ok(onSpy.calledWith('modalclose'), 'modalclose event listener added');
// Additionally, check if isListening_ flag is set
assert.ok(this.spatialNav.isListening_, 'isListening_ flag is set');
onSpy.restore();
});
QUnit.test('stop method removes event listeners', function(assert) {
const offSpy = sinon.spy(this.player, 'off');
this.spatialNav.start();
this.spatialNav.stop();
assert.ok(offSpy.calledWith('keydown'), 'keydown event listener removed');
assert.notOk(this.spatialNav.isListening_, 'isListening_ flag is unset');
offSpy.restore();
});
QUnit.test('onKeyDown_ handles navigation keys', function(assert) {
// Ensure onKeyDown_ is bound correctly.
assert.equal(typeof this.spatialNav.onKeyDown_, 'function', 'onKeyDown_ should be a function');
assert.equal(this.spatialNav.onKeyDown_.hasOwnProperty('prototype'), false, 'onKeyDown_ should be bound to the instance');
// Prepare a spy for the move method to track its calls.
const moveSpy = sinon.spy(this.spatialNav, 'move');
// Create and dispatch a mock keydown event.
const event = new KeyboardEvent('keydown', { // eslint-disable-line no-undef
key: 'ArrowRight',
code: 'ArrowRight',
keyCode: 39
});
// Directly invoke the onKeyDown_ handler to simulate receiving the event.
this.spatialNav.onKeyDown_(event);
// Assert that move was called correctly.
assert.ok(moveSpy.calledOnce, 'move method should be called once on keydown event');
assert.ok(moveSpy.calledWith('right'), 'move method should be called with "right" argument');
// Restore the spy to clean up.
moveSpy.restore();
});
QUnit.test('onKeyDown_ handles media keys', function(assert) {
const performMediaActionSpy = sinon.spy(this.spatialNav, 'performMediaAction_');
// Create a mock event for the 'play' key, using the hardcoded keyCode 415.
const event = new KeyboardEvent('keydown', { // eslint-disable-line no-undef
keyCode: 415
});
// Directly call the onKeyDown_ handler.
this.spatialNav.onKeyDown_(event);
// Assert that the performMediaAction_ method was called.
assert.ok(performMediaActionSpy.calledOnce, 'performMediaAction_ method should be called once for media play key');
assert.ok(performMediaActionSpy.calledWith('play'), 'performMediaAction_ should be called with "play"');
performMediaActionSpy.restore();
});
QUnit.test('onKeyDown_ handles Back key when target is closeable', function(assert) {
// Create a spy for the close method.
const closeSpy = sinon.spy();
// Create a spy for the preventDefault method.
const preventDefaultSpy = sinon.spy();
// Create a mock event target that is closeable.
const closeableTarget = {
close: closeSpy,
closeable: () => true
};
// Create a mock event for the 'Back' key, including a properly mocked originalEvent.
const event = {
preventDefault: preventDefaultSpy,
target: closeableTarget,
originalEvent: {
keyCode: SpatialNavigationKeyCodes.BACK,
preventDefault: preventDefaultSpy
}
};
// Stub the SpatialNavigationKeyCodes.isEventKey to return true when the 'Back' key is pressed.
sinon.stub(SpatialNavigationKeyCodes, 'isEventKey').callsFake((evt, keyName) => keyName === 'Back');
// Call the onKeyDown_ method with the mock event.
this.spatialNav.onKeyDown_(event);
// Asserts
assert.ok(SpatialNavigationKeyCodes.isEventKey.calledWith(event.originalEvent, 'Back'), 'isEventKey should be called with Back');
assert.ok(preventDefaultSpy.calledOnce, 'preventDefault should be called once');
assert.ok(closeSpy.calledOnce, 'close method should be called on the target');
// Restore stubs
SpatialNavigationKeyCodes.isEventKey.restore();
});
QUnit.test('performMediaAction_ executes play', function(assert) {
const playSpy = sinon.spy(this.player, 'play');
this.spatialNav.performMediaAction_('play');
assert.ok(playSpy.calledOnce, 'play method should be called once for "play" action');
playSpy.restore();
});
QUnit.test('performMediaAction_ executes pause', function(assert) {
const pauseSpy = sinon.spy(this.player, 'pause');
sinon.stub(this.player, 'paused').returns(false);
this.spatialNav.performMediaAction_('pause');
assert.ok(pauseSpy.calledOnce, 'pause method should be called once for "pause" action');
pauseSpy.restore();
});
QUnit.test('performMediaAction_ executes fast forward', function(assert) {
const userSeekSpy = sinon.spy(this.spatialNav, 'userSeek_');
const STEP_SECONDS = 5;
const initialTime = 30;
this.player.currentTime = () => initialTime;
this.spatialNav.performMediaAction_('ff');
const expectedNewTime = initialTime + STEP_SECONDS;
assert.ok(userSeekSpy.calledOnce, 'userSeek_ method should be called once for "fast forward" action');
assert.ok(userSeekSpy.calledWith(expectedNewTime), `userSeek_ method should be called with correct time offset: expected ${expectedNewTime}, got ${userSeekSpy.firstCall.args[0]}`);
userSeekSpy.restore();
});
QUnit.test('performMediaAction_ executes rewind', function(assert) {
const userSeekSpy = sinon.spy(this.spatialNav, 'userSeek_');
const STEP_SECONDS = 5;
const initialTime = 30;
this.player.currentTime = () => initialTime;
this.spatialNav.performMediaAction_('rw');
const expectedNewTime = initialTime - STEP_SECONDS;
assert.ok(userSeekSpy.calledOnce, 'userSeek_ method should be called once for "rewind" action');
assert.ok(userSeekSpy.calledWith(expectedNewTime), `userSeek_ method should be called with correct time offset: expected ${expectedNewTime}, got ${userSeekSpy.firstCall.args[0]}`);
userSeekSpy.restore();
});
QUnit.test('focus method sets focus on a player component', function(assert) {
this.spatialNav.start();
const component = this.player.getChild('bigPlayButton');
assert.ok(component, 'The target component exists.');
// Mock getIsAvailableToBeFocused to always return true
component.getIsAvailableToBeFocused = () => true;
// Spy on the focus method to check if it's called
const focusSpy = sinon.spy(component, 'focus');
this.spatialNav.focus(component);
assert.ok(focusSpy.calledOnce, 'focus method called on component');
// Clean up
focusSpy.restore();
});
QUnit.test('refocusComponent method refocuses the last focused component after losing focus', function(assert) {
this.spatialNav.start();
// Get the bigPlayButton component from the player
const bigPlayButton = this.player.getChild('bigPlayButton');
// Mock getIsAvailableToBeFocused to always return true for testing
bigPlayButton.getIsAvailableToBeFocused = () => true;
// Focus the bigPlayButton and set it as the last focused component
this.spatialNav.focus(bigPlayButton);
// Simulate losing focus
bigPlayButton.el().blur();
// Call refocusComponent to attempt to refocus the last focused component
this.spatialNav.refocusComponent();
// Check if the bigPlayButton is focused again
assert.strictEqual(this.spatialNav.lastFocusedComponent_, bigPlayButton, 'lastFocusedComponent_ should be set to the blurred component');
});
QUnit.test('move method changes focus to the right component', function(assert) {
this.spatialNav.start();
const rightComponent = {
name: () => 'rightComponent',
el: () => document.createElement('div'),
focus: sinon.spy(),
getPositions: () => ({ center: { x: 300, y: 100 }, boundingClientRect: { top: 0, left: 300, bottom: 200, right: 400 } }),
getIsAvailableToBeFocused: () => true
};
const currentComponent = {
name: () => 'currentComponent',
el: () => document.createElement('div'),
focus: sinon.spy(),
getPositions: () => ({ center: { x: 100, y: 100 }, boundingClientRect: { top: 0, left: 100, bottom: 200, right: 200 } }),
getIsAvailableToBeFocused: () => true
};
this.spatialNav.focusableComponents = [currentComponent, rightComponent];
this.spatialNav.getCurrentComponent = () => currentComponent;
this.spatialNav.move('right');
assert.ok(rightComponent.focus.calledOnce, 'Focus should move to the right component');
assert.notOk(currentComponent.focus.called, 'Focus should not remain on the current component');
});
QUnit.test('move method changes focus to the left component', function(assert) {
this.spatialNav.start();
const leftComponent = {
name: () => 'leftComponent',
el: () => document.createElement('div'),
focus: sinon.spy(),
getPositions: () => ({ center: { x: 0, y: 100 }, boundingClientRect: { top: 0, left: 0, bottom: 200, right: 100 } }),
getIsAvailableToBeFocused: () => true
};
const currentComponent = {
name: () => 'currentComponent',
el: () => document.createElement('div'),
focus: sinon.spy(),
getPositions: () => ({ center: { x: 200, y: 100 }, boundingClientRect: { top: 0, left: 200, bottom: 200, right: 300 } }),
getIsAvailableToBeFocused: () => true
};
this.spatialNav.focusableComponents = [leftComponent, currentComponent];
this.spatialNav.getCurrentComponent = () => currentComponent;
this.spatialNav.move('left');
assert.ok(leftComponent.focus.calledOnce, 'Focus should move to the left component');
assert.notOk(currentComponent.focus.called, 'Focus should not remain on the current component');
});
QUnit.test('move method changes focus to the above component', function(assert) {
this.spatialNav.start();
const aboveComponent = {
name: () => 'aboveComponent',
el: () => document.createElement('div'),
focus: sinon.spy(),
getPositions: () => ({ center: { x: 100, y: 0 }, boundingClientRect: { top: 0, left: 0, bottom: 100, right: 200 } }),
getIsAvailableToBeFocused: () => true
};
const currentComponent = {
name: () => 'currentComponent',
el: () => document.createElement('div'),
focus: sinon.spy(),
getPositions: () => ({ center: { x: 100, y: 200 }, boundingClientRect: { top: 200, left: 0, bottom: 300, right: 200 } }),
getIsAvailableToBeFocused: () => true
};
this.spatialNav.focusableComponents = [aboveComponent, currentComponent];
this.spatialNav.getCurrentComponent = () => currentComponent;
this.spatialNav.move('up');
assert.ok(aboveComponent.focus.calledOnce, 'Focus should move to the above component');
assert.notOk(currentComponent.focus.called, 'Focus should not remain on the current component');
});
QUnit.test('move method changes focus to the below component', function(assert) {
this.spatialNav.start();
const belowComponent = {
name: () => 'belowComponent',
el: () => document.createElement('div'),
focus: sinon.spy(),
getPositions: () => ({ center: { x: 100, y: 300 }, boundingClientRect: { top: 300, left: 0, bottom: 400, right: 200 } }),
getIsAvailableToBeFocused: () => true
};
const currentComponent = {
name: () => 'currentComponent',
el: () => document.createElement('div'),
focus: sinon.spy(),
getPositions: () => ({ center: { x: 100, y: 100 }, boundingClientRect: { top: 0, left: 0, bottom: 200, right: 200 } }),
getIsAvailableToBeFocused: () => true
};
this.spatialNav.focusableComponents = [belowComponent, currentComponent];
this.spatialNav.getCurrentComponent = () => currentComponent;
this.spatialNav.move('down');
assert.ok(belowComponent.focus.calledOnce, 'Focus should move to the below component');
assert.notOk(currentComponent.focus.called, 'Focus should not remain on the current component');
});
QUnit.test('getCurrentComponent method returns the current focused component', function(assert) {
this.spatialNav.start();
// Get the bigPlayButton component from the player
const bigPlayButton = this.player.getChild('bigPlayButton');
// Mock getIsAvailableToBeFocused to always return true for testing
bigPlayButton.getIsAvailableToBeFocused = () => true;
// Focus the bigPlayButton
this.spatialNav.focus(bigPlayButton);
// Call getCurrentComponent to get the current focused component
const currentComponent = this.spatialNav.getCurrentComponent();
// Check if the currentComponent is the bigPlayButton
assert.strictEqual(currentComponent, bigPlayButton, 'getCurrentComponent should return the focused component');
});
QUnit.test('add method adds a new focusable component', function(assert) {
this.spatialNav.start();
// Create a mock component with an 'el_' property and 'el' method
const newComponent = {
name: () => 'newComponent',
el_: document.createElement('div'),
el() {
return this.el_;
},
focus: sinon.spy(),
getPositions: () => ({ center: { x: 100, y: 100 }, boundingClientRect: { top: 100, left: 100, bottom: 200, right: 200 } }),
getIsAvailableToBeFocused: () => true,
getIsFocusable: () => true
};
// Add the new component
this.spatialNav.add(newComponent);
// Check if the new component is added to the list of focusable components
assert.strictEqual(this.spatialNav.focusableComponents.includes(newComponent), true, 'New component should be added');
});
QUnit.test('remove method removes a focusable component', function(assert) {
this.spatialNav.start();
// Create a mock component
const componentToRemove = {
name: () => 'componentToRemove',
el_: document.createElement('div'),
el() {
return this.el_;
},
focus: sinon.spy(),
getPositions: () => ({ center: { x: 100, y: 100 }, boundingClientRect: { top: 100, left: 100, bottom: 200, right: 200 } }),
getIsAvailableToBeFocused: () => true,
getIsFocusable: () => true
};
// Add the component to be removed
this.spatialNav.add(componentToRemove);
// Remove the component
this.spatialNav.remove(componentToRemove);
// Check if the component is removed from the list of focusable components
assert.strictEqual(this.spatialNav.focusableComponents.includes(componentToRemove), false, 'Component should be removed');
});
QUnit.test('clear method removes all focusable components', function(assert) {
this.spatialNav.start();
// Create mock components
const component1 = {
name: () => 'component1',
el_: document.createElement('div'),
el() {
return this.el_;
},
focus: sinon.spy(),
getPositions: () => ({ center: { x: 100, y: 100 }, boundingClientRect: { top: 100, left: 100, bottom: 200, right: 200 } }),
getIsAvailableToBeFocused: () => true,
getIsFocusable: () => true
};
const component2 = {
name: () => 'component2',
el_: document.createElement('div'),
el() {
return this.el_;
},
focus: sinon.spy(),
getPositions: () => ({ center: { x: 100, y: 100 }, boundingClientRect: { top: 100, left: 100, bottom: 200, right: 200 } }),
getIsAvailableToBeFocused: () => true,
getIsFocusable: () => true
};
// Add the components
this.spatialNav.add(component1);
this.spatialNav.add(component2);
// Clear all components
this.spatialNav.clear();
// Check if the focusableComponents array is empty after clearing
assert.strictEqual(this.spatialNav.focusableComponents.length, 0, 'All components should be cleared');
});
QUnit.test('should call `searchForTrackSelect()` if spatial navigation is enabled on click event', function(assert) {
const element = document.createElement('div');
element.classList.add('vjs-text-track-settings');
const clickEvent = new MouseEvent('click', { // eslint-disable-line no-undef
view: this.window,
bubbles: true,
cancelable: true,
currentTarget: element
});
Object.defineProperty(clickEvent, 'relatedTarget', {writable: false, value: element});
Object.defineProperty(clickEvent, 'currentTarget', {writable: false, value: element});
const trackSelectSpy = sinon.spy(this.spatialNav, 'searchForTrackSelect_');
feat: implement spatial navigation (#8570) * feat(player): add spatialNavigation feature Adds spatialNavigation feature to enhance user experience - Implemented spatial navigation in slider component - Enhanced player functionality for improved navigation * feat(player): add spatialNavigation class Adds spatialNavigation class to manage spatial-navigation-polyfill - Set class SpatialNavigation on its own file - Imported SpatialNavigation class on component class * feat(player): update spatialNavigation class Adds 3 methods to spatialNavigation class to manage spatial-navigation-polyfill - Added start() to: Start listen of keydown events - Added stop() to: Stop listen key down events - Added getComponents() to: Get current focusable components * feat(player): modify spatialNavigation class & modify component class Modify spatialNavigation class: -Remove unrequired version of function ‘getComponents’ Modify component class: -Add function ‘getIsFocusable’ * Added methods getPositions, handleFocus and handleBLur for spatial navigation needs * feat(player): modify Component class, BigPlayButton class & ClickableComponent class Modify Component class: -Add method getIsAvailableToBeFocused -Modify method getIsFocusable to only focus on finding focusable candidates Modify spatialNavigation class: -Remove unrequired method ‘getIsFocusable’ Modify component class: -Remove unrequired method ‘getIsFocusable’ * Added import in player.js, Created base methods inside spatial-navigation.js * feat(player): modify Component class & SpatialNavigation class Modify Component class: -Modify method getIsAvailableToBeFocused to be more strict on candidates Modify spatialNavigation class: -Modify method getComponents to get all focusable components * feat(player): modify Component class Modify Component class: -Add documentation to ‘isVisible’ function * added keydown event logic for spatial-navigation * feat(player): modify SpatialNavigation class Modify SpatialNavigation class: -Modify documentation of functions * feat(player): modify SpatialNavigation class Modify SpatialNavigation class: -Add ‘clear’ & ‘remove’ methods * feat(player): modify SpatialNavigation class Modify SpatialNavigation class: -Add documentation of functions * feat(player): modify SpatialNavigation class Modify SpatialNavigation class: -Add function ‘getCurretComponent’‘’ * feat(player): modify SpatialNavigation class Modify SpatialNavigation class: -Add documentation for ‘findBestCandidate’ method * Added logic for moving focus to the best candidate * Implemented move, findBestCandidate, isInDirection, and calculateDistance methods for spatial navigation logic * Added a new player option enableKeydownListener, Added gap: 1px to control-bar for spatial-navigation-polyfill needs * feat(player): modify SpatialNavigation class & Component class Modify SpatialNavigation class: -Add function ‘handlePlayerBlur’ -Add function ‘handlePlayerFocus’ Modify Component class: -Modify ‘handleBlur’ -Modify ‘handleFocus’ * Removed enableKeydownListener flag, as user should start the SpatialNavigation manually * Added functionality to track changes in the focusableComponents list (custom event focusableComponentsChanged) * feat(player): modify SpatialNavigation class, ModalDialog & Component class Modify SpatialNavigation class: -Add ‘lastFocusedComponent’ -Add function ‘refocusComponent’ Modify ModalDialog class: -Add condition on ‘close’ function Modify Component class: -Modify ‘handleBlur’ to store blurred component * feat(player): modify ModalDialog Modify ModalDialog: -Add condition to close Modal on Backspace * Refactor SpatialNavigation to use player.spatialNavigation * Added a new custom event endOfFocusableComponents * Added new styles for focused elements in case spatial navigation is enabled * feat(player): modify SpatialNavigation class: -Add condition so getComponents can get as candidates the UI elements from the playlist-ui * Changed to window.SpatialNabigation to this.player_.spatialNavigation * feat(player): modify text-track-settings, created test-track-settings-colors.js, text-track-settings-font.js,text-track-fieldset.js & text-track-select.js: Modify text-track-settings class: - Add changes so newly created components can work as content of the modal. - Create new components as a refactor of the contents of text-track-settings * changed handleKeyDown inside component.js, getComponents method is now iterating player.children * feat(player): create TrackSettingsControls Component & Modify TextTrackSettings Create TrackSettingsControls Component: -Create Component to show buttons reset & done as components. Modify TextTrackSettings: -Add Component TrackSettingsControls in TextTrackSettings * feat(player): Modify ModalDialog Modify ModalDialog: -Add condition for stop propagation of event inside of ModalDialog when spatialNavigation is enabled * getIsFocusable and getIsAvailableToBeFocused methods are now accepting el as a parameter, added a new methods findSuitableDOMChild and focus for spatialNavigation class * feat(player): Modify TextTrackSettings: Modify TextTrackSettings: -Remove unrequired methods to create DOM elements since now those are created by Components. * feat(player): Modify CaptionSettingsMenuItem: Modify CaptionSettingsMenuItem: -Add condition to focus component of TextTrackSelect when modal is open * feat(player): Modify TextTrackSelect & TextTrackFieldset: Modify TextTrackSelect : Modify TextTrackFieldset: -Add comments to certain functions to explain the code * feat(player): Modify TrackSettingsControls: Modify TrackSettingsControls: -Remove unrequired comments & add comments to certain functions to explain the code * feat(player): Modify SpatialNavigation, Component & ModalDialog: Modify SpatialNavigation: Modify Component: Modify ModalDialog: -Add & update comments of documentation. * Handle ENTER keydown in Modals when spatial navigation is enabled * feat(player): Modify ModalDialog, spatialNavigation, TrackSettingsControls, TextTrackFieldset, TextTrackSelect, TrackSettingsColors, TrackSettingsFont: Modify ModalDialog: Modify spatialNavigation: Modify TrackSettingsControls: Modify TextTrackFieldset: Modify TextTrackSelect: Modify TrackSettingsColors: Modify TrackSettingsFont: -Add & update comments of documentation. * Implement additional RCU controls * feat(player): Modify Component class: Modify Component : -Remove unrequired condition inside of handleFocus method. * feat(player): Modify ModalDialog & CaptionSettingsMenuItem Modify ModalDialog: Modify CaptionSettingsMenuItem: -Modify spatialNavigation condition to be more specific regarding spatialNavigation implementation. * feat(player): Modify SpatialNavigation class: Modify SpatialNavigation : -Fix bug where ‘enter’ press was not working properly on select component inside of the ‘vjs-text-track-settings’ modal. * feat(player): Modify SpatialNavigation class: Modify SpatialNavigation : -Minor improvements on the loops of certain functions to stop when they have found the element they are looking for. -Implement minor spacing formatting on switch statement. * Update src/js/component.js More understandable documentation. Co-authored-by: Dzianis Dashkevich <98566601+dzianis-dashkevich@users.noreply.github.com> * Update src/js/component.js More understandable documentation. Co-authored-by: Dzianis Dashkevich <98566601+dzianis-dashkevich@users.noreply.github.com> * feat(player): Modify SpatialNavigation & Component class: Modify Component class : Modify SpatialNavigation class : -Modify ‘getIsFocusable’ function to use ‘this.el_’ instead of ‘el’ parameter * feat(player): Modify SpatialNavigation class: Modify SpatialNavigation class : -Refactor onKeyDown function to use static data & return when pause is true. * feat(player): Modify SpatialNavigation class: Modify SpatialNavigation class : -Refactor to use ‘.el()’ instead of ‘.el_’ * Update src/js/spatial-navigation.js Co-authored-by: Walter Seymour <walterseymour15@gmail.com> * feat(player): Modify ModalDialog class & MenuItem class: Modify ModalDialog class : Modify MenuItem class : -Correct typo of ‘isSpatialNavlistening’ to ‘isSpatialNavListening’. * removed unused property, remove this.focus, which was added for testing purposes * Changed parameters to private, removed redundant code, removed initialFocusedComponent parameter, change STEP_SECONDS to static * feat(player): solve remaining conflict: Modify Spatial Navigation class : - Solve conflict * feat(player): Rename TrackSettingsColors & TrackSettingsFont * feat(player): Remove unrequired functions calls from components TextTrackSettingsColors & TextTrackSettingsFont. * feat(player): Update spatial-navigation.js's keypress return keyword. * bind focus and blur just if spatial navigation is enabled, add 1px gap if spatial navigation is enabled * feat(player): Modify calls on 'isListening' & 'isPaused' for ModalDialog & TextTrackMenuItem * feat(player): remove unrequired object on component 'TrackSettingsControls' * Removed 1px gap * feat(player): Rename function ‘getComponents’ to ‘updateFocusableComponents’ * Changed SpatialNavigation class to extend EventTarget, removed redundant methods for events * fix(player): fix call of 'getIsAvailableToBeFocused' that was throwing an error. * removed Static maps for key presses and extended keycode with the missing keys * refactor(player): Modify functions of 'getIsDisabled', 'getIsExpresslyInert' & 'getIsFocusable' to be more in pair when stablished code of the player. * Conditional assignment for keycode.codes.back based on platform, changed Backspace to Back key for Modal closing * Extend the object for reverse lookup, prenet Up/down keys to open a menu if spatial navigation is anabled * refactor(player): Refactor 'SpatialNavKeycodes' file to not patch 'keycode' dependency * fix(pllayer): fix issue related to 'back' not being used properly in function 'isEventKey' * feat(player): Rename imports of 'spatial-navigation-keycode' to have their extension * feat(player): Add example of use of 'Client app uses a global spatial-navigation solution' * feat(player): rename 'spatial-navigation-keycode.js' filename * Fix on src chnage issue, ESC button closing modal, expand vjs-modal-dialog * change file name and object name * fix: Update ids of labels to use 'guid' so unit test works properly * fix: update localized text in text-track-settings-font & text-track-settings * Mark some methods as private * fix: modify content of modal 'text-track-settings' to change language properly * fix: add missing '.' in jsdoc of text-track components * feature: add unit test for 'text-track-select' component * Add test for Spatial Navigation * test(player): Add minor test related to 'handleBlur' & 'handleFocus' * feat(player): Remove unrequired files from 'react-video-nav-app' * test(player): Add small test to check if 'getPositions' returns required properties * test(player): add test to verify 'getPositions()' properties are not empty * Add missing tests for performMediaAction_ and move * test(player): add test to for 'component.js' related to 'handleBlur' * test(player): add minor test in component related to test keypress propagation event * test(player): add test for component related to 'getIsAvailableToBeFocused' function * test(player): add test for Modal Dialog related to call function of spatial navigation * test(player): add tests for 'spatial-navigation-key-codes' * test(player): add tests for keycodes related to 'should return event name if keyCode is not available' * test(player): add minor test for case when not required parametters are passed * test(player): add test for 'caption-settings-menu-item' * feat(player): remove 'react-video-nav-app' * Move handleFocus and handleBlur from components.js to spatial-navigation.js * refactor(player): refactor 'searchForTrackSelect' to be handled in the spatial navigation * remove unrequired code in function 'searchForTrackSelect' * update documentation comment to be in pair to its current use * remove spatial navigation keydown from modal dialog and move it to spatial navigation class, modify the modal-dialog test accordingly * remove useless tests * Remove caption-settings-menu-item.test.js * Add minor test to 'searchForTrackSelect' in spatial-navigation.test.js * Add unit test for back key and listening to events --------- Co-authored-by: CarlosVillasenor <carlosdeveloper9@gmail.com> Co-authored-by: Dzianis Dashkevich <98566601+dzianis-dashkevich@users.noreply.github.com> Co-authored-by: Walter Seymour <walterseymour15@gmail.com> Co-authored-by: Carlos Villasenor Castillo <cvillasenor@Carloss-MacBook-Pro.local>
2024-04-18 03:34:52 +02:00
const textTrackSelectComponent = new TextTrackSelect(this.player, {
SelectOptions: ['Option 1', 'Option 2', 'Option 3'],
legendId: '1',
id: 1,
labelId: '1'
});
this.spatialNav.updateFocusableComponents = () => [textTrackSelectComponent];
this.spatialNav.handlePlayerBlur_(clickEvent);
assert.ok(trackSelectSpy.calledOnce);
});