diff --git a/src/js/utils/linked-item.js b/src/js/utils/linked-item.js new file mode 100644 index 000000000..923ff20ab --- /dev/null +++ b/src/js/utils/linked-item.js @@ -0,0 +1,6 @@ +export default class LinkedItem { + constructor() { + this.next = null; + this.prev = null; + } +} diff --git a/src/js/utils/linked-list.js b/src/js/utils/linked-list.js new file mode 100644 index 000000000..6c861f27a --- /dev/null +++ b/src/js/utils/linked-list.js @@ -0,0 +1,73 @@ +export default class LinkedList { + constructor() { + this.head = null; + this.tail = null; + } + prepend(item) { + this.insertBefore(item, this.head); + } + append(item) { + this.insertBefore(item, null); + } + insertAfter(item, prevItem) { + let nextItem = null; + if (prevItem) { + nextItem = prevItem.next; + } + this.insertBefore(item, nextItem); + } + insertBefore(item, nextItem) { + this.remove(item); + if (nextItem && nextItem.prev) { + // middle of the items + let prevItem = nextItem.prev; + item.next = nextItem; + nextItem.prev = item; + item.prev = prevItem; + prevItem.next = item; + } else if (nextItem) { + // first item + if (this.head === nextItem) { + item.next = nextItem; + nextItem.prev = item; + } else { + this.tail = item; + } + this.head = item; + } else { + // last item + if (this.tail) { + item.prev = this.tail; + this.tail.next = item; + } + if (!this.head) { + this.head = item; + } + this.tail = item; + } + } + remove(item) { + if (item.next && item.prev) { + // Middle of the list + item.next.prev = item.prev; + item.prev.next = item.next; + } else { + if (item === this.head) { + // Head of the list + if (item.next) { + item.next.prev = null; + } + this.head = item.next; + } + if (item === this.tail) { + // Tail of the list + if (item.prev) { + item.prev.next = null; + } + this.tail = item.prev; + } + } + item.prev = null; + item.next = null; + } +} diff --git a/tests/unit/utils/linked-list-test.js b/tests/unit/utils/linked-list-test.js new file mode 100644 index 000000000..9b8e27041 --- /dev/null +++ b/tests/unit/utils/linked-list-test.js @@ -0,0 +1,148 @@ +const {module, test} = QUnit; + +import LinkedList from 'content-kit-editor/utils/linked-list'; +import LinkedItem from 'content-kit-editor/utils/linked-item'; + +module('Unit: Utils: LinkedList'); + +test('initial state', (assert) => { + let list = new LinkedList(); + assert.equal(list.head, null, 'head is null'); + assert.equal(list.tail, null ,'tail is null'); +}); + +['append', 'prepend', 'insertBefore', 'insertAfter'].forEach(method => { + test(`#${method} initial item`, (assert) => { + let list = new LinkedList(); + let item = new LinkedItem(); + list[method](item); + assert.equal(list.head, item, 'head is item'); + assert.equal(list.tail, item, 'tail is item'); + assert.equal(item.next, null, 'item next is null'); + assert.equal(item.prev, null, 'item prev is null'); + }); +}); + +test(`#append second item`, (assert) => { + let list = new LinkedList(); + let itemOne = new LinkedItem(); + let itemTwo = new LinkedItem(); + list.append(itemOne); + list.append(itemTwo); + assert.equal(list.head, itemOne, 'head is itemOne'); + assert.equal(list.tail, itemTwo, 'tail is itemTwo'); + assert.equal(itemOne.prev, null, 'itemOne prev is null'); + assert.equal(itemOne.next, itemTwo, 'itemOne next is itemTwo'); + assert.equal(itemTwo.prev, itemOne, 'itemTwo prev is itemOne'); + assert.equal(itemTwo.next, null, 'itemTwo next is null'); +}); + +test(`#prepend first item`, (assert) => { + let list = new LinkedList(); + let itemOne = new LinkedItem(); + let itemTwo = new LinkedItem(); + list.prepend(itemTwo); + list.prepend(itemOne); + assert.equal(list.head, itemOne, 'head is itemOne'); + assert.equal(list.tail, itemTwo, 'tail is itemTwo'); + assert.equal(itemOne.prev, null, 'itemOne prev is null'); + assert.equal(itemOne.next, itemTwo, 'itemOne next is itemTwo'); + assert.equal(itemTwo.prev, itemOne, 'itemTwo prev is itemOne'); + assert.equal(itemTwo.next, null, 'itemTwo next is null'); +}); + +test(`#insertBefore a middle item`, (assert) => { + let list = new LinkedList(); + let itemOne = new LinkedItem(); + let itemTwo = new LinkedItem(); + let itemThree = new LinkedItem(); + list.prepend(itemOne); + list.append(itemThree); + list.insertBefore(itemTwo, itemThree); + assert.equal(list.head, itemOne, 'head is itemOne'); + assert.equal(list.tail, itemThree, 'tail is itemThree'); + assert.equal(itemOne.prev, null, 'itemOne prev is null'); + assert.equal(itemOne.next, itemTwo, 'itemOne next is itemTwo'); + assert.equal(itemTwo.prev, itemOne, 'itemTwo prev is itemOne'); + assert.equal(itemTwo.next, itemThree, 'itemTwo next is itemThree'); + assert.equal(itemThree.prev, itemTwo, 'itemThree prev is itemTwo'); + assert.equal(itemThree.next, null, 'itemThree next is null'); +}); + +test(`#insertAfter a middle item`, (assert) => { + let list = new LinkedList(); + let itemOne = new LinkedItem(); + let itemTwo = new LinkedItem(); + let itemThree = new LinkedItem(); + list.prepend(itemOne); + list.append(itemThree); + list.insertAfter(itemTwo, itemOne); + assert.equal(list.head, itemOne, 'head is itemOne'); + assert.equal(list.tail, itemThree, 'tail is itemThree'); + assert.equal(itemOne.prev, null, 'itemOne prev is null'); + assert.equal(itemOne.next, itemTwo, 'itemOne next is itemTwo'); + assert.equal(itemTwo.prev, itemOne, 'itemTwo prev is itemOne'); + assert.equal(itemTwo.next, itemThree, 'itemTwo next is itemThree'); + assert.equal(itemThree.prev, itemTwo, 'itemThree prev is itemTwo'); + assert.equal(itemThree.next, null, 'itemThree next is null'); +}); + +test(`#remove an only item`, (assert) => { + let list = new LinkedList(); + let item = new LinkedItem(); + list.append(item); + list.remove(item); + assert.equal(list.head, null, 'head is null'); + assert.equal(list.tail, null, 'tail is null'); + assert.equal(item.prev, null, 'item prev is null'); + assert.equal(item.next, null, 'item next is null'); +}); + +test(`#remove a first item`, (assert) => { + let list = new LinkedList(); + let itemOne = new LinkedItem(); + let itemTwo = new LinkedItem(); + list.append(itemOne); + list.append(itemTwo); + list.remove(itemOne); + assert.equal(list.head, itemTwo, 'head is itemTwo'); + assert.equal(list.tail, itemTwo, 'tail is itemTwo'); + assert.equal(itemOne.prev, null, 'itemOne prev is null'); + assert.equal(itemOne.next, null, 'itemOne next is null'); + assert.equal(itemTwo.prev, null, 'itemTwo prev is null'); + assert.equal(itemTwo.next, null, 'itemTwo next is null'); +}); + +test(`#remove a second item`, (assert) => { + let list = new LinkedList(); + let itemOne = new LinkedItem(); + let itemTwo = new LinkedItem(); + list.append(itemOne); + list.append(itemTwo); + list.remove(itemTwo); + assert.equal(list.head, itemOne, 'head is itemOne'); + assert.equal(list.tail, itemOne, 'tail is itemOne'); + assert.equal(itemOne.prev, null, 'itemOne prev is null'); + assert.equal(itemOne.next, null, 'itemOne next is null'); + assert.equal(itemTwo.prev, null, 'itemTwo prev is null'); + assert.equal(itemTwo.next, null, 'itemTwo next is null'); +}); + +test(`#remove a middle item`, (assert) => { + let list = new LinkedList(); + let itemOne = new LinkedItem(); + let itemTwo = new LinkedItem(); + let itemThree = new LinkedItem(); + list.append(itemOne); + list.append(itemTwo); + list.append(itemThree); + list.remove(itemTwo); + assert.equal(list.head, itemOne, 'head is itemOne'); + assert.equal(list.tail, itemThree, 'tail is itemThree'); + assert.equal(itemOne.prev, null, 'itemOne prev is null'); + assert.equal(itemOne.next, itemThree, 'itemOne next is itemThree'); + assert.equal(itemTwo.prev, null, 'itemTwo prev is null'); + assert.equal(itemTwo.next, null, 'itemTwo next is null'); + assert.equal(itemThree.prev, itemOne, 'itemThree prev is itemOne'); + assert.equal(itemThree.next, null, 'itemThree next is null'); +});