195 lines
3.8 KiB
TypeScript
195 lines
3.8 KiB
TypeScript
import type { ProjectGraph } from '@nrwl/devkit';
|
|
import { checkCircularPath } from './graph-utils';
|
|
|
|
describe('should find the path between nodes', () => {
|
|
it('should return empty path when when there are no connecting edges', () => {
|
|
/*
|
|
|
|
A -> B -> C - > E
|
|
|
|
*/
|
|
|
|
const graph = {
|
|
nodes: ['A', 'B'],
|
|
dependencies: {
|
|
A: ['B'],
|
|
},
|
|
};
|
|
|
|
const g = transformGraph(graph);
|
|
const path = getPath(g, { from: 'B', to: 'A' });
|
|
|
|
expect(path).toEqual([]);
|
|
});
|
|
|
|
it('should find direct path', () => {
|
|
/*
|
|
|
|
A -> B
|
|
|
|
*/
|
|
|
|
const graph = {
|
|
nodes: ['A', 'B'],
|
|
dependencies: {
|
|
A: ['B'],
|
|
},
|
|
};
|
|
|
|
const g = transformGraph(graph);
|
|
const path = getPath(g, { from: 'A', to: 'B' });
|
|
|
|
expect(path).toEqual(['A', 'B']);
|
|
});
|
|
|
|
it('should find indirect path', () => {
|
|
/*
|
|
|
|
A -> B -> E -> F
|
|
\
|
|
C -> D
|
|
|
|
*/
|
|
|
|
const graph = {
|
|
nodes: ['A', 'B', 'C', 'D', 'E', 'F'],
|
|
dependencies: {
|
|
A: ['B'],
|
|
B: ['C', 'E'],
|
|
C: ['D'],
|
|
E: ['F'],
|
|
},
|
|
};
|
|
|
|
const g = transformGraph(graph);
|
|
const path = getPath(g, { from: 'A', to: 'F' });
|
|
|
|
expect(path).toEqual(['A', 'B', 'E', 'F']);
|
|
});
|
|
|
|
it('should find indirect path in a graph that has a simple cycle', () => {
|
|
/*
|
|
|
|
A -> B -> C -> F
|
|
\ /
|
|
E <-- D
|
|
|
|
*/
|
|
|
|
const graph = {
|
|
nodes: ['A', 'B', 'C', 'D', 'E', 'F'],
|
|
dependencies: {
|
|
A: ['B'],
|
|
B: ['C'],
|
|
C: ['D', 'F'],
|
|
D: ['E'],
|
|
E: ['A'],
|
|
},
|
|
};
|
|
|
|
const g = transformGraph(graph);
|
|
const path = getPath(g, { from: 'A', to: 'F' });
|
|
|
|
expect(path).toEqual(['A', 'B', 'C', 'F']);
|
|
});
|
|
|
|
it('should find indirect path in a graph that has a cycle', () => {
|
|
/*
|
|
|
|
B <- A -> D -> E
|
|
\ /^
|
|
C
|
|
|
|
*/
|
|
|
|
const graph = {
|
|
nodes: ['A', 'B', 'C', 'D', 'E'],
|
|
dependencies: {
|
|
A: ['B', 'D'],
|
|
B: ['C'],
|
|
C: ['A'],
|
|
D: ['E'],
|
|
},
|
|
};
|
|
|
|
const g = transformGraph(graph);
|
|
const path = getPath(g, { from: 'A', to: 'E' });
|
|
|
|
expect(path).toEqual(['A', 'D', 'E']);
|
|
});
|
|
|
|
it('should find indirect path in a graph with inner and outer cycles', () => {
|
|
/*
|
|
|
|
A --> B
|
|
/^ \ /^ \
|
|
C D E F
|
|
^\ / ^\ /
|
|
G <-- H
|
|
|
|
*/
|
|
|
|
const graph = {
|
|
nodes: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'],
|
|
dependencies: {
|
|
A: ['B', 'D'],
|
|
B: ['F'],
|
|
C: ['A'],
|
|
D: ['G'],
|
|
E: ['B'],
|
|
F: ['H'],
|
|
G: ['C'],
|
|
H: ['G', 'E'],
|
|
},
|
|
};
|
|
|
|
const g = transformGraph(graph);
|
|
|
|
const path1 = getPath(g, { from: 'A', to: 'H' });
|
|
expect(path1).toEqual(['A', 'B', 'F', 'H']);
|
|
|
|
const path2 = getPath(g, { from: 'A', to: 'G' });
|
|
expect(path2).toEqual(['A', 'D', 'G']);
|
|
|
|
const path3 = getPath(g, { from: 'B', to: 'D' });
|
|
expect(path3).toEqual(['B', 'F', 'H', 'G', 'C', 'A', 'D']);
|
|
});
|
|
});
|
|
|
|
interface SimplifiedGraph {
|
|
nodes: Array<string>;
|
|
dependencies: Record<string, Array<string>>;
|
|
}
|
|
|
|
function transformGraph(graph: SimplifiedGraph): ProjectGraph {
|
|
const nodes = graph.nodes.reduce((acc, name) => {
|
|
return {
|
|
...acc,
|
|
[name]: { name, type: 'app' },
|
|
};
|
|
}, {});
|
|
|
|
const dependencies = Object.keys(graph.dependencies).reduce((acc, dep) => {
|
|
const targets = graph.dependencies[dep].reduce((acc, name) => {
|
|
return [...acc, { source: dep, target: name }];
|
|
}, []);
|
|
|
|
return {
|
|
...acc,
|
|
[dep]: targets,
|
|
};
|
|
}, {});
|
|
|
|
return {
|
|
nodes,
|
|
dependencies,
|
|
};
|
|
}
|
|
|
|
function getPath(graph: ProjectGraph, node: { from: string; to: string }) {
|
|
const src = graph.nodes[node.to];
|
|
const dest = graph.nodes[node.from];
|
|
const path = checkCircularPath(graph, src, dest);
|
|
return path.map((n) => n.name);
|
|
}
|