* fix: eslint operator-linebreak issue * fix: eslint quotes issue * fix: react jsx indent and props issues * fix: eslint trailing spaces issues * fix: eslint line around directives issue * fix: eslint semi rule * fix: eslint newline per chain rule * fix: eslint space infix ops rule * fix: eslint space-in-parens issue * fix: eslint space before function paren issue * fix: eslint space before blocks issue * fix: eslint arrow body style issue * fix: eslint dot-location issue * fix: eslint quotes issue * fix: eslint quote props issue * fix: eslint operator assignment issue * fix: eslint new line after import issue * fix: indent issues * fix: operator assignment issue * fix: all autofixable eslint issues * fix: all react related fixable issues * fix: autofixable eslint issues * chore: remove all template literals * fix: remaining autofixable issues * chore: apply amnesty on all existing issues * fix: failing xss-lint issues * refactor: apply amnesty on remaining issues * refactor: apply amnesty on new issues * fix: remove file level suppressions * refactor: apply amnesty on new issues
440 lines
20 KiB
JavaScript
440 lines
20 KiB
JavaScript
/* globals _ */
|
|
(function() {
|
|
'use strict';
|
|
|
|
describe('Video Context Menu', function() {
|
|
var state, openMenu, keyPressEvent, openSubmenuMouse, openSubmenuKeyboard, closeSubmenuMouse,
|
|
closeSubmenuKeyboard, menu, menuItems, menuSubmenuItem, submenu, submenuItems, overlay, $playButton;
|
|
|
|
openMenu = function() {
|
|
var $container = $('.video');
|
|
$container.find('video').trigger('contextmenu');
|
|
menu = $container.children('.contextmenu');
|
|
menuItems = menu.children('.menu-item').not('.submenu-item');
|
|
menuSubmenuItem = menu.children('.menu-item.submenu-item');
|
|
submenu = menuSubmenuItem.children('.submenu');
|
|
submenuItems = submenu.children('.menu-item');
|
|
overlay = $container.children('.overlay');
|
|
$playButton = $('.video_control.play');
|
|
};
|
|
|
|
keyPressEvent = function(key) {
|
|
return $.Event('keydown', {keyCode: key});
|
|
};
|
|
|
|
// eslint-disable-next-line no-shadow
|
|
openSubmenuMouse = function(menuSubmenuItem) {
|
|
menuSubmenuItem.mouseover();
|
|
jasmine.clock().tick(200);
|
|
expect(menuSubmenuItem).toHaveClass('is-opened');
|
|
};
|
|
|
|
// eslint-disable-next-line no-shadow
|
|
openSubmenuKeyboard = function(menuSubmenuItem, keyCode) {
|
|
menuSubmenuItem.focus().trigger(keyPressEvent(keyCode || $.ui.keyCode.RIGHT));
|
|
expect(menuSubmenuItem).toHaveClass('is-opened');
|
|
expect(menuSubmenuItem.children().last().children().first()).toBeFocused();
|
|
};
|
|
|
|
// eslint-disable-next-line no-shadow
|
|
closeSubmenuMouse = function(menuSubmenuItem) {
|
|
menuSubmenuItem.mouseleave();
|
|
jasmine.clock().tick(200);
|
|
expect(menuSubmenuItem).not.toHaveClass('is-opened');
|
|
};
|
|
|
|
// eslint-disable-next-line no-shadow
|
|
closeSubmenuKeyboard = function(menuSubmenuItem) {
|
|
menuSubmenuItem.children().first().focus().trigger(keyPressEvent($.ui.keyCode.LEFT));
|
|
expect(menuSubmenuItem).not.toHaveClass('is-opened');
|
|
expect(menuSubmenuItem).toBeFocused();
|
|
};
|
|
|
|
beforeEach(function() {
|
|
jasmine.clock().install();
|
|
// $.cookie is mocked, make sure we have a state with an unmuted volume.
|
|
$.cookie.and.returnValue('100');
|
|
jasmine.addMatchers({
|
|
toHaveCorrectLabels: function() {
|
|
return {
|
|
compare: function(actual, labelsList) {
|
|
return {
|
|
pass: _.difference(labelsList, _.map(actual, function(item) {
|
|
return $(item).text();
|
|
})).length === 0
|
|
};
|
|
}
|
|
};
|
|
}
|
|
});
|
|
});
|
|
|
|
afterEach(function() {
|
|
$('source').remove();
|
|
_.result(state.storage, 'clear');
|
|
_.result($('video').data('contextmenu'), 'destroy');
|
|
_.result(state.videoPlayer, 'destroy');
|
|
jasmine.clock().uninstall();
|
|
});
|
|
|
|
describe('constructor', function() {
|
|
it('the structure should be created on first `contextmenu` call', function() {
|
|
state = jasmine.initializePlayer();
|
|
expect(menu).not.toExist();
|
|
openMenu();
|
|
/*
|
|
Make sure we have the expected HTML structure:
|
|
- Play (Pause)
|
|
- Mute (Unmute)
|
|
- Fill browser (Exit full browser)
|
|
- Speed >
|
|
- 0.75x
|
|
- 1.0x
|
|
- 1.25x
|
|
- 1.50x
|
|
*/
|
|
|
|
// Only one context menu per video container
|
|
expect(menu).toBeInDOM();
|
|
expect(menu).toHaveClass('is-opened');
|
|
expect(menuItems).toHaveCorrectLabels(['Play', 'Mute', 'Fill browser']);
|
|
expect(menuSubmenuItem.children('span')).toHaveText('Speed');
|
|
expect(submenuItems).toHaveCorrectLabels(['0.75x', '1.0x', '1.25x', '1.50x']);
|
|
// Check that one of the speed submenu item is selected
|
|
expect(_.size(submenuItems.filter('.is-selected'))).toBe(1);
|
|
});
|
|
|
|
it('add ARIA attributes to menu, menu items, submenu and submenu items', function() {
|
|
state = jasmine.initializePlayer();
|
|
openMenu();
|
|
// Menu and its items.
|
|
expect(menu).toHaveAttr('role', 'menu');
|
|
menuItems.each(function() {
|
|
expect($(this)).toHaveAttrs({
|
|
'aria-selected': 'false',
|
|
role: 'menuitem'
|
|
});
|
|
});
|
|
|
|
expect(menuSubmenuItem).toHaveAttrs({
|
|
'aria-expanded': 'false',
|
|
'aria-haspopup': 'true',
|
|
role: 'menuitem'
|
|
});
|
|
|
|
// Submenu and its items.
|
|
expect(submenu).toHaveAttr('role', 'menu');
|
|
submenuItems.each(function() {
|
|
expect($(this)).toHaveAttr('role', 'menuitem');
|
|
expect($(this)).toHaveAttr('aria-selected');
|
|
});
|
|
});
|
|
|
|
it('is not used by Youtube type of video player', function() {
|
|
state = jasmine.initializePlayer('video.html');
|
|
expect($('video, iframe')).not.toHaveData('contextmenu');
|
|
});
|
|
});
|
|
|
|
describe('methods:', function() {
|
|
beforeEach(function() {
|
|
state = jasmine.initializePlayer();
|
|
openMenu();
|
|
});
|
|
|
|
it('menu can be destroyed successfully', function() {
|
|
var menuitemEvents = ['click', 'keydown', 'contextmenu', 'mouseover'],
|
|
menuEvents = ['keydown', 'contextmenu', 'mouseleave', 'mouseover'];
|
|
|
|
menu.data('menu').destroy();
|
|
expect(menu).not.toBeInDOM();
|
|
expect(overlay).not.toBeInDOM();
|
|
_.each(menuitemEvents, function(eventName) {
|
|
expect(menuItems.first()).not.toHandle(eventName);
|
|
});
|
|
_.each(menuEvents, function(eventName) {
|
|
expect(menuSubmenuItem).not.toHandle(eventName);
|
|
});
|
|
_.each(menuEvents, function(eventName) {
|
|
expect(menu).not.toHandle(eventName);
|
|
});
|
|
expect($('video')).not.toHandle('contextmenu');
|
|
expect($('video')).not.toHaveData('contextmenu');
|
|
});
|
|
|
|
it('can change label for the submenu', function() {
|
|
expect(menuSubmenuItem.children('span')).toHaveText('Speed');
|
|
menuSubmenuItem.data('menu').setLabel('New Name');
|
|
expect(menuSubmenuItem.children('span')).toHaveText('New Name');
|
|
});
|
|
|
|
it('can change label for the menuitem', function() {
|
|
expect(menuItems.first()).toHaveText('Play');
|
|
menuItems.first().data('menu').setLabel('Pause');
|
|
expect(menuItems.first()).toHaveText('Pause');
|
|
});
|
|
});
|
|
|
|
describe('when video is right-clicked', function() {
|
|
beforeEach(function() {
|
|
state = jasmine.initializePlayer();
|
|
jasmine.mockFullscreenAPI();
|
|
openMenu();
|
|
});
|
|
|
|
it('context menu opens', function() {
|
|
expect(menu).toHaveClass('is-opened');
|
|
expect(overlay).toBeInDOM();
|
|
});
|
|
|
|
it('mouseover and mouseleave behave as expected', function() {
|
|
openSubmenuMouse(menuSubmenuItem);
|
|
expect(menuSubmenuItem).toHaveClass('is-opened');
|
|
closeSubmenuMouse(menuSubmenuItem);
|
|
expect(menuSubmenuItem).not.toHaveClass('is-opened');
|
|
submenuItems.eq(1).mouseover();
|
|
expect(submenuItems.eq(1)).toBeFocused();
|
|
});
|
|
|
|
it('mouse left-clicking outside of the context menu will close it', function() {
|
|
// Left-click outside of open menu, for example on Play button
|
|
$playButton.click();
|
|
expect(menu).not.toHaveClass('is-opened');
|
|
expect(overlay).not.toBeInDOM();
|
|
});
|
|
|
|
it('mouse right-clicking outside of video will close it', function() {
|
|
// Right-click outside of open menu for example on Play button
|
|
$playButton.trigger('contextmenu');
|
|
expect(menu).not.toHaveClass('is-opened');
|
|
expect(overlay).not.toBeInDOM();
|
|
});
|
|
|
|
it('mouse right-clicking inside video but outside of context menu will not close it', function() {
|
|
spyOn(menu.data('menu'), 'pointInContainerBox').and.returnValue(true);
|
|
overlay.trigger('contextmenu');
|
|
expect(menu).toHaveClass('is-opened');
|
|
expect(overlay).toBeInDOM();
|
|
});
|
|
|
|
it('mouse right-clicking inside video but outside of context menu will close submenus', function() {
|
|
spyOn(menu.data('menu'), 'pointInContainerBox').and.returnValue(true);
|
|
openSubmenuMouse(menuSubmenuItem);
|
|
expect(menuSubmenuItem).toHaveClass('is-opened');
|
|
overlay.trigger('contextmenu');
|
|
expect(menuSubmenuItem).not.toHaveClass('is-opened');
|
|
});
|
|
|
|
it('mouse left/right-clicking behaves as expected on play/pause menu item', function() {
|
|
var menuItem = menuItems.first();
|
|
spyOn(state.videoPlayer, 'isPlaying');
|
|
spyOn(state.videoPlayer, 'play').and.callFake(function() {
|
|
state.videoPlayer.isPlaying.and.returnValue(true);
|
|
state.el.trigger('play');
|
|
});
|
|
spyOn(state.videoPlayer, 'pause').and.callFake(function() {
|
|
state.videoPlayer.isPlaying.and.returnValue(false);
|
|
state.el.trigger('pause');
|
|
});
|
|
// Left-click on play
|
|
menuItem.click();
|
|
expect(state.videoPlayer.play).toHaveBeenCalled();
|
|
expect(menuItem).toHaveText('Pause');
|
|
openMenu();
|
|
// Left-click on pause
|
|
menuItem.click();
|
|
expect(state.videoPlayer.pause).toHaveBeenCalled();
|
|
expect(menuItem).toHaveText('Play');
|
|
state.videoPlayer.play.calls.reset();
|
|
// Right-click on play
|
|
menuItem.trigger('contextmenu');
|
|
expect(state.videoPlayer.play).toHaveBeenCalled();
|
|
expect(menuItem).toHaveText('Pause');
|
|
});
|
|
|
|
it('mouse left/right-clicking behaves as expected on mute/unmute menu item', function() {
|
|
var menuItem = menuItems.eq(1);
|
|
// Left-click on mute
|
|
menuItem.click();
|
|
expect(state.videoVolumeControl.getMuteStatus()).toBe(true);
|
|
expect(menuItem).toHaveText('Unmute');
|
|
openMenu();
|
|
// Left-click on unmute
|
|
menuItem.click();
|
|
expect(state.videoVolumeControl.getMuteStatus()).toBe(false);
|
|
expect(menuItem).toHaveText('Mute');
|
|
// Right-click on mute
|
|
menuItem.trigger('contextmenu');
|
|
expect(state.videoVolumeControl.getMuteStatus()).toBe(true);
|
|
expect(menuItem).toHaveText('Unmute');
|
|
openMenu();
|
|
// Right-click on unmute
|
|
menuItem.trigger('contextmenu');
|
|
expect(state.videoVolumeControl.getMuteStatus()).toBe(false);
|
|
expect(menuItem).toHaveText('Mute');
|
|
});
|
|
|
|
it('mouse left/right-clicking behaves as expected on go to Exit full browser menu item', function() {
|
|
var menuItem = menuItems.eq(2);
|
|
// Left-click on Fill browser
|
|
menuItem.click();
|
|
expect(state.isFullScreen).toBe(true);
|
|
expect(menuItem).toHaveText('Exit full browser');
|
|
openMenu();
|
|
// Left-click on Exit full browser
|
|
menuItem.click();
|
|
expect(state.isFullScreen).toBe(false);
|
|
expect(menuItem).toHaveText('Fill browser');
|
|
// Right-click on Fill browser
|
|
menuItem.trigger('contextmenu');
|
|
expect(state.isFullScreen).toBe(true);
|
|
expect(menuItem).toHaveText('Exit full browser');
|
|
openMenu();
|
|
// Right-click on Exit full browser
|
|
menuItem.trigger('contextmenu');
|
|
expect(state.isFullScreen).toBe(false);
|
|
expect(menuItem).toHaveText('Fill browser');
|
|
});
|
|
|
|
it('mouse left/right-clicking behaves as expected on speed submenu item', function() {
|
|
// Set speed to 0.75x
|
|
state.videoSpeedControl.setSpeed('0.75');
|
|
// Left-click on second submenu speed (1.0x)
|
|
openSubmenuMouse(menuSubmenuItem);
|
|
submenuItems.eq(1).click();
|
|
|
|
// Expect speed to be 1.0x
|
|
expect(state.videoSpeedControl.currentSpeed).toBe('1.0');
|
|
// Expect speed submenu item 0.75x not to be active
|
|
expect(submenuItems.first()).not.toHaveClass('is-selected');
|
|
// Expect speed submenu item 1.0x to be active
|
|
expect(submenuItems.eq(1)).toHaveClass('is-selected');
|
|
|
|
// Set speed to 0.75x
|
|
state.videoSpeedControl.setSpeed('0.75');
|
|
// Right-click on second submenu speed (1.0x)
|
|
openSubmenuMouse(menuSubmenuItem);
|
|
submenuItems.eq(1).trigger('contextmenu');
|
|
|
|
// Expect speed to be 1.0x
|
|
expect(state.videoSpeedControl.currentSpeed).toBe('1.0');
|
|
// Expect speed submenu item 0.75x not to be active
|
|
expect(submenuItems.first()).not.toHaveClass('is-selected');
|
|
// Expect speed submenu item 1.0x to be active
|
|
expect(submenuItems.eq(1)).toHaveClass('is-selected');
|
|
});
|
|
});
|
|
|
|
describe('Keyboard interactions', function() {
|
|
beforeEach(function() {
|
|
state = jasmine.initializePlayer();
|
|
openMenu();
|
|
});
|
|
|
|
it('focus the first item of the just opened menu on UP keydown', function() {
|
|
menu.trigger(keyPressEvent($.ui.keyCode.UP));
|
|
expect(menuSubmenuItem).toBeFocused();
|
|
});
|
|
|
|
it('focus the last item of the just opened menu on DOWN keydown', function() {
|
|
menu.trigger(keyPressEvent($.ui.keyCode.DOWN));
|
|
expect(menuItems.first()).toBeFocused();
|
|
});
|
|
|
|
it('open the submenu on ENTER keydown', function() {
|
|
openSubmenuKeyboard(menuSubmenuItem, $.ui.keyCode.ENTER);
|
|
expect(menuSubmenuItem).toHaveClass('is-opened');
|
|
expect(submenuItems.first()).toBeFocused();
|
|
});
|
|
|
|
it('open the submenu on SPACE keydown', function() {
|
|
openSubmenuKeyboard(menuSubmenuItem, $.ui.keyCode.SPACE);
|
|
expect(menuSubmenuItem).toHaveClass('is-opened');
|
|
expect(submenuItems.first()).toBeFocused();
|
|
});
|
|
|
|
it('open the submenu on RIGHT keydown', function() {
|
|
openSubmenuKeyboard(menuSubmenuItem, $.ui.keyCode.RIGHT);
|
|
expect(menuSubmenuItem).toHaveClass('is-opened');
|
|
expect(submenuItems.first()).toBeFocused();
|
|
});
|
|
|
|
it('close the menu on ESCAPE keydown', function() {
|
|
menu.trigger(keyPressEvent($.ui.keyCode.ESCAPE));
|
|
expect(menu).not.toHaveClass('is-opened');
|
|
expect(overlay).not.toBeInDOM();
|
|
});
|
|
|
|
it('close the submenu on ESCAPE keydown', function() {
|
|
openSubmenuKeyboard(menuSubmenuItem);
|
|
menuSubmenuItem.trigger(keyPressEvent($.ui.keyCode.ESCAPE));
|
|
expect(menuSubmenuItem).not.toHaveClass('is-opened');
|
|
expect(overlay).not.toBeInDOM();
|
|
});
|
|
|
|
it('close the submenu on LEFT keydown on submenu items', function() {
|
|
closeSubmenuKeyboard(menuSubmenuItem);
|
|
});
|
|
|
|
it('do nothing on RIGHT keydown on submenu item', function() {
|
|
submenuItems.eq(1).focus().trigger(keyPressEvent($.ui.keyCode.RIGHT)); // Mute
|
|
// Is still focused.
|
|
expect(submenuItems.eq(1)).toBeFocused();
|
|
});
|
|
|
|
it('do nothing on TAB keydown on menu item', function() {
|
|
submenuItems.eq(1).focus().trigger(keyPressEvent($.ui.keyCode.TAB)); // Mute
|
|
// Is still focused.
|
|
expect(submenuItems.eq(1)).toBeFocused();
|
|
});
|
|
|
|
it('UP and DOWN keydown function as expected on menu/submenu items', function() {
|
|
menuItems.eq(0).focus(); // Play
|
|
expect(menuItems.eq(0)).toBeFocused();
|
|
menuItems.eq(0).trigger(keyPressEvent($.ui.keyCode.DOWN));
|
|
expect(menuItems.eq(1)).toBeFocused(); // Mute
|
|
menuItems.eq(1).trigger(keyPressEvent($.ui.keyCode.DOWN));
|
|
expect(menuItems.eq(2)).toBeFocused(); // Fullscreen
|
|
menuItems.eq(2).trigger(keyPressEvent($.ui.keyCode.DOWN));
|
|
expect(menuSubmenuItem).toBeFocused(); // Speed
|
|
menuSubmenuItem.trigger(keyPressEvent($.ui.keyCode.DOWN));
|
|
expect(menuItems.eq(0)).toBeFocused(); // Play
|
|
|
|
menuItems.eq(0).trigger(keyPressEvent($.ui.keyCode.UP));
|
|
expect(menuSubmenuItem).toBeFocused(); // Speed
|
|
// Check if hidden item can be skipped correctly.
|
|
menuItems.eq(2).hide(); // hide Fullscreen item
|
|
menuSubmenuItem.trigger(keyPressEvent($.ui.keyCode.UP));
|
|
expect(menuItems.eq(1)).toBeFocused(); // Mute
|
|
menuItems.eq(1).trigger(keyPressEvent($.ui.keyCode.UP));
|
|
expect(menuItems.eq(0)).toBeFocused(); // Play
|
|
});
|
|
|
|
it('current item is still focused if all siblings are hidden', function() {
|
|
menuItems.eq(0).focus(); // Play
|
|
expect(menuItems.eq(0)).toBeFocused(); // hide all siblings
|
|
menuItems.eq(0).siblings().hide();
|
|
menuSubmenuItem.trigger(keyPressEvent($.ui.keyCode.DOWN));
|
|
expect(menuItems.eq(0)).toBeFocused();
|
|
menuSubmenuItem.trigger(keyPressEvent($.ui.keyCode.UP));
|
|
expect(menuItems.eq(0)).toBeFocused();
|
|
});
|
|
|
|
it('ENTER keydown on menu/submenu item selects its data and closes menu', function() {
|
|
menuItems.eq(2).focus().trigger(keyPressEvent($.ui.keyCode.ENTER)); // Fullscreen
|
|
expect(menuItems.eq(2)).toHaveClass('is-selected');
|
|
expect(menuItems.eq(2).siblings()).not.toHaveClass('is-selected');
|
|
expect(state.isFullScreen).toBeTruthy();
|
|
expect(menuItems.eq(2)).toHaveText('Exit full browser');
|
|
});
|
|
|
|
it('SPACE keydown on menu/submenu item selects its data and closes menu', function() {
|
|
submenuItems.eq(2).focus().trigger(keyPressEvent($.ui.keyCode.SPACE)); // 1.25x
|
|
expect(submenuItems.eq(2)).toHaveClass('is-selected');
|
|
expect(submenuItems.eq(2).siblings()).not.toHaveClass('is-selected');
|
|
expect(state.videoSpeedControl.currentSpeed).toBe('1.25');
|
|
});
|
|
});
|
|
});
|
|
}());
|