Patch #37486 » 0003-context_menu.patch
| public/javascripts/context_menu.js | ||
|---|---|---|
| 1 | 1 |
/* Redmine - project management software |
| 2 | 2 |
Copyright (C) 2006-2022 Jean-Philippe Lang */ |
| 3 | 3 | |
| 4 |
var observing;
|
|
| 4 |
let observing;
|
|
| 5 | 5 | |
| 6 | 6 |
function rightClick(event) {
|
| 7 |
var target = $(event.target);
|
|
| 7 |
const target = $(event.target);
|
|
| 8 | 8 |
if (target.is('a:not(.js-contextmenu)')) {return;}
|
| 9 |
var tr = target.closest('.hascontextmenu').first();
|
|
| 9 | ||
| 10 |
const tr = target.closest('.hascontextmenu').first();
|
|
| 10 | 11 |
if (tr.length < 1) {return;}
|
| 11 | 12 |
event.preventDefault(); |
| 13 | ||
| 12 | 14 |
if (!isSelected(tr)) {
|
| 13 | 15 |
unselectAll(); |
| 14 | 16 |
addSelection(tr); |
| ... | ... | |
| 18 | 20 |
} |
| 19 | 21 | |
| 20 | 22 |
function click(event) {
|
| 21 |
var target = $(event.target); |
|
| 22 |
var lastSelected; |
|
| 23 |
const target = $(event.target); |
|
| 23 | 24 | |
| 24 | 25 |
if (target.is('a') && target.hasClass('submenu')) {
|
| 25 | 26 |
event.preventDefault(); |
| ... | ... | |
| 27 | 28 |
} |
| 28 | 29 |
hide(); |
| 29 | 30 |
if (target.is('a') || target.is('img')) { return; }
|
| 31 | ||
| 30 | 32 |
if (event.which == 1 || (navigator.appVersion.match(/\bMSIE\b/))) {
|
| 31 |
var tr = target.closest('.hascontextmenu').first();
|
|
| 33 |
const tr = target.closest('.hascontextmenu').first();
|
|
| 32 | 34 |
if (tr.length > 0) {
|
| 33 | 35 |
// a row was clicked |
| 34 |
if (target.is('td.checkbox')) {
|
|
| 35 |
// the td containing the checkbox was clicked, toggle the checkbox |
|
| 36 |
target = target.find('input').first();
|
|
| 37 |
target.prop("checked", !target.prop("checked"));
|
|
| 38 |
} |
|
| 39 |
if (target.is('input')) {
|
|
| 40 |
// a checkbox may be clicked |
|
| 41 |
if (target.prop('checked')) {
|
|
| 42 |
tr.addClass('context-menu-selection');
|
|
| 43 |
} else {
|
|
| 44 |
tr.removeClass('context-menu-selection');
|
|
| 45 |
} |
|
| 46 |
} else {
|
|
| 47 |
if (event.ctrlKey || event.metaKey) {
|
|
| 48 |
toggleSelection(tr); |
|
| 49 |
clearDocumentSelection(); |
|
| 50 |
} else if (event.shiftKey) {
|
|
| 51 |
lastSelected = getLastSelected(); |
|
| 52 |
if (lastSelected.length) {
|
|
| 53 |
var toggling = false; |
|
| 54 |
$('.hascontextmenu').each(function(){
|
|
| 55 |
if (toggling || $(this).is(tr)) {
|
|
| 56 |
addSelection($(this)); |
|
| 57 |
clearDocumentSelection(); |
|
| 58 |
} |
|
| 59 |
if ($(this).is(tr) || $(this).is(lastSelected)) {
|
|
| 60 |
toggling = !toggling; |
|
| 61 |
} |
|
| 62 |
}); |
|
| 63 |
} else {
|
|
| 64 |
addSelection(tr); |
|
| 65 |
} |
|
| 66 |
} else {
|
|
| 67 |
unselectAll(); |
|
| 68 |
addSelection(tr); |
|
| 69 |
} |
|
| 70 |
setLastSelected(tr); |
|
| 71 |
} |
|
| 36 |
selectRows(target, tr, event); |
|
| 72 | 37 |
} else {
|
| 73 | 38 |
// click is outside the rows |
| 74 | 39 |
if (target.is('a') && (target.hasClass('disabled') || target.hasClass('submenu'))) {
|
| ... | ... | |
| 82 | 47 |
} |
| 83 | 48 |
} |
| 84 | 49 | |
| 50 |
export function selectRows($element, row, { ctrlKey, metaKey, shiftKey } ) {
|
|
| 51 |
let $target = $element; |
|
| 52 |
if ($target.is('td.checkbox')) {
|
|
| 53 |
// the td containing the checkbox was clicked, toggle the checkbox |
|
| 54 |
$target = $target.find('input').first();
|
|
| 55 |
$target.prop("checked", !$target.prop("checked"));
|
|
| 56 |
} |
|
| 57 |
if ($target.is('input')) {
|
|
| 58 |
// a checkbox may be clicked |
|
| 59 |
row.toggleClass('context-menu-selection', $target.prop('checked'))
|
|
| 60 |
} else {
|
|
| 61 |
if (ctrlKey || metaKey) {
|
|
| 62 |
toggleSelection(row); |
|
| 63 |
clearDocumentSelection(); |
|
| 64 |
} else if (shiftKey) {
|
|
| 65 |
const lastSelected = getLastSelected(); |
|
| 66 |
if (lastSelected.length) {
|
|
| 67 |
const selected = addMultipleSelection($('.hascontextmenu'), lastSelected, row);
|
|
| 68 |
selected.forEach($e => addSelection($e)); |
|
| 69 |
} else {
|
|
| 70 |
addSelection(row); |
|
| 71 |
} |
|
| 72 |
} else {
|
|
| 73 |
unselectAll(); |
|
| 74 |
addSelection(row); |
|
| 75 |
} |
|
| 76 |
setLastSelected(row); |
|
| 77 |
} |
|
| 78 |
} |
|
| 79 | ||
| 80 |
export function addMultipleSelection($rows, lastSelected, clicked) {
|
|
| 81 |
let toggling = false; |
|
| 82 |
const selected = []; |
|
| 83 |
$rows.each((i, elm) => {
|
|
| 84 |
const $elm = $(elm); |
|
| 85 |
if (!$elm.is(lastSelected) && (toggling || $elm.is(clicked))) {
|
|
| 86 |
selected.push($elm); |
|
| 87 |
clearDocumentSelection(); |
|
| 88 |
} |
|
| 89 |
if ($elm.is(lastSelected) !== $elm.is(clicked)) {
|
|
| 90 |
toggling = !toggling; |
|
| 91 |
} |
|
| 92 |
}); |
|
| 93 |
return selected; |
|
| 94 |
} |
|
| 95 | ||
| 85 | 96 |
function create() {
|
| 86 | 97 |
if ($('#context-menu').length < 1) {
|
| 87 |
var menu = document.createElement("div");
|
|
| 98 |
const menu = document.createElement("div");
|
|
| 88 | 99 |
menu.setAttribute("id", "context-menu");
|
| 89 | 100 |
menu.setAttribute("style", "display:none;");
|
| 90 | 101 |
document.getElementById("content").appendChild(menu);
|
| 91 | 102 |
} |
| 92 | 103 |
} |
| 93 | 104 | |
| 94 |
function show(event) {
|
|
| 95 |
var mouse_x = event.pageX; |
|
| 96 |
var mouse_y = event.pageY; |
|
| 97 |
var mouse_y_c = event.clientY; |
|
| 98 |
var render_x = mouse_x; |
|
| 99 |
var render_y = mouse_y; |
|
| 100 |
var dims; |
|
| 101 |
var menu_width; |
|
| 102 |
var menu_height; |
|
| 103 |
var window_width; |
|
| 104 |
var window_height; |
|
| 105 |
var max_width; |
|
| 106 |
var max_height; |
|
| 107 |
var url; |
|
| 108 | ||
| 109 |
$('#context-menu').css('left', (render_x + 'px'));
|
|
| 110 |
$('#context-menu').css('top', (render_y + 'px'));
|
|
| 111 |
$('#context-menu').html('');
|
|
| 112 | ||
| 113 |
url = $(event.target).parents('form').first().data('cm-url');
|
|
| 105 |
function show({ pageX: mouse_x, pageY: mouse_y, clientY: mouse_y_c, target: target }) {
|
|
| 106 |
const $element = $('#context-menu');
|
|
| 107 |
$element.css({ left: `${mouse_x}px`, top: `${mouse_y}px` });
|
|
| 108 |
$element.html('');
|
|
| 109 | ||
| 110 |
const form = $(target).parents('form').first();
|
|
| 111 |
const url = form.data('cm-url');
|
|
| 114 | 112 |
if (url == null) {alert('no url'); return;}
|
| 115 | 113 | |
| 116 | 114 |
$.ajax({
|
| 117 | 115 |
url: url, |
| 118 |
data: $(event.target).parents('form').first().serialize(),
|
|
| 119 |
success: function(data, textStatus, jqXHR) {
|
|
| 120 |
$('#context-menu').html(data);
|
|
| 121 |
menu_width = $('#context-menu').width();
|
|
| 122 |
menu_height = $('#context-menu').height();
|
|
| 123 |
max_width = mouse_x + 2*menu_width; |
|
| 124 |
max_height = mouse_y_c + menu_height; |
|
| 125 | ||
| 126 |
var ws = window_size(); |
|
| 127 |
window_width = ws.width; |
|
| 128 |
window_height = ws.height; |
|
| 129 | ||
| 130 |
/* display the menu above and/or to the left of the click if needed */ |
|
| 131 |
if (max_width > window_width) {
|
|
| 132 |
render_x -= menu_width; |
|
| 133 |
$('#context-menu').addClass('reverse-x');
|
|
| 134 |
} else {
|
|
| 135 |
$('#context-menu').removeClass('reverse-x');
|
|
| 136 |
} |
|
| 116 |
data: form.serialize(), |
|
| 117 |
success: (data, textStatus, jqXHR) => {
|
|
| 118 |
$element.html(data); |
|
| 137 | 119 | |
| 138 |
if (max_height > window_height) {
|
|
| 139 |
render_y -= menu_height; |
|
| 140 |
$('#context-menu').addClass('reverse-y');
|
|
| 141 |
// adding class for submenu |
|
| 142 |
if (mouse_y_c < 325) {
|
|
| 143 |
$('#context-menu .folder').addClass('down');
|
|
| 144 |
} |
|
| 145 |
} else {
|
|
| 146 |
// adding class for submenu |
|
| 147 |
if (window_height - mouse_y_c < 345) {
|
|
| 148 |
$('#context-menu .folder').addClass('up');
|
|
| 149 |
} |
|
| 150 |
$('#context-menu').removeClass('reverse-y');
|
|
| 151 |
} |
|
| 120 |
const menu_width = $element.width(); |
|
| 121 |
const menu_height = $element.height(); |
|
| 122 |
const max_width = mouse_x + 2 * menu_width; |
|
| 123 |
const max_height = mouse_y_c + menu_height; |
|
| 124 |
const ws = window_size(); |
|
| 125 | ||
| 126 |
const is_reverse_x = max_width > ws.width; |
|
| 127 |
const arg_x = { render_pos: mouse_x, menu_size: menu_width, position: 'left', class_name: 'reverse_x' }
|
|
| 128 |
const action_x = is_reverse_x ? reverseRenderAction(arg_x) |
|
| 129 |
: normalRenderAction(arg_x); |
|
| 152 | 130 | |
| 153 |
if (render_x <= 0) render_x = 1; |
|
| 154 |
if (render_y <= 0) render_y = 1; |
|
| 155 |
$('#context-menu').css('left', (render_x + 'px'));
|
|
| 156 |
$('#context-menu').css('top', (render_y + 'px'));
|
|
| 157 |
$('#context-menu').show();
|
|
| 131 |
const is_reverse_y = max_height > ws.height; |
|
| 132 |
const arg_y = { render_pos: mouse_y, menu_size: menu_height, position: 'top', class_name: 'reverse_y' }
|
|
| 133 |
const action_y = is_reverse_y ? reverseRenderAction(arg_y) |
|
| 134 |
: normalRenderAction(arg_y); |
|
| 158 | 135 | |
| 159 |
//if (window.parseStylesheets) { window.parseStylesheets(); } // IE
|
|
| 136 |
const arg_submenu = { window_height: ws.height, mouse_y_c }
|
|
| 137 |
const action_submenu = is_reverse_y ? reverseFolderAction(arg_submenu) |
|
| 138 |
: normalFolderAction(arg_submenu); |
|
| 139 |
action_x($element); |
|
| 140 |
action_y($element); |
|
| 141 |
// adding class for submenu |
|
| 142 |
action_submenu($element); |
|
| 143 | ||
| 144 |
$element.show(); |
|
| 160 | 145 |
} |
| 161 | 146 |
}); |
| 162 | 147 |
} |
| 163 | 148 | |
| 149 |
export function reverseRenderAction({ render_pos, menu_size, position, class_name }) {
|
|
| 150 |
return element => {
|
|
| 151 |
element.addClass(class_name) |
|
| 152 |
const n = adjustLessThanZero(render_pos - menu_size); |
|
| 153 |
element.css(position, `${n}px`);
|
|
| 154 |
} |
|
| 155 |
} |
|
| 156 | ||
| 157 |
export function normalRenderAction({ render_pos, menu_size, position, class_name }) {
|
|
| 158 |
return element => {
|
|
| 159 |
element.removeClass(class_name); |
|
| 160 |
const n = adjustLessThanZero(render_pos); |
|
| 161 |
element.css(position, `${n}px`);
|
|
| 162 |
} |
|
| 163 |
} |
|
| 164 | ||
| 165 |
export function reverseFolderAction({ window_height, mouse_y_c }) {
|
|
| 166 |
return element => {
|
|
| 167 |
if (mouse_y_c < 325) {
|
|
| 168 |
element.find('.folder').addClass('down');
|
|
| 169 |
} |
|
| 170 |
} |
|
| 171 |
} |
|
| 172 | ||
| 173 |
export function normalFolderAction({ window_height, mouse_y_c }) {
|
|
| 174 |
return element => {
|
|
| 175 |
if (window_height - mouse_y_c < 345) {
|
|
| 176 |
element.find('.folder').addClass('up');
|
|
| 177 |
} |
|
| 178 |
} |
|
| 179 |
} |
|
| 180 | ||
| 181 |
function adjustLessThanZero(n) {
|
|
| 182 |
return n <= 0 ? 1 : n; |
|
| 183 |
} |
|
| 184 | ||
| 164 | 185 |
function setLastSelected(tr) {
|
| 165 | 186 |
$('.cm-last').removeClass('cm-last');
|
| 166 | 187 |
tr.addClass('cm-last');
|
| ... | ... | |
| 172 | 193 | |
| 173 | 194 |
function unselectAll() {
|
| 174 | 195 |
$('input[type=checkbox].toggle-selection').prop('checked', false);
|
| 175 |
$('.hascontextmenu').each(function(){
|
|
| 176 |
removeSelection($(this));
|
|
| 196 |
$('.hascontextmenu').each((i, el) => {
|
|
| 197 |
removeSelection($(el));
|
|
| 177 | 198 |
}); |
| 178 | 199 |
$('.cm-last').removeClass('cm-last');
|
| 179 | 200 |
} |
| ... | ... | |
| 220 | 241 |
function init() {
|
| 221 | 242 |
create(); |
| 222 | 243 |
unselectAll(); |
| 223 |
|
|
| 244 | ||
| 224 | 245 |
if (!observing) {
|
| 225 | 246 |
$(document).click(click); |
| 226 | 247 |
$(document).contextmenu(rightClick); |
| 227 | 248 |
$(document).on('click', '.js-contextmenu', rightClick);
|
| 228 | 249 |
observing = true; |
| 229 | 250 |
} |
| 251 |
$('input[type=checkbox].toggle-selection').on('change', toggleIssuesSelection);
|
|
| 230 | 252 |
} |
| 231 | 253 | |
| 232 |
function toggleIssuesSelection(el) {
|
|
| 233 |
var checked = $(this).prop('checked');
|
|
| 234 |
var boxes = $(this).parents('table').find('input[name=ids\\[\\]]');
|
|
| 254 |
function toggleIssuesSelection(event) {
|
|
| 255 |
const $target = $(event.target) |
|
| 256 |
const checked = $target.prop('checked');
|
|
| 257 |
const boxes = $target.parents('table').find('input[name=ids\\[\\]]');
|
|
| 235 | 258 |
boxes.prop('checked', checked).parents('.hascontextmenu').toggleClass('context-menu-selection', checked);
|
| 236 | 259 |
} |
| 237 | 260 | |
| 238 | 261 |
function window_size() {
|
| 239 |
var w;
|
|
| 240 |
var h;
|
|
| 262 |
let w;
|
|
| 263 |
let h;
|
|
| 241 | 264 |
if (window.innerWidth) {
|
| 242 | 265 |
w = window.innerWidth; |
| 243 | 266 |
h = window.innerHeight; |
| ... | ... | |
| 251 | 274 |
return {width: w, height: h};
|
| 252 | 275 |
} |
| 253 | 276 | |
| 254 |
$(document).ready(function(){
|
|
| 255 |
init(); |
|
| 256 |
$('input[type=checkbox].toggle-selection').on('change', toggleIssuesSelection);
|
|
| 257 |
}); |
|
| 277 |
init(); |
|
| test/javascripts/context_menu.html | ||
|---|---|---|
| 1 |
<html> |
|
| 2 |
<head> |
|
| 3 |
<script src="../../public/javascripts/jquery-3.6.0-ui-1.13.1-ujs-6.1.3.1.js"></script> |
|
| 4 |
</head> |
|
| 5 |
<body> |
|
| 6 |
<div id="content"> |
|
| 7 |
<!-- test for row clicking --> |
|
| 8 |
<table> |
|
| 9 |
<tr class="hascontextmenu"><td class="checkbox"><input type="checkbox"/></td><td class="noinput"></td></tr> |
|
| 10 |
<tr class="hascontextmenu"><td class="checkbox"><input type="checkbox"/></td><td class="noinput"></td></tr> |
|
| 11 |
<tr class="hascontextmenu"><td class="checkbox"><input type="checkbox"/></td><td class="noinput"></td></tr> |
|
| 12 |
<tr class="hascontextmenu"><td class="checkbox"><input type="checkbox"/></td><td class="noinput"></td></tr> |
|
| 13 |
<tr class="hascontextmenu"><td class="checkbox"><input type="checkbox"/></td><td class="noinput"></td></tr> |
|
| 14 |
<tr class="hascontextmenu"><td class="checkbox"><input type="checkbox"/></td><td class="noinput"></td></tr> |
|
| 15 |
</table> |
|
| 16 |
</div> |
|
| 17 |
<!-- test for size and position of menu --> |
|
| 18 |
<div id="menu"> |
|
| 19 |
<div class="folder"></div> |
|
| 20 |
<div class="folder"></div> |
|
| 21 |
</div> |
|
| 22 |
</body> |
|
| 23 |
</html> |
|
| test/javascripts/context_menu.test.js | ||
|---|---|---|
| 1 |
import { setup, suite, test } from 'mocha';
|
|
| 2 |
import { assert } from 'chai';
|
|
| 3 |
import { prepare } from './helper.js';
|
|
| 4 | ||
| 5 |
suite('context menu', () => {
|
|
| 6 | ||
| 7 |
setup((done) => {
|
|
| 8 |
prepare(import.meta.url, './context_menu.html').then(dom => {
|
|
| 9 |
dom.window.addEventListener('load', (e) => {
|
|
| 10 |
global.window = dom.window; |
|
| 11 |
global.document = dom.window.document; |
|
| 12 |
global.$ = dom.window.$; |
|
| 13 |
done(); |
|
| 14 |
}); |
|
| 15 |
}); |
|
| 16 |
}); |
|
| 17 | ||
| 18 |
test('create contextmenu element', async () => {
|
|
| 19 |
await import('../../public/javascripts/context_menu.js');
|
|
| 20 |
const menu = document.getElementById('context-menu');
|
|
| 21 |
assert.isNotNull(menu); |
|
| 22 |
}); |
|
| 23 | ||
| 24 |
suite('when a row is clicked', async () => {
|
|
| 25 |
let $rows; |
|
| 26 |
let selectRows; |
|
| 27 | ||
| 28 |
setup(async () => {
|
|
| 29 |
$rows = $('.hascontextmenu');
|
|
| 30 |
({ selectRows } = await import('../../public/javascripts/context_menu.js'));
|
|
| 31 |
}); |
|
| 32 | ||
| 33 |
test('When the checkbox is clicked directly, select the row', () => {
|
|
| 34 |
const target = $($rows[0]).find('input');
|
|
| 35 |
target.prop('checked', true);
|
|
| 36 |
const tr = target.closest('.hascontextmenu').first();
|
|
| 37 |
selectRows(target, tr, {} )
|
|
| 38 |
assert.isTrue(tr.hasClass('context-menu-selection'));
|
|
| 39 |
}); |
|
| 40 | ||
| 41 |
test('When the td containing the checkbox is clicked, toggle the checkbox and select the row', () => {
|
|
| 42 |
const target = $($rows[0]).find('td.checkbox');
|
|
| 43 |
const tr = target.closest('.hascontextmenu').first();
|
|
| 44 |
selectRows(target, tr, {} )
|
|
| 45 |
assert.isTrue(target.find('input').prop('checked'));
|
|
| 46 |
assert.isTrue(tr.hasClass('context-menu-selection'));
|
|
| 47 |
}); |
|
| 48 | ||
| 49 |
test('When the td not containing the checkbox is clicked with no modifier, toggle the checkbox and select the row', () => {
|
|
| 50 |
const target0 = $($rows[0]).find('td.noinput');
|
|
| 51 |
const tr0 = target0.closest('.hascontextmenu').first();
|
|
| 52 |
selectRows(target0, tr0, {} )
|
|
| 53 |
assert.isTrue(tr0.find('input').prop('checked'));
|
|
| 54 |
assert.isTrue(tr0.hasClass('context-menu-selection'));
|
|
| 55 | ||
| 56 |
const target1 = $($rows[1]).find('td.noinput');
|
|
| 57 |
const tr1 = target1.closest('.hascontextmenu').first();
|
|
| 58 |
selectRows(target1, tr1, {} )
|
|
| 59 | ||
| 60 |
assert.isFalse(tr0.find('input').prop('checked'));
|
|
| 61 |
assert.isFalse(tr0.hasClass('context-menu-selection'));
|
|
| 62 | ||
| 63 |
assert.isTrue(tr1.find('input').prop('checked'));
|
|
| 64 |
assert.isTrue(tr1.hasClass('context-menu-selection'));
|
|
| 65 |
}); |
|
| 66 | ||
| 67 |
test('When the td not containing the checkbox is clicked with ctrl key, toggle the checkbox and select the row', () => {
|
|
| 68 |
const target0 = $($rows[0]).find('td.noinput');
|
|
| 69 |
const tr0 = target0.closest('.hascontextmenu').first();
|
|
| 70 |
selectRows(target0, tr0, {} )
|
|
| 71 |
assert.isTrue(tr0.find('input').prop('checked'));
|
|
| 72 |
assert.isTrue(tr0.hasClass('context-menu-selection'));
|
|
| 73 | ||
| 74 |
const target3 = $($rows[3]).find('td.noinput');
|
|
| 75 |
const tr3 = target3.closest('.hascontextmenu').first();
|
|
| 76 |
selectRows(target3, tr3, {ctrlKey:true} )
|
|
| 77 | ||
| 78 |
assert.isTrue(tr0.find('input').prop('checked'));
|
|
| 79 |
assert.isTrue(tr0.hasClass('context-menu-selection'));
|
|
| 80 | ||
| 81 |
assert.isTrue(tr3.find('input').prop('checked'));
|
|
| 82 |
assert.isTrue(tr3.hasClass('context-menu-selection'));
|
|
| 83 |
}); |
|
| 84 |
}); |
|
| 85 | ||
| 86 |
suite('With shift key + left click, multiple rows can be selected ', async () => {
|
|
| 87 | ||
| 88 |
let $rows; |
|
| 89 |
let addMultipleSelection; |
|
| 90 | ||
| 91 |
setup(async () => {
|
|
| 92 |
$rows = $('.hascontextmenu');
|
|
| 93 |
({ addMultipleSelection } = await import('../../public/javascripts/context_menu.js'));
|
|
| 94 |
}); |
|
| 95 | ||
| 96 |
test('The last selected row is lower than the clicked', async () => {
|
|
| 97 |
const selected = await addMultipleSelection($rows, $rows[5], $rows[0]); |
|
| 98 |
assert.equal(selected.length, 5); |
|
| 99 |
}); |
|
| 100 | ||
| 101 |
test('The last selected row is above the clicked', async () => {
|
|
| 102 |
const selected = await addMultipleSelection($rows, $rows[0], $rows[5]); |
|
| 103 |
assert.equal(selected.length, 5); |
|
| 104 |
}); |
|
| 105 | ||
| 106 |
test('The last selected row is same as the clicked', async () => {
|
|
| 107 |
const selected = await addMultipleSelection($rows, $rows[0], $rows[0]); |
|
| 108 |
assert.equal(selected.length, 0); |
|
| 109 |
}); |
|
| 110 |
}); |
|
| 111 | ||
| 112 |
suite('get context menu position', () => {
|
|
| 113 | ||
| 114 |
let $element; |
|
| 115 |
let reverseRenderAction; |
|
| 116 |
let normalRenderAction; |
|
| 117 |
let reverseFolderAction; |
|
| 118 |
let normalFolderAction; |
|
| 119 | ||
| 120 |
setup(async () => {
|
|
| 121 |
const div = await document.getElementById('menu');
|
|
| 122 |
$element = $(div); |
|
| 123 |
({ reverseRenderAction, normalRenderAction, reverseFolderAction, normalFolderAction } = await import('../../public/javascripts/context_menu.js'));
|
|
| 124 |
}); |
|
| 125 | ||
| 126 |
test('reverseRenderAction returns function to change DOM Element', () => {
|
|
| 127 |
const obj = { render_pos: 100, menu_size: 50, position: 'left', class_name: 'reverse-x' }
|
|
| 128 |
const action = reverseRenderAction(obj); |
|
| 129 |
action($element); |
|
| 130 |
assert.isTrue($element.hasClass('reverse-x'));
|
|
| 131 |
assert.equal('50px', $element.css('left'));
|
|
| 132 |
}); |
|
| 133 | ||
| 134 |
test('normalRenderAction returns function to change DOM Element', () => {
|
|
| 135 |
const obj = { render_pos: 100, menu_size: 50, position: 'left', class_name: 'reverse-x' }
|
|
| 136 |
const action = normalRenderAction(obj); |
|
| 137 |
action($element); |
|
| 138 |
assert.isFalse($element.hasClass('reverse-x'));
|
|
| 139 |
assert.equal('100px', $element.css('left'));
|
|
| 140 |
}); |
|
| 141 | ||
| 142 |
test('reverseFolderAction returns funtion to change submenu element', () => {
|
|
| 143 |
const obj = { window_height: 100, mouse_y_c: 100 }
|
|
| 144 |
const action = reverseFolderAction(obj) |
|
| 145 |
action($element); |
|
| 146 |
assert.isTrue($element.find('.folder').toArray().every(e => e.classList.contains('down')));
|
|
| 147 |
}); |
|
| 148 | ||
| 149 |
test('reverseFolderAction returns funtion that dont change submenu element', () => {
|
|
| 150 |
const obj = { window_height: 100, mouse_y_c: 325 }
|
|
| 151 |
const action = reverseFolderAction(obj) |
|
| 152 |
action($element); |
|
| 153 |
assert.isTrue($element.find('.folder').toArray().every(e => !e.classList.contains('down')));
|
|
| 154 |
}); |
|
| 155 | ||
| 156 |
test('normalFolderAction returns funtion to change submenu element', () => {
|
|
| 157 |
const obj = { window_height: 100, mouse_y_c: 100 }
|
|
| 158 |
const action = normalFolderAction(obj) |
|
| 159 |
action($element); |
|
| 160 |
assert.isTrue($element.find('.folder').toArray().every(e => e.classList.contains('up')));
|
|
| 161 |
}); |
|
| 162 | ||
| 163 |
test('normalFolderAction returns funtion that dont change submenu element', () => {
|
|
| 164 |
const obj = { window_height: 445, mouse_y_c: 100 }
|
|
| 165 |
const action = normalFolderAction(obj) |
|
| 166 |
action($element); |
|
| 167 |
assert.isTrue($element.find('.folder').toArray().every(e => !e.classList.contains('up')));
|
|
| 168 |
}); |
|
| 169 |
}); |
|
| 170 |
}); |
|
| 171 | ||
| test/javascripts/helper.js | ||
|---|---|---|
| 1 |
import path from 'path'; |
|
| 2 |
import { JSDOM } from 'jsdom';
|
|
| 3 | ||
| 4 |
export const prepare = (url, filename) => {
|
|
| 5 |
const __dirname = path.dirname(new URL(url).pathname) |
|
| 6 |
const html = path.resolve(__dirname, filename) |
|
| 7 |
const options = {
|
|
| 8 |
runScripts: "dangerously", |
|
| 9 |
resources: "usable" |
|
| 10 |
} |
|
| 11 |
return JSDOM.fromFile(html, options); |
|
| 12 |
} |
|