@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.
@@ -1,602 +1,278 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
1
4
  const { SortableList } = require('../sortable_list');
2
5
 
3
- describe('SortableList', () => {
4
- let mockSortableList;
5
- let mockItems;
6
- let eventHandlers;
7
- let itemEventHandlers;
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
- // Mock setTimeout
138
- jest.useFakeTimers();
139
- });
12
+ const container = document.getElementById('sortable');
13
+ const items = [...container.querySelectorAll('[draggable="true"]')];
140
14
 
141
- afterEach(() => {
142
- jest.useRealTimers();
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
- describe('init', () => {
146
- test('should initialize sortable list with draggable items', () => {
147
- SortableList.init(mockSortableList);
21
+ return { container, items };
22
+ }
148
23
 
149
- expect(mockSortableList.find).toHaveBeenCalledWith('[draggable="true"]');
150
- });
151
-
152
- test('should add dragstart event listeners to all items', () => {
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
- test('should add dragend event listeners to all items', () => {
163
- SortableList.init(mockSortableList);
29
+ describe('SortableList', () => {
164
30
 
165
- itemEventHandlers.forEach(handlers => {
166
- expect(handlers.dragend).toBeDefined();
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 add dragover event listener to sortable list', () => {
171
- SortableList.init(mockSortableList);
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
- SortableList.init(mockSortableList, 100);
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 initialize with default clientYOffset of 0', () => {
192
- SortableList.init(mockSortableList);
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 and drop functionality', () => {
74
+ describe('drag events on items', () => {
200
75
  beforeEach(() => {
201
- SortableList.init(mockSortableList);
76
+ jest.useFakeTimers();
202
77
  });
203
78
 
204
- test('should add dragging class on dragstart after timeout', () => {
205
- const handlers = itemEventHandlers[0];
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
- // Initially no class added
210
- expect(mockItems[0].classList.add).not.toHaveBeenCalled();
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
- expect(global.$).toHaveBeenCalledWith(mockItems[0]);
216
- expect(mockItems[0].classList.add).toHaveBeenCalledWith('dragging');
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 handlers = itemEventHandlers[1];
221
-
222
- handlers.dragend();
99
+ const { container, items } = setupList();
100
+ SortableList.init(container);
223
101
 
224
- expect(global.$).toHaveBeenCalledWith(mockItems[1]);
225
- expect(mockItems[1].classList.remove).toHaveBeenCalledWith('dragging');
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 handlers = itemEventHandlers[2];
108
+ const { container, items } = setupList();
109
+ SortableList.init(container);
230
110
 
231
- // Start dragging
232
- handlers.dragstart();
111
+ items[2].dispatchEvent(new Event('dragstart'));
233
112
  jest.runAllTimers();
234
- expect(mockItems[2].classList.add).toHaveBeenCalledWith('dragging');
113
+ expect(items[2].classList.contains('dragging')).toBe(true);
235
114
 
236
- // Stop dragging
237
- handlers.dragend();
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 being dragged in sequence', () => {
242
- // Drag first item
243
- itemEventHandlers[0].dragstart();
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(mockItems[0].classList.add).toHaveBeenCalledWith('dragging');
246
- itemEventHandlers[0].dragend();
247
- expect(mockItems[0].classList.remove).toHaveBeenCalledWith('dragging');
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
- // Drag second item
250
- itemEventHandlers[1].dragstart();
129
+ items[1].dispatchEvent(new Event('dragstart'));
251
130
  jest.runAllTimers();
252
- expect(mockItems[1].classList.add).toHaveBeenCalledWith('dragging');
253
- itemEventHandlers[1].dragend();
254
- expect(mockItems[1].classList.remove).toHaveBeenCalledWith('dragging');
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 mockEvent = {
265
- preventDefault: jest.fn()
266
- };
267
-
268
- eventHandlers.dragenter(mockEvent);
139
+ const { container } = setupList();
140
+ SortableList.init(container);
269
141
 
270
- expect(mockEvent.preventDefault).toHaveBeenCalled();
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 mockEvent = {
275
- preventDefault: jest.fn()
276
- };
148
+ const { container } = setupList();
149
+ SortableList.init(container);
277
150
 
278
151
  expect(() => {
279
- eventHandlers.dragenter(mockEvent);
152
+ container.dispatchEvent(new Event('dragenter', { cancelable: true }));
280
153
  }).not.toThrow();
281
154
  });
282
155
  });
283
156
 
284
- describe('dragover and item reordering', () => {
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 mockEvent = {
301
- preventDefault: jest.fn(),
302
- clientY: 75
303
- };
304
-
305
- eventHandlers.dragover(mockEvent);
159
+ const { container } = setupList();
160
+ SortableList.init(container);
306
161
 
307
- expect(mockEvent.preventDefault).toHaveBeenCalled();
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 find dragging item on dragover', () => {
311
- const mockEvent = {
312
- preventDefault: jest.fn(),
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
- mockSortableList.find.mockClear();
317
- eventHandlers.dragover(mockEvent);
171
+ items[0].classList.add('dragging');
172
+ const spy = jest.spyOn(container, 'insertBefore');
318
173
 
319
- expect(mockSortableList.find).toHaveBeenCalledWith('.dragging');
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(mockSortableList.find).toHaveBeenCalledWith('[draggable="true"]:not(.dragging)');
178
+ expect(spy).toHaveBeenCalledWith(items[0], items[1]);
332
179
  });
333
180
 
334
- test('should insert item before next sibling when dragging down', () => {
335
- // Drag first item (offsetTop 0) to position between second and third items
336
- const mockEvent = {
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
- eventHandlers.dragover(mockEvent);
185
+ items[0].classList.add('dragging');
186
+ const spy = jest.spyOn(container, 'insertBefore').mockImplementation(() => {});
360
187
 
361
- expect(mockContainer.insertBefore).toHaveBeenCalledWith(
362
- draggingItem,
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
- test('should calculate position with clientYOffset', () => {
368
- SortableList.init(mockSortableList, 20);
191
+ expect(spy).toHaveBeenCalledWith(items[0], undefined);
192
+ spy.mockRestore();
193
+ });
369
194
 
370
- // Mark first item as dragging again
371
- itemEventHandlers[0].dragstart();
372
- jest.runAllTimers();
195
+ test('should insert at exact midpoint of sibling', () => {
196
+ const { container, items } = setupList(3);
197
+ SortableList.init(container);
373
198
 
374
- const mockEvent = {
375
- preventDefault: jest.fn(),
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
- eventHandlers.dragover(mockEvent);
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(mockContainer.insertBefore).toHaveBeenCalled();
206
+ expect(spy).toHaveBeenCalledWith(items[0], items[1]);
393
207
  });
394
208
 
395
- test('should handle dragover at top of list', () => {
396
- const mockEvent = {
397
- preventDefault: jest.fn(),
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
- eventHandlers.dragover(mockEvent);
213
+ items[0].classList.add('dragging');
214
+ const spy = jest.spyOn(container, 'insertBefore');
413
215
 
414
- // Should insert before the first sibling
415
- expect(mockContainer.insertBefore).toHaveBeenCalledWith(
416
- draggingItem,
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
- test('should handle dragover at bottom of list', () => {
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 insert at exact midpoint of sibling', () => {
448
- // Test at exact midpoint: item at offsetTop 50, height 50, midpoint is 75
449
- const mockEvent = {
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
- eventHandlers.dragover(mockEvent);
227
+ items[0].classList.add('dragging');
228
+ const spy = jest.spyOn(container, 'querySelector');
466
229
 
467
- expect(mockContainer.insertBefore).toHaveBeenCalledWith(
468
- draggingItem,
469
- mockItems[1]
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 empty draggable items list', () => {
476
- mockSortableList.find = jest.fn((selector) => {
477
- if (selector === '[draggable="true"]') {
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
- SortableList.init(mockSortableList, -50);
243
+ const { container, items } = setupList(3);
244
+ SortableList.init(container, -50);
510
245
 
511
- itemEventHandlers[0].dragstart();
512
- jest.runAllTimers();
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
- SortableList.init(mockSortableList, 10000);
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 items', () => {
543
- mockItems[1].offsetHeight = 0;
544
-
545
- SortableList.init(mockSortableList);
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
- expect(() => {
567
- eventHandlers.dragover(mockEvent);
568
- }).not.toThrow();
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
- mockItems[0].offsetHeight = 30;
573
- mockItems[1].offsetHeight = 70;
574
- mockItems[2].offsetHeight = 100;
575
-
576
- SortableList.init(mockSortableList);
577
-
578
- itemEventHandlers[0].dragstart();
579
- jest.runAllTimers();
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
  });