diff --git a/src/composables/CalculateSize.ts b/src/composables/CalculateSize.ts index 5ecd8154ed53a603cf93b99f82a853b42eca5612..24f3957b4e8c889b98c82833487f8bef79f6a67f 100644 --- a/src/composables/CalculateSize.ts +++ b/src/composables/CalculateSize.ts @@ -402,7 +402,11 @@ export function shiftAllToGetTopLeftCoord(network:Network,style:GraphStyleProper */ export function getTopLeftCoordFromCenter(node:Node,style:GraphStyleProperties):Coordinate{ const size = getSizeNodePixel(node,style); - return {x:node.x-size.width/2,y:node.y-size.height/2} + let x = node.x-size.width/2; + let y = node.y-size.height/2; + x=parseFloat(x.toFixed(2)); + y=parseFloat(y.toFixed(2)); + return {x:x,y:y} } /** @@ -414,5 +418,9 @@ export function getTopLeftCoordFromCenter(node:Node,style:GraphStyleProperties): */ export function getCenterCoordFromTopLeft(node:Node,style:GraphStyleProperties):Coordinate{ const size = getSizeNodePixel(node,style); - return {x:node.x+size.width/2,y:node.y+size.height/2} + let x = node.x+size.width/2; + let y = node.y+size.height/2; + x=parseFloat(x.toFixed(2)); + y=parseFloat(y.toFixed(2)); + return {x:x,y:y} } \ No newline at end of file diff --git a/src/composables/CheckNetwork.ts b/src/composables/CheckNetwork.ts index 0c54945d6e4509da435fe2cc0c8f72533f19c342..aa98a7b859f99cc3e75a488cf1d8cbd1681b02ac 100644 --- a/src/composables/CheckNetwork.ts +++ b/src/composables/CheckNetwork.ts @@ -33,7 +33,7 @@ import {Network, Node, Link, GraphStyleProperties} from '../types/TypeVizCore'; * @throws Will throw an error if the network contains invalid keys when `checkExactKeysInterface` is true. * @throws Will throw an error if the invalid nodes or links. */ -export function checkNetworkFormat(network: Network, checkExactKeysInterface: boolean,checkIDForViz:boolean=true): void { +export async function checkNetworkFormat(network: Network, checkExactKeysInterface: boolean,checkIDForViz:boolean=true): Promise<void> { // Check if object if(typeof network !== 'object') { @@ -136,7 +136,6 @@ export function checkNodeFormat(nodeId:string, node: Node, checkExactKeysInterfa // If checkExactInterface is false, check if the node has metadataLayout (not allowed has used for algorithm) if (!checkExactKeysInterface && Object.keys(node).includes('metadataLayout')) { - console.log('Node contains metadataLayout'); throw new Error(`Node ${nodeId} contains metadataLayout, which is not allowed.`); } } diff --git a/src/composables/LayoutMain.ts b/src/composables/LayoutMain.ts index 879a4d79796c6b5c35f21bdd93fe69218fa1326e..652ff15ff967f2bd727ef8b40a3445e366a5b31b 100644 --- a/src/composables/LayoutMain.ts +++ b/src/composables/LayoutMain.ts @@ -1,6 +1,6 @@ // Type imports import { defaultParameters,Parameters } from "../types/Parameters"; -import { StartNodesType } from "../types/EnumArgs"; +import { PathType, StartNodesType } from "../types/EnumArgs"; import { SubgraphNetwork } from "../types/SubgraphNetwork"; import { TypeSubgraph } from "../types/Subgraph"; import { Network , GraphStyleProperties} from "../types/TypeVizCore"; @@ -18,6 +18,7 @@ import { shiftAllToGetTopLeftCoord } from "./CalculateSize"; import { getStartNodes } from "./CalculateStartNodes"; import { networktoNetworkLayout } from "./ConvertFromNetwork"; import { networkLayoutToNetwork } from "./ConvertToNetwork"; +import { checkNetworkFormat } from "./CheckNetwork"; @@ -41,41 +42,30 @@ import { networkLayoutToNetwork } from "./ConvertToNetwork"; * @param parameters - The optional parameters for the algorithm. * @returns A promise that resolves to the modified network after applying the algorithm. * @throws An error if the network or networkStyle is not defined or empty. + * @throws An error if the network is not in the correct format. */ export async function algorithmOnNetwork(network:Network,networkStyle:GraphStyleProperties,parameters:Parameters=defaultParameters):Promise<Network>{ - // check if the network is not empty - if ( !network || Object.keys(network.nodes).length===0){ - console.warn('The network is not defined or has no nodes : the algorithm will not be executed'); - throw new Error('The network is not defined or has no nodes : the algorithm will not be executed'); - } + // check if the network has the correct format + await checkNetworkFormat(network,false, true); - // check if the networkStyle is not empty - if ( !networkStyle || Object.keys(networkStyle).length===0){ - console.warn('The networkStyle is not defined or has no properties : the algorithm will not be executed'); - throw new Error('The networkStyle is not defined or has no properties : the algorithm will not be executed'); + // check if the network is not empty + if ( !network || Object.keys(network.nodes).length===0 || network.links.length===0){ + throw new Error('The network is not defined, has no nodes or no links : the algorithm will not be executed'); } // convert network to networkLayout let networkLayout:NetworkLayout=networktoNetworkLayout(network); - // initialize the subgraphNetwork object let subgraphNetwork:SubgraphNetwork={ network:networkLayout, networkStyle:networkStyle } - - try { - // change coordinates of the network with the algorithm - await allSteps(subgraphNetwork,parameters,true,true); - // convert networkLayout to network - return networkLayoutToNetwork(subgraphNetwork.network); - - } catch(err){ - console.log(" Error during execution of algorithm : " + err); - throw err; - } + // change coordinates of the network with the algorithm + await allSteps(subgraphNetwork,parameters,false); + // convert networkLayout to network + return networkLayoutToNetwork(subgraphNetwork.network); } @@ -84,11 +74,10 @@ export async function algorithmOnNetwork(network:Network,networkStyle:GraphStyle * Apply all steps of the algorithm to change node coordinates of a network. SubgraphNetwork is an object that contains the network to change and all the information needed during the steps. * @param subgraphNetwork object that contains the network, network style, attributs for viz, subgraph information and side compounds * @param parameters parameters for the algorithm - * @param shiftCoord change the coordinates to have the one of the top left corner of nodes, if false, the coordinates are the centers * @param printNameStep print the name of the steps during execution * @returns a promise of the subgraphNetwork */ -export async function allSteps(subgraphNetwork: SubgraphNetwork,parameters:Parameters,shiftCoord:boolean=true,printNameStep:boolean=false):Promise<SubgraphNetwork> { +export async function allSteps(subgraphNetwork: SubgraphNetwork,parameters:Parameters,printNameStep:boolean=false):Promise<SubgraphNetwork> { let network=subgraphNetwork.network; let networkStyle=subgraphNetwork.networkStyle; @@ -100,7 +89,6 @@ export async function allSteps(subgraphNetwork: SubgraphNetwork,parameters:Param console.log('---------------'); } - // duplicate side compounds and put them aside if (printNameStep) console.log('SideCompound duplication and put aside'); await putDuplicatedSideCompoundAside(subgraphNetwork,parameters.doDuplicateSideCompounds,parameters.doPutAsideSideCompounds).then( @@ -134,7 +122,7 @@ export async function allSteps(subgraphNetwork: SubgraphNetwork,parameters:Param ).then( () => { // if no cycle, we don't need to do the cycle step - if (parameters.doCycle && subgraphNetwork[TypeSubgraph.CYCLE] && Object.keys(subgraphNetwork[TypeSubgraph.CYCLE]).length===0){ + if (parameters.doCycle && (!subgraphNetwork[TypeSubgraph.CYCLE] || Object.keys(subgraphNetwork[TypeSubgraph.CYCLE]).length===0)){ parameters.doCycle=false; console.warn('doCycle is true but no cycle found : doCycle set to false'); } @@ -153,6 +141,9 @@ export async function allSteps(subgraphNetwork: SubgraphNetwork,parameters:Param // get main chains if (parameters.doMainChain){ if (printNameStep) console.log('Find main chain'); + if(parameters.pathType===PathType.ALL && !parameters.merge){ + console.warn('PathType is ALL, but because merge is false, only longest keeped when no merge : use LONGEST instead or set merge to true'); + } const sources=await getStartNodes(network,parameters.startNodeTypeMainChain); await addMainChainFromSources(subgraphNetwork, sources,parameters.getSubgraph, parameters.merge,parameters.pathType); } @@ -210,7 +201,7 @@ export async function allSteps(subgraphNetwork: SubgraphNetwork,parameters:Param ).then( () => { // shift coordinates to have top left corner coordinate (because of svg drawing) - if (shiftCoord){ + if (parameters.shiftCoord){ if (printNameStep) console.log('Shift coordinates nodes to have center at the old coordinates'); shiftAllToGetTopLeftCoord(network,networkStyle); } diff --git a/src/composables/LayoutSugiyama.ts b/src/composables/LayoutSugiyama.ts index ca42c17508feb9dc650e3a0ed9a7830ec2509764..f5cee69119f4e284f18064dad3218c97c65f1e8d 100644 --- a/src/composables/LayoutSugiyama.ts +++ b/src/composables/LayoutSugiyama.ts @@ -33,7 +33,6 @@ import { instance } from "@viz-js/viz"; */ export async function vizLayout(subgraphNetwork:SubgraphNetwork,assignRank:boolean=false, cycle:boolean=true,addNodes:boolean=true, groupOrCluster:"group"|"cluster"="cluster",orderChange:boolean=false,printDot:boolean=false,dpi:number=72,factorLenghtEdge:number=3,callbackFunction = () => {}): Promise<SubgraphNetwork> { - await instance().then( async viz => { // attributes for viz const sep =await getSepAttributesInches(subgraphNetwork.network,subgraphNetwork.networkStyle,factorLenghtEdge); diff --git a/src/composables/SubgraphForViz.ts b/src/composables/SubgraphForViz.ts index b479d2ac771c0c2042c6252f28eff6ddb3567f0b..d63934be39276255dcbc3258c00a16b981cead47 100644 --- a/src/composables/SubgraphForViz.ts +++ b/src/composables/SubgraphForViz.ts @@ -72,8 +72,6 @@ export function addMainChainForViz(vizGraph: Graph, nameMainChain: string, subgr vizGraph.subgraphs = []; } vizGraph.subgraphs.push(clusterViz); - - console.log(clusterViz); return vizGraph; } diff --git a/src/composables/__tests__/CheckNetwork.test.ts b/src/composables/__tests__/CheckNetwork.test.ts index 1ba841ae49050b22c96aaa61386ec3eb7781a7a7..e628267bf104cadcbd461da8bccbd29c220bca24 100644 --- a/src/composables/__tests__/CheckNetwork.test.ts +++ b/src/composables/__tests__/CheckNetwork.test.ts @@ -263,48 +263,48 @@ describe('CheckNetwork', () => { }; // Test: Network must be an object - it('should throw an error if the network is not an object', () => { - expect(() => CheckNetwork.checkNetworkFormat(56 as any, false,false)) + it('should throw an error if the network is not an object', async () => { + await expect(CheckNetwork.checkNetworkFormat(56 as any, false,false)).rejects .toThrow('Network is not an object.'); }); // Test: Mandatory keys are present - it('should throw an error if the network lacks a mandatory key (id)', () => { + it('should throw an error if the network lacks a mandatory key (id)', async () => { const networkWithoutId = { nodes: {}, links: [] } as any; - expect(() => CheckNetwork.checkNetworkFormat(networkWithoutId, false,false)) + await expect(CheckNetwork.checkNetworkFormat(networkWithoutId, false,false)).rejects .toThrow('Network lacks key: id'); }); - it('should throw an error if the network lacks a mandatory key (nodes)', () => { + it('should throw an error if the network lacks a mandatory key (nodes)', async () => { const networkWithoutNodes = { id: 'network1', links: [] } as any; - expect(() => CheckNetwork.checkNetworkFormat(networkWithoutNodes, false,false)) + await expect(CheckNetwork.checkNetworkFormat(networkWithoutNodes, false,false)).rejects .toThrow('Network lacks key: nodes'); }); - it('should throw an error if the network lacks a mandatory key (links)', () => { + it('should throw an error if the network lacks a mandatory key (links)', async () => { const networkWithoutLinks = { id: 'network1', nodes: {} } as any; - expect(() => CheckNetwork.checkNetworkFormat(networkWithoutLinks, false,false)) + await expect(async () => await CheckNetwork.checkNetworkFormat(networkWithoutLinks, false,false)).rejects .toThrow('Network lacks key: links'); }); // Test: Exact interface check - it('should pass if the network contains only the exact keys', () => { - expect(() => CheckNetwork.checkNetworkFormat(validNetwork, true,false)).not.toThrow(); + it('should pass if the network contains only the exact keys', async () => { + await expect(CheckNetwork.checkNetworkFormat(validNetwork, true,false)).resolves.not.toThrow(); }); - it('should throw an error if the network contains extra keys when exact interface is checked', () => { + it('should throw an error if the network contains extra keys when exact interface is checked', async () => { const networkWithExtraKey = { ...validNetwork, extraKey: 'extraValue' } as any; - expect(() => CheckNetwork.checkNetworkFormat(networkWithExtraKey, true,false)) + await expect(CheckNetwork.checkNetworkFormat(networkWithExtraKey, true,false)).rejects .toThrow('Network has an invalid key: extraKey'); }); - it('should pass if the network contains extra keys when exact interface is not checked', () => { + it('should pass if the network contains extra keys when exact interface is not checked', async() => { const networkWithExtraKey = { ...validNetwork, extraKey: 'extraValue' } as any; - expect(() => CheckNetwork.checkNetworkFormat(networkWithExtraKey, false,false)).not.toThrow(); + await expect(CheckNetwork.checkNetworkFormat(networkWithExtraKey, false,false)).resolves.not.toThrow(); }); // Test: Node validation failure - it('should throw an error if a node in the network is invalid', () => { + it('should throw an error if a node in the network is invalid', async () => { const invalidNode = { id: '3', y: 0 }; // Missing the 'x' key const networkWithInvalidNode = { ...validNetwork, @@ -312,12 +312,12 @@ describe('CheckNetwork', () => { }; // Assuming checkNodeFormat throws the appropriate error - expect(() => CheckNetwork.checkNetworkFormat(networkWithInvalidNode, false,false)) + await expect(CheckNetwork.checkNetworkFormat(networkWithInvalidNode, false,false)).rejects .toThrow('Node 3 lacks key: x'); }); // Test: Link validation failure - it('should throw an error if a link in the network is invalid', () => { + it('should throw an error if a link in the network is invalid', async () => { const invalidLink = { id: 'link1', source: validNode1 }; // Missing the 'target' key const networkWithInvalidLink = { ...validNetwork, @@ -325,21 +325,21 @@ describe('CheckNetwork', () => { }; // Assuming checkLinkFormat throws the appropriate error - expect(() => CheckNetwork.checkNetworkFormat(networkWithInvalidLink, false,false)) + await expect(CheckNetwork.checkNetworkFormat(networkWithInvalidLink, false,false)).rejects .toThrow('Link lacks key: target'); }); // Test: check if of node for Viz - it('should pass with check of id for Viz', () => { - expect(() => CheckNetwork.checkNetworkFormat(validNetwork, false,true)).not.toThrow(); + it('should pass with check of id for Viz', async () => { + await expect(CheckNetwork.checkNetworkFormat(validNetwork, false,true)).resolves.not.toThrow(); }); - it('should throw an error if id is not valid for Viz', () => { + it('should throw an error if id is not valid for Viz', async () => { const networkWithInvalidNodeID = { ...validNetwork, nodes: { '1node': {id:"1node",x:0,y:0} } }; - expect(() => CheckNetwork.checkNetworkFormat(networkWithInvalidNodeID, false,true)).toThrow('Invalid string: 1node'); + await expect(CheckNetwork.checkNetworkFormat(networkWithInvalidNodeID, false,true)).rejects.toThrow('Invalid string: 1node'); }); }); @@ -371,12 +371,12 @@ describe('CheckNetwork', () => { // Test: All node classes have corresponding styles it('should pass if all node classes have corresponding styles in networkStyle.nodeStyles', () => { - expect(() => CheckNetwork.checkNetworkStyleFormat(validNetwork, validGraphStyle)).not.toThrow(); + expect( () => CheckNetwork.checkNetworkStyleFormat(validNetwork, validGraphStyle)).not.toThrow(); }); // Test: All link classes have corresponding styles it('should pass if all link classes have corresponding styles in networkStyle.linkStyles', () => { - expect(() => CheckNetwork.checkNetworkStyleFormat(validNetwork, validGraphStyle)).not.toThrow(); + expect( () => CheckNetwork.checkNetworkStyleFormat(validNetwork, validGraphStyle)).not.toThrow(); }); // Test: Missing nodeStyles when node classes exist @@ -384,7 +384,7 @@ describe('CheckNetwork', () => { const missingNodeStyle: GraphStyleProperties = { linkStyles: validGraphStyle.linkStyles // Missing nodeStyles }; - expect(() => CheckNetwork.checkNetworkStyleFormat(validNetwork, missingNodeStyle)) + expect( () => CheckNetwork.checkNetworkStyleFormat(validNetwork, missingNodeStyle)) .toThrow('Node classes are present in the network but no nodeStyles are provided.'); }); @@ -393,7 +393,7 @@ describe('CheckNetwork', () => { const missingLinkStyle: GraphStyleProperties = { nodeStyles: validGraphStyle.nodeStyles // Missing linkStyles }; - expect(() => CheckNetwork.checkNetworkStyleFormat(validNetwork, missingLinkStyle)) + expect( () => CheckNetwork.checkNetworkStyleFormat(validNetwork, missingLinkStyle)) .toThrow('Link classes are present in the network but no linkStyles are provided.'); }); @@ -403,7 +403,7 @@ describe('CheckNetwork', () => { nodeStyles: { class1: { stroke: 'blue' } }, // Missing class2 linkStyles: validGraphStyle.linkStyles }; - expect(() => CheckNetwork.checkNetworkStyleFormat(validNetwork, incompleteNodeStyles)) + expect( () => CheckNetwork.checkNetworkStyleFormat(validNetwork, incompleteNodeStyles)) .toThrow('Node class class2 is missing in networkStyle.nodeStyles.'); }); @@ -413,7 +413,7 @@ describe('CheckNetwork', () => { nodeStyles: validGraphStyle.nodeStyles, linkStyles: {} // Missing linkClass1 }; - expect(() => CheckNetwork.checkNetworkStyleFormat(validNetwork, incompleteLinkStyles)) + expect( () => CheckNetwork.checkNetworkStyleFormat(validNetwork, incompleteLinkStyles)) .toThrow('Link class linkClass1 is missing in networkStyle.linkStyles.'); }); @@ -429,7 +429,7 @@ describe('CheckNetwork', () => { }; const emptyGraphStyle: GraphStyleProperties = {}; - expect(() => CheckNetwork.checkNetworkStyleFormat(simpleNetwork, emptyGraphStyle)).not.toThrow(); + expect( () => CheckNetwork.checkNetworkStyleFormat(simpleNetwork, emptyGraphStyle)).not.toThrow(); }); diff --git a/src/composables/__tests__/LayoutMain.test.ts b/src/composables/__tests__/LayoutMain.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..feae6dc540e9bcac85ddf8354a636eac0cee4568 --- /dev/null +++ b/src/composables/__tests__/LayoutMain.test.ts @@ -0,0 +1,522 @@ +// Type imports +import { defaultParameters,Parameters } from "../../types/Parameters"; +import { GraphStyleProperties, Link, Network } from "../../types/TypeVizCore"; +import { NetworkLayout } from "../../types/NetworkLayout"; +import { SubgraphNetwork } from "../../types/SubgraphNetwork"; +import { TypeSubgraph } from "../../types/Subgraph"; +import {Node} from "../../types/TypeVizCore"; + +// Composable imports +import * as LayoutMain from "../LayoutMain"; +import * as LayoutManageSideCompounds from "../LayoutManageSideCompounds"; +import * as LayoutReversibleReactions from "../LayoutReversibleReactions"; +import * as LayoutSugiyamaForce from "../LayoutSugiyama"; +import * as LayoutFindCycle from "../LayoutFindCycle"; +import * as LayoutMainChain from "../LayoutMainChain"; +import * as LayoutDrawCycle from "../LayoutDrawCycle"; +import * as CalculateSize from "../CalculateSize"; +import * as CalculateStartNodes from "../CalculateStartNodes"; +import * as ConvertFromNetwork from "../ConvertFromNetwork"; +import * as ConvertToNetwork from "../ConvertToNetwork"; +import * as CheckNetwork from "../CheckNetwork"; +import exp from "constants"; + + +jest.mock('d3',( ) => {}); + + +describe('LayoutMain', () => { + + + afterEach(() => { + jest.clearAllMocks(); + }); + + + describe('allSteps', () => { + + let subgraphNetwork:SubgraphNetwork; + let parameters:Parameters; + + let putDuplicatedSideCompoundAsideMock:jest.SpyInstance; + let vizLayoutMock:jest.SpyInstance; + let duplicateReversibleReactionsMock:jest.SpyInstance; + let addDirectedCycleToSubgraphNetworkMock:jest.SpyInstance; + let getStartNodesMock:jest.SpyInstance; + let chooseReversibleReactionMock:jest.SpyInstance; + let addMainChainFromSourcesMock:jest.SpyInstance; + let addMiniBranchToMainChainMock:jest.SpyInstance; + let coordinateAllCyclesMock:jest.SpyInstance; + let drawAllCyclesGroupMock:jest.SpyInstance; + let reinsertionSideCompoundsMock:jest.SpyInstance; + let shiftAllToGetTopLeftCoordMock:jest.SpyInstance; + + + beforeEach(() => { + + // DATA + + const networkStyle:GraphStyleProperties = {}; + const networkLayout:NetworkLayout = { + id: 'network', + nodes: {}, + links : [] + }; + + subgraphNetwork={ + network:networkLayout, + networkStyle:networkStyle + } + + parameters = { + ...defaultParameters, + doDuplicateSideCompounds: true, + doPutAsideSideCompounds: true, + doReactionReversible: true, + doMainChain: true, + doMiniBranch: true, + doCycle: true, + shiftCoord: true, + }; + + // MOCKS + putDuplicatedSideCompoundAsideMock = jest.spyOn(LayoutManageSideCompounds, 'putDuplicatedSideCompoundAside'); + putDuplicatedSideCompoundAsideMock.mockReturnValue(Promise.resolve(subgraphNetwork)); + + vizLayoutMock = jest.spyOn(LayoutSugiyamaForce, 'vizLayout'); + vizLayoutMock.mockReturnValue(Promise.resolve(subgraphNetwork)); + + duplicateReversibleReactionsMock = jest.spyOn(LayoutReversibleReactions, 'duplicateReversibleReactions'); + duplicateReversibleReactionsMock.mockReturnValue(Promise.resolve()); + + addDirectedCycleToSubgraphNetworkMock = jest.spyOn(LayoutFindCycle, 'addDirectedCycleToSubgraphNetwork'); + addDirectedCycleToSubgraphNetworkMock.mockImplementation((subgraphNetwork:SubgraphNetwork) => { + subgraphNetwork[TypeSubgraph.CYCLE] = { + "cycle":{name:"cycle", nodes:[],type:TypeSubgraph.CYCLE} + }; + return subgraphNetwork; + }); + + getStartNodesMock = jest.spyOn(CalculateStartNodes, 'getStartNodes'); + getStartNodesMock.mockReturnValue(Promise.resolve([] as string[])); + + chooseReversibleReactionMock = jest.spyOn(LayoutReversibleReactions, 'chooseReversibleReaction'); + chooseReversibleReactionMock.mockReturnValue(Promise.resolve(subgraphNetwork)); + + addMainChainFromSourcesMock = jest.spyOn(LayoutMainChain, 'addMainChainFromSources'); + addMainChainFromSourcesMock.mockReturnValue(subgraphNetwork); + + addMiniBranchToMainChainMock = jest.spyOn(LayoutMainChain, 'addMiniBranchToMainChain'); + addMiniBranchToMainChainMock.mockReturnValue(subgraphNetwork); + + coordinateAllCyclesMock = jest.spyOn(LayoutDrawCycle, 'coordinateAllCycles'); + coordinateAllCyclesMock.mockReturnValue(Promise.resolve(subgraphNetwork)); + + drawAllCyclesGroupMock = jest.spyOn(LayoutDrawCycle, 'drawAllCyclesGroup'); + drawAllCyclesGroupMock.mockImplementation(() => {}); + + reinsertionSideCompoundsMock = jest.spyOn(LayoutManageSideCompounds, 'reinsertionSideCompounds'); + reinsertionSideCompoundsMock.mockReturnValue(Promise.resolve(subgraphNetwork)); + + shiftAllToGetTopLeftCoordMock = jest.spyOn(CalculateSize, 'shiftAllToGetTopLeftCoord'); + shiftAllToGetTopLeftCoordMock.mockImplementation(() => {}); + + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + + + it('should call all steps', async () => { + // TEST + await LayoutMain.allSteps(subgraphNetwork, parameters); + + // EXPECT + expect(putDuplicatedSideCompoundAsideMock).toHaveBeenCalledTimes(1); + expect(vizLayoutMock).toHaveBeenCalledTimes(3); + expect(duplicateReversibleReactionsMock).toHaveBeenCalledTimes(1); + expect(addDirectedCycleToSubgraphNetworkMock).toHaveBeenCalledTimes(1); + expect(getStartNodesMock).toHaveBeenCalledTimes(2); + expect(chooseReversibleReactionMock).toHaveBeenCalledTimes(1); + expect(addMainChainFromSourcesMock).toHaveBeenCalledTimes(1); + expect(addMiniBranchToMainChainMock).toHaveBeenCalledTimes(1); + expect(coordinateAllCyclesMock).toHaveBeenCalledTimes(1); + expect(drawAllCyclesGroupMock).toHaveBeenCalledTimes(1); + expect(reinsertionSideCompoundsMock).toHaveBeenCalledTimes(1); + expect(shiftAllToGetTopLeftCoordMock).toHaveBeenCalledTimes(1); + + }); + + it("shouldn't do main chain", async () => { + // DATA + parameters.doMainChain = false; + + // TEST + await LayoutMain.allSteps(subgraphNetwork, parameters); + + // EXPECT + expect(putDuplicatedSideCompoundAsideMock).toHaveBeenCalledTimes(1); + expect(vizLayoutMock).toHaveBeenCalledTimes(3); + expect(duplicateReversibleReactionsMock).toHaveBeenCalledTimes(1); + expect(addDirectedCycleToSubgraphNetworkMock).toHaveBeenCalledTimes(1); + expect(getStartNodesMock).toHaveBeenCalledTimes(1); + expect(chooseReversibleReactionMock).toHaveBeenCalledTimes(1); + expect(addMainChainFromSourcesMock).not.toHaveBeenCalled(); + expect(addMiniBranchToMainChainMock).not.toHaveBeenCalled(); + expect(coordinateAllCyclesMock).toHaveBeenCalledTimes(1); + expect(drawAllCyclesGroupMock).toHaveBeenCalledTimes(1); + expect(reinsertionSideCompoundsMock).toHaveBeenCalledTimes(1); + expect(shiftAllToGetTopLeftCoordMock).toHaveBeenCalledTimes(1); + + }); + + it("shouldn't do reaction reversible", async () => { + // DATA + parameters.doReactionReversible = false; + + // TEST + await LayoutMain.allSteps(subgraphNetwork, parameters); + + // EXPECT + expect(putDuplicatedSideCompoundAsideMock).toHaveBeenCalledTimes(1); + expect(vizLayoutMock).toHaveBeenCalledTimes(3); + expect(duplicateReversibleReactionsMock).not.toHaveBeenCalled(); + expect(addDirectedCycleToSubgraphNetworkMock).toHaveBeenCalledTimes(1); + expect(getStartNodesMock).toHaveBeenCalledTimes(1); + expect(chooseReversibleReactionMock).not.toHaveBeenCalled(); + expect(addMainChainFromSourcesMock).toHaveBeenCalledTimes(1); + expect(addMiniBranchToMainChainMock).toHaveBeenCalledTimes(1); + expect(coordinateAllCyclesMock).toHaveBeenCalledTimes(1); + expect(drawAllCyclesGroupMock).toHaveBeenCalledTimes(1); + expect(reinsertionSideCompoundsMock).toHaveBeenCalledTimes(1); + expect(reinsertionSideCompoundsMock).toHaveBeenCalledWith(expect.anything(),expect.anything(),false); + expect(shiftAllToGetTopLeftCoordMock).toHaveBeenCalledTimes(1); + + }); + + it("shouldn't do reaction reversible and main chain", async () => { + // DATA + parameters.doReactionReversible = false; + parameters.doMainChain = false; + + // TEST + await LayoutMain.allSteps(subgraphNetwork, parameters); + + // EXPECT + expect(putDuplicatedSideCompoundAsideMock).toHaveBeenCalledTimes(1); + expect(vizLayoutMock).toHaveBeenCalledTimes(2); + expect(duplicateReversibleReactionsMock).not.toHaveBeenCalled(); + expect(addDirectedCycleToSubgraphNetworkMock).toHaveBeenCalledTimes(1); + expect(getStartNodesMock).not.toHaveBeenCalled(); + expect(chooseReversibleReactionMock).not.toHaveBeenCalled(); + expect(addMainChainFromSourcesMock).not.toHaveBeenCalled(); + expect(addMiniBranchToMainChainMock).not.toHaveBeenCalled(); + expect(coordinateAllCyclesMock).toHaveBeenCalledTimes(1); + expect(drawAllCyclesGroupMock).toHaveBeenCalledTimes(1); + expect(reinsertionSideCompoundsMock).toHaveBeenCalledWith(expect.anything(),expect.anything(),false); + expect(shiftAllToGetTopLeftCoordMock).toHaveBeenCalledTimes(1); + + }); + + it("shouldn't do cycle", async () => { + // DATA + parameters.doCycle = false; + + // TEST + await LayoutMain.allSteps(subgraphNetwork, parameters); + + // EXPECT + expect(putDuplicatedSideCompoundAsideMock).toHaveBeenCalledTimes(1); + expect(vizLayoutMock).toHaveBeenCalledTimes(2); + expect(duplicateReversibleReactionsMock).toHaveBeenCalledTimes(1); + expect(addDirectedCycleToSubgraphNetworkMock).not.toHaveBeenCalled(); + expect(getStartNodesMock).toHaveBeenCalledTimes(2); + expect(chooseReversibleReactionMock).toHaveBeenCalledTimes(1); + expect(addMainChainFromSourcesMock).toHaveBeenCalledTimes(1); + expect(addMiniBranchToMainChainMock).toHaveBeenCalledTimes(1); + expect(coordinateAllCyclesMock).not.toHaveBeenCalled(); + expect(drawAllCyclesGroupMock).not.toHaveBeenCalled(); + expect(reinsertionSideCompoundsMock).toHaveBeenCalledTimes(1); + expect(shiftAllToGetTopLeftCoordMock).toHaveBeenCalledTimes(1); + + }); + + it("should do cycle but no cycle found", async () => { + // MOCK + addDirectedCycleToSubgraphNetworkMock.mockImplementation((subgraphNetwork:SubgraphNetwork) => { + return subgraphNetwork; + }); + + // TEST + await LayoutMain.allSteps(subgraphNetwork, parameters); + + // EXPECT + expect(putDuplicatedSideCompoundAsideMock).toHaveBeenCalledTimes(1); + expect(vizLayoutMock).toHaveBeenCalledTimes(2); + expect(duplicateReversibleReactionsMock).toHaveBeenCalledTimes(1); + expect(addDirectedCycleToSubgraphNetworkMock).toHaveBeenCalledTimes(1); + expect(getStartNodesMock).toHaveBeenCalledTimes(2); + expect(chooseReversibleReactionMock).toHaveBeenCalledTimes(1); + expect(addMainChainFromSourcesMock).toHaveBeenCalledTimes(1); + expect(addMiniBranchToMainChainMock).toHaveBeenCalledTimes(1); + expect(coordinateAllCyclesMock).not.toHaveBeenCalled(); + expect(drawAllCyclesGroupMock).not.toHaveBeenCalled(); + expect(reinsertionSideCompoundsMock).toHaveBeenCalledTimes(1); + expect(shiftAllToGetTopLeftCoordMock).toHaveBeenCalledTimes(1); + + }); + + it("shouldn't do mini branch", async () => { + // DATA + parameters.doMiniBranch = false; + + // TEST + await LayoutMain.allSteps(subgraphNetwork, parameters); + + // EXPECT + expect(putDuplicatedSideCompoundAsideMock).toHaveBeenCalledTimes(1); + expect(vizLayoutMock).toHaveBeenCalledTimes(3); + expect(duplicateReversibleReactionsMock).toHaveBeenCalledTimes(1); + expect(addDirectedCycleToSubgraphNetworkMock).toHaveBeenCalledTimes(1); + expect(getStartNodesMock).toHaveBeenCalledTimes(2); + expect(chooseReversibleReactionMock).toHaveBeenCalledTimes(1); + expect(addMainChainFromSourcesMock).toHaveBeenCalledTimes(1); + expect(addMiniBranchToMainChainMock).not.toHaveBeenCalled(); + expect(coordinateAllCyclesMock).toHaveBeenCalledTimes(1); + expect(drawAllCyclesGroupMock).toHaveBeenCalledTimes(1); + expect(reinsertionSideCompoundsMock).toHaveBeenCalledTimes(1); + expect(shiftAllToGetTopLeftCoordMock).toHaveBeenCalledTimes(1); + + }); + + it("shouldn't shift coordinates", async () => { + // DATA + parameters.shiftCoord = false; + + // TEST + await LayoutMain.allSteps(subgraphNetwork, parameters); + + // EXPECT + expect(putDuplicatedSideCompoundAsideMock).toHaveBeenCalledTimes(1); + expect(vizLayoutMock).toHaveBeenCalledTimes(3); + expect(duplicateReversibleReactionsMock).toHaveBeenCalledTimes(1); + expect(addDirectedCycleToSubgraphNetworkMock).toHaveBeenCalledTimes(1); + expect(getStartNodesMock).toHaveBeenCalledTimes(2); + expect(chooseReversibleReactionMock).toHaveBeenCalledTimes(1); + expect(addMainChainFromSourcesMock).toHaveBeenCalledTimes(1); + expect(addMiniBranchToMainChainMock).toHaveBeenCalledTimes(1); + expect(coordinateAllCyclesMock).toHaveBeenCalledTimes(1); + expect(drawAllCyclesGroupMock).toHaveBeenCalledTimes(1); + expect(reinsertionSideCompoundsMock).toHaveBeenCalledTimes(1); + expect(shiftAllToGetTopLeftCoordMock).not.toHaveBeenCalled(); + + }); + + it("shouldn't duplicate side compound", async () => { + // DATA + parameters.doDuplicateSideCompounds = false; + + // TEST + await LayoutMain.allSteps(subgraphNetwork, parameters); + + // EXPECT + expect(putDuplicatedSideCompoundAsideMock).toHaveBeenCalledTimes(1); + expect(putDuplicatedSideCompoundAsideMock).toHaveBeenCalledWith(expect.anything(),false,expect.anything()); + expect(vizLayoutMock).toHaveBeenCalledTimes(3); + expect(duplicateReversibleReactionsMock).toHaveBeenCalledTimes(1); + expect(addDirectedCycleToSubgraphNetworkMock).toHaveBeenCalledTimes(1); + expect(getStartNodesMock).toHaveBeenCalledTimes(2); + expect(chooseReversibleReactionMock).toHaveBeenCalledTimes(1); + expect(addMainChainFromSourcesMock).toHaveBeenCalledTimes(1); + expect(addMiniBranchToMainChainMock).toHaveBeenCalledTimes(1); + expect(coordinateAllCyclesMock).toHaveBeenCalledTimes(1); + expect(drawAllCyclesGroupMock).toHaveBeenCalledTimes(1); + expect(reinsertionSideCompoundsMock).not.toHaveBeenCalled(); + expect(shiftAllToGetTopLeftCoordMock).toHaveBeenCalledTimes(1); + + }); + + it("shouldn't put aside side compound", async () => { + // DATA + parameters.doPutAsideSideCompounds = false; + + // TEST + await LayoutMain.allSteps(subgraphNetwork, parameters); + + // EXPECT + expect(putDuplicatedSideCompoundAsideMock).toHaveBeenCalledTimes(1); + expect(putDuplicatedSideCompoundAsideMock).toHaveBeenCalledWith(expect.anything(),expect.anything(),false); + expect(vizLayoutMock).toHaveBeenCalledTimes(3); + expect(duplicateReversibleReactionsMock).toHaveBeenCalledTimes(1); + expect(addDirectedCycleToSubgraphNetworkMock).toHaveBeenCalledTimes(1); + expect(getStartNodesMock).toHaveBeenCalledTimes(2); + expect(chooseReversibleReactionMock).toHaveBeenCalledTimes(1); + expect(addMainChainFromSourcesMock).toHaveBeenCalledTimes(1); + expect(addMiniBranchToMainChainMock).toHaveBeenCalledTimes(1); + expect(coordinateAllCyclesMock).toHaveBeenCalledTimes(1); + expect(drawAllCyclesGroupMock).toHaveBeenCalledTimes(1); + expect(reinsertionSideCompoundsMock).not.toHaveBeenCalled(); + expect(shiftAllToGetTopLeftCoordMock).toHaveBeenCalledTimes(1); + + }); + + it("shouldn't put aside side and duplicate compound", async () => { + // DATA + parameters.doDuplicateSideCompounds = false; + parameters.doPutAsideSideCompounds = false; + + // TEST + await LayoutMain.allSteps(subgraphNetwork, parameters); + + // EXPECT + expect(putDuplicatedSideCompoundAsideMock).toHaveBeenCalledTimes(1); + expect(putDuplicatedSideCompoundAsideMock).toHaveBeenCalledWith(expect.anything(),false,false); + expect(vizLayoutMock).toHaveBeenCalledTimes(3); + expect(duplicateReversibleReactionsMock).toHaveBeenCalledTimes(1); + expect(addDirectedCycleToSubgraphNetworkMock).toHaveBeenCalledTimes(1); + expect(getStartNodesMock).toHaveBeenCalledTimes(2); + expect(chooseReversibleReactionMock).toHaveBeenCalledTimes(1); + expect(addMainChainFromSourcesMock).toHaveBeenCalledTimes(1); + expect(addMiniBranchToMainChainMock).toHaveBeenCalledTimes(1); + expect(coordinateAllCyclesMock).toHaveBeenCalledTimes(1); + expect(drawAllCyclesGroupMock).toHaveBeenCalledTimes(1); + expect(reinsertionSideCompoundsMock).not.toHaveBeenCalled(); + expect(shiftAllToGetTopLeftCoordMock).toHaveBeenCalledTimes(1); + + }); + + + it("shouldn't do any special step", async () => { + // DATA + parameters.doDuplicateSideCompounds = false; + parameters.doPutAsideSideCompounds = false; + parameters.doReactionReversible = false; + parameters.doMainChain = false; + parameters.doMiniBranch = false; + parameters.doCycle = false; + parameters.shiftCoord = false; + + // TEST + await LayoutMain.allSteps(subgraphNetwork, parameters); + + // EXPECT + expect(putDuplicatedSideCompoundAsideMock).toHaveBeenCalledTimes(1); + expect(putDuplicatedSideCompoundAsideMock).toHaveBeenCalledWith(expect.anything(),false,false); + expect(vizLayoutMock).toHaveBeenCalledTimes(1); + expect(duplicateReversibleReactionsMock).not.toHaveBeenCalled(); + expect(addDirectedCycleToSubgraphNetworkMock).not.toHaveBeenCalled(); + expect(getStartNodesMock).not.toHaveBeenCalled(); + expect(chooseReversibleReactionMock).not.toHaveBeenCalled(); + expect(addMainChainFromSourcesMock).not.toHaveBeenCalled(); + expect(addMiniBranchToMainChainMock).not.toHaveBeenCalled(); + expect(coordinateAllCyclesMock).not.toHaveBeenCalled(); + expect(drawAllCyclesGroupMock).not.toHaveBeenCalled(); + expect(reinsertionSideCompoundsMock).not.toHaveBeenCalled(); + expect(shiftAllToGetTopLeftCoordMock).not.toHaveBeenCalled(); + + }); + + }); + + + describe('algorithmOnNetwork', () => { + + let networktoNetworkLayoutMock:jest.SpyInstance; + let networkLayoutToNetworkMock:jest.SpyInstance; + let checkNetworkFormat:jest.SpyInstance; + + let nodes:{ [key: string]: Node }; + + let links:Link[]; + + let networkLayout:NetworkLayout; + + beforeEach(() => { + + nodes={ + 'A':{ id: 'A', x: 0, y: 0 }, + 'B':{ id: 'B', x: 0, y: 0 }, + }; + + links=[ + {id:"link",source:nodes.A,target:nodes.B} + ] + + networkLayout = { + id: 'network', + nodes: nodes, + links : links + }; + + // MOCK + + networktoNetworkLayoutMock = jest.spyOn(ConvertFromNetwork, 'networktoNetworkLayout'); + + networkLayoutToNetworkMock = jest.spyOn(ConvertToNetwork, 'networkLayoutToNetwork'); + + checkNetworkFormat = jest.spyOn(CheckNetwork, 'checkNetworkFormat'); + + }); + + afterEach(() => { + networktoNetworkLayoutMock.mockRestore(); + networkLayoutToNetworkMock.mockRestore(); + }); + + it ('should throw error if incorrect network', async () => { + // MOCK + checkNetworkFormat.mockImplementationOnce(() => { + throw new Error("Incorrect network format"); + }); + + await expect(LayoutMain.algorithmOnNetwork(networkLayout, {})).rejects.toThrow("Incorrect network format"); + + }); + + it ('should throw error if network is empty', async () => { + + const emptyNetwork: Network = { id:"network", nodes: {}, links: [] }; + + await expect(LayoutMain.algorithmOnNetwork(emptyNetwork, {})).rejects.toThrow('The network is not defined, has no nodes or no links : the algorithm will not be executed'); + }); + + it ('should throw error if network has no links', async () => { + const noNodesNetwork: Network = { id:"network", nodes:nodes, links: [] }; + + await expect(LayoutMain.algorithmOnNetwork(noNodesNetwork, {})).rejects.toThrow('The network is not defined, has no nodes or no links : the algorithm will not be executed'); + + }); + + + it ('should apply both fonction of conversion', async () => { + // MOCK + networktoNetworkLayoutMock.mockImplementationOnce((network:Network)=>{return network }); + networkLayoutToNetworkMock.mockImplementationOnce((network:NetworkLayout)=>{return network}); + + const result= await LayoutMain.algorithmOnNetwork(networkLayout, {}); + + expect(networktoNetworkLayoutMock).toHaveBeenCalledTimes(1); + expect(networkLayoutToNetworkMock).toHaveBeenCalledTimes(1); + + }); + + it('should call all steps and change at least one position', async () => { + + const result= await LayoutMain.algorithmOnNetwork(networkLayout, {}); + + let changed = false; + for (const node of Object.values(result.nodes)) { + if (node.x !== 0 || node.y !== 0) { + changed = true; + break; + } + } + expect(changed).toBe(true); + }); + + + }); + + +}); \ No newline at end of file diff --git a/src/types/Parameters.ts b/src/types/Parameters.ts index cce9be9adbdaee9c87218a97e2aba2768fe66ae3..a39695e3fe7a9193f3568acb9cb0cd9b58643c8b 100644 --- a/src/types/Parameters.ts +++ b/src/types/Parameters.ts @@ -43,7 +43,7 @@ export let defaultParameters: Parameters = { doReactionReversible: true, // user can change this parameter - doMainChain: false, // user can change this parameter + doMainChain: true, // user can change this parameter getSubgraph : getPathSourcesToTargetNode, startNodeTypeMainChain: StartNodesType.RANK_SOURCE, // usefull to allow user to change this parameter with RANK_ONLY or SOURCE_ONLY ? (if source-only, put source_all for reaction rev and no first viz) pathType: PathType.ALL_LONGEST, // user can change this parameter @@ -51,7 +51,7 @@ export let defaultParameters: Parameters = { doMiniBranch: true, // usefull choice ? run metrix to see if it's usefull groupOrCluster: "cluster", - doCycle: false, // user can change this parameter + doCycle: true, // user can change this parameter allowInternalCycles: false, addNodes: true,