Son CV dans un terminal web en Javascript! https://terminal-cv.gregandev.fr
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

311 lines
8.4 KiB

// @flow strict-local
import assert from 'assert';
import sinon from 'sinon';
import Graph from '../src/Graph';
import {toNodeId} from '../src/types';
describe('Graph', () => {
it('constructor should initialize an empty graph', () => {
let graph = new Graph();
assert.deepEqual(graph.nodes, new Map());
assert.deepEqual(graph.getAllEdges(), []);
});
it('addNode should add a node to the graph', () => {
let graph = new Graph();
let node = {};
let id = graph.addNode(node);
assert.equal(graph.nodes.get(id), node);
});
it("errors when removeNode is called with a node that doesn't belong", () => {
let graph = new Graph();
assert.throws(() => {
graph.removeNode(toNodeId(-1));
}, /Does not have node/);
});
it('errors when traversing a graph with no root', () => {
let graph = new Graph();
assert.throws(() => {
graph.traverse(() => {});
}, /A start node is required to traverse/);
});
it("errors when traversing a graph with a startNode that doesn't belong", () => {
let graph = new Graph();
assert.throws(() => {
graph.traverse(() => {}, toNodeId(-1));
}, /Does not have node/);
});
it("errors if replaceNodeIdsConnectedTo is called with a node that doesn't belong", () => {
let graph = new Graph();
assert.throws(() => {
graph.replaceNodeIdsConnectedTo(toNodeId(-1), []);
}, /Does not have node/);
});
it("errors when adding an edge to a node that doesn't exist", () => {
let graph = new Graph();
let node = graph.addNode({});
assert.throws(() => {
graph.addEdge(node, toNodeId(-1));
}, /"to" node '-1' not found/);
});
it("errors when adding an edge from a node that doesn't exist", () => {
let graph = new Graph();
let node = graph.addNode({});
assert.throws(() => {
graph.addEdge(toNodeId(-1), node);
}, /"from" node '-1' not found/);
});
it('hasNode should return a boolean based on whether the node exists in the graph', () => {
let graph = new Graph();
let node = graph.addNode({});
assert(graph.hasNode(node));
assert(!graph.hasNode(toNodeId(-1)));
});
it('addEdge should add an edge to the graph', () => {
let graph = new Graph();
let nodeA = graph.addNode('a');
let nodeB = graph.addNode('b');
graph.addEdge(nodeA, nodeB);
assert(graph.hasEdge(nodeA, nodeB));
});
it('isOrphanedNode should return true or false if the node is orphaned or not', () => {
let graph = new Graph();
let nodeA = graph.addNode('a');
let nodeB = graph.addNode('b');
let nodeC = graph.addNode('c');
graph.addEdge(nodeA, nodeB);
graph.addEdge(nodeA, nodeC, 1);
assert(graph.isOrphanedNode(nodeA));
assert(!graph.isOrphanedNode(nodeB));
assert(!graph.isOrphanedNode(nodeC));
});
it('removeEdge should prune the graph at that edge', () => {
// a
// / \
// b - d
// /
// c
let graph = new Graph();
let nodeA = graph.addNode('a');
let nodeB = graph.addNode('b');
let nodeC = graph.addNode('c');
let nodeD = graph.addNode('d');
graph.addEdge(nodeA, nodeB);
graph.addEdge(nodeA, nodeD);
graph.addEdge(nodeB, nodeC);
graph.addEdge(nodeB, nodeD);
graph.removeEdge(nodeA, nodeB);
assert(graph.nodes.has(nodeA));
assert(graph.nodes.has(nodeD));
assert(!graph.nodes.has(nodeB));
assert(!graph.nodes.has(nodeC));
assert.deepEqual(graph.getAllEdges(), [{from: nodeA, to: nodeD, type: 1}]);
});
it('removing a node recursively deletes orphaned nodes', () => {
// before:
// a
// / \
// b c
// / \ \
// d e f
// /
// g
//
// after:
// a
// \
// c
// \
// f
let graph = new Graph();
let nodeA = graph.addNode('a');
let nodeB = graph.addNode('b');
let nodeC = graph.addNode('c');
let nodeD = graph.addNode('d');
let nodeE = graph.addNode('e');
let nodeF = graph.addNode('f');
let nodeG = graph.addNode('g');
graph.addEdge(nodeA, nodeB);
graph.addEdge(nodeA, nodeC);
graph.addEdge(nodeB, nodeD);
graph.addEdge(nodeB, nodeE);
graph.addEdge(nodeC, nodeF);
graph.addEdge(nodeD, nodeG);
graph.removeNode(nodeB);
assert.deepEqual([...graph.nodes.keys()], [nodeA, nodeC, nodeF]);
assert.deepEqual(graph.getAllEdges(), [
{from: nodeA, to: nodeC, type: 1},
{from: nodeC, to: nodeF, type: 1},
]);
});
it('removing a node recursively deletes orphaned nodes if there is no path to the root', () => {
// before:
// a
// / \
// b c
// / \ \
// |-d e f
// |/
// g
//
// after:
// a
// \
// c
// \
// f
let graph = new Graph();
let nodeA = graph.addNode('a');
let nodeB = graph.addNode('b');
let nodeC = graph.addNode('c');
let nodeD = graph.addNode('d');
let nodeE = graph.addNode('e');
let nodeF = graph.addNode('f');
let nodeG = graph.addNode('g');
graph.setRootNodeId(nodeA);
graph.addEdge(nodeA, nodeB);
graph.addEdge(nodeA, nodeC);
graph.addEdge(nodeB, nodeD);
graph.addEdge(nodeG, nodeD);
graph.addEdge(nodeB, nodeE);
graph.addEdge(nodeC, nodeF);
graph.addEdge(nodeD, nodeG);
graph.removeNode(nodeB);
assert.deepEqual([...graph.nodes.keys()], [nodeA, nodeC, nodeF]);
assert.deepEqual(graph.getAllEdges(), [
{from: nodeA, to: nodeC, type: 1},
{from: nodeC, to: nodeF, type: 1},
]);
});
it('removing an edge to a node that cycles does not remove it if there is a path to the root', () => {
// a
// |
// b <----
// / \ |
// c d |
// \ / |
// e -----
let graph = new Graph();
let nodeA = graph.addNode('a');
let nodeB = graph.addNode('b');
let nodeC = graph.addNode('c');
let nodeD = graph.addNode('d');
let nodeE = graph.addNode('e');
graph.setRootNodeId(nodeA);
graph.addEdge(nodeA, nodeB);
graph.addEdge(nodeB, nodeC);
graph.addEdge(nodeB, nodeD);
graph.addEdge(nodeC, nodeE);
graph.addEdge(nodeD, nodeE);
graph.addEdge(nodeE, nodeB);
const getNodeIds = () => [...graph.nodes.keys()];
let nodesBefore = getNodeIds();
graph.removeEdge(nodeC, nodeE);
assert.deepEqual(nodesBefore, getNodeIds());
assert.deepEqual(graph.getAllEdges(), [
{from: nodeA, to: nodeB, type: 1},
{from: nodeB, to: nodeC, type: 1},
{from: nodeB, to: nodeD, type: 1},
{from: nodeD, to: nodeE, type: 1},
{from: nodeE, to: nodeB, type: 1},
]);
});
it('removing a node with only one inbound edge does not cause it to be removed as an orphan', () => {
let graph = new Graph();
let nodeA = graph.addNode('a');
let nodeB = graph.addNode('b');
graph.setRootNodeId(nodeA);
graph.addEdge(nodeA, nodeB);
let spy = sinon.spy(graph, 'removeNode');
try {
graph.removeNode(nodeB);
assert(spy.calledOnceWithExactly(nodeB));
} finally {
spy.restore();
}
});
it("replaceNodeIdsConnectedTo should update a node's downstream nodes", () => {
let graph = new Graph();
let nodeA = graph.addNode('a');
let nodeB = graph.addNode('b');
let nodeC = graph.addNode('c');
graph.addEdge(nodeA, nodeB);
graph.addEdge(nodeA, nodeC);
let nodeD = graph.addNode('d');
graph.replaceNodeIdsConnectedTo(nodeA, [nodeB, nodeD]);
assert(graph.hasNode(nodeA));
assert(graph.hasNode(nodeB));
assert(!graph.hasNode(nodeC));
assert(graph.hasNode(nodeD));
assert.deepEqual(graph.getAllEdges(), [
{from: nodeA, to: nodeB, type: 1},
{from: nodeA, to: nodeD, type: 1},
]);
});
it('traverses along edge types if a filter is given', () => {
let graph = new Graph();
let nodeA = graph.addNode('a');
let nodeB = graph.addNode('b');
let nodeC = graph.addNode('c');
let nodeD = graph.addNode('d');
graph.addEdge(nodeA, nodeB, 2);
graph.addEdge(nodeA, nodeD);
graph.addEdge(nodeB, nodeC);
graph.addEdge(nodeB, nodeD, 2);
graph.setRootNodeId(nodeA);
let visited = [];
graph.traverse(
nodeId => {
visited.push(nodeId);
},
null, // use root as startNode
2,
);
assert.deepEqual(visited, [nodeA, nodeB, nodeD]);
});
});