@osimatic/helpers-js 1.5.2 → 1.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/count_down.js +46 -48
- package/date_time.js +0 -1
- package/details_sub_array.js +65 -50
- package/flash_message.js +10 -6
- package/form_date.js +144 -153
- package/form_helper.js +283 -232
- package/google_charts.js +154 -144
- package/google_maps.js +1 -1
- package/import_from_csv.js +198 -160
- package/multi_files_input.js +44 -35
- package/multiple_action_in_table.js +123 -109
- package/package.json +1 -1
- package/paging.js +103 -84
- package/select_all.js +65 -70
- package/sortable_list.js +12 -13
- package/tests/count_down.test.js +131 -352
- package/tests/details_sub_array.test.js +213 -258
- package/tests/flash_message.test.js +21 -153
- package/tests/form_date.test.js +287 -961
- package/tests/form_helper.test.js +553 -673
- package/tests/google_charts.test.js +338 -339
- package/tests/google_maps.test.js +3 -15
- package/tests/import_from_csv.test.js +421 -640
- package/tests/multi_files_input.test.js +305 -737
- package/tests/multiple_action_in_table.test.js +442 -429
- package/tests/open_street_map.test.js +15 -23
- package/tests/paging.test.js +344 -475
- package/tests/select_all.test.js +232 -318
- package/tests/sortable_list.test.js +176 -500
- package/tests/user.test.js +163 -54
- package/user.js +35 -38
|
@@ -1,602 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
1
4
|
const { SortableList } = require('../sortable_list');
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
// Reset event handlers storage
|
|
11
|
-
eventHandlers = {
|
|
12
|
-
dragover: null,
|
|
13
|
-
dragenter: null
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
itemEventHandlers = [];
|
|
17
|
-
|
|
18
|
-
// Mock items (draggable elements)
|
|
19
|
-
mockItems = [
|
|
20
|
-
{
|
|
21
|
-
offsetTop: 0,
|
|
22
|
-
offsetHeight: 50,
|
|
23
|
-
classList: {
|
|
24
|
-
add: jest.fn(),
|
|
25
|
-
remove: jest.fn()
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
offsetTop: 50,
|
|
30
|
-
offsetHeight: 50,
|
|
31
|
-
classList: {
|
|
32
|
-
add: jest.fn(),
|
|
33
|
-
remove: jest.fn()
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
offsetTop: 100,
|
|
38
|
-
offsetHeight: 50,
|
|
39
|
-
classList: {
|
|
40
|
-
add: jest.fn(),
|
|
41
|
-
remove: jest.fn()
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
// Create jQuery-like objects for each item
|
|
47
|
-
const jQueryItems = mockItems.map((item, index) => {
|
|
48
|
-
const handlers = {
|
|
49
|
-
dragstart: null,
|
|
50
|
-
dragend: null
|
|
51
|
-
};
|
|
52
|
-
itemEventHandlers[index] = handlers;
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
on: jest.fn((event, handler) => {
|
|
56
|
-
handlers[event] = handler;
|
|
57
|
-
return jQueryItems[index];
|
|
58
|
-
}),
|
|
59
|
-
addClass: jest.fn(function(className) {
|
|
60
|
-
item.classList.add(className);
|
|
61
|
-
return this;
|
|
62
|
-
}),
|
|
63
|
-
removeClass: jest.fn(function(className) {
|
|
64
|
-
item.classList.remove(className);
|
|
65
|
-
return this;
|
|
66
|
-
}),
|
|
67
|
-
get: jest.fn(() => [item]),
|
|
68
|
-
_rawElement: item
|
|
69
|
-
};
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// Mock sortable list container
|
|
73
|
-
const mockContainer = {
|
|
74
|
-
insertBefore: jest.fn()
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
mockSortableList = {
|
|
78
|
-
find: jest.fn((selector) => {
|
|
79
|
-
if (selector === '[draggable="true"]') {
|
|
80
|
-
return {
|
|
81
|
-
get: jest.fn(() => mockItems)
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
if (selector === '.dragging') {
|
|
85
|
-
// Find the item with dragging class
|
|
86
|
-
const draggingIndex = mockItems.findIndex(item =>
|
|
87
|
-
item.classList.add.mock.calls.some(call => call[0] === 'dragging')
|
|
88
|
-
);
|
|
89
|
-
if (draggingIndex !== -1) {
|
|
90
|
-
return jQueryItems[draggingIndex];
|
|
91
|
-
}
|
|
92
|
-
return { get: jest.fn(() => [null]) };
|
|
93
|
-
}
|
|
94
|
-
if (selector === '[draggable="true"]:not(.dragging)') {
|
|
95
|
-
// Return items without dragging class
|
|
96
|
-
const nonDraggingItems = mockItems.filter((item, index) => {
|
|
97
|
-
const hasBeenMarkedDragging = item.classList.add.mock.calls.some(
|
|
98
|
-
call => call[0] === 'dragging'
|
|
99
|
-
);
|
|
100
|
-
const hasBeenUnmarkedDragging = item.classList.remove.mock.calls.some(
|
|
101
|
-
call => call[0] === 'dragging'
|
|
102
|
-
);
|
|
103
|
-
// If it was marked dragging and not unmarked, it's dragging
|
|
104
|
-
return !(hasBeenMarkedDragging && !hasBeenUnmarkedDragging);
|
|
105
|
-
});
|
|
106
|
-
return {
|
|
107
|
-
get: jest.fn(() => nonDraggingItems)
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
return { get: jest.fn(() => []) };
|
|
111
|
-
}),
|
|
112
|
-
on: jest.fn((event, handler) => {
|
|
113
|
-
eventHandlers[event] = handler;
|
|
114
|
-
return mockSortableList;
|
|
115
|
-
}),
|
|
116
|
-
get: jest.fn(() => [mockContainer])
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
// Mock jQuery $ function
|
|
120
|
-
global.$ = jest.fn((selector) => {
|
|
121
|
-
// If selector is an element, wrap it
|
|
122
|
-
if (selector && typeof selector === 'object' && selector._rawElement) {
|
|
123
|
-
return selector;
|
|
124
|
-
}
|
|
125
|
-
const itemIndex = mockItems.indexOf(selector);
|
|
126
|
-
if (itemIndex !== -1) {
|
|
127
|
-
return jQueryItems[itemIndex];
|
|
128
|
-
}
|
|
129
|
-
return {
|
|
130
|
-
on: jest.fn().mockReturnThis(),
|
|
131
|
-
addClass: jest.fn().mockReturnThis(),
|
|
132
|
-
removeClass: jest.fn().mockReturnThis(),
|
|
133
|
-
get: jest.fn(() => [selector])
|
|
134
|
-
};
|
|
135
|
-
});
|
|
6
|
+
function setupList(nbItems = 3) {
|
|
7
|
+
document.body.innerHTML = `
|
|
8
|
+
<ul id="sortable">
|
|
9
|
+
${Array.from({ length: nbItems }, (_, i) => `<li draggable="true" id="item${i}">Item ${i}</li>`).join('')}
|
|
10
|
+
</ul>`;
|
|
136
11
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
});
|
|
12
|
+
const container = document.getElementById('sortable');
|
|
13
|
+
const items = [...container.querySelectorAll('[draggable="true"]')];
|
|
140
14
|
|
|
141
|
-
|
|
142
|
-
|
|
15
|
+
// jsdom doesn't compute layout — mock offsetTop/offsetHeight
|
|
16
|
+
items.forEach((item, i) => {
|
|
17
|
+
Object.defineProperty(item, 'offsetTop', { value: i * 50, configurable: true });
|
|
18
|
+
Object.defineProperty(item, 'offsetHeight', { value: 50, configurable: true });
|
|
143
19
|
});
|
|
144
20
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
SortableList.init(mockSortableList);
|
|
21
|
+
return { container, items };
|
|
22
|
+
}
|
|
148
23
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
SortableList.init(mockSortableList);
|
|
154
|
-
|
|
155
|
-
// Verify $ was called for each item to wrap them (twice per item: dragstart + dragend)
|
|
156
|
-
expect(global.$).toHaveBeenCalledTimes(mockItems.length * 2);
|
|
157
|
-
mockItems.forEach(item => {
|
|
158
|
-
expect(global.$).toHaveBeenCalledWith(item);
|
|
159
|
-
});
|
|
160
|
-
});
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
document.body.innerHTML = '';
|
|
26
|
+
jest.useRealTimers();
|
|
27
|
+
});
|
|
161
28
|
|
|
162
|
-
|
|
163
|
-
SortableList.init(mockSortableList);
|
|
29
|
+
describe('SortableList', () => {
|
|
164
30
|
|
|
165
|
-
|
|
166
|
-
|
|
31
|
+
describe('init', () => {
|
|
32
|
+
test('should add dragover event listener to container', () => {
|
|
33
|
+
const { container } = setupList();
|
|
34
|
+
const spy = jest.spyOn(container, 'addEventListener');
|
|
35
|
+
SortableList.init(container);
|
|
36
|
+
expect(spy).toHaveBeenCalledWith('dragover', expect.any(Function));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('should add dragenter event listener to container', () => {
|
|
40
|
+
const { container } = setupList();
|
|
41
|
+
const spy = jest.spyOn(container, 'addEventListener');
|
|
42
|
+
SortableList.init(container);
|
|
43
|
+
expect(spy).toHaveBeenCalledWith('dragenter', expect.any(Function));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('should add event listeners to all draggable items', () => {
|
|
47
|
+
const { container, items } = setupList(3);
|
|
48
|
+
SortableList.init(container);
|
|
49
|
+
const spies = items.map(item => jest.spyOn(item, 'addEventListener'));
|
|
50
|
+
// Re-init to capture calls
|
|
51
|
+
SortableList.init(container);
|
|
52
|
+
spies.forEach(spy => {
|
|
53
|
+
expect(spy).toHaveBeenCalledWith('dragstart', expect.any(Function));
|
|
54
|
+
expect(spy).toHaveBeenCalledWith('dragend', expect.any(Function));
|
|
167
55
|
});
|
|
168
56
|
});
|
|
169
57
|
|
|
170
|
-
test('should
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
expect(mockSortableList.on).toHaveBeenCalledWith('dragover', expect.any(Function));
|
|
174
|
-
expect(eventHandlers.dragover).toBeDefined();
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
test('should add dragenter event listener to sortable list', () => {
|
|
178
|
-
SortableList.init(mockSortableList);
|
|
179
|
-
|
|
180
|
-
expect(mockSortableList.on).toHaveBeenCalledWith('dragenter', expect.any(Function));
|
|
181
|
-
expect(eventHandlers.dragenter).toBeDefined();
|
|
58
|
+
test('should initialize with default clientYOffset of 0', () => {
|
|
59
|
+
const { container } = setupList();
|
|
60
|
+
expect(() => SortableList.init(container)).not.toThrow();
|
|
182
61
|
});
|
|
183
62
|
|
|
184
63
|
test('should initialize with custom clientYOffset', () => {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
// Should not throw error
|
|
188
|
-
expect(mockSortableList.find).toHaveBeenCalled();
|
|
64
|
+
const { container } = setupList();
|
|
65
|
+
expect(() => SortableList.init(container, 100)).not.toThrow();
|
|
189
66
|
});
|
|
190
67
|
|
|
191
|
-
test('should
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
// Should not throw error
|
|
195
|
-
expect(mockSortableList.find).toHaveBeenCalled();
|
|
68
|
+
test('should handle empty item list', () => {
|
|
69
|
+
const { container } = setupList(0);
|
|
70
|
+
expect(() => SortableList.init(container)).not.toThrow();
|
|
196
71
|
});
|
|
197
72
|
});
|
|
198
73
|
|
|
199
|
-
describe('drag
|
|
74
|
+
describe('drag events on items', () => {
|
|
200
75
|
beforeEach(() => {
|
|
201
|
-
|
|
76
|
+
jest.useFakeTimers();
|
|
202
77
|
});
|
|
203
78
|
|
|
204
|
-
test('should add dragging class
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
handlers.dragstart();
|
|
79
|
+
test('should add dragging class after dragstart timeout', () => {
|
|
80
|
+
const { container, items } = setupList();
|
|
81
|
+
SortableList.init(container);
|
|
208
82
|
|
|
209
|
-
|
|
210
|
-
expect(
|
|
83
|
+
items[0].dispatchEvent(new Event('dragstart'));
|
|
84
|
+
expect(items[0].classList.contains('dragging')).toBe(false);
|
|
211
85
|
|
|
212
|
-
// After timeout
|
|
213
86
|
jest.runAllTimers();
|
|
87
|
+
expect(items[0].classList.contains('dragging')).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('should not add dragging class immediately on dragstart', () => {
|
|
91
|
+
const { container, items } = setupList();
|
|
92
|
+
SortableList.init(container);
|
|
214
93
|
|
|
215
|
-
|
|
216
|
-
expect(
|
|
94
|
+
items[0].dispatchEvent(new Event('dragstart'));
|
|
95
|
+
expect(items[0].classList.contains('dragging')).toBe(false);
|
|
217
96
|
});
|
|
218
97
|
|
|
219
98
|
test('should remove dragging class on dragend', () => {
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
handlers.dragend();
|
|
99
|
+
const { container, items } = setupList();
|
|
100
|
+
SortableList.init(container);
|
|
223
101
|
|
|
224
|
-
|
|
225
|
-
|
|
102
|
+
items[1].classList.add('dragging');
|
|
103
|
+
items[1].dispatchEvent(new Event('dragend'));
|
|
104
|
+
expect(items[1].classList.contains('dragging')).toBe(false);
|
|
226
105
|
});
|
|
227
106
|
|
|
228
107
|
test('should add and remove dragging class in sequence', () => {
|
|
229
|
-
const
|
|
108
|
+
const { container, items } = setupList();
|
|
109
|
+
SortableList.init(container);
|
|
230
110
|
|
|
231
|
-
|
|
232
|
-
handlers.dragstart();
|
|
111
|
+
items[2].dispatchEvent(new Event('dragstart'));
|
|
233
112
|
jest.runAllTimers();
|
|
234
|
-
expect(
|
|
113
|
+
expect(items[2].classList.contains('dragging')).toBe(true);
|
|
235
114
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
expect(mockItems[2].classList.remove).toHaveBeenCalledWith('dragging');
|
|
115
|
+
items[2].dispatchEvent(new Event('dragend'));
|
|
116
|
+
expect(items[2].classList.contains('dragging')).toBe(false);
|
|
239
117
|
});
|
|
240
118
|
|
|
241
|
-
test('should handle multiple items
|
|
242
|
-
|
|
243
|
-
|
|
119
|
+
test('should handle multiple items dragged in sequence', () => {
|
|
120
|
+
const { container, items } = setupList();
|
|
121
|
+
SortableList.init(container);
|
|
122
|
+
|
|
123
|
+
items[0].dispatchEvent(new Event('dragstart'));
|
|
244
124
|
jest.runAllTimers();
|
|
245
|
-
expect(
|
|
246
|
-
|
|
247
|
-
expect(
|
|
125
|
+
expect(items[0].classList.contains('dragging')).toBe(true);
|
|
126
|
+
items[0].dispatchEvent(new Event('dragend'));
|
|
127
|
+
expect(items[0].classList.contains('dragging')).toBe(false);
|
|
248
128
|
|
|
249
|
-
|
|
250
|
-
itemEventHandlers[1].dragstart();
|
|
129
|
+
items[1].dispatchEvent(new Event('dragstart'));
|
|
251
130
|
jest.runAllTimers();
|
|
252
|
-
expect(
|
|
253
|
-
|
|
254
|
-
expect(
|
|
131
|
+
expect(items[1].classList.contains('dragging')).toBe(true);
|
|
132
|
+
items[1].dispatchEvent(new Event('dragend'));
|
|
133
|
+
expect(items[1].classList.contains('dragging')).toBe(false);
|
|
255
134
|
});
|
|
256
135
|
});
|
|
257
136
|
|
|
258
137
|
describe('dragenter event', () => {
|
|
259
|
-
beforeEach(() => {
|
|
260
|
-
SortableList.init(mockSortableList);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
138
|
test('should prevent default on dragenter', () => {
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
eventHandlers.dragenter(mockEvent);
|
|
139
|
+
const { container } = setupList();
|
|
140
|
+
SortableList.init(container);
|
|
269
141
|
|
|
270
|
-
|
|
142
|
+
const event = new Event('dragenter', { cancelable: true });
|
|
143
|
+
container.dispatchEvent(event);
|
|
144
|
+
expect(event.defaultPrevented).toBe(true);
|
|
271
145
|
});
|
|
272
146
|
|
|
273
147
|
test('should handle dragenter without error', () => {
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
};
|
|
148
|
+
const { container } = setupList();
|
|
149
|
+
SortableList.init(container);
|
|
277
150
|
|
|
278
151
|
expect(() => {
|
|
279
|
-
|
|
152
|
+
container.dispatchEvent(new Event('dragenter', { cancelable: true }));
|
|
280
153
|
}).not.toThrow();
|
|
281
154
|
});
|
|
282
155
|
});
|
|
283
156
|
|
|
284
|
-
describe('dragover and
|
|
285
|
-
let mockContainer;
|
|
286
|
-
let draggingItem;
|
|
287
|
-
|
|
288
|
-
beforeEach(() => {
|
|
289
|
-
SortableList.init(mockSortableList);
|
|
290
|
-
mockContainer = mockSortableList.get()[0];
|
|
291
|
-
|
|
292
|
-
// Setup: mark first item as dragging
|
|
293
|
-
itemEventHandlers[0].dragstart();
|
|
294
|
-
jest.runAllTimers();
|
|
295
|
-
|
|
296
|
-
draggingItem = mockItems[0];
|
|
297
|
-
});
|
|
298
|
-
|
|
157
|
+
describe('dragover and reordering', () => {
|
|
299
158
|
test('should prevent default on dragover', () => {
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
clientY: 75
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
eventHandlers.dragover(mockEvent);
|
|
159
|
+
const { container } = setupList();
|
|
160
|
+
SortableList.init(container);
|
|
306
161
|
|
|
307
|
-
|
|
162
|
+
const event = new Event('dragover', { cancelable: true });
|
|
163
|
+
container.dispatchEvent(event);
|
|
164
|
+
expect(event.defaultPrevented).toBe(true);
|
|
308
165
|
});
|
|
309
166
|
|
|
310
|
-
test('should
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
clientY: 75
|
|
314
|
-
};
|
|
167
|
+
test('should insert dragging item before correct sibling', () => {
|
|
168
|
+
const { container, items } = setupList(3);
|
|
169
|
+
SortableList.init(container);
|
|
315
170
|
|
|
316
|
-
|
|
317
|
-
|
|
171
|
+
items[0].classList.add('dragging');
|
|
172
|
+
const spy = jest.spyOn(container, 'insertBefore');
|
|
318
173
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
test('should find siblings (non-dragging items) on dragover', () => {
|
|
323
|
-
const mockEvent = {
|
|
324
|
-
preventDefault: jest.fn(),
|
|
325
|
-
clientY: 75
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
mockSortableList.find.mockClear();
|
|
329
|
-
eventHandlers.dragover(mockEvent);
|
|
174
|
+
// item1: offsetTop=50, height=50, midpoint=75 → clientY=74 ≤ 75 → insert before item1
|
|
175
|
+
const event = Object.assign(new Event('dragover', { cancelable: true }), { clientY: 74 });
|
|
176
|
+
container.dispatchEvent(event);
|
|
330
177
|
|
|
331
|
-
expect(
|
|
178
|
+
expect(spy).toHaveBeenCalledWith(items[0], items[1]);
|
|
332
179
|
});
|
|
333
180
|
|
|
334
|
-
test('should insert
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
preventDefault: jest.fn(),
|
|
338
|
-
clientY: 75 // Between item 2 (50-100) and item 3 (100-150)
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
const findSpy = jest.spyOn(mockSortableList, 'find');
|
|
342
|
-
|
|
343
|
-
// Mock the return for .dragging
|
|
344
|
-
findSpy.mockImplementation((selector) => {
|
|
345
|
-
if (selector === '.dragging') {
|
|
346
|
-
return {
|
|
347
|
-
get: jest.fn(() => [draggingItem])
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
if (selector === '[draggable="true"]:not(.dragging)') {
|
|
351
|
-
// Return items 2 and 3 (not dragging)
|
|
352
|
-
return {
|
|
353
|
-
get: jest.fn(() => [mockItems[1], mockItems[2]])
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
return { get: jest.fn(() => mockItems) };
|
|
357
|
-
});
|
|
181
|
+
test('should insert at end when clientY is past all items', () => {
|
|
182
|
+
const { container, items } = setupList(3);
|
|
183
|
+
SortableList.init(container);
|
|
358
184
|
|
|
359
|
-
|
|
185
|
+
items[0].classList.add('dragging');
|
|
186
|
+
const spy = jest.spyOn(container, 'insertBefore').mockImplementation(() => {});
|
|
360
187
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
mockItems[1] // Should insert before item 2
|
|
364
|
-
);
|
|
365
|
-
});
|
|
188
|
+
const event = Object.assign(new Event('dragover', { cancelable: true }), { clientY: 999 });
|
|
189
|
+
container.dispatchEvent(event);
|
|
366
190
|
|
|
367
|
-
|
|
368
|
-
|
|
191
|
+
expect(spy).toHaveBeenCalledWith(items[0], undefined);
|
|
192
|
+
spy.mockRestore();
|
|
193
|
+
});
|
|
369
194
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
195
|
+
test('should insert at exact midpoint of sibling', () => {
|
|
196
|
+
const { container, items } = setupList(3);
|
|
197
|
+
SortableList.init(container);
|
|
373
198
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
clientY: 55 // With offset of 20, effective position is 75
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
const findSpy = jest.spyOn(mockSortableList, 'find');
|
|
380
|
-
findSpy.mockImplementation((selector) => {
|
|
381
|
-
if (selector === '.dragging') {
|
|
382
|
-
return { get: jest.fn(() => [draggingItem]) };
|
|
383
|
-
}
|
|
384
|
-
if (selector === '[draggable="true"]:not(.dragging)') {
|
|
385
|
-
return { get: jest.fn(() => [mockItems[1], mockItems[2]]) };
|
|
386
|
-
}
|
|
387
|
-
return { get: jest.fn(() => mockItems) };
|
|
388
|
-
});
|
|
199
|
+
items[0].classList.add('dragging');
|
|
200
|
+
const spy = jest.spyOn(container, 'insertBefore');
|
|
389
201
|
|
|
390
|
-
|
|
202
|
+
// item1 midpoint = 50 + 50/2 = 75 → clientY=75 ≤ 75 → insert before item1
|
|
203
|
+
const event = Object.assign(new Event('dragover', { cancelable: true }), { clientY: 75 });
|
|
204
|
+
container.dispatchEvent(event);
|
|
391
205
|
|
|
392
|
-
expect(
|
|
206
|
+
expect(spy).toHaveBeenCalledWith(items[0], items[1]);
|
|
393
207
|
});
|
|
394
208
|
|
|
395
|
-
test('should
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
clientY: 10 // Very top
|
|
399
|
-
};
|
|
400
|
-
|
|
401
|
-
const findSpy = jest.spyOn(mockSortableList, 'find');
|
|
402
|
-
findSpy.mockImplementation((selector) => {
|
|
403
|
-
if (selector === '.dragging') {
|
|
404
|
-
return { get: jest.fn(() => [draggingItem]) };
|
|
405
|
-
}
|
|
406
|
-
if (selector === '[draggable="true"]:not(.dragging)') {
|
|
407
|
-
return { get: jest.fn(() => [mockItems[1], mockItems[2]]) };
|
|
408
|
-
}
|
|
409
|
-
return { get: jest.fn(() => mockItems) };
|
|
410
|
-
});
|
|
209
|
+
test('should apply clientYOffset to position calculation', () => {
|
|
210
|
+
const { container, items } = setupList(3);
|
|
211
|
+
SortableList.init(container, 20);
|
|
411
212
|
|
|
412
|
-
|
|
213
|
+
items[0].classList.add('dragging');
|
|
214
|
+
const spy = jest.spyOn(container, 'insertBefore');
|
|
413
215
|
|
|
414
|
-
//
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
mockItems[1]
|
|
418
|
-
);
|
|
419
|
-
});
|
|
216
|
+
// clientY=54 + offset=20 = 74 ≤ 75 → insert before item1
|
|
217
|
+
const event = Object.assign(new Event('dragover', { cancelable: true }), { clientY: 54 });
|
|
218
|
+
container.dispatchEvent(event);
|
|
420
219
|
|
|
421
|
-
|
|
422
|
-
const mockEvent = {
|
|
423
|
-
preventDefault: jest.fn(),
|
|
424
|
-
clientY: 200 // Past all items
|
|
425
|
-
};
|
|
426
|
-
|
|
427
|
-
const findSpy = jest.spyOn(mockSortableList, 'find');
|
|
428
|
-
findSpy.mockImplementation((selector) => {
|
|
429
|
-
if (selector === '.dragging') {
|
|
430
|
-
return { get: jest.fn(() => [draggingItem]) };
|
|
431
|
-
}
|
|
432
|
-
if (selector === '[draggable="true"]:not(.dragging)') {
|
|
433
|
-
return { get: jest.fn(() => [mockItems[1], mockItems[2]]) };
|
|
434
|
-
}
|
|
435
|
-
return { get: jest.fn(() => mockItems) };
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
eventHandlers.dragover(mockEvent);
|
|
439
|
-
|
|
440
|
-
// When no sibling found, nextSibling is undefined
|
|
441
|
-
expect(mockContainer.insertBefore).toHaveBeenCalledWith(
|
|
442
|
-
draggingItem,
|
|
443
|
-
undefined
|
|
444
|
-
);
|
|
220
|
+
expect(spy).toHaveBeenCalledWith(items[0], items[1]);
|
|
445
221
|
});
|
|
446
222
|
|
|
447
|
-
test('should
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
preventDefault: jest.fn(),
|
|
451
|
-
clientY: 75
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
const findSpy = jest.spyOn(mockSortableList, 'find');
|
|
455
|
-
findSpy.mockImplementation((selector) => {
|
|
456
|
-
if (selector === '.dragging') {
|
|
457
|
-
return { get: jest.fn(() => [draggingItem]) };
|
|
458
|
-
}
|
|
459
|
-
if (selector === '[draggable="true"]:not(.dragging)') {
|
|
460
|
-
return { get: jest.fn(() => [mockItems[1], mockItems[2]]) };
|
|
461
|
-
}
|
|
462
|
-
return { get: jest.fn(() => mockItems) };
|
|
463
|
-
});
|
|
223
|
+
test('should find dragging item via querySelector on dragover', () => {
|
|
224
|
+
const { container, items } = setupList();
|
|
225
|
+
SortableList.init(container);
|
|
464
226
|
|
|
465
|
-
|
|
227
|
+
items[0].classList.add('dragging');
|
|
228
|
+
const spy = jest.spyOn(container, 'querySelector');
|
|
466
229
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
);
|
|
230
|
+
container.dispatchEvent(Object.assign(new Event('dragover', { cancelable: true }), { clientY: 0 }));
|
|
231
|
+
|
|
232
|
+
expect(spy).toHaveBeenCalledWith('.dragging');
|
|
471
233
|
});
|
|
472
234
|
});
|
|
473
235
|
|
|
474
236
|
describe('edge cases', () => {
|
|
475
|
-
test('should handle
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
return { get: jest.fn(() => []) };
|
|
479
|
-
}
|
|
480
|
-
return { get: jest.fn(() => []) };
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
expect(() => {
|
|
484
|
-
SortableList.init(mockSortableList);
|
|
485
|
-
}).not.toThrow();
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
test('should handle sortable list with single item', () => {
|
|
489
|
-
mockSortableList.find = jest.fn((selector) => {
|
|
490
|
-
if (selector === '[draggable="true"]') {
|
|
491
|
-
return { get: jest.fn(() => [mockItems[0]]) };
|
|
492
|
-
}
|
|
493
|
-
if (selector === '.dragging') {
|
|
494
|
-
return { get: jest.fn(() => [mockItems[0]]) };
|
|
495
|
-
}
|
|
496
|
-
if (selector === '[draggable="true"]:not(.dragging)') {
|
|
497
|
-
return { get: jest.fn(() => []) };
|
|
498
|
-
}
|
|
499
|
-
return { get: jest.fn(() => []) };
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
SortableList.init(mockSortableList);
|
|
503
|
-
|
|
504
|
-
// Should initialize without error
|
|
505
|
-
expect(mockSortableList.on).toHaveBeenCalledWith('dragover', expect.any(Function));
|
|
237
|
+
test('should handle single item', () => {
|
|
238
|
+
const { container } = setupList(1);
|
|
239
|
+
expect(() => SortableList.init(container)).not.toThrow();
|
|
506
240
|
});
|
|
507
241
|
|
|
508
242
|
test('should handle negative clientYOffset', () => {
|
|
509
|
-
|
|
243
|
+
const { container, items } = setupList(3);
|
|
244
|
+
SortableList.init(container, -50);
|
|
510
245
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
const mockEvent = {
|
|
515
|
-
preventDefault: jest.fn(),
|
|
516
|
-
clientY: 100
|
|
517
|
-
};
|
|
518
|
-
|
|
519
|
-
const findSpy = jest.spyOn(mockSortableList, 'find');
|
|
520
|
-
findSpy.mockImplementation((selector) => {
|
|
521
|
-
if (selector === '.dragging') {
|
|
522
|
-
return { get: jest.fn(() => [mockItems[0]]) };
|
|
523
|
-
}
|
|
524
|
-
if (selector === '[draggable="true"]:not(.dragging)') {
|
|
525
|
-
return { get: jest.fn(() => [mockItems[1], mockItems[2]]) };
|
|
526
|
-
}
|
|
527
|
-
return { get: jest.fn(() => mockItems) };
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
// Should work with negative offset: 100 + (-50) = 50
|
|
531
|
-
eventHandlers.dragover(mockEvent);
|
|
532
|
-
|
|
533
|
-
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
|
246
|
+
items[0].classList.add('dragging');
|
|
247
|
+
const event = Object.assign(new Event('dragover', { cancelable: true }), { clientY: 100 });
|
|
248
|
+
expect(() => container.dispatchEvent(event)).not.toThrow();
|
|
534
249
|
});
|
|
535
250
|
|
|
536
251
|
test('should handle very large clientYOffset', () => {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
expect(mockSortableList.on).toHaveBeenCalled();
|
|
252
|
+
const { container } = setupList(3);
|
|
253
|
+
expect(() => SortableList.init(container, 10000)).not.toThrow();
|
|
540
254
|
});
|
|
541
255
|
|
|
542
|
-
test('should handle zero height
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
SortableList.init(
|
|
546
|
-
|
|
547
|
-
itemEventHandlers[0].dragstart();
|
|
548
|
-
jest.runAllTimers();
|
|
549
|
-
|
|
550
|
-
const mockEvent = {
|
|
551
|
-
preventDefault: jest.fn(),
|
|
552
|
-
clientY: 50
|
|
553
|
-
};
|
|
554
|
-
|
|
555
|
-
const findSpy = jest.spyOn(mockSortableList, 'find');
|
|
556
|
-
findSpy.mockImplementation((selector) => {
|
|
557
|
-
if (selector === '.dragging') {
|
|
558
|
-
return { get: jest.fn(() => [mockItems[0]]) };
|
|
559
|
-
}
|
|
560
|
-
if (selector === '[draggable="true"]:not(.dragging)') {
|
|
561
|
-
return { get: jest.fn(() => [mockItems[1], mockItems[2]]) };
|
|
562
|
-
}
|
|
563
|
-
return { get: jest.fn(() => mockItems) };
|
|
564
|
-
});
|
|
256
|
+
test('should handle items with zero height', () => {
|
|
257
|
+
const { container, items } = setupList(3);
|
|
258
|
+
Object.defineProperty(items[1], 'offsetHeight', { value: 0, configurable: true });
|
|
259
|
+
SortableList.init(container);
|
|
565
260
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
261
|
+
items[0].classList.add('dragging');
|
|
262
|
+
const event = Object.assign(new Event('dragover', { cancelable: true }), { clientY: 50 });
|
|
263
|
+
expect(() => container.dispatchEvent(event)).not.toThrow();
|
|
569
264
|
});
|
|
570
265
|
|
|
571
266
|
test('should handle items with varying heights', () => {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
SortableList.init(
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
const mockEvent = {
|
|
582
|
-
preventDefault: jest.fn(),
|
|
583
|
-
clientY: 50
|
|
584
|
-
};
|
|
585
|
-
|
|
586
|
-
const findSpy = jest.spyOn(mockSortableList, 'find');
|
|
587
|
-
findSpy.mockImplementation((selector) => {
|
|
588
|
-
if (selector === '.dragging') {
|
|
589
|
-
return { get: jest.fn(() => [mockItems[0]]) };
|
|
590
|
-
}
|
|
591
|
-
if (selector === '[draggable="true"]:not(.dragging)') {
|
|
592
|
-
return { get: jest.fn(() => [mockItems[1], mockItems[2]]) };
|
|
593
|
-
}
|
|
594
|
-
return { get: jest.fn(() => mockItems) };
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
expect(() => {
|
|
598
|
-
eventHandlers.dragover(mockEvent);
|
|
599
|
-
}).not.toThrow();
|
|
267
|
+
const { container, items } = setupList(3);
|
|
268
|
+
Object.defineProperty(items[0], 'offsetHeight', { value: 30, configurable: true });
|
|
269
|
+
Object.defineProperty(items[1], 'offsetHeight', { value: 70, configurable: true });
|
|
270
|
+
Object.defineProperty(items[2], 'offsetHeight', { value: 100, configurable: true });
|
|
271
|
+
SortableList.init(container);
|
|
272
|
+
|
|
273
|
+
items[0].classList.add('dragging');
|
|
274
|
+
const event = Object.assign(new Event('dragover', { cancelable: true }), { clientY: 50 });
|
|
275
|
+
expect(() => container.dispatchEvent(event)).not.toThrow();
|
|
600
276
|
});
|
|
601
277
|
});
|
|
602
278
|
});
|