diff --git a/docs/README.md b/docs/README.md index 49f3742..5fb705b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -24,11 +24,3 @@ list.push("lorem"); - [Working with other Data objects](methods/with-other-objects.md) - [Searching and Filtering](methods/searching-filtering.md) - [Transforming Lists](methods/transforming-lists.md) - -## Planned Features: - -- Support for asynchronous callback functions -- New Methods: - - `findNode` - - `values` (similar to entries) -- constructor that takes any iterable (replaces `List.fromArray`) diff --git a/docs/methods/adding-removing.md b/docs/methods/adding-removing.md index 30bceb8..8bafefa 100644 --- a/docs/methods/adding-removing.md +++ b/docs/methods/adding-removing.md @@ -16,7 +16,7 @@ list.toString(); // "lorem,ipsum,dolor" Removes the last element from the List and returns it. If the List is empty `undefined` is returned. ```js -const list = List.fromArray(["lorem", "ipsum"]); +const list = new List(["lorem", "ipsum"]); list.pop(); // "ipsum" list.pop(); // "lorem" @@ -28,7 +28,7 @@ list.pop(); // undefined Removes the first element from the List and returns it. If the List is empty `undefined` is returned. ```js -const list = List.fromArray(["lorem", "ipsum"]); +const list = new List(["lorem", "ipsum"]); list.shift(); // "lorem" list.shift(); // "ipsum" @@ -46,16 +46,6 @@ list.unshift("ipsum").unshift("dolor"); list.toString(); // "dolor,ipsum,lorem" ``` -## `getNode` - -Gets `ListNode` at specific index. If the index is outside of the range of the List `undefined` is returned. - -```js -const list = new List(); -list.pop("lorem"); -list.getNode(0); // ListNode { value: "lorem" } -``` - ## `get` Gets value at specific index. If the index is outside of the range of the List `undefined` is returned. @@ -85,7 +75,7 @@ Inserts value at index and returns `true`. If the index is outside of the range If the index would correspond to the next new element this method acts as an alias to `push`. ```js -const list = List.fromArray(["lorem", "ipsum"]); +const list = new List(["lorem", "ipsum"]); list.insert(1, "foo"); list.insert(4, "bar"); list.toString(); // "lorem,foo,ipsum,bar" @@ -97,7 +87,17 @@ Removes one or more elements starting at a given index and returns `true`. If the index is outside the range of the List or a amount smaller than one is given `false` is returned. ```js -const list = List.fromArray("foobazzbar".split("")); +const list = new List("foobazzbar".split("")); list.remove(3, 4); list.join(""); // "foobar" ``` + +## `insertMany` + +Inserts all values from an Iterable into List at a given index. Further methods can be chained after this method. + +```js +const list = new List([0, 1, 2]); +list.insertMany(1, [0.5, 0.75]); +list.join(", "); // "0, 0.5, 0.75, 1, 2" +``` diff --git a/docs/methods/searching-filtering.md b/docs/methods/searching-filtering.md index aa4cb7f..f4245cb 100644 --- a/docs/methods/searching-filtering.md +++ b/docs/methods/searching-filtering.md @@ -46,7 +46,7 @@ list.some(v => v.endsWith("foo")); // false Creates new list with only the values that the test callback returns `true` for. ```js -const list = List.fromArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); +const list = new List([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); const filtered = list.filter(v => v % 2 == 0); filtered.toString(); // "0,2,4,6,8" ``` diff --git a/docs/methods/transforming-lists.md b/docs/methods/transforming-lists.md index 3fb2c1a..1cac23e 100644 --- a/docs/methods/transforming-lists.md +++ b/docs/methods/transforming-lists.md @@ -102,7 +102,7 @@ Creates a new List that contains a slice of value from the original List startin Values are copied to the new List, meaning that modifying either List will not manipulate the other! ```js -const list = List.fromArray(["foo", "bar", "baz", "foobar", "lorem", "ipsum"]); +const list = new List(["foo", "bar", "baz", "foobar", "lorem", "ipsum"]); list.slice(2, 5).toString(); // "baz,foobar,lorem" list.slice(-3, 6).toString(); // "foobar,lorem,ipsum" list.slice(1, -1).toString(); // "bar,baz,foobar,lorem" @@ -115,7 +115,7 @@ Creates a new List with all values sorted by a comparison callback function. The If no callback is passed values are sorted in ascending ASCII character order. ```js -const list = List.fromArray([0, 7, 11, 8, 9, 15, -3]); +const list = new List([0, 7, 11, 8, 9, 15, -3]); const sorted = list.sort((a, b) => a - b); sorted.toString(); // -3,0,7,8,9,11,15 ``` diff --git a/docs/methods/with-other-objects.md b/docs/methods/with-other-objects.md index e081bdf..6b3bd59 100644 --- a/docs/methods/with-other-objects.md +++ b/docs/methods/with-other-objects.md @@ -10,29 +10,6 @@ list.push("ipsum").push("dolor").unshift("lorem"); const arr = list.toArray(); // ["lorem", "ipsum", "dolor"] ``` -## `insertArray` - -Inserts all values from an Array into List at a given index and returns `true`. -If the index is outside of the range of the List `false` is returned. - -```js -const list = List.fromArray([0, 1, 2]); -list.insertArray(1, [0.5, 0.75]); -list.join(", "); // "0, 0.5, 0.75, 1, 2" -``` - -## `insertList` - -Inserts all values from another List into List at a given index and returns `true`. -If the index is outside of the range of the List `false` is returned. - -```js -const listA = List.fromArray([0, 1, 2]); -const listB = List.fromArray("foo".split("")); -listA.insertList(2, listB); -listA.toString(); // "0,1,f,o,o,2" -``` - ## `clone` Creates a copy of the current List @@ -53,16 +30,3 @@ const newList = list.concat(["baz", "foobar"]); list.toString(); // "foo,bar" newList.toString(); // "foo,bar,baz,foobar" ``` - -## `entries` - -Creates iterable of index, value pairs for every entry in the List. - -```js -const list = List.fromArray(["foo", "bar"]); -for (const [i, v] of list.entries()) { - console.log(`Index: ${i}, Value: ${v}`); - // Index: 0, Value: foo - // Index: 1, Value: bar -} -``` diff --git a/docs/static-methods.md b/docs/static-methods.md index bd8cb87..bb4b956 100644 --- a/docs/static-methods.md +++ b/docs/static-methods.md @@ -1,13 +1,5 @@ # Static Methods -## `fromArray` - -Creates a new List from any Array. This method has been deprecated in v1.2.0 and will be removed in a future version. Use the new constructor instead (see [Creating a List](./README.md#creating-a-list))! - -```js -const list = List.fromArray([1, 2, 3]); -``` - ## `isList` Checks if an object (or any other value) is a List diff --git a/src/index.ts b/src/index.ts index 865f7dd..67d3d95 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,21 +1,4 @@ -class ListNode { - /** - * Next node in the linked list - */ - next: ListNode | null = null; - /** - * Previous node in the linked list - */ - prev: ListNode | null = null; - /** - * Current value of this node - */ - value: T; - - constructor(value: T) { - this.value = value; - } -} +import { ListNode } from "./node"; export type { ListNode }; @@ -30,8 +13,8 @@ export type ListTest = ( ) => boolean; export class List { - #head: ListNode | null = null; - #tail: ListNode | null = null; + #head?: ListNode; + #tail?: ListNode; #length: number = 0; constructor(from?: Iterable) { @@ -39,17 +22,17 @@ export class List { // spawn iterator const iterator = from[Symbol.iterator](); - let iterable = iterator.next(); - if (iterable.done) return; + let result = iterator.next(); + if (result.done) return; // prepare first node - const head = new ListNode(iterable.value); + const head = new ListNode(result.value); let prev = head; let length = 1; // iterate and create list - while (!(iterable = iterator.next()).done) { - const node = new ListNode(iterable.value); + while (!(result = iterator.next()).done) { + const node = new ListNode(result.value); node.prev = prev; prev.next = node; prev = node; @@ -62,21 +45,6 @@ export class List { this.#length = length; } - /** - * Creates new List from Array. This method has been deprecated in v1.2.0 and - * will be removed in a future version. Use the new constructor instead! - * @param arr Array to turn into List - * @returns List - * @deprecated - */ - static fromArray(arr: T[] | readonly T[]): List { - const list = new List(); - for (let i = 0; i < arr.length; i++) { - list.push(arr[i]); - } - return list; - } - /** * Checks if an object (or any other value) is a List * @param list Object that may be a list @@ -116,7 +84,7 @@ export class List { */ push(value: T) { const node = new ListNode(value); - if (this.#tail == null) { + if (!this.#tail) { this.#head = node; } else { this.#tail.next = node; @@ -133,14 +101,14 @@ export class List { * @returns Value or `undefined` */ pop() { - if (this.#tail == null) return undefined; + if (!this.#tail) return; const node = this.#tail; const prev = node.prev; if (!prev) { - this.#head = null; + this.#head = undefined; } else { - prev.next = null; + prev.next = undefined; } this.#tail = prev; @@ -155,14 +123,14 @@ export class List { */ shift() { const node = this.#head; - if (!node) return undefined; + if (!node) return; const next = node.next; - node.next = null; + node.next = undefined; if (!next) { - this.#tail = null; + this.#tail = undefined; } else { - next.prev = null; + next.prev = undefined; } this.#head = next; @@ -195,10 +163,10 @@ export class List { * @param n Index of the element * @returns `ListNode` or `undefined` */ - getNode(n: number) { - if (n < 0 || n >= this.#length) return undefined; + private getNode(n: number) { + if (n < 0 || n >= this.#length) return; const mid = this.#length / 2; - let curr: ListNode | null; + let curr: ListNode | undefined; if (n < mid) { curr = this.#head; @@ -214,7 +182,7 @@ export class List { } } - return curr || undefined; + return curr; } /** @@ -296,8 +264,8 @@ export class List { for (let i = 0; i < amount; i++) { if (!curr) return true; const { prev, next } = curr; - curr.next = null; - curr.prev = null; + curr.next = undefined; + curr.prev = undefined; this.#length--; if (prev) { @@ -324,120 +292,62 @@ export class List { * @returns Array */ toArray(): T[] { - const arr = new Array(); - - let curr = this.#head; - while (curr) { - arr.push(curr.value); - curr = curr.next; - } - - return arr; + return Array.from(this); } /** - * Utility method for insertArray and insertList that connects a lose list - * of already connected nodes into the existing list at a given index - * @param index index at which to insert - * @param first first inserted node - * @param last last inserted node - * @returns void + * Inserts all values from another iterable (List, Array, etc.) into List at a + * given index. If the index is out of range values will be inserted at the + * start or end of the List as applicable. + * @param index + * @param iterable + * @returns `this` Reference */ - private connectLose(index: number, first: ListNode, last: ListNode) { - switch (index) { - case 0: { - const oldHead = this.#head; - if (oldHead) { - last.next = oldHead; - oldHead.prev = last; - } else { - this.#tail = last; - } - this.#head = first; - break; - } - - case this.length: { - const oldTail = this.#tail; - if (oldTail) { - oldTail.next = first; - first.prev = oldTail; - } else { - this.#head = first; - } - this.#tail = last; - break; - } - - default: { - const target = this.getNode(index); - if (!target) return false; + insertMany(index: number, iterable: Iterable) { + let prev: undefined | ListNode; // current node in loop + let next: undefined | ListNode; // node after inserted + + // get nodes before and after inserted values if applicable + if (index <= 0) { + prev = undefined; + next = this.#head; + this.#head = undefined; + } else if (index >= this.length) { + prev = this.#tail; + next = undefined; + } else { + next = this.getNode(index); + prev = next?.prev; + } - const prev = target.prev; - first.prev = prev; - if (prev) prev.next = first; + // append to list + for (const val of iterable) { + const node = new ListNode(val); - last.next = target; - target.prev = last; + // connect to previous element, if none this is the new first list node + if (prev) { + node.prev = prev; + prev.next = node; + } else { + this.#head = node; } - } - } - /** - * Inserts all values from an Array into List at a given index and - * returns `true`. If the index is outside of the range of the List - * `false` is returned. - * @param index Index at which to start insertion - * @param arr Array of Values - * @returns boolean - */ - insertArray(index: number, arr: T[] | readonly T[]): boolean { - if (index < 0 || index > this.length) return false; - if (arr.length < 1) return true; - - // create new list - const firstInserted = new ListNode(arr[0]); - let currentInserted = firstInserted; - for (let i = 1; i < arr.length; i++) { - const node = new ListNode(arr[i]); - currentInserted.next = node; - node.prev = currentInserted; - currentInserted = node; + prev = node; + this.#length++; } - this.connectLose(index, firstInserted, currentInserted); - this.#length += arr.length; - return true; - } - - /** - * Inserts all values from another List into List at a given index and - * returns `true`. If the index is outside of the range of the List `false` is - * returned. - * @param index Index at which to start insertion - * @param list List from which to take values - * @returns boolean - */ - insertList(index: number, list: List): boolean { - if (index < 0 || index > this.length || !List.isList(list)) return false; - const listHead = list.getNode(0); - if (!listHead) return true; // list.length == 0 - - // create new list - const firstInserted = new ListNode(listHead.value); - let currentInserted = firstInserted; - let iter = listHead.next; - while (iter) { - const node = new ListNode(iter.value); - currentInserted.next = node; - node.prev = currentInserted; - currentInserted = node; - iter = iter.next; + // connect to node after inserted values if applicable, + // or update tail otherwise as there are no values past this + if (prev) { + if (next) { + next.prev = prev; + prev.next = next; + } else { + this.#tail = prev; + } } - this.connectLose(index, firstInserted, currentInserted); - this.#length += list.length; - return true; + return this; } /** @@ -445,12 +355,7 @@ export class List { * @returns new List */ clone(): List { - const newList = new List(); - let curr = this.#head; - while (curr) { - newList.push(curr.value); - curr = curr.next; - } + const newList = new List(this); return newList; } @@ -459,25 +364,12 @@ export class List { * @param value List or Array of Values * @returns new List */ - concat(value: List | Array | readonly T[]): List { + concat(value: Iterable): List { const newList = this.clone(); - if (List.isList(value)) { - newList.insertList(newList.length, value); - } else { - newList.insertArray(newList.length, value); - } + newList.insertMany(newList.length, value); return newList; } - /** - * Creates iterable of index, value pairs for every entry in the List. - * @returns IterableIterator - */ - // not tested as this is basically a proxy of the existing Array method - entries() { - return this.toArray().entries(); - } - /** * Checks if a test callback returns `true` for every value * @param callback Test callback @@ -709,7 +601,7 @@ export class List { (end ?? 0) < 0 ? Math.max(0, this.#length + (end ?? 0)) : (end ?? this.#length); - let curr: ListNode | null | undefined = this.getNode(startAt); + let curr: ListNode | undefined = this.getNode(startAt); let index = startAt; while (curr && index < endAt) { @@ -732,7 +624,7 @@ export class List { */ sort(callback?: (a: T, b: T) => number): List { const arr = this.toArray().sort(callback); - return List.fromArray(arr); + return new List(arr); } /** @@ -764,4 +656,35 @@ export class List { toString() { return this.join(","); } + + *[Symbol.iterator]() { + const seenNodes = new Set>(); + let curr = this.#head; + let idx = 0; + + while (idx < this.length) { + if (curr) { + yield curr.value; + seenNodes.add(curr); + curr = curr.next; + idx++; + continue; + } + + if (idx >= this.length) return; + + // find current element assuming list got modified + let temp = this.#head; + let i = 0; + while (temp && i < idx) { + temp = temp.next; + i++; + } + + if (temp && i == idx) { + curr = temp; + continue; + } + } + } } diff --git a/src/node.ts b/src/node.ts new file mode 100644 index 0000000..187d7a9 --- /dev/null +++ b/src/node.ts @@ -0,0 +1,18 @@ +export class ListNode { + /** + * Next node in the linked list + */ + next?: ListNode; + /** + * Previous node in the linked list + */ + prev?: ListNode; + /** + * Current value of this node + */ + value: T; + + constructor(value: T) { + this.value = value; + } +} diff --git a/tests/getNode.test.ts b/tests/getNode.test.ts index 3463160..6e6701a 100644 --- a/tests/getNode.test.ts +++ b/tests/getNode.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/dot-notation */ import { test } from "uvu"; import * as assert from "uvu/assert"; import { List } from "../src"; @@ -5,26 +6,26 @@ import { lorem } from "./utils/lorem"; test("Gets undefined from empty list", () => { const list = new List(); - assert.is(list.getNode(0), undefined, "return undefined for empty list"); + assert.is(list["getNode"](0), undefined, "return undefined for empty list"); }); test("Gets undefined for numbers outside of List's range", () => { const list = new List(lorem); assert.is( - list.getNode(-1), + list["getNode"](-1), undefined, "reject negative number with undefined" ); assert.is( - list.getNode(7), + list["getNode"](7), undefined, "reject number equal to length with undefined" ); assert.is( - list.getNode(8), + list["getNode"](8), undefined, "reject number greater than length with undefined" ); @@ -35,7 +36,7 @@ test("Gets correct value for valid index", () => { for (let i = 0; i < lorem.length; i++) { assert.is( - list.getNode(i)?.value, + list["getNode"](i)?.value, lorem[i], `get Node from list for index ${i}` ); diff --git a/tests/insertArray.test.ts b/tests/insertArray.test.ts deleted file mode 100644 index 307a391..0000000 --- a/tests/insertArray.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { test } from "uvu"; -import * as assert from "uvu/assert"; -import { List } from "../src"; -import { assertValidList } from "./utils/assertValidList"; -import { lorem } from "./utils/lorem"; - -test("rejects negative index", () => { - const list = new List(); - assert.not(list.insertArray(-7, lorem), "reject negative index with false"); -}); - -test("can insert at the end of the list", () => { - const list = new List(["first", "second"]); - assert.ok(list.insertArray(2, lorem), "inserted array at end of list"); - const expected = ["first", "second", ...lorem]; - for (let i = 0; i < expected.length; i++) { - assert.is( - list.get(i), - expected[i], - `inserted array at end of list (index: ${i})` - ); - } - assert.is(list.length, expected.length, "updated length after insertion"); - assert.is(list.tail, expected[expected.length - 1], "updated tail correctly"); - assertValidList(list); -}); - -test("can insert at the start of the list", () => { - const list = new List(["second-to-last", "last"]); - assert.ok(list.insertArray(0, lorem), "inserted array at start of list"); - const expected = [...lorem, "second-to-last", "last"]; - for (let i = 0; i < expected.length; i++) { - assert.is( - list.get(i), - expected[i], - `inserted array at start of list (index: ${i})` - ); - } - assert.is(list.length, expected.length, "updated length after insertion"); - assert.is(list.head, expected[0], "updated head correctly"); - assertValidList(list); -}); - -test("can insert in the middle of the list", () => { - const list = new List(["first", "second", "second-to-last", "last"]); - assert.ok(list.insertArray(2, lorem), "inserted array in middle of list"); - const expected = ["first", "second", ...lorem, "second-to-last", "last"]; - for (let i = 0; i < expected.length; i++) { - assert.is( - list.get(i), - expected[i], - `inserted array in middle of list (index: ${i})` - ); - } - assert.is(list.length, expected.length, "updated length after insertion"); - assertValidList(list); -}); - -test("rejects index that is too large", () => { - const list = new List(["first", "second", "second-to-last", "last"]); - assert.not( - list.insertArray(list.length + 1, lorem), - "reject index larger than length with false" - ); -}); - -test.run(); diff --git a/tests/insertList.test.ts b/tests/insertList.test.ts deleted file mode 100644 index 19fd2fc..0000000 --- a/tests/insertList.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { test } from "uvu"; -import * as assert from "uvu/assert"; -import { List } from "../src"; -import { assertValidList } from "./utils/assertValidList"; -import { lorem, loremList } from "./utils/lorem"; - -test("rejects negative index", () => { - const list = new List(); - assert.not( - list.insertList(-7, loremList), - "reject negative index with false" - ); -}); - -test("can insert at the end of the list", () => { - const list = new List(["first", "second"]); - assert.ok(list.insertList(2, loremList), "inserted list at the end"); - const expected = ["first", "second", ...lorem]; - for (let i = 0; i < expected.length; i++) { - assert.is( - list.get(i), - expected[i], - `inserted list at the end (index: ${i})` - ); - } - assert.is(list.length, expected.length, "updated length after insertion"); - assert.is(list.tail, expected[expected.length - 1], "updated tail correctly"); - assertValidList(list); -}); - -test("can insert at the start of the list", () => { - const list = new List(["second-to-last", "last"]); - assert.ok(list.insertList(0, loremList), "inserted at start of list"); - const expected = [...lorem, "second-to-last", "last"]; - for (let i = 0; i < expected.length; i++) { - assert.is( - list.get(i), - expected[i], - `inserted at start of list (index: ${i})` - ); - } - assert.is(list.length, expected.length, "updated length after insertion"); - assert.is(list.head, expected[0], "updated head correctly"); - assertValidList(list); -}); - -test("can insert in the middle of the list", () => { - const list = new List(["first", "second", "second-to-last", "last"]); - assert.ok(list.insertList(2, loremList), "inserted in middle of list"); - const expected = ["first", "second", ...lorem, "second-to-last", "last"]; - for (let i = 0; i < expected.length; i++) { - assert.is( - list.get(i), - expected[i], - `inserted in middle of list (index: ${i})` - ); - } - assert.is(list.length, expected.length, "updated length after insertion"); - assertValidList(list); -}); - -test("rejects index that is too large", () => { - const list = new List(["first", "second", "second-to-last", "last"]); - assert.not( - list.insertList(list.length + 1, loremList), - "reject index larger than length with false" - ); -}); - -test.run(); diff --git a/tests/insertMany.test.ts b/tests/insertMany.test.ts new file mode 100644 index 0000000..922667e --- /dev/null +++ b/tests/insertMany.test.ts @@ -0,0 +1,114 @@ +import { test } from "uvu"; +import * as assert from "uvu/assert"; +import { List } from "../src"; +import { assertValidList } from "./utils/assertValidList"; +import { lorem, loremList } from "./utils/lorem"; + +const elementsL = ["first", "second", "third"]; +const elementsR = ["second-to-last", "last"]; +const expected = elementsL.concat(elementsR); + +test("can insert at the end of the list", () => { + const list = new List(elementsL); + list.insertMany(3, elementsR); + assertValidList(list); + + for (let i = 0; i < expected.length; i++) { + assert.is( + list.get(i), + expected[i], + `inserted list at the end (index: ${i})` + ); + } + + assert.is(list.length, expected.length, "updated length after insertion"); + assert.is(list.tail, expected[expected.length - 1], "updated tail correctly"); +}); + +test("can insert at the start of the list", () => { + const list = new List(elementsR); + list.insertMany(0, elementsL); + assertValidList(list); + + for (let i = 0; i < expected.length; i++) { + assert.is( + list.get(i), + expected[i], + `inserted at start of list (index: ${i})` + ); + } + + assert.is(list.length, expected.length, "updated length after insertion"); + assert.is(list.head, expected[0], "updated head correctly"); +}); + +test("can insert in the middle of the list", () => { + const list = new List(["first", "second", "second-to-last", "last"]); + list.insertMany(2, loremList); + const expected = ["first", "second", ...lorem, "second-to-last", "last"]; + assertValidList(list); + + for (let i = 0; i < expected.length; i++) { + assert.is( + list.get(i), + expected[i], + `inserted in middle of list (index: ${i})` + ); + } + + assert.is(list.length, expected.length, "updated length after insertion"); +}); + +test("inserts at end of list if index too large", () => { + const list = new List(elementsL); + list.insertMany(list.length + 1, elementsR); + assertValidList(list); + + for (let i = 0; i < expected.length; i++) { + assert.is( + list.get(i), + expected[i], + `inserted list at the end (index: ${i})` + ); + } + + assert.is(list.length, expected.length, "updated length after insertion"); + assert.is(list.tail, expected[expected.length - 1], "updated tail correctly"); +}); + +test("inserts at start of list if index too low", () => { + const list = new List(elementsR); + list.insertMany(-7, elementsL); + assertValidList(list); + + for (let i = 0; i < expected.length; i++) { + assert.is( + list.get(i), + expected[i], + `inserted at start of list (index: ${i})` + ); + } + + assert.is(list.length, expected.length, "updated length after insertion"); + assert.is(list.head, expected[0], "updated head correctly"); +}); + +test("inserts into empty list correctly", () => { + const list = new List(); + list.insertMany(0, expected); + assertValidList(list); + + for (let i = 0; i < expected.length; i++) { + assert.is( + list.get(i), + expected[i], + `inserted at start of list (index: ${i})` + ); + } + + assert.is(list.length, expected.length, "updated length after insertion"); + assert.is(list.head, expected[0], "updated head correctly"); + assert.is(list.tail, expected[expected.length - 1], "updated tail correctly"); +}); + +test.run(); diff --git a/tests/remove.test.ts b/tests/remove.test.ts index 64d2f11..c2be56d 100644 --- a/tests/remove.test.ts +++ b/tests/remove.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/dot-notation */ import { test } from "uvu"; import * as assert from "uvu/assert"; import { List } from "../src"; @@ -24,13 +25,13 @@ test("can remove item after head", () => { assert.is(list.head, "lorem", "head unchanged"); assert.is(list.get(1), lorem[2], "removed item at index 1"); assert.is( - list.getNode(1)?.prev, - list.getNode(0), + list["getNode"](1)?.prev, + list["getNode"](0), "new item at index 1 points back to head" ); assert.is( - list.getNode(0)?.next, - list.getNode(1), + list["getNode"](0)?.next, + list["getNode"](1), "head updated to point to new item at index 1" ); assert.is(list.length, lorem.length - 1, "updated length"); @@ -43,8 +44,8 @@ test("can remove from start of list", () => { assert.ok(list.remove(0, 2), "remove two items from start of the list"); assert.is(list.head, lorem[2], "updated head"); assert.is( - list.getNode(0)?.prev, - null, + list["getNode"](0)?.prev, + undefined, "head does not point back at removed node" ); assert.is(list.length, lorem.length - 2, "updated length"); @@ -57,8 +58,8 @@ test("can remove from end of list", () => { assert.ok(list.remove(4, 10), "remove all element starting with index 4"); assert.is(list.tail, lorem[3], "updated tail"); assert.is( - list.getNode(3)?.next, - null, + list["getNode"](3)?.next, + undefined, "tail does not point forward to removed node" ); assert.is(list.length, 4, "updated length"); diff --git a/tests/symbol-iterator.test.ts b/tests/symbol-iterator.test.ts new file mode 100644 index 0000000..f0e5017 --- /dev/null +++ b/tests/symbol-iterator.test.ts @@ -0,0 +1,45 @@ +import { test } from "uvu"; +import * as assert from "uvu/assert"; +import { List } from "../src"; + +test("it iterates over basic list", () => { + const list = new List([1, 2, 3, 4, 5, 6, 7, 8, 9]); + let i = 1; + for (const val of list) { + assert.is(val, i); + i++; + } +}); + +test("it continues to iterate after splice", () => { + const subjectArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; + const subjectList = new List(subjectArray); + const outputArray = new Array(); + const outputList = new Array(); + + // array + for (const v of subjectArray) { + if (v == 4) subjectArray.splice(3, 3); + outputArray.push(v); + } + + // list + for (const v of subjectList) { + if (v == 4) subjectList.remove(3, 3); + outputList.push(v); + } + + try { + assert.equal(outputList, outputArray); + } catch (e) { + console.log({ + subjectArray: subjectArray.join(", "), + subjectList: subjectList.join(", "), + outputArray: outputArray.join(", "), + outputList: outputList.join(", ") + }); + throw e; + } +}); + +test.run(); diff --git a/tests/toArray.test.ts b/tests/toArray.test.ts index 7cfee09..a0ad46e 100644 --- a/tests/toArray.test.ts +++ b/tests/toArray.test.ts @@ -8,7 +8,7 @@ test("Can convert List to Array", () => { assert.equal(list.toArray(), [], "return empty array for empty list"); - list.insertArray(0, lorem); + list.insertMany(0, lorem); assert.equal(list.toArray(), lorem, "return equal array to input"); }); diff --git a/tests/utils/assertValidList.ts b/tests/utils/assertValidList.ts index a082122..ed2597b 100644 --- a/tests/utils/assertValidList.ts +++ b/tests/utils/assertValidList.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/dot-notation */ import * as assert from "uvu/assert"; import type { List } from "../../src"; @@ -6,12 +7,12 @@ export function assertValidList(list: List) { case 0: { assert.is(list.head, undefined, "no head value for empty list"); assert.is(list.tail, undefined, "no tail value for empty list"); - assert.not(list.getNode(0), "no node returned for empty list"); + assert.not(list["getNode"](0), "no node returned for empty list"); return; } case 1: { - const head = list.getNode(0); + const head = list["getNode"](0); assert.ok(head, "find head node of list"); assert.not(head.next, "head node has no next node for length of 1"); assert.not(head.prev, "head node has no prev node"); @@ -27,7 +28,7 @@ export function assertValidList(list: List) { "head node has the same value as list.tail" ); - const next = list.getNode(1); + const next = list["getNode"](1); assert.not( next, "no node found at index 1 for lenght of 1 (should only have index of 0" @@ -37,7 +38,7 @@ export function assertValidList(list: List) { default: { // test head to tail - const head = list.getNode(0); + const head = list["getNode"](0); assert.ok(head, "find head node of list"); assert.not(head.prev, "head node has no prev node"); assert.is( @@ -63,7 +64,10 @@ export function assertValidList(list: List) { // gets returned by list.getNode and // has the same value as list.tail assert.not(prev.next, "final node has no next linked node"); - assert.ok(prev === list.getNode(idx - 1), "can find final node by index"); + assert.ok( + prev === list["getNode"](idx - 1), + "can find final node by index" + ); assert.is( prev.value, list.tail,