├── .nvmrc ├── .npmignore ├── .travis.yml ├── .gitignore ├── tslint.json ├── .babelrc ├── .editorconfig ├── CHANGELOG.md ├── tsconfig.json ├── LICENSE ├── package.json ├── README.md └── src ├── index.ts └── index.spec.ts /.nvmrc: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | .editorconfig 3 | .travis.yml 4 | index.js 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "stable" 5 | addons: 6 | sauce_connect: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .DS_Store? 3 | .*DS_Store* 4 | 5 | lib-cov 6 | *.seed 7 | *.log 8 | *.csv 9 | *.dat 10 | *.out 11 | *.pid 12 | *.gz 13 | 14 | pids 15 | logs 16 | results 17 | 18 | npm-debug.log 19 | node_modules 20 | 21 | .idea 22 | 23 | # app specific 24 | dist 25 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "indent": [true, "spaces", 2], 5 | "member-access": [true, "no-public"], 6 | "only-arrow-functions": false, 7 | "quotemark": [true, "single"], 8 | "switch-default": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | plugins: [ 3 | "array-includes", 4 | "add-module-exports", 5 | "transform-es2015-modules-commonjs", 6 | "transform-es2015-spread", 7 | "transform-es2015-parameters", 8 | "transform-es2015-destructuring", 9 | "transform-object-rest-spread" 10 | ] 11 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | end_of_line = lf 9 | insert_final_newline = true 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | 13 | [{package.json, .babelrc}] 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.3.0 2 | 3 | * Breaking change: `sort()` return map values are now `{node, children}` where `children` is also a map. See README for examples. 4 | 5 | ## 0.2.0 6 | 7 | * Breaking change: TopologicalSort class is now a named export, not the default CommonJS `module.exports` 8 | * Package source code re-written in Typescript 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "module": "commonjs", 5 | "allowJs": false, 6 | "alwaysStrict": true, 7 | "declaration": true, 8 | "importHelpers": true, 9 | "moduleResolution": "node", 10 | "noFallthroughCasesInSwitch": true, 11 | "noImplicitAny": false, 12 | "noImplicitThis": false, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "sourceMap": true, 16 | "strictNullChecks": true, 17 | "target": "esnext" 18 | }, 19 | "include": [ 20 | "src" 21 | ], 22 | "exclude": [ 23 | "node_modules" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Dmitry Sorin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "topological-sort", 3 | "version": "0.3.0", 4 | "description": "Topological sort", 5 | "main": "dist/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/1999/topological-sort.git" 9 | }, 10 | "scripts": { 11 | "build": "tsc", 12 | "clean": "rm -fr dist/", 13 | "lint": "tslint -p . -c tslint.json 'src/*.ts'", 14 | "prepublishOnly": "npm run clean && npm run build", 15 | "test:unit": "mocha --require ts-node/register src/*.spec.ts", 16 | "test": "npm run lint && npm run test:unit" 17 | }, 18 | "author": "Dmitrii Sorin ", 19 | "license": "MIT", 20 | "engines": { 21 | "node": ">=8" 22 | }, 23 | "devDependencies": { 24 | "@types/mocha": "^5.2.5", 25 | "@types/node": "^12.0.0", 26 | "mocha": "^6.0.0", 27 | "ts-node": "^8.0.3", 28 | "tslint": "^5.11.0", 29 | "typescript": "^3.0.1" 30 | }, 31 | "keywords": [ 32 | "topological sort", 33 | "topological", 34 | "sort", 35 | "graph", 36 | "dependencies" 37 | ], 38 | "types": "dist/index.d.ts" 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Topological sort 2 | 3 | [![Greenkeeper badge](https://badges.greenkeeper.io/1999/topological-sort.svg)](https://greenkeeper.io/) 4 | [![Build Status](https://img.shields.io/travis/1999/topological-sort.svg?style=flat)](https://travis-ci.org/1999/topological-sort) 5 | [![DevDependency Status](http://img.shields.io/david/dev/1999/topological-sort.svg?style=flat)](https://david-dm.org/1999/topological-sort#info=devDependencies) 6 | 7 | This package is distributed as Javascript, but you can also use it in your TypeScript project. 8 | 9 | ## API 10 | ### Javascript example 11 | 12 | ```javascript 13 | const { TopologicalSort } = require('topological-sort'); 14 | 15 | // you can pass nodes as a map into constructor: 16 | const nodes = new Map(); 17 | nodes.set('variables', variablesObj); 18 | nodes.set('mixins', mixinsObj); 19 | const sortOp = new TopologicalSort(nodes); 20 | 21 | // ...or add them to existing object instance with addNode() or addNodes(): 22 | sortOp.addNode('block', blocksObj); 23 | sortOp.addNodes(new Map([ 24 | ['block_mod_val1', blockModObj1], 25 | ['block_mod_val2', blockModObj2] 26 | ])); 27 | 28 | // then you should add adges between nodes 29 | sortOp.addEdge('variables', 'mixins'); // from, to 30 | sortOp.addEdge('mixins', 'block'); 31 | sortOp.addEdge('variables', 'block'); 32 | sortOp.addEdge('block', 'block_mod_val2'); 33 | sortOp.addEdge('block', 'block_mod_val1'); 34 | 35 | // sorting is simple: it returns a new map wih sorted elements 36 | // if circular dependency is found, sort() operation throws an AssertionError 37 | const sorted = sortOp.sort(); 38 | const sortedKeys = [...sorted.keys()]; // ['variables', 'mixins', 'block', 'block_mod_val1', 'block_mod_val2'] 39 | 40 | // values of the `sorted` map are objects with this shape: `{ children, node }` 41 | // where node is the node object that you provided 42 | // and children is a map which values have the same shape 43 | const { node: variablesObj, children: variablesChildren } = sorted.get('variables'); 44 | const { node: blocksObj1 } = variablesChildren.get('block'); 45 | const { node: blocksObj2 } = sorted.get('block'); 46 | assert(blocksObj1 === blocksObj2); // true 47 | ``` 48 | 49 | ### Typescript example 50 | 51 | ```typescript 52 | import TopologicalSort from 'topological-sort'; 53 | 54 | // TopologicalSort class instances have a map inside. 55 | // This map stores the references between your nodes (edges) 56 | // "NodesKeyType" is the type for your tree node identifiers 57 | // "NodesValueType" is the type for your tree nodes 58 | const nodes = new Map(); 59 | nodes.set('variables', variablesObj); 60 | nodes.set('mixins', mixinsObj); 61 | const sortOp = new TopologicalSort(nodes); 62 | 63 | // `sortedKeys` is a topologically sorted list of node keys 64 | sortOp.addEdge('variables', 'mixins'); 65 | const sorted = sortOp.sort(); 66 | const sortedKeys = [...sorted.keys()]; // ['variables', 'mixins'] 67 | 68 | // `sorted` contains all nodes and their children 69 | const { node: variablesObj, children: variablesChildren } = sorted.get('variables'); 70 | const { node: blocksObj1 } = variablesChildren.get('block'); 71 | const { node: blocksObj2 } = sorted.get('block'); 72 | assert(blocksObj1 === blocksObj2); // true 73 | ``` 74 | 75 | ## More info: 76 | 77 | * https://en.wikipedia.org/wiki/Topological_sorting 78 | * https://www.cs.usfca.edu/~galles/visualization/TopoSortDFS.html 79 | * http://www.geeksforgeeks.org/topological-sorting/ 80 | * https://www.youtube.com/watch?v=ddTC4Zovtbc 81 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | export interface INodeWithChildren { 4 | children: InternalNodesMap; 5 | node: ValueType; 6 | } 7 | 8 | export type InternalNodesMap = Map>; 9 | 10 | class TopologicalSort { 11 | private nodes: InternalNodesMap; 12 | private visitedNodes: Set>; 13 | private sortedKeysStack: KeyType[]; 14 | 15 | constructor(nodes: Map) { 16 | this.nodes = new Map(); 17 | this.addMultipleInternalNodes(nodes); 18 | } 19 | 20 | addNode(key: KeyType, node: ValueType) { 21 | return this.addInternalNode(key, node); 22 | } 23 | 24 | addNodes(nodes: Map) { 25 | this.addMultipleInternalNodes(nodes); 26 | } 27 | 28 | addEdge(fromKey: KeyType, toKey: KeyType) { 29 | assert(this.nodes.has(fromKey), `Source node with ${fromKey} key should exist`); 30 | assert(this.nodes.has(toKey), `Target node with ${toKey} key should exist`); 31 | 32 | const sourceNode = this.nodes.get(fromKey); 33 | const targetNode = this.nodes.get(toKey); 34 | 35 | assert.strictEqual(sourceNode !== undefined, true, `Source node with key ${fromKey} doesn't exist`); 36 | assert.strictEqual(targetNode !== undefined, true, `Target node with key ${toKey} doesn't exist`); 37 | 38 | assert.strictEqual( 39 | sourceNode!.children.has(toKey), 40 | false, 41 | `Source node ${fromKey} already has an adge to target node ${toKey}`, 42 | ); 43 | 44 | sourceNode!.children.set(toKey, targetNode!); 45 | } 46 | 47 | sort(): Map> { 48 | this.visitedNodes = new Set(); 49 | this.sortedKeysStack = []; 50 | const output = new Map>(); 51 | 52 | for (const [key] of this.nodes) { 53 | this.exploreNode(key, []); 54 | } 55 | 56 | for (let i = this.sortedKeysStack.length - 1; i >= 0; i--) { 57 | const node = this.nodes.get(this.sortedKeysStack[i])!; 58 | output.set(this.sortedKeysStack[i], node); 59 | } 60 | 61 | return output; 62 | } 63 | 64 | private exploreNode(nodeKey: KeyType, explorePath: KeyType[]) { 65 | const newExplorePath = [...explorePath, nodeKey]; 66 | 67 | // we should check circular dependencies starting from node 2 68 | if (explorePath.length) { 69 | assert( 70 | !explorePath.includes(nodeKey), 71 | `Node ${nodeKey} forms circular dependency: ${newExplorePath.join(' -> ')}`, 72 | ); 73 | } 74 | 75 | const node = this.nodes.get(nodeKey); 76 | if (this.visitedNodes.has(node!)) { 77 | return; 78 | } 79 | 80 | // mark node as visited so that it and its children 81 | // won't be explored next time 82 | this.visitedNodes.add(node!); 83 | 84 | for (const [childNodeKey] of node!.children) { 85 | this.exploreNode(childNodeKey, newExplorePath); 86 | } 87 | 88 | this.sortedKeysStack.push(nodeKey); 89 | } 90 | 91 | private addInternalNode(key: KeyType, node: ValueType) { 92 | assert.strictEqual(this.nodes.has(key), false, `Node ${key} already exists`); 93 | 94 | this.nodes.set(key, { 95 | children: new Map(), 96 | node, 97 | }); 98 | 99 | return this; 100 | } 101 | 102 | private addMultipleInternalNodes(nodes: Map) { 103 | const nodesFlat = [...nodes]; 104 | 105 | for (let i = nodes.size - 1; i >= 0; i--) { 106 | const [key, node] = nodesFlat[i]; 107 | this.addInternalNode(key, node); 108 | } 109 | } 110 | 111 | } 112 | 113 | export default TopologicalSort; 114 | export { TopologicalSort }; 115 | -------------------------------------------------------------------------------- /src/index.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { TopologicalSort } from '../src'; 3 | 4 | /** 5 | * Validation routine for edges 6 | * 7 | * @param {TopologicalSort} sortOp 8 | * @param {Array} edges 9 | */ 10 | function checkEdgesValid(sortOp, edges) { 11 | edges.forEach((edge) => sortOp.addEdge(edge.from, edge.to)); 12 | 13 | const res = sortOp.sort(); 14 | const sortedKeys = [...res.keys()]; 15 | 16 | edges.forEach((edge) => { 17 | assert( 18 | sortedKeys.indexOf(edge.from) < sortedKeys.indexOf(edge.to), 19 | `Node ${edge.from} should be placed before ${edge.to} in sorted output`, 20 | ); 21 | }); 22 | } 23 | 24 | describe('topological-sort', () => { 25 | it('should throw if addNode() is invoked with already existing node', () => { 26 | const nodes = new Map(); 27 | const sortOp = new TopologicalSort(nodes); 28 | 29 | assert.doesNotThrow(() => { 30 | sortOp.addNode(1, {}); 31 | }, 'addNode() should not throw if nodes list is empty'); 32 | 33 | assert.throws(() => { 34 | sortOp.addNode(1, {}); 35 | }, 'addNode() should throw an error if node with this key already exists'); 36 | }); 37 | 38 | it('should throw if addEdge() is invoked with wrong params', () => { 39 | const nodes = new Map([['A', 1], ['B', 2]]); 40 | const sortOp = new TopologicalSort(nodes); 41 | 42 | assert.doesNotThrow(() => { 43 | sortOp.addEdge('A', 'B'); 44 | }, 'addEdge() should not throw for valid params'); 45 | 46 | assert.throws(() => { 47 | sortOp.addEdge('A', 'C'); 48 | }, 'addNode() should throw an error if any of the params are not valid node keys'); 49 | 50 | assert.throws(() => { 51 | sortOp.addEdge('C', 'D'); 52 | }, 'addNode() should throw an error if any of the params are not valid node keys'); 53 | 54 | assert.throws(() => { 55 | sortOp.addEdge('A', 'C'); 56 | }, 'addNode() should throw an error if edge already exists'); 57 | }); 58 | 59 | it('should throw if addNodes() is invoked with already existing node', () => { 60 | const nodes = new Map([['A', 1], ['B', 2]]); 61 | const sortOp = new TopologicalSort(nodes); 62 | 63 | assert.throws(() => { 64 | const newNodes = new Map([['B', 3]]); 65 | sortOp.addNodes(newNodes); 66 | }, 'addNodes() should throw an error if nodes already exists'); 67 | }); 68 | 69 | it('should not change order of input nodes if no edges exist', () => { 70 | const nodes = new Map([['A', 1], ['B', 2], ['C', 3]]); 71 | const sortOp = new TopologicalSort(nodes); 72 | const res = sortOp.sort(); 73 | const sortedKeys = [...res.keys()]; 74 | 75 | assert.strictEqual(sortedKeys[0], 'A'); 76 | assert.strictEqual(sortedKeys[1], 'B'); 77 | assert.strictEqual(sortedKeys[2], 'C'); 78 | }); 79 | 80 | it('should return map after sort()', () => { 81 | const nodes = new Map([['A', 1], ['B', 2], ['C', 3]]); 82 | const sortOp = new TopologicalSort(nodes); 83 | const res = sortOp.sort(); 84 | 85 | assert.strictEqual(res instanceof Map, true, 'TopologicalSort sort() result variable should be a Map instance'); 86 | assert.strictEqual(res.size, 3); 87 | }); 88 | 89 | it('should sort nodes passed in constructor only + addEdge()', () => { 90 | const nodes = new Map([ 91 | ['A', 1], 92 | ['B', 2], 93 | ['C', 3], 94 | ['D', 4], 95 | ['E', 5], 96 | ['F', 6], 97 | ['G', 7], 98 | ['H', 8], 99 | ]); 100 | const sortOp = new TopologicalSort(nodes); 101 | const edges = [ 102 | {from: 'A', to: 'C'}, 103 | {from: 'B', to: 'C'}, 104 | {from: 'B', to: 'D'}, 105 | {from: 'C', to: 'E'}, 106 | {from: 'D', to: 'F'}, 107 | {from: 'E', to: 'F'}, 108 | {from: 'E', to: 'H'}, 109 | {from: 'F', to: 'G'}, 110 | ]; 111 | 112 | checkEdgesValid(sortOp, edges); 113 | }); 114 | 115 | it('should sort nodes passed in constructor + addNode()', () => { 116 | const nodes = new Map([['variables', 'file://...']]); 117 | const sortOp = new TopologicalSort(nodes); 118 | 119 | sortOp.addNode('mixins', 'file://...'); 120 | sortOp.addNode('block', 'file://...'); 121 | sortOp.addNode('block_mod_val', 'file://...'); 122 | 123 | const edges = [ 124 | {from: 'variables', to: 'mixins'}, 125 | {from: 'variables', to: 'block'}, 126 | {from: 'mixins', to: 'block'}, 127 | {from: 'block', to: 'block_mod_val'}, 128 | ]; 129 | 130 | checkEdgesValid(sortOp, edges); 131 | }); 132 | 133 | it('should sort nodes passed in constructor + addNodes()', () => { 134 | const nodes = new Map([['variables', 'file://...']]); 135 | const sortOp = new TopologicalSort(nodes); 136 | 137 | sortOp.addNodes(new Map([ 138 | ['mixins', 'file://...'], 139 | ['argument', 'file://...'], 140 | ['mixins', 'file://...'], 141 | ['user', 'file://...'], 142 | ['user-avatar', 'file://...'], 143 | ])); 144 | 145 | const edges = [ 146 | {from: 'variables', to: 'mixins'}, 147 | {from: 'variables', to: 'argument'}, 148 | {from: 'mixins', to: 'argument'}, 149 | {from: 'argument', to: 'user'}, 150 | {from: 'user-avatar', to: 'user'}, 151 | {from: 'variables', to: 'user-avatar'}, 152 | {from: 'mixins', to: 'user-avatar'}, 153 | {from: 'variables', to: 'user'}, 154 | {from: 'mixins', to: 'user'}, 155 | ]; 156 | 157 | checkEdgesValid(sortOp, edges); 158 | }); 159 | 160 | it('should sort nodes in the same order', () => { 161 | const nodes = new Map([ 162 | ['variables', 'file://...'], 163 | ['mixins', 'file://...'], 164 | ['argument', 'file://...'], 165 | ['mixins', 'file://...'], 166 | ['user', 'file://...'], 167 | ['user-avatar', 'file://...'], 168 | ]); 169 | const sortOp = new TopologicalSort(nodes); 170 | 171 | const edges = [ 172 | {from: 'variables', to: 'mixins'}, 173 | {from: 'variables', to: 'argument'}, 174 | {from: 'mixins', to: 'argument'}, 175 | {from: 'argument', to: 'user'}, 176 | {from: 'user-avatar', to: 'user'}, 177 | {from: 'variables', to: 'user-avatar'}, 178 | {from: 'mixins', to: 'user-avatar'}, 179 | {from: 'variables', to: 'user'}, 180 | {from: 'mixins', to: 'user'}, 181 | ]; 182 | 183 | edges.forEach(({from, to}) => sortOp.addEdge(from, to)); 184 | 185 | const res = sortOp.sort(); 186 | const sortedKeys = [...res.keys()]; 187 | 188 | sortOp.sort(); 189 | const sortedKeys2 = [...res.keys()]; 190 | 191 | sortedKeys.forEach((_, index) => { 192 | assert.strictEqual( 193 | sortedKeys[index], 194 | sortedKeys2[index], 195 | `Sorted nodes differ at index ${index}`, 196 | ); 197 | }); 198 | }); 199 | 200 | it('should throw if node edges form circular dependency', () => { 201 | const nodes = new Map([ 202 | ['variables', 'file://...'], 203 | ['mixins', 'file://...'], 204 | ['argument', 'file://...'], 205 | ['mixins', 'file://...'], 206 | ['user', 'file://...'], 207 | ['user-avatar', 'file://...'], 208 | ]); 209 | const sortOp = new TopologicalSort(nodes); 210 | 211 | const edges = [ 212 | {from: 'mixins', to: 'variables'}, 213 | {from: 'variables', to: 'argument'}, 214 | {from: 'mixins', to: 'argument'}, 215 | {from: 'argument', to: 'user'}, 216 | {from: 'user-avatar', to: 'user'}, 217 | {from: 'variables', to: 'user-avatar'}, 218 | {from: 'mixins', to: 'user-avatar'}, 219 | {from: 'variables', to: 'user'}, 220 | {from: 'user', to: 'mixins'}, 221 | ]; 222 | 223 | edges.forEach(({from, to}) => sortOp.addEdge(from, to)); 224 | 225 | assert.throws(() => { 226 | sortOp.sort(); 227 | }, 'sort() should throw if there are circular dependencies'); 228 | }); 229 | 230 | it('should throw if node edges form circular dependency starting from index 1', () => { 231 | const nodes = new Map([ 232 | ['A', 'file://...'], 233 | ['B', 'file://...'], 234 | ['C', 'file://...'], 235 | ['D', 'file://...'], 236 | ]); 237 | const sortOp = new TopologicalSort(nodes); 238 | 239 | const edges = [ 240 | {from: 'A', to: 'B'}, 241 | {from: 'B', to: 'C'}, 242 | {from: 'C', to: 'D'}, 243 | {from: 'D', to: 'B'}, 244 | ]; 245 | 246 | edges.forEach(({from, to}) => sortOp.addEdge(from, to)); 247 | 248 | assert.throws(() => { 249 | sortOp.sort(); 250 | }, 'sort() should throw if there are circular dependencies'); 251 | }); 252 | 253 | it('shouldn\'t throw for missing circular dependency', () => { 254 | const nodes = new Map([ 255 | ['B', 'file://...'], 256 | ['A', 'file://...'], 257 | ]); 258 | const sortOp = new TopologicalSort(nodes); 259 | sortOp.addEdge('A', 'B'); 260 | 261 | assert.doesNotThrow(() => { 262 | sortOp.sort(); 263 | }, 'sort() should not throw if node without dependencies is standing at index 0'); 264 | }); 265 | 266 | it('shouldn\'t loose content associated with map keys after sort', () => { 267 | const nodes = new Map([ 268 | ['A', 'some A contents'], 269 | ['B', 'some B contents'], 270 | ]); 271 | const sortOp = new TopologicalSort(nodes); 272 | sortOp.addEdge('A', 'B'); 273 | 274 | const sorted = sortOp.sort(); 275 | assert.strictEqual(sorted.get('A')!.node, 'some A contents'); 276 | assert.strictEqual(sorted.get('B')!.node, 'some B contents'); 277 | }); 278 | }); 279 | --------------------------------------------------------------------------------