Skip to content

Commit

Permalink
Update: Improve tests, implement insertBefore
Browse files Browse the repository at this point in the history
  • Loading branch information
nzakas committed Nov 15, 2018
1 parent 0a48db4 commit 36eddb5
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 34 deletions.
105 changes: 83 additions & 22 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,32 @@

"use strict";

const FIRST = { toString() { return "FIRST" } };
const LAST = { toString() { return "LAST" } };

// hidden fields on the class
const first = Symbol("first");
const last = Symbol("last");
const nexts = Symbol("nexts");
const prevs = Symbol("prevs");

function assertValidItem(item) {
if (item == null) {
throw new TypeError("Key cannot be null or undefined.");
}
}

function assertNoDuplicates(item, set) {
if (set.has(item)) {
throw new Error("Duplicate item.");
}
}

function assertExists(item, set) {
if (!set.has(item)) {
throw new Error(`Item '${item}' does not exist.`);
}
}



class OrderedSet {

constructor() {
Expand All @@ -23,7 +41,6 @@ class OrderedSet {
this[last] = undefined;
}


next(item) {
return this[nexts].get(item);
}
Expand All @@ -38,15 +55,8 @@ class OrderedSet {

add(item) {

// ensure no duplicates
if (this.has(item)) {
throw new Error("Duplicate item.");
}

// undefined is used as a special value, so can't be a key
if (item === undefined) {
throw new Error("Item must not be undefined.");
}
assertValidItem(item);
assertNoDuplicates(item, this);

// special case for first item
if (this[first] === undefined) {
Expand All @@ -65,15 +75,9 @@ class OrderedSet {

insertAfter(item, relatedItem) {

// ensure no duplicates
if (this.has(item)) {
throw new Error("Duplicate item.");
}

// check for missing data
if (!this[nexts].has(relatedItem)) {
throw new Error("Second argument not found in set.");
}
assertValidItem(item);
assertNoDuplicates(item, this);
assertExists(relatedItem, this);

const curNext = this.next(relatedItem);
this[nexts].set(relatedItem, item);
Expand All @@ -87,19 +91,76 @@ class OrderedSet {
}
}

insertBefore(item, relatedItem) {

assertValidItem(item);
assertNoDuplicates(item, this);
assertExists(relatedItem, this);

const curPrev = this.previous(relatedItem);
this[prevs].set(relatedItem, item);
this[prevs].set(item, curPrev);
this[nexts].set(item, relatedItem);

// special case: relatedItem is the first item
if (relatedItem === this[first]) {
this[first] = item;
} else {
this[nexts].set(curPrev, item);
}
}

remove(item) {

assertValidItem(item);
assertExists(item, this);

const curPrev = this.previous(item);
const curNext = this.next(item);

if (curPrev !== undefined) {
this[nexts].set(curPrev, curNext);
}

if (curNext !== undefined) {
this[prevs].set(curNext, curPrev);
}

this[prevs].remove(item);
this[nexts].remove(item);
}

get size() {
return this[nexts].size;
}

first() {
return this[first];
}

last() {
return this[last];
}

*[Symbol.iterator]() {

let item = this[first];

while (item) {
yield item;
item = this.next(item);
}
}

*reverse() {
let item = this[last];

while (item) {
yield item;
item = this.previous(item);
}
}

}

module.exports = OrderedSet;
74 changes: 62 additions & 12 deletions tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@
const OrderedSet = require("../src/index.js");
const assert = require("chai").assert;

/**
* Asserts that the contents of the OrderedSet exactly match
* an array of results, backwards and forwards.
*
* @param {OrderedSet} set The set to check.
* @param {Array} result The expected contents of the set.
* @throws {AssertionError} If the set contents don't match the result.
* @returns {void}
*/
function assertOrder(set, result) {
assert.strictEqual(set.size, result.length);
assert.strictEqual(set.first(), result[0]);
assert.strictEqual(set.last(), result[result.length - 1]);
assert.deepStrictEqual([...set], result);
assert.deepStrictEqual([...set.reverse()], result.reverse());
}

describe("OrderedSet", () => {

Expand All @@ -19,46 +35,80 @@ describe("OrderedSet", () => {

it("adds a new item when passed one item", () => {
const set = new OrderedSet();
const result = [item];

set.add(item);
assert.strictEqual(set.size, 1);
assert.strictEqual([...set][0], item);

assertOrder(set, result);
});

it("adds new items when passed multiple items", () => {
const set = new OrderedSet();
const result = [item, item2, item3];

set.add(item);
set.add(item2);
set.add(item3);
assert.strictEqual(set.size, 3);
assert.deepStrictEqual([...set], [ item, item2, item3 ]);

assertOrder(set, result);
});
});

describe("insertAfter()", () => {
describe("insertBefore()", () => {

const item ="a";
const item2 = "b";
const item3 = "c";

it("inserts an item at the end", () => {
it("inserts an item at the start", () => {
const set = new OrderedSet();
const result = [item2, item];

set.add(item);
set.insertAfter(item2, item);
assert.strictEqual(set.size, 2);
assert.strictEqual([...set][0], item);
assert.strictEqual([...set][1], item2);
set.insertBefore(item2, item);

assertOrder(set, result);
});

it("inserts an item in the middle", () => {
const set = new OrderedSet();
const result = [item, item2, item3];

set.add(item);
set.add(item3);
set.insertBefore(item2, item3);

assertOrder(set, result);
});
});

describe("insertAfter()", () => {

const item ="a";
const item2 = "b";
const item3 = "c";

it("inserts an item in the middle", () => {
const set = new OrderedSet();
const result = [item, item2, item3];

set.add(item);
set.add(item3);
set.insertAfter(item2, item);
assert.strictEqual(set.size, 3);
assert.deepStrictEqual([...set], [ item, item2, item3 ]);

assertOrder(set, result);
});

it("inserts an item at the end", () => {
const set = new OrderedSet();
const result = [item, item2];

set.add(item);
set.insertAfter(item2, item);

assertOrder(set, result);
});

});

});

0 comments on commit 36eddb5

Please sign in to comment.