dig
Version:
Graph algorithms
491 lines (411 loc) • 12.7 kB
JavaScript
require("../test-env");
var graphs = require("../test-graphs");
exports.describeConstructor = function(ctor) {
it("returns an empty graph", function() {
assert.deepEqual([], ctor().nodes());
});
};
exports.describeOrder = function(ctor) {
it("returns the number of nodes in the graph", function() {
var g = ctor();
g.addNodes(1, 2);
assert.equal(2, g.order());
});
it("returns 0 for a graph with no nodes", function() {
assert.equal(0, ctor().order());
});
it("decreases when a node is removed", function() {
var g = ctor();
g.addNode(1);
g.removeNode(1);
assert.equal(0, g.order());
});
it("does not change when adding the same node a second time", function() {
var g = ctor();
g.addNode(1);
g.addNode(1);
assert.equal(1, g.order());
});
};
exports.describeSize = function(ctor) {
it("returns the number of edges in the graph", function() {
var g = ctor();
g.addNodes(1, 2);
g.addEdge(1, 2);
assert.equal(1, g.size());
});
it("returns 0 for a graph with no edges", function() {
assert.equal(0, ctor().size());
});
it("decreases when a node is removed", function() {
var g = ctor();
g.addNodes(1, 2);
g.addEdge(1, 2);
g.removeEdge(1, 2);
assert.equal(0, g.size());
});
};
exports.describeCopy = function(ctor) {
var source, copy;
beforeEach(function() {
source = ctor();
source.addNodes(1, 2, 3);
source.node(1).a = 1;
source.addEdge(1, 2);
copy = source.copy();
});
it("copies all nodes from the source graph", function() {
assert.graphEqual(source, copy);
});
it("doesn't share node changes between copy and source", function() {
copy.addNode(4);
assert.isFalse(source.hasNode(4));
copy.node(2).key = "a";
assert.isUndefined(source.node(2).key);
source.addNode(5);
assert.isFalse(copy.hasNode(5));
source.node(3).key = "b";
assert.isUndefined(copy.node(3).key);
});
it("copies all edges from the source graph", function() {
assert.isTrue(copy.hasEdge(1, 2));
});
it("doesn't share edge changes between copy and source", function() {
copy.addEdge(1, 3);
assert.isFalse(source.hasEdge(1, 3));
source.addEdge(2, 3);
assert.isFalse(copy.hasEdge(2, 3));
});
};
exports.describeEquals = function(ctor) {
it("returns true for a graph that has the same nodes and edges", function() {
var graph = ctor();
graph.addNodes(1, 2);
graph.addEdge(1, 2);
var graph2 = ctor();
graph2.addNodes(1, 2);
graph2.addEdge(1, 2);
assert.isTrue(graph.equals(graph2));
assert.isTrue(graph2.equals(graph));
});
it("returns false for graphs that have different nodes", function() {
var graph = ctor();
graph.addNode(1);
var graph2 = ctor();
graph2.addNode(2);
assert.isFalse(graph.equals(graph2));
assert.isFalse(graph2.equals(graph));
});
it("returns false for graphs that have different node attributes", function() {
var g = ctor();
g.addNodes(1, 2);
g.node(1).key = "value";
g.addEdge(1, 2);
var g2 = ctor();
g2.addNodes(1, 2);
g2.addEdge(1, 2);
assert.isFalse(g.equals(g2));
assert.isFalse(g2.equals(g));
});
it("returns false for graphs that have different edges", function() {
var graph = ctor();
graph.addNodes(1, 2, 3);
graph.addEdge(1, 2);
var graph2 = ctor();
graph2.addNodes(1, 2, 3);
graph2.addEdge(2, 3);
assert.isFalse(graph.equals(graph2));
assert.isFalse(graph2.equals(graph));
});
it("returns false for graphs that have different edge attributes", function() {
var g = ctor();
g.addNodes(1, 2, 3);
g.addEdge(1, 2);
var g2 = ctor();
g2.addNodes(1, 2, 3);
g2.addEdge(1, 2, {a: 1});
assert.isFalse(g.equals(g2));
assert.isFalse(g2.equals(g));
});
};
exports.describeNodes = function(ctor) {
it("returns `[]` for an empty graph", function() {
assert.deepEqual([], ctor().nodes());
});
it("returns all nodes in the graph", function() {
var g = ctor();
g.addNode(1);
g.addNode(2);
assert.deepEqual([1, 2].sort(), g.nodes().sort());
});
};
exports.describeHasNode = function(ctor) {
it("returns true if the node is in the graph", function() {
var g = ctor();
g.addNode(1);
assert.isTrue(g.hasNode(1));
});
it("returns false if the node is not in the graph", function() {
assert.isFalse(ctor().hasNode(1));
});
};
exports.describeAddNode = function(ctor) {
it("coerces nodes to strings", function() {
var g = ctor();
g.addNodes(1);
g.addNode("a");
g.addNode(false);
g.addNode(undefined);
g.addNode(null);
assert.isTrue(g.hasNode(1));
assert.isTrue(g.hasNode("1"));
assert.isTrue(g.hasNode("a"));
assert.isTrue(g.hasNode(false));
assert.isTrue(g.hasNode("false"));
assert.isTrue(g.hasNode(undefined));
assert.isTrue(g.hasNode("undefined"));
assert.isTrue(g.hasNode(null));
assert.isTrue(g.hasNode("null"));
});
it("returns true if a new node is added", function() {
assert.isTrue(ctor().addNode(1));
});
it("returns false if an existing node is added", function() {
var g = ctor();
g.addNode(1);
assert.isFalse(g.addNode(1));
});
it("optionally allows attributes to be set on a node", function() {
var g = ctor();
var attrs = {xyz: 123};
g.addNode(1, attrs);
assert.deepEqual({xyz: 123}, g.node(1));
// attributes are shallow copies, so changes to the original object should
// not be reflected in the graph.
attrs.abc = 456;
assert.deepEqual({xyz: 123}, g.node(1));
});
it("optionally merges attributes if the node already exists", function() {
var g = ctor();
g.addNode(1, {a: 1, b: 2});
g.addNode(1, {b: 3, c: 4});
assert.deepEqual({a:1, b: 3, c: 4}, g.node(1));
});
it("throws an error if the attributes are not an object", function() {
var g = ctor();
assert.throws(function() { g.addNode(1, 3); });
assert.throws(function() { g.addNode(1, "a"); });
assert.throws(function() { g.addNode(1, false); });
assert.throws(function() { g.addNode(1, null); });
assert.throws(function() { g.addNode(1, undefined); });
});
};
exports.describeAddNodes = function(ctor) {
it("adds no nodes for 0 arguments", function() {
var g = ctor();
g.addNodes();
assert.deepEqual([], g.nodes());
});
it("allows a vararg list of nodes to be added", function() {
var g = ctor();
g.addNodes(1, 2, 3, 4);
assert.isTrue(g.hasNode(1));
assert.isTrue(g.hasNode(2));
assert.isTrue(g.hasNode(3));
assert.isTrue(g.hasNode(4));
});
it("returns undefined", function() {
var g = ctor();
assert.isUndefined(g.addNodes(1, 2, 3, 4));
});
it("does not add a node twice", function() {
var g = ctor();
g.addNodes(1, 2, 2);
assert.isTrue(g.hasNode(1));
assert.isTrue(g.hasNode(2));
assert.equal(2, g.order());
});
};
exports.describeNode = function(ctor) {
it("returns the attributes for the node", function() {
var g = ctor();
g.addNode(1);
assert.isUndefined(g.node(1).key);
g.node(1).key = 1;
assert.equal(1, g.node(1).key);
});
it("throws an error if the node doesn't exist", function() {
assert.throws(function() { ctor().node(1); });
});
};
exports.describeRemoveNode = function(ctor) {
it("returns true if the node was removed from the graph", function() {
var g = ctor();
g.addNode(1);
assert.isTrue(g.removeNode(1));
});
it("returns false if the node was not in the graph", function() {
assert.isFalse(ctor().removeNode(1));
});
it("removes edges incident to the node", function() {
var g = ctor();
g.addNodes(1, 2);
g.addEdge(1, 2);
g.removeNode(1);
assert.isFalse(g.hasEdge(1, 2));
});
};
exports.describeEdges = function(ctor) {
it("returns `[]` for a graph with no edges", function() {
assert.deepEqual([], ctor().edges());
});
};
exports.describeHasEdge = function(ctor) {
it("returns true if the edge is in the graph", function() {
var g = ctor();
g.addNodes(1, 2);
g.addEdge(1, 2);
assert.isTrue(g.hasEdge(1, 2));
});
it("returns false if the edge is not in the graph", function() {
var g = ctor();
g.addNodes(1, 2, 3);
g.addEdge(1, 2);
assert.isFalse(g.hasEdge(2, 3));
});
};
exports.describeAddEdge = function(ctor) {
it("returns true if the edge was added", function() {
var g = ctor();
g.addNodes(1, 2);
assert.isTrue(g.addEdge(1, 2));
});
it("returns false if the edge was already in the graph", function() {
var g = ctor();
g.addNodes(1, 2);
g.addEdge(1, 2);
assert.isFalse(g.addEdge(1, 2));
});
it("optionally allows attributes to be set on the edge", function() {
var g = ctor();
var attrs = {xyz: 123};
g.addNodes(1, 2);
g.addEdge(1, 2, attrs);
assert.deepEqual({xyz: 123}, g.edge(1, 2));
// attributes are shallow copies, so changes to the original object should
// not be reflected in the graph.
attrs.abc = 456;
assert.deepEqual({xyz: 123}, g.edge(1, 2));
});
it("optionally merges attributes if the edge already exists", function() {
var g = ctor();
g.addNodes(1, 2);
g.addEdge(1, 2, {a: 1, b: 2});
g.addEdge(1, 2, {b: 3, c: 4});
assert.deepEqual({a:1, b: 3, c: 4}, g.edge(1, 2));
});
it("throws an error if one of the nodes was not in the graph", function() {
var g = ctor();
g.addNode(1);
assert.throws(function() { g.addEdge(1, 2); });
assert.isFalse(g.hasEdge(1, 2));
});
it("throws an error if the attributes are not an object", function() {
var g = ctor();
g.addNodes(1, 2);
assert.throws(function() { g.addEdge(1, 2, 3); });
assert.throws(function() { g.addEdge(1, 2, "a"); });
assert.throws(function() { g.addEdge(1, 2, false); });
assert.throws(function() { g.addEdge(1, 2, null); });
assert.throws(function() { g.addEdge(1, 2, undefined); });
});
}
exports.describeAddPath = function(ctor) {
it("adds no edge for 0 arguments", function() {
var g = ctor();
g.addPath();
assert.deepEqual([], g.edges());
});
it("adds no edge for 1 argument", function() {
var g = ctor();
g.addNode(1);
g.addPath(1);
assert.deepEqual([], g.edges());
});
it("adds a single edge for 2 arguments", function() {
var g = ctor();
g.addNodes(1, 2);
g.addPath(1, 2);
assert.isTrue(g.hasEdge(1, 2));
assert.equal(1, g.size());
});
it("adds edges pairwise (node-1, node-2), (node-2, node-3)", function() {
var g = ctor();
g.addNodes(1, 2, 3);
g.addPath(1, 2, 3);
assert.isTrue(g.hasEdge(1, 2));
assert.isTrue(g.hasEdge(2, 3));
assert.equal(2, g.size());
});
it("allows cycles to be added", function() {
var g = ctor();
g.addNodes(1, 2, 3);
g.addPath(1, 2, 3, 1);
assert.isTrue(g.hasEdge(1, 2));
assert.isTrue(g.hasEdge(2, 3));
assert.isTrue(g.hasEdge(3, 1));
});
it("throws an error for edges with a node not in the graph", function() {
var g = ctor();
g.addNodes(1, 2);
assert.throws(function() { g.addPath(1, 2, 3); });
});
it("returns undefined", function() {
var g = ctor();
g.addNodes(1, 2);
assert.isUndefined(g.addPath(1, 2));
});
};
exports.describeEdge = function(ctor) {
it("returns the attributes for the edge", function() {
var g = ctor();
g.addNodes(1, 2);
g.addEdge(1, 2);
assert.isUndefined(g.edge(1, 2).key);
g.edge(1, 2).key = 1;
assert.equal(1, g.edge(1, 2).key);
});
it("throws an error if the edge doesn't exist", function() {
var g = ctor();
g.addNodes(1, 2);
assert.throws(function() { g.edge(1, 2); });
});
};
exports.describeRemoveEdge = function(ctor) {
it("returns true if the edge was removed from the graph", function() {
var g = ctor();
g.addNodes(1, 2);
g.addEdge(1, 2);
assert.isTrue(g.removeEdge(1, 2));
assert.isFalse(g.hasEdge(1, 2));
});
it("returns false if the edge is not in the graph", function() {
var g = ctor();
g.addNodes(1, 2);
assert.isFalse(g.removeEdge(1, 2));
});
};
exports.describeDegree = function(ctor) {
it("returns the number of edges incident to a node", function() {
var g = ctor();
g.addNodes(1, 2, 3);
g.addEdge(1, 2);
g.addEdge(1, 3);
assert.equal(2, g.degree(1));
assert.equal(1, g.degree(2));
assert.equal(1, g.degree(3));
});
it("throws an error when the node is not in the graph", function() {
assert.throws(function() { ctor().degree(1); });
});
};