Skip to content

Commit c88910b

Browse files
author
Hesam Bahrami
authored
feat: nesting levels (#35)
* set the integer value of nestingLevels property upon instantiation * configure jest to automatically restore mock state before every test * add the getTargetedNodeDepth and nestingThresholdReached methods * make sure no placeholder list is added when the nesting threshold has reached * remove the redundant default value for the constructor argument * add a sample
1 parent 3b171d2 commit c88910b

File tree

4 files changed

+208
-14
lines changed

4 files changed

+208
-14
lines changed

dev/nesting-levels.html

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
7+
<title>Nested Sort | Nesting Levels Option Demo</title>
8+
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300&display=swap" rel="stylesheet">
9+
<link href="css/main.css" rel="stylesheet">
10+
</head>
11+
<body>
12+
13+
<div class="container">
14+
<h1>Nesting Levels Option</h1>
15+
16+
<label for="nesting-level">Nesting Levels:</label>
17+
<input type="number" id="nesting-level" onchange="updateNestingLevels()" value="-1">
18+
19+
<ul id="draggable">
20+
<li data-id="1">Topic 1</li>
21+
<li data-id="2">Topic 2</li>
22+
<li data-id="3">Topic 3</li>
23+
<li data-id="4">Topic 4</li>
24+
<li data-id="5">Topic 5</li>
25+
<li data-id="6">Topic 6</li>
26+
<li data-id="7">Topic 7</li>
27+
<li data-id="8">Topic 8</li>
28+
<li data-id="9">Topic 9</li>
29+
<li data-id="10">Topic 10</li>
30+
</ul>
31+
</div>
32+
33+
<!-- Scripts -->
34+
<script src="../dist/nested-sort.umd.js"></script>
35+
<script>
36+
function updateNestingLevels() {
37+
if (window.ns) window.ns.destroy()
38+
window.ns = new NestedSort({
39+
actions: {
40+
onDrop: function (data) {
41+
console.log(data)
42+
}
43+
},
44+
el: '#draggable',
45+
droppingEdge: 5,
46+
nestingLevels: document.getElementById('nesting-level').value
47+
})
48+
}
49+
50+
updateNestingLevels()
51+
</script>
52+
</body>
53+
</html>

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module.exports = {
22
setupFiles: [
33
'./test/__mocks__/dom.js',
44
],
5+
restoreMocks: true,
56
coveragePathIgnorePatterns: [
67
'/node_modules/',
78
'/test/',

src/main.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class NestedSort {
1010
* @param {string} el
1111
* @param {array|string} listClassNames
1212
* @param {array|string} listItemClassNames
13+
* @param {number|string} nestingLevels
1314
* @param {object} [propertyMap={}]
1415
*/
1516
constructor({
@@ -20,8 +21,9 @@ class NestedSort {
2021
init = true,
2122
listClassNames,
2223
listItemClassNames,
24+
nestingLevels,
2325
propertyMap = {},
24-
} = {}) {
26+
}) {
2527
this.data = data
2628
this.selector = el
2729
this.sortableList = null
@@ -74,6 +76,9 @@ class NestedSort {
7476
drop: this.onDrop.bind(this),
7577
}
7678

79+
const intNestingLevels = parseInt(nestingLevels)
80+
this.nestingLevels = isNaN(intNestingLevels) ? -1 : intNestingLevels // values less than 0 mean infinite levels of nesting
81+
7782
this.maybeInitDataDom()
7883
this.addListAttributes()
7984
if (init) this.initDragAndDrop()
@@ -306,6 +311,26 @@ class NestedSort {
306311
return this.targetedNode.nodeName === 'UL' && this.targetedNode.classList.contains(this.classNames.placeholder)
307312
}
308313

314+
getTargetedNodeDepth() {
315+
let depth = 0
316+
let el = this.targetedNode
317+
const list = this.getSortableList()
318+
319+
while (list !== el.parentElement) {
320+
if (el.parentElement.nodeName === 'UL') depth++
321+
el = el.parentElement
322+
}
323+
324+
return depth
325+
}
326+
327+
nestingThresholdReached() {
328+
if (this.nestingLevels < 0) return false
329+
if (this.nestingLevels === 0) return true
330+
331+
return this.getTargetedNodeDepth() >= this.nestingLevels
332+
}
333+
309334
analysePlaceHolderSituation() {
310335
if (!this.targetedNode || this.areNested(this.targetedNode, this.draggedNode)) {
311336
return []
@@ -319,7 +344,8 @@ class NestedSort {
319344
}
320345
} else if (this.targetedNode !== this.draggedNode
321346
&& this.targetedNode.nodeName === 'LI'
322-
&& !this.targetedNode.querySelectorAll('ul').length) {
347+
&& !this.targetedNode.querySelectorAll('ul').length
348+
&& !this.nestingThresholdReached()) {
323349
actions.push('add')
324350
}
325351

test/main.test.js

Lines changed: 126 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,22 @@ describe('NestedSort', () => {
3939
spy.mockRestore()
4040
})
4141
})
42+
43+
describe('nestingLevels property assignment', () => {
44+
it('should be set to -1 if nestingLevels option cannot be converted to an integer', () => {
45+
[null, undefined, NaN, '', 'foo'].forEach(nestingLevels => {
46+
const ns = initDataDrivenList({ nestingLevels })
47+
expect(ns.nestingLevels).toBe(-1)
48+
})
49+
})
50+
51+
it('should be set to the integer equivalent of the nestingLevels option if it is greater than or equal to -1', () => {
52+
['-6', -4, '-1', -1, 0, '0', 1, '1', 37, '114'].forEach(nestingLevels => {
53+
const ns = initDataDrivenList({ nestingLevels })
54+
expect(ns.nestingLevels).toBe(parseInt(nestingLevels))
55+
})
56+
})
57+
})
4258
})
4359

4460
describe('How it deals with List Class Names', () => {
@@ -753,14 +769,15 @@ describe('NestedSort', () => {
753769

754770
describe('when cursorIsIndentedEnough() returns true and mouseIsTooCloseToTop() returns false', () => {
755771
it('should return en empty array if targetedNode is the same as draggedNode', () => {
772+
jest.spyOn(NestedSort.prototype, 'areNested').mockReturnValue(false)
773+
jest.spyOn(NestedSort.prototype, 'cursorIsIndentedEnough').mockReturnValue(true)
774+
jest.spyOn(NestedSort.prototype, 'mouseIsTooCloseToTop').mockReturnValue(false)
775+
jest.spyOn(NestedSort.prototype, 'nestingThresholdReached').mockReturnValue(false)
756776
const ns = initDataDrivenList()
757777

758778
// to bypass the early return
759779
ns.targetedNode = document.querySelector('li[data-id="1"]')
760-
ns.areNested = jest.fn().mockReturnValue(false)
761780

762-
ns.cursorIsIndentedEnough = jest.fn().mockReturnValue(true)
763-
ns.mouseIsTooCloseToTop = jest.fn().mockReturnValue(false)
764781
ns.draggedNode = ns.targetedNode
765782

766783
const actions = ns.analysePlaceHolderSituation()
@@ -769,14 +786,15 @@ describe('NestedSort', () => {
769786
})
770787

771788
it('should return en empty array if targetedNode name is not LI', () => {
789+
jest.spyOn(NestedSort.prototype, 'areNested').mockReturnValue(false)
790+
jest.spyOn(NestedSort.prototype, 'cursorIsIndentedEnough').mockReturnValue(true)
791+
jest.spyOn(NestedSort.prototype, 'mouseIsTooCloseToTop').mockReturnValue(false)
792+
jest.spyOn(NestedSort.prototype, 'nestingThresholdReached').mockReturnValue(false)
772793
const ns = initDataDrivenList()
773794

774795
// to bypass the early return
775796
ns.targetedNode = document.createElement('ul')
776-
ns.areNested = jest.fn().mockReturnValue(false)
777797

778-
ns.cursorIsIndentedEnough = jest.fn().mockReturnValue(true)
779-
ns.mouseIsTooCloseToTop = jest.fn().mockReturnValue(false)
780798
ns.draggedNode = document.querySelector('li[data-id="2"]')
781799

782800
const actions = ns.analysePlaceHolderSituation()
@@ -785,14 +803,15 @@ describe('NestedSort', () => {
785803
})
786804

787805
it('should return en empty array if targetedNode contains a ul element', () => {
806+
jest.spyOn(NestedSort.prototype, 'areNested').mockReturnValue(false)
807+
jest.spyOn(NestedSort.prototype, 'cursorIsIndentedEnough').mockReturnValue(true)
808+
jest.spyOn(NestedSort.prototype, 'mouseIsTooCloseToTop').mockReturnValue(false)
809+
jest.spyOn(NestedSort.prototype, 'nestingThresholdReached').mockReturnValue(false)
788810
const ns = initDataDrivenList()
789811

790812
// to bypass the early return
791813
ns.targetedNode = document.querySelector('li[data-id="1"]')
792-
ns.areNested = jest.fn().mockReturnValue(false)
793814

794-
ns.cursorIsIndentedEnough = jest.fn().mockReturnValue(true)
795-
ns.mouseIsTooCloseToTop = jest.fn().mockReturnValue(false)
796815
ns.draggedNode = document.querySelector('li[data-id="2"]')
797816
ns.targetedNode.appendChild(document.createElement('ul'))
798817

@@ -801,15 +820,33 @@ describe('NestedSort', () => {
801820
expect(actions).toEqual([])
802821
})
803822

823+
it('should return en empty array if nestingThresholdReached() returns true', () => {
824+
jest.spyOn(NestedSort.prototype, 'areNested').mockReturnValue(false)
825+
jest.spyOn(NestedSort.prototype, 'cursorIsIndentedEnough').mockReturnValue(true)
826+
jest.spyOn(NestedSort.prototype, 'mouseIsTooCloseToTop').mockReturnValue(false)
827+
jest.spyOn(NestedSort.prototype, 'nestingThresholdReached').mockReturnValue(true)
828+
const ns = initDataDrivenList()
829+
830+
// to bypass the early return
831+
ns.targetedNode = document.querySelector('li[data-id="1"]')
832+
833+
ns.draggedNode = document.querySelector('li[data-id="2"]')
834+
835+
const actions = ns.analysePlaceHolderSituation()
836+
837+
expect(actions).toEqual([])
838+
})
839+
804840
it('should return en array with `add` as its only item when all conditions meet', () => {
841+
jest.spyOn(NestedSort.prototype, 'areNested').mockReturnValue(false)
842+
jest.spyOn(NestedSort.prototype, 'cursorIsIndentedEnough').mockReturnValue(true)
843+
jest.spyOn(NestedSort.prototype, 'mouseIsTooCloseToTop').mockReturnValue(false)
844+
jest.spyOn(NestedSort.prototype, 'nestingThresholdReached').mockReturnValue(false)
805845
const ns = initDataDrivenList()
806846

807847
// to bypass the early return
808848
ns.targetedNode = document.querySelector('li[data-id="1"]')
809-
ns.areNested = jest.fn().mockReturnValue(false)
810849

811-
ns.cursorIsIndentedEnough = jest.fn().mockReturnValue(true)
812-
ns.mouseIsTooCloseToTop = jest.fn().mockReturnValue(false)
813850
ns.draggedNode = document.querySelector('li[data-id="2"]')
814851

815852
const actions = ns.analysePlaceHolderSituation()
@@ -1113,4 +1150,81 @@ describe('NestedSort', () => {
11131150
expect(Object.values(mainList.classList)).not.toContain('nested-sort--enabled')
11141151
})
11151152
})
1153+
1154+
describe('getTargetedElementDepth method', () => {
1155+
it('should return the correct depth of the targeted element', () => {
1156+
const ns = initDataDrivenList({
1157+
data: [
1158+
{id: 1, text: '1'},
1159+
{id: 11, text: '1-1', parent: 1},
1160+
{id: 111, text: '1-1-1', parent: 11},
1161+
{id: 1111, text: '1-1-1-1', parent: 111},
1162+
{id: 11111, text: '1-1-1-1-1', parent: 1111},
1163+
],
1164+
});
1165+
1166+
[1, 11, 111, 1111, 11111].forEach(id => {
1167+
ns.targetedNode = document.querySelector(`[data-id="${id}"]`)
1168+
const depth = ns.getTargetedNodeDepth()
1169+
expect(depth).toBe(id.toString().split('').length - 1)
1170+
})
1171+
})
1172+
})
1173+
1174+
describe('nestingThresholdReached method', () => {
1175+
it('should return false if nesting levels equals a negative integer', () => {
1176+
const ns = initDataDrivenList({ nestingLevels: -1 })
1177+
const result = ns.nestingThresholdReached()
1178+
1179+
expect(result).toBe(false)
1180+
})
1181+
1182+
it('should return true if nesting levels equals 0', () => {
1183+
const ns = initDataDrivenList({ nestingLevels: 0 })
1184+
const result = ns.nestingThresholdReached()
1185+
1186+
expect(result).toBe(true)
1187+
})
1188+
1189+
it('should return false if getTargetedNodeDepth() returns a value less than the nesting levels', () => {
1190+
[
1191+
{nestingLevels: '2', targetedNodeDepth: 1},
1192+
{nestingLevels: '3', targetedNodeDepth: 1},
1193+
{nestingLevels: '3', targetedNodeDepth: 2},
1194+
{nestingLevels: '11', targetedNodeDepth: 10},
1195+
].forEach(({nestingLevels, targetedNodeDepth}) => {
1196+
const ns = initDataDrivenList({ nestingLevels })
1197+
const spy = jest.spyOn(ns, 'getTargetedNodeDepth').mockReturnValue(targetedNodeDepth)
1198+
const result = ns.nestingThresholdReached()
1199+
1200+
expect(spy).toHaveBeenCalledTimes(1)
1201+
expect(result).toBe(false)
1202+
})
1203+
})
1204+
1205+
it('should return true if getTargetedNodeDepth() returns a value greater than the nesting levels', () => {
1206+
[
1207+
{nestingLevels: '1', targetedNodeDepth: 2},
1208+
{nestingLevels: '1', targetedNodeDepth: 3},
1209+
{nestingLevels: '3', targetedNodeDepth: 4},
1210+
{nestingLevels: '11', targetedNodeDepth: 13},
1211+
].forEach(({nestingLevels, targetedNodeDepth}) => {
1212+
const ns = initDataDrivenList({ nestingLevels })
1213+
const spy = jest.spyOn(ns, 'getTargetedNodeDepth').mockReturnValue(targetedNodeDepth)
1214+
const result = ns.nestingThresholdReached()
1215+
1216+
expect(spy).toHaveBeenCalledTimes(1)
1217+
expect(result).toBe(true)
1218+
})
1219+
})
1220+
1221+
it('should return true if getTargetedNodeDepth() returns a value equal to the nesting levels', () => {
1222+
const ns = initDataDrivenList({ nestingLevels: '2' })
1223+
const spy = jest.spyOn(ns, 'getTargetedNodeDepth').mockReturnValue(2)
1224+
const result = ns.nestingThresholdReached()
1225+
1226+
expect(spy).toHaveBeenCalledTimes(1)
1227+
expect(result).toBe(true)
1228+
})
1229+
})
11161230
})

0 commit comments

Comments
 (0)