topological-sort
Version:
Topological sort
229 lines • 9.39 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const assert = require("assert");
const src_1 = require("../src");
/**
* Validation routine for edges
*
* @param {TopologicalSort} sortOp
* @param {Array<Object{from, to}>} edges
*/
function checkEdgesValid(sortOp, edges) {
edges.forEach((edge) => sortOp.addEdge(edge.from, edge.to));
const res = sortOp.sort();
const sortedKeys = [...res.keys()];
edges.forEach((edge) => {
assert(sortedKeys.indexOf(edge.from) < sortedKeys.indexOf(edge.to), `Node ${edge.from} should be placed before ${edge.to} in sorted output`);
});
}
describe('topological-sort', () => {
it('should throw if addNode() is invoked with already existing node', () => {
const nodes = new Map();
const sortOp = new src_1.TopologicalSort(nodes);
assert.doesNotThrow(() => {
sortOp.addNode(1, {});
}, 'addNode() should not throw if nodes list is empty');
assert.throws(() => {
sortOp.addNode(1, {});
}, 'addNode() should throw an error if node with this key already exists');
});
it('should throw if addEdge() is invoked with wrong params', () => {
const nodes = new Map([['A', 1], ['B', 2]]);
const sortOp = new src_1.TopologicalSort(nodes);
assert.doesNotThrow(() => {
sortOp.addEdge('A', 'B');
}, 'addEdge() should not throw for valid params');
assert.throws(() => {
sortOp.addEdge('A', 'C');
}, 'addNode() should throw an error if any of the params are not valid node keys');
assert.throws(() => {
sortOp.addEdge('C', 'D');
}, 'addNode() should throw an error if any of the params are not valid node keys');
assert.throws(() => {
sortOp.addEdge('A', 'C');
}, 'addNode() should throw an error if edge already exists');
});
it('should throw if addNodes() is invoked with already existing node', () => {
const nodes = new Map([['A', 1], ['B', 2]]);
const sortOp = new src_1.TopologicalSort(nodes);
assert.throws(() => {
const newNodes = new Map([['B', 3]]);
sortOp.addNodes(newNodes);
}, 'addNodes() should throw an error if nodes already exists');
});
it('should not change order of input nodes if no edges exist', () => {
const nodes = new Map([['A', 1], ['B', 2], ['C', 3]]);
const sortOp = new src_1.TopologicalSort(nodes);
const res = sortOp.sort();
const sortedKeys = [...res.keys()];
assert.strictEqual(sortedKeys[0], 'A');
assert.strictEqual(sortedKeys[1], 'B');
assert.strictEqual(sortedKeys[2], 'C');
});
it('should return map after sort()', () => {
const nodes = new Map([['A', 1], ['B', 2], ['C', 3]]);
const sortOp = new src_1.TopologicalSort(nodes);
const res = sortOp.sort();
assert.strictEqual(res instanceof Map, true, 'TopologicalSort sort() result variable should be a Map instance');
assert.strictEqual(res.size, 3);
});
it('should sort nodes passed in constructor only + addEdge()', () => {
const nodes = new Map([
['A', 1],
['B', 2],
['C', 3],
['D', 4],
['E', 5],
['F', 6],
['G', 7],
['H', 8],
]);
const sortOp = new src_1.TopologicalSort(nodes);
const edges = [
{ from: 'A', to: 'C' },
{ from: 'B', to: 'C' },
{ from: 'B', to: 'D' },
{ from: 'C', to: 'E' },
{ from: 'D', to: 'F' },
{ from: 'E', to: 'F' },
{ from: 'E', to: 'H' },
{ from: 'F', to: 'G' },
];
checkEdgesValid(sortOp, edges);
});
it('should sort nodes passed in constructor + addNode()', () => {
const nodes = new Map([['variables', 'file://...']]);
const sortOp = new src_1.TopologicalSort(nodes);
sortOp.addNode('mixins', 'file://...');
sortOp.addNode('block', 'file://...');
sortOp.addNode('block_mod_val', 'file://...');
const edges = [
{ from: 'variables', to: 'mixins' },
{ from: 'variables', to: 'block' },
{ from: 'mixins', to: 'block' },
{ from: 'block', to: 'block_mod_val' },
];
checkEdgesValid(sortOp, edges);
});
it('should sort nodes passed in constructor + addNodes()', () => {
const nodes = new Map([['variables', 'file://...']]);
const sortOp = new src_1.TopologicalSort(nodes);
sortOp.addNodes(new Map([
['mixins', 'file://...'],
['argument', 'file://...'],
['mixins', 'file://...'],
['user', 'file://...'],
['user-avatar', 'file://...'],
]));
const edges = [
{ from: 'variables', to: 'mixins' },
{ from: 'variables', to: 'argument' },
{ from: 'mixins', to: 'argument' },
{ from: 'argument', to: 'user' },
{ from: 'user-avatar', to: 'user' },
{ from: 'variables', to: 'user-avatar' },
{ from: 'mixins', to: 'user-avatar' },
{ from: 'variables', to: 'user' },
{ from: 'mixins', to: 'user' },
];
checkEdgesValid(sortOp, edges);
});
it('should sort nodes in the same order', () => {
const nodes = new Map([
['variables', 'file://...'],
['mixins', 'file://...'],
['argument', 'file://...'],
['mixins', 'file://...'],
['user', 'file://...'],
['user-avatar', 'file://...'],
]);
const sortOp = new src_1.TopologicalSort(nodes);
const edges = [
{ from: 'variables', to: 'mixins' },
{ from: 'variables', to: 'argument' },
{ from: 'mixins', to: 'argument' },
{ from: 'argument', to: 'user' },
{ from: 'user-avatar', to: 'user' },
{ from: 'variables', to: 'user-avatar' },
{ from: 'mixins', to: 'user-avatar' },
{ from: 'variables', to: 'user' },
{ from: 'mixins', to: 'user' },
];
edges.forEach(({ from, to }) => sortOp.addEdge(from, to));
const res = sortOp.sort();
const sortedKeys = [...res.keys()];
sortOp.sort();
const sortedKeys2 = [...res.keys()];
sortedKeys.forEach((_, index) => {
assert.strictEqual(sortedKeys[index], sortedKeys2[index], `Sorted nodes differ at index ${index}`);
});
});
it('should throw if node edges form circular dependency', () => {
const nodes = new Map([
['variables', 'file://...'],
['mixins', 'file://...'],
['argument', 'file://...'],
['mixins', 'file://...'],
['user', 'file://...'],
['user-avatar', 'file://...'],
]);
const sortOp = new src_1.TopologicalSort(nodes);
const edges = [
{ from: 'mixins', to: 'variables' },
{ from: 'variables', to: 'argument' },
{ from: 'mixins', to: 'argument' },
{ from: 'argument', to: 'user' },
{ from: 'user-avatar', to: 'user' },
{ from: 'variables', to: 'user-avatar' },
{ from: 'mixins', to: 'user-avatar' },
{ from: 'variables', to: 'user' },
{ from: 'user', to: 'mixins' },
];
edges.forEach(({ from, to }) => sortOp.addEdge(from, to));
assert.throws(() => {
sortOp.sort();
}, 'sort() should throw if there are circular dependencies');
});
it('should throw if node edges form circular dependency starting from index 1', () => {
const nodes = new Map([
['A', 'file://...'],
['B', 'file://...'],
['C', 'file://...'],
['D', 'file://...'],
]);
const sortOp = new src_1.TopologicalSort(nodes);
const edges = [
{ from: 'A', to: 'B' },
{ from: 'B', to: 'C' },
{ from: 'C', to: 'D' },
{ from: 'D', to: 'B' },
];
edges.forEach(({ from, to }) => sortOp.addEdge(from, to));
assert.throws(() => {
sortOp.sort();
}, 'sort() should throw if there are circular dependencies');
});
it('shouldn\'t throw for missing circular dependency', () => {
const nodes = new Map([
['B', 'file://...'],
['A', 'file://...'],
]);
const sortOp = new src_1.TopologicalSort(nodes);
sortOp.addEdge('A', 'B');
assert.doesNotThrow(() => {
sortOp.sort();
}, 'sort() should not throw if node without dependencies is standing at index 0');
});
it('shouldn\'t loose content associated with map keys after sort', () => {
const nodes = new Map([
['A', 'some A contents'],
['B', 'some B contents'],
]);
const sortOp = new src_1.TopologicalSort(nodes);
sortOp.addEdge('A', 'B');
const sorted = sortOp.sort();
assert.strictEqual(sorted.get('A').node, 'some A contents');
assert.strictEqual(sorted.get('B').node, 'some B contents');
});
});
//# sourceMappingURL=index.spec.js.map