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 |
} |