diff --git a/src/composables/LayoutMainChain.ts b/src/composables/LayoutMainChain.ts index b979bb54fcbe87e4280113037be324ab4389e2c7..491995d0200354655a6c96a4fd470e1c47b3675f 100644 --- a/src/composables/LayoutMainChain.ts +++ b/src/composables/LayoutMainChain.ts @@ -9,7 +9,8 @@ import { DFSWithSources, DFSsourceDAG } from "./AlgorithmDFS"; import { networkToGDSGraph } from "./ConvertFromNetwork"; import { getStartNodes } from "./CalculateStartNodes"; import { BFS } from "./AlgorithmBFS"; -import { addSubgraphToNetwork, createSubgraph, updateNodeMetadataSubgraph } from './SubgraphForSubgraphNetwork'; +import { addSubgraphToNetwork, createSubgraph } from './SubgraphForSubgraphNetwork'; +import { path } from "d3"; /** * This file contains functions find and add main chains and mini-branch (form main chain) in a subgraphNetwork. @@ -93,11 +94,7 @@ export async function addMainChainFromSources(subgraphNetwork:SubgraphNetwork, s // create subgraph and add it const newMainChainId="mainChain__"+mainChainID; const newMainChain= createSubgraph(newMainChainId, mainChain.nodes,[],TypeSubgraph.MAIN_CHAIN); - mainChains[newMainChainId]=newMainChain; - // add metadata for node in cluster - mainChain.nodes.forEach(nodeID=>{ - updateNodeMetadataSubgraph(network, nodeID, newMainChainId, TypeSubgraph.MAIN_CHAIN); - }); + subgraphNetwork=addSubgraphToNetwork(subgraphNetwork,newMainChain,TypeSubgraph.MAIN_CHAIN); } }); @@ -163,35 +160,37 @@ export async function addMiniBranchToMainChain(subgraphNetwork:SubgraphNetwork): */ export async function getPathSourcesToTargetNode(network:Network, sources:string[],merge:boolean=true,pathType:PathType=PathType.ALL_LONGEST):Promise<{[key:string]:{nodes:Array<string>, height:number}}>{ - //console.log('DAG_Dijkstra'); - let pathsFromSources:{[key:string]:{nodes:Array<string>, height:number}}={}; // for each source : do an independant dfs - sources.forEach(async source=>{ + for (const source of sources){ // DFS to get a DAG from this source, and get topological sort const {dfs,graph}=await DFSsourceDAG(network,[source]); // get max distance from source node for all nodes, and by which parent nodes the node had been accessed const {distances, parents}=await DistanceFromSourceDAG(graph,dfs,pathType); + console.log('distances',distances); // get the farthest node from source (node with max distance) const targetNodes=findMaxKeys(distances); // for each target node : (if several path wanted) if (pathType==PathType.ALL_LONGEST || pathType==PathType.ALL){ - targetNodes.key.forEach(async target => { + for (const target of targetNodes.key){ // get the parents that goes from source to target node + console.log('parents',parents); + console.log('target',target); const nodesBetweenSourceTarget=BFS(parents,target); // merge with an existing path if node in common // height is the max distance +1 pathsFromSources=await mergeNewPath(source,{nodes:nodesBetweenSourceTarget, height:targetNodes.max+1},pathsFromSources,merge); - }); + console.log('pathsFromSources',pathsFromSources); + }; } else if(pathType==PathType.LONGEST){ // if only one path wanted : take the first // get the parents that goes from source to target node const nodesBetweenSourceTarget=BFS(parents,targetNodes.key[0]); // merge with an existing path if node in common - pathsFromSources= await mergeNewPath(source,{nodes:nodesBetweenSourceTarget, height:targetNodes.max},pathsFromSources,merge); + pathsFromSources= await mergeNewPath(source,{nodes:nodesBetweenSourceTarget, height:targetNodes.max+1},pathsFromSources,merge); } - }); + } return pathsFromSources; } @@ -245,7 +244,6 @@ async function DistanceFromSourceDAG(graph:{[key:string]:Function}, topologicalO } }) }); - return {distances:distanceFromSource, parents:parentsFromSource}; } @@ -282,30 +280,53 @@ function findMaxKeys(obj: { [key: string]: number }): {key:string[],max:number} */ async function mergeNewPath(source:string,newPath:{nodes:Array<string>, height:number},pathsFromSources:{[key:string]:{nodes:Array<string>, height:number}}, merge:boolean=true):Promise<{[key:string]:{nodes:Array<string>, height:number}}>{ const keys=Object.keys(pathsFromSources).sort(); - let hasmerged=false; - if (merge) { - keys.forEach(key=>{ - const pathNodes = pathsFromSources[key].nodes; - // Check for common nodes, but target nodes - const commonNodes = pathNodes.find(node => newPath.nodes.includes(node)); - if (commonNodes) { + + // if no path in the object : add the new path + if (keys.length===0){ + pathsFromSources[source]=newPath; + return pathsFromSources; + } + + // for each path in the object + keys.forEach(key=>{ + const pathNodes = pathsFromSources[key].nodes; + console.log('pathNodes',pathNodes); + // Check for common nodes + const commonNodes = pathNodes.find(node => newPath.nodes.includes(node)); + console.log('commonNodes',commonNodes); + if (commonNodes && commonNodes.length>0){ + console.log('commonNodes',commonNodes); + if(merge){ // Merge paths const mergedPath = Array.from(new Set(pathNodes.concat(newPath.nodes))); - // Create new key - const newKey = `${key}__${source}`; + console.log('mergedPath',mergedPath); + // Create new key if necessary + let newKey:string = key; + const sourceAlreadyInKey=key.split('__').includes(source); + if(!sourceAlreadyInKey){ + newKey= `${key}__${source}`; + } // Update pathsFromSources object const newheight=newPath.height>pathsFromSources[key].height?newPath.height:pathsFromSources[key].height; pathsFromSources[newKey] = {nodes:mergedPath,height:newheight}; - // Remove old key - delete pathsFromSources[key]; - hasmerged=true; + + // Remove old key if the name has changed + if(!sourceAlreadyInKey){ + delete pathsFromSources[key]; + } + }else{ + // Highest path is kept + if(newPath.height>pathsFromSources[key].height){ + delete pathsFromSources[key]; + pathsFromSources[source]=newPath; + } } - }); - } - // if no merge : added on it's own - if (!hasmerged){ - pathsFromSources[source]=newPath; - } + }else{ + // If no common nodes : path added on it's own + if(pathsFromSources[source]) throw new Error('source already in pathsFromSources, but supposed to have no common nodes'); + pathsFromSources[source]=newPath; + } + }); return pathsFromSources; } diff --git a/src/composables/LayoutManageSideCompounds.ts b/src/composables/LayoutManageSideCompounds.ts index 0c580efee35956ed338881bee081874d2f0c16f5..5b51fde891450e47d478cbb5554dbe8a61722ba2 100644 --- a/src/composables/LayoutManageSideCompounds.ts +++ b/src/composables/LayoutManageSideCompounds.ts @@ -10,11 +10,6 @@ import { removeAllSelectedNodes , duplicateAllNodesByAttribut} from "./VizCoreFu import { getMeanNodesSizePixel, inchesToPixels, minEdgeLength, pixelsToInches } from "./CalculateSize"; import { sideCompoundAttribute,isDuplicate, isSideCompound, setAsSideCompound } from "./GetSetAttributsNodes"; -// General imports -//import { e, S } from "vitest/dist/reporters-1evA5lom"; -import { c } from "vite/dist/node/types.d-aGj9QkWt"; -import { resolve } from "path"; -import { error } from "console"; diff --git a/src/composables/LayoutSugiyama.ts b/src/composables/LayoutSugiyama.ts index b5e730ede497eaaf08e41699a0f7f5b7b2bad54d..117ff68305d1f7247f11602eece3fada55daa481 100644 --- a/src/composables/LayoutSugiyama.ts +++ b/src/composables/LayoutSugiyama.ts @@ -8,13 +8,7 @@ import { networkToDOT } from './ConvertFromNetwork'; import { changeNetworkFromViz } from './ConvertToNetwork'; // General imports -//import * as d3 from 'd3'; -import { reactive } from "vue"; -// import cytoscape from 'cytoscape'; -// import fcose from 'cytoscape-fcose'; -// import cosebilkent from 'cytoscape-cose-bilkent'; -import dagre from 'dagrejs'; -import { Graph, instance } from "@viz-js/viz"; +import { instance } from "@viz-js/viz"; /** diff --git a/src/composables/__tests__/LayoutMainChain.test.ts b/src/composables/__tests__/LayoutMainChain.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..d18827b615941c4e467cfca0d4273e78053754a3 --- /dev/null +++ b/src/composables/__tests__/LayoutMainChain.test.ts @@ -0,0 +1,690 @@ +// Type imports +import { PathType, StartNodesType } from "../../types/EnumArgs"; +import { SubgraphNetwork } from "../../types/SubgraphNetwork"; +import {TypeSubgraph, type Subgraph} from "../../types/Subgraph"; +import { Network } from "../../types/TypeVizCore"; + +// Composable imports +import * as LayoutMainChain from "../LayoutMainChain"; +import * as AlgorithmDFS from "../AlgorithmDFS"; +import * as ConvertFromNetwork from "../ConvertFromNetwork"; +import * as CalculateStartNodes from "../CalculateStartNodes"; +import * as AlgorithmBFS from "../AlgorithmBFS"; +import * as SubgraphForSubgraphNetwork from '../SubgraphForSubgraphNetwork'; +import { before } from "node:test"; + + +describe('LayoutMainChain', () => { + + const createSubgraphMock = jest.spyOn(SubgraphForSubgraphNetwork, 'createSubgraph'); + const networkToGDSGraphMock = jest.spyOn(ConvertFromNetwork, 'networkToGDSGraph'); + + const addSubgraphToNetworkMock=jest.spyOn(SubgraphForSubgraphNetwork, 'addSubgraphToNetwork'); + addSubgraphToNetworkMock.mockImplementation((subgraphNetwork:SubgraphNetwork,subgraph:Subgraph,type:TypeSubgraph) => { + if (!subgraphNetwork[type]) subgraphNetwork[type]={}; + if(!(subgraph.name in subgraphNetwork[type])){ + subgraphNetwork[type][subgraph.name]=subgraph; + } + return subgraphNetwork; + }); + + let network: Network; + let subgraphNetwork: SubgraphNetwork; + + beforeEach(() => { + network = { + id:"network", + nodes:{}, + links:[] + }; + + subgraphNetwork={ + network: network, + networkStyle: {}, + }; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('addMainChainFromSources', () => { + + const getStartNodesMock= jest.spyOn(CalculateStartNodes, 'getStartNodes'); + getStartNodesMock.mockReturnValue(Promise.resolve(["A", "B"])); + + beforeEach(() => { + + createSubgraphMock.mockImplementation((name:string,nodes:string[])=>{ + return { + name: name, + nodes: nodes, + type: TypeSubgraph.MAIN_CHAIN, + } + + }); + + }); + + + it('shouldn t use getStartNodes because already list of sources', async () => { + // DATA + const sources=["A", "B"]; + const getMainChains= async function(){ + return {}; + } + + // TEST + await LayoutMainChain.addMainChainFromSources(subgraphNetwork, sources,getMainChains); + + // EXPECT + expect(getStartNodesMock).not.toHaveBeenCalled(); + }); + + it('should use getStartNodes because already list of sources', async () => { + // DATA + const sources=StartNodesType.RANK_ONLY; + const getMainChains= async function(){ + return {}; + } + + // TEST + await LayoutMainChain.addMainChainFromSources(subgraphNetwork, sources,getMainChains); + + // EXPECT + expect(getStartNodesMock).toHaveBeenCalledTimes(1); + }); + + it('should add 2 mainChain', async () => { + // DATA + const getMainChains= async function(network: Network, sources: Array<string>): + Promise<{[key:string]:{nodes:Array<string>, height:number}}>{ + return {mainChain1:{nodes:["A", "B"],height:8}, mainChain2:{nodes:["C", "D"],height:8}}; + } + const mainChainExpected : {[key:string]:Subgraph}={ + mainChain__mainChain1: { + name: 'mainChain__mainChain1', + nodes: [ 'A', 'B' ], + type: TypeSubgraph.MAIN_CHAIN + }, + mainChain__mainChain2: { + name: 'mainChain__mainChain2', + nodes: [ 'C', 'D' ], + type: TypeSubgraph.MAIN_CHAIN + } + }; + + // TEST + await LayoutMainChain.addMainChainFromSources(subgraphNetwork, StartNodesType.RANK_ONLY,getMainChains); + + // EXPECT + expect(createSubgraphMock).toHaveBeenCalledTimes(2); + expect(addSubgraphToNetworkMock).toHaveBeenCalledTimes(2); + expect(subgraphNetwork[TypeSubgraph.MAIN_CHAIN]).toBeDefined(); + expect(subgraphNetwork[TypeSubgraph.MAIN_CHAIN]).toEqual(mainChainExpected); + + }); + + it('should only add 1 mainChain bacause one is to small', async () => { + // DATA + const minHeight=8; + const getMainChains= async function(network: Network, sources: Array<string>): + Promise<{[key:string]:{nodes:Array<string>, height:number}}>{ + return {mainChain1:{nodes:["A", "B"],height:3}, mainChain2:{nodes:["C", "D"],height:8}}; + } + const mainChainExpected : {[key:string]:Subgraph}={ + mainChain__mainChain2: { + name: 'mainChain__mainChain2', + nodes: [ 'C', 'D' ], + type: TypeSubgraph.MAIN_CHAIN + } + }; + + // TEST + await LayoutMainChain.addMainChainFromSources(subgraphNetwork, StartNodesType.RANK_ONLY,getMainChains,true,PathType.ALL,minHeight); + + // EXPECT + expect(createSubgraphMock).toHaveBeenCalledTimes(1); + expect(addSubgraphToNetworkMock).toHaveBeenCalledTimes(1); + expect(subgraphNetwork[TypeSubgraph.MAIN_CHAIN]).toBeDefined(); + expect(subgraphNetwork[TypeSubgraph.MAIN_CHAIN]).toEqual(mainChainExpected); + + }); + + + }); + + describe('addMiniBranchToMainChain', () => { + + beforeEach(() => { + + createSubgraphMock.mockImplementation((name:string,nodes:string[],classes: Array<string>, type: TypeSubgraph, parentSubgraph?: {name:string,type:TypeSubgraph})=>{ + return { + name: name, + nodes: nodes, + type: TypeSubgraph.SECONDARY_CHAIN, + parentSubgraph: parentSubgraph + } + + }); + + networkToGDSGraphMock.mockImplementation(async (network)=>{ + return { + outdegree: jest.fn((id: string) => { + if (id === 'F'|| id === 'E') return 0; + return 1; + }), + adjacent:jest.fn((id: string) => { + switch (id) { + case 'A': + return ['B', 'D']; + case 'B': + return ['C']; + case 'C': + return ['F']; + case 'D': + return ['E']; + default: + return []; + } + + }) + } + }); + + }); + + it('shouldn t add miniBranch because no mainChain', async () => { + + // TEST + await LayoutMainChain.addMiniBranchToMainChain(subgraphNetwork); + + // EXPECT + expect(networkToGDSGraphMock).not.toHaveBeenCalled(); + + }); + + it('shouldn t add miniBranch because no product of main chain has outdegree of 0', async () => { + // DATA + subgraphNetwork[TypeSubgraph.MAIN_CHAIN]={ + mainChain1: { + name: 'mainChain1', + nodes: [ 'A' ,'B'], + type: TypeSubgraph.MAIN_CHAIN + } + }; + + // TEST + await LayoutMainChain.addMiniBranchToMainChain(subgraphNetwork); + + // EXPECT + expect(networkToGDSGraphMock).toHaveBeenCalledTimes(1); + if(subgraphNetwork[TypeSubgraph.SECONDARY_CHAIN]){ + expect(subgraphNetwork[TypeSubgraph.SECONDARY_CHAIN]).toEqual({}); + } + + }); + + + it('should add miniBranch', async () => { + // DATA + subgraphNetwork[TypeSubgraph.MAIN_CHAIN]={ + mainChain1: { + name: 'mainChain1', + nodes: [ 'A' ,'C'], + type: TypeSubgraph.MAIN_CHAIN + }, + mainChain2: { + name: 'mainChain2', + nodes: [ 'D' ], + type: TypeSubgraph.MAIN_CHAIN + } + }; + + const secondaryChainExpected : {[key:string]:Subgraph}={ + minibranch_mainChain1: { + name: 'minibranch_mainChain1', + nodes: [ 'F' ], + type: TypeSubgraph.SECONDARY_CHAIN, + parentSubgraph: { name: 'mainChain1', type: TypeSubgraph.MAIN_CHAIN } + }, + minibranch_mainChain2: { + name: 'minibranch_mainChain2', + nodes: [ 'E' ], + type: TypeSubgraph.SECONDARY_CHAIN, + parentSubgraph: { name: 'mainChain2', type: TypeSubgraph.MAIN_CHAIN } + } + } + + // TEST + await LayoutMainChain.addMiniBranchToMainChain(subgraphNetwork); + + // EXPECT + expect(networkToGDSGraphMock).toHaveBeenCalledTimes(1); + expect(createSubgraphMock).toHaveBeenCalledTimes(2); + expect(subgraphNetwork[TypeSubgraph.SECONDARY_CHAIN]).toBeDefined(); + expect(subgraphNetwork[TypeSubgraph.SECONDARY_CHAIN]).toEqual(secondaryChainExpected); + + }); + + + }); + + describe('getPathSourcesToTargetNode', () => { + + let DFSsourceDAGMock = jest.spyOn(AlgorithmDFS, 'DFSsourceDAG'); + let BFSMock = jest.spyOn(AlgorithmBFS, 'BFS'); + + let sources: Array<string>; + let graphGDS:{[key:string]:Function}; + + + describe('case one source with one target', () => { + + beforeEach(() => { + // DATA + sources=["A"]; + + // MOCK + graphGDS={ + adjacent:jest.fn((id: string) => { + switch (id) { + case 'A': + return ['B', 'F','E']; + case 'B': + return ['C']; + case 'C': + return ['D']; + case 'E': + return ['D']; + case 'F': + return ['G']; + case 'G': + return ['D']; + default: + return []; + } + + }), + getEdgeWeight:jest.fn(() => (1)), + nodes: jest.fn(() => (['A', 'B', 'C', 'D', 'E', 'F', 'G'])) + } + + networkToGDSGraphMock.mockImplementation(async ()=>{ + return graphGDS; + }); + + DFSsourceDAGMock.mockReturnValue(Promise.resolve( + {dfs:[ + 'A', 'F', 'G', + 'E', 'B', 'C', + 'D' + ], + graph:graphGDS} + )); + }); + + + test ('all longest path', async () => { + + BFSMock.mockReturnValue([ 'D', 'G', 'C', 'F', 'B', 'A' ]); + + // DATA + const pathExpected = { A: { nodes: [ 'D', 'G', 'C', 'F', 'B', 'A' ], height: 4 } }; + + + // TEST + const result = await LayoutMainChain.getPathSourcesToTargetNode(network, sources, true ,PathType.ALL_LONGEST); + + // EXPECT + expect(DFSsourceDAGMock).toHaveBeenCalledTimes(1); + expect(BFSMock).toHaveBeenCalledTimes(1); + expect(BFSMock).toHaveBeenCalledWith({ + A: [], + F: [ 'A' ], + G: [ 'F' ], + E: [ 'A' ], + B: [ 'A' ], + C: [ 'B' ], + D: [ 'G', 'C' ] + }, 'D'); + expect(result).toEqual(pathExpected); + + + }); + + + + test ('longest path', async () => { + + BFSMock.mockReturnValue([ 'D', 'G', 'F','A' ]); + + // DATA + const pathExpected = { A: { nodes: [ 'D', 'G', 'F','A' ], height: 4 } }; + + // TEST + const result = await LayoutMainChain.getPathSourcesToTargetNode(network, sources, true ,PathType.LONGEST); + + // EXPECT + expect(DFSsourceDAGMock).toHaveBeenCalledTimes(1); + expect(BFSMock).toHaveBeenCalledTimes(1); + expect(BFSMock).toHaveBeenCalledWith({ + A: [], + F: [ 'A' ], + G: [ 'F' ], + E: [ 'A' ], + B: [ 'A' ], + C: [ 'B' ], + D: [ 'G' ] + }, 'D'); + expect(result).toEqual(pathExpected); + + + }); + + test ('all path', async () => { + + BFSMock.mockReturnValue([ 'D','G','E', 'C', 'F','A','B' ]); + + // DATA + const pathExpected = { + A: { nodes: [ 'D','G','E', 'C', 'F','A','B' ], height: 4 } + }; + + // TEST + const result = await LayoutMainChain.getPathSourcesToTargetNode(network, sources, true ,PathType.ALL); + + // EXPECT + expect(DFSsourceDAGMock).toHaveBeenCalledTimes(1); + expect(BFSMock).toHaveBeenCalledTimes(1); + expect(BFSMock).toHaveBeenCalledWith({ + A: [], + F: [ 'A' ], + G: [ 'F' ], + E: [ 'A' ], + B: [ 'A' ], + C: [ 'B' ], + D: [ 'G', 'E', 'C' ] + }, 'D'); + expect(result).toEqual(pathExpected); + + + }); + + }); + + describe('case one source with several targets', () => { + + + beforeEach(() => { + // DATA + sources=["A"]; + + // MOCK + graphGDS={ + adjacent:jest.fn((id: string) => { + switch (id) { + case 'A': + return ['B','D']; + case 'B': + return ['C']; + case 'D': + return ['E']; + default: + return []; + } + + }), + getEdgeWeight:jest.fn(() => (1)), + nodes: jest.fn(() => (['A', 'B', 'C', 'D', 'E'])) + } + + networkToGDSGraphMock.mockImplementation(async ()=>{ + return graphGDS; + }); + + DFSsourceDAGMock.mockReturnValue(Promise.resolve( + {dfs:[ + 'A','D','E', 'B', 'C', + ], + graph:graphGDS} + )); + + + }); + + test('case of merge', async () => { + // MOCK + BFSMock.mockReturnValueOnce([ 'C','B','A']); + BFSMock.mockReturnValueOnce([ 'E','D','A']); + + // DATA + const pathExpected = { + A: { nodes: [ 'C','B','A','E','D' ], height: 3 } + }; + + // TEST + const result = await LayoutMainChain.getPathSourcesToTargetNode(network, sources, true ,PathType.ALL_LONGEST); + + // EXPECT + expect(DFSsourceDAGMock).toHaveBeenCalledTimes(1); + expect(BFSMock).toHaveBeenCalledTimes(2); + expect(BFSMock).toHaveBeenCalledWith({ A: [], D: [ 'A' ], E: [ 'D' ], B: [ 'A' ], C: [ 'B' ] }, 'C'); + expect(BFSMock).toHaveBeenLastCalledWith({ A: [], D: [ 'A' ], E: [ 'D' ], B: [ 'A' ], C: [ 'B' ] }, 'E'); + expect(result).toEqual(pathExpected); + + }); + + test('case of no merge', async () => { + // MOCK + BFSMock.mockReturnValueOnce([ 'C','B','A']); + BFSMock.mockReturnValueOnce([ 'E','D','A']); + + // DATA + const pathExpected = { + A: { nodes: [ 'C','B','A' ], height: 3 } + }; + + // TEST + const result = await LayoutMainChain.getPathSourcesToTargetNode(network, sources, false ,PathType.ALL_LONGEST); + + // EXPECT + expect(DFSsourceDAGMock).toHaveBeenCalledTimes(1); + expect(BFSMock).toHaveBeenCalledTimes(2); + expect(BFSMock).toHaveBeenCalledWith({ A: [], D: [ 'A' ], E: [ 'D' ], B: [ 'A' ], C: [ 'B' ] }, 'C'); + expect(BFSMock).toHaveBeenLastCalledWith({ A: [], D: [ 'A' ], E: [ 'D' ], B: [ 'A' ], C: [ 'B' ] }, 'E'); + expect(result).toEqual(pathExpected); + + }); + + }); + + describe('case multiple sources with one target for each', () => { + + beforeEach(() => { + // DATA + sources=["A","D"]; + + // MOCK + graphGDS={ + adjacent:jest.fn((id: string) => { + switch (id) { + case 'A': + return ['B']; + case 'B': + return ['C','F']; + case 'D': + return ['G']; + case 'E': + return ['F']; + case 'G': + return ['E']; + case 'F': + return ['H']; + default: + return []; + } + + }), + getEdgeWeight:jest.fn(() => (1)), + nodes: jest.fn(() => (['A', 'B', 'C', 'D', 'E', 'F','G','H'])) + } + + networkToGDSGraphMock.mockImplementation(async ()=>{ + return graphGDS; + }); + + }); + + test ('independant case', async () => { + // MOCK + graphGDS={ + adjacent:jest.fn((id: string) => { + switch (id) { + case 'A': + return ['B']; + case 'B': + return ['C']; + case 'D': + return ['E']; + case 'E': + return ['F']; + default: + return []; + } + + }), + getEdgeWeight:jest.fn(() => (1)), + nodes: jest.fn(() => (['A', 'B', 'C', 'D', 'E', 'F'])) + } + + networkToGDSGraphMock.mockImplementation(async ()=>{ + return graphGDS; + }); + + // DFS start : A + DFSsourceDAGMock.mockReturnValueOnce(Promise.resolve( + {dfs:[ + 'A', 'B', 'C', + ], + graph:graphGDS} + )); + // DFS start : D + DFSsourceDAGMock.mockReturnValueOnce(Promise.resolve( + {dfs:[ + 'D', 'E', 'F', + ], + graph:graphGDS} + )); + + // BFS start : C + BFSMock.mockReturnValueOnce([ 'C','B','A' ]); + // BFS start : F + BFSMock.mockReturnValueOnce([ 'F','E','D' ]); + + // DATA + const pathExpected = { + A: { nodes: [ 'C','B','A' ], height: 3 }, + D: { nodes: [ 'F','E','D' ], height: 3 } + }; + + // TEST + const result = await LayoutMainChain.getPathSourcesToTargetNode(network, sources, true ,PathType.ALL); + + // EXPECT + expect(DFSsourceDAGMock).toHaveBeenCalledTimes(2); + expect(DFSsourceDAGMock).toHaveBeenCalledWith(expect.anything(),['A']); + expect(DFSsourceDAGMock).toHaveBeenLastCalledWith(expect.anything(),['D']); + expect(BFSMock).toHaveBeenCalledTimes(2); + expect(BFSMock).toHaveBeenCalledWith({ A: [], B: [ 'A' ], C: [ 'B' ] }, 'C'); + expect(BFSMock).toHaveBeenLastCalledWith({ D: [], E: [ 'D' ], F: [ 'E' ]}, 'F'); + expect(result).toEqual(pathExpected); + }); + + test ('dependant case (same unique target), merge = true', async () => { + // MOCK + + // DFS start : A + DFSsourceDAGMock.mockReturnValueOnce(Promise.resolve( + {dfs:[ + 'A', 'B','F','H', 'C', + ], + graph:graphGDS} + )); + + // DFS start : D + DFSsourceDAGMock.mockReturnValueOnce(Promise.resolve( + {dfs:[ + 'D', 'G','E', 'F','H', + ], + graph:graphGDS} + )); + + // BFS start : H (DFS with A) + BFSMock.mockReturnValueOnce(['H','F','B','A' ]); + // BFS start : H (DFS with D) + BFSMock.mockReturnValueOnce([ 'H','F','E','G','D' ]); + + const pathExpected = { A__D: { nodes: [ 'H','F','B','A','E','G','D' ], height: 5 } }; + + // TEST + const result = await LayoutMainChain.getPathSourcesToTargetNode(network, sources, true ,PathType.ALL); + + // EXPECT + expect(DFSsourceDAGMock).toHaveBeenCalledTimes(2); + expect(DFSsourceDAGMock).toHaveBeenCalledWith(expect.anything(),['A']); + expect(DFSsourceDAGMock).toHaveBeenLastCalledWith(expect.anything(),['D']); + expect(BFSMock).toHaveBeenCalledTimes(2); + expect(BFSMock).toHaveBeenCalledWith({ A: [], B: [ 'A' ], F: [ 'B' ], H: [ 'F' ], C: [ 'B' ] }, 'H'); + expect(BFSMock).toHaveBeenLastCalledWith({ D: [], G: [ 'D' ], E: [ 'G' ], F: [ 'E' ], H: [ 'F' ] }, 'H'); + expect(result).toEqual(pathExpected); + + }); + + test ('dependant case, merge = false', async () => { + + // MOCK + + // DFS start : A + DFSsourceDAGMock.mockReturnValueOnce(Promise.resolve( + {dfs:[ + 'A', 'B','F','H', 'C', + ], + graph:graphGDS} + )); + + // DFS start : D + DFSsourceDAGMock.mockReturnValueOnce(Promise.resolve( + {dfs:[ + 'D', 'G','E', 'F','H', + ], + graph:graphGDS} + )); + + // BFS start : H (DFS with A) + BFSMock.mockReturnValueOnce(['H','F','B','A' ]); + // BFS start : H (DFS with D) + BFSMock.mockReturnValueOnce([ 'H','F','E','G','D' ]); + + const pathExpected = { D: { nodes: ['H','F','E','G','D' ], height: 5 } }; + + // TEST + const result = await LayoutMainChain.getPathSourcesToTargetNode(network, sources, false ,PathType.ALL); + + // EXPECT + expect(DFSsourceDAGMock).toHaveBeenCalledTimes(2); + expect(DFSsourceDAGMock).toHaveBeenCalledWith(expect.anything(),['A']); + expect(DFSsourceDAGMock).toHaveBeenLastCalledWith(expect.anything(),['D']); + expect(BFSMock).toHaveBeenCalledTimes(2); + expect(BFSMock).toHaveBeenCalledWith({ A: [], B: [ 'A' ], F: [ 'B' ], H: [ 'F' ], C: [ 'B' ] }, 'H'); + expect(BFSMock).toHaveBeenLastCalledWith({ D: [], G: [ 'D' ], E: [ 'G' ], F: [ 'E' ], H: [ 'F' ] }, 'H'); + expect(result).toEqual(pathExpected); + }); + + + }); + + + }); + +}); \ No newline at end of file