1+ import { describe , it , vi , beforeEach , expect } from "vitest" ;
2+ import cytoscape from "cytoscape" ;
3+ import { LayoutCorrectionService } from "../visualizer/services/layout-correction-service.js" ;
4+ import { BoundingBox , CalmNode } from '../visualizer/contracts/contracts.js' ;
5+ import { afterEach } from "node:test" ;
6+
7+ function generateMockNodeObj ( id : string , parentId ?: string ) : CalmNode {
8+ const nodeObj = {
9+ classes : `class-${ id } ` ,
10+ data : {
11+ description : `Node ${ id } description` ,
12+ type : `type-${ id } ` ,
13+ label : `Node ${ id } ` ,
14+ id : id ,
15+ parent : parentId ,
16+ _displayPlaceholderWithDesc : `Display ${ id } with desc` ,
17+ _displayPlaceholderWithoutDesc : `Display ${ id } without desc` ,
18+ extraField : `extraValue-${ id } ` ,
19+ } ,
20+ } ;
21+ if ( parentId != null ) {
22+ nodeObj . data . parent = parentId ;
23+ }
24+ return nodeObj as CalmNode ;
25+ }
26+
27+ function generateBoundingBox ( x1 : number , y1 : number , w : number , h : number ) : BoundingBox {
28+ return {
29+ x1 : x1 ,
30+ x2 : x1 + w ,
31+ y1 : y1 ,
32+ y2 : y1 + h ,
33+ w : w ,
34+ h : h
35+ } ;
36+ }
37+
38+ function generateMockCyRefGetElementById ( x1 : number , y1 : number , w : number , h : number ) : cytoscape . NodeSingular {
39+ return {
40+ boundingBox : vi . fn ( ) . mockReturnValue ( generateBoundingBox ( x1 , y1 , w , h ) ) ,
41+ position : vi . fn ( ) ,
42+ } as unknown as cytoscape . NodeSingular ;
43+ }
44+
45+ describe ( LayoutCorrectionService . name , ( ) => {
46+
47+ let mockCyRef : cytoscape . Core ;
48+ let mockCyRefGetElementById : cytoscape . NodeSingular ;
49+
50+ beforeEach ( ( ) => {
51+ mockCyRefGetElementById = generateMockCyRefGetElementById ( 0 , 0 , 100 , 100 ) ;
52+ mockCyRef = {
53+ getElementById : vi . fn ( ) . mockImplementation ( ( ) => mockCyRefGetElementById ) ,
54+ nodes : vi . fn ( ) . mockReturnValue ( [ ] ) ,
55+ edges : vi . fn ( ) . mockReturnValue ( [ ] ) ,
56+ } as unknown as cytoscape . Core ;
57+ } ) ;
58+
59+ afterEach ( ( ) => {
60+ vi . resetAllMocks ( ) ;
61+ } ) ;
62+
63+ function getInstance ( ) : LayoutCorrectionService {
64+ return new LayoutCorrectionService ( ) ;
65+ }
66+
67+ it ( 'should call getElementById and bounding box functions on nodes to determine the nodes to be moved' , ( ) => {
68+ const instance = getInstance ( ) ;
69+ const nodes : CalmNode [ ] = [
70+ generateMockNodeObj ( 'node1' ) ,
71+ generateMockNodeObj ( 'node2' ) ,
72+ generateMockNodeObj ( 'node3' , 'node1' ) ,
73+ ] ;
74+
75+ instance . calculateAndUpdateNodePositions ( mockCyRef , nodes ) ;
76+ expect ( mockCyRef . getElementById ) . toHaveBeenCalledWith ( "node1" ) ;
77+ expect ( mockCyRef . getElementById ) . toHaveBeenCalledWith ( "node2" ) ;
78+ expect ( mockCyRef . getElementById ) . toHaveBeenCalledWith ( "node3" ) ;
79+ expect ( mockCyRefGetElementById . boundingBox ) . toHaveBeenCalledTimes ( 7 ) ;
80+ } ) ;
81+
82+ it ( 'should update position for nodes that are inside non-parents' , ( ) => {
83+ const instance = getInstance ( ) ;
84+ //Here, node 2 is inside node 1 but node 1 is not node 2's parent
85+ //So, node 2 is expected to be moved, but node 1 and node 3 are not
86+ const nodes : CalmNode [ ] = [
87+ generateMockNodeObj ( 'node1' ) ,
88+ generateMockNodeObj ( 'node2' ) ,
89+ generateMockNodeObj ( 'node3' , 'node1' ) ,
90+ ] ;
91+
92+ instance . calculateAndUpdateNodePositions ( mockCyRef , nodes ) ;
93+ //Once for node 2
94+ expect ( mockCyRefGetElementById . position ) . toHaveBeenCalledWith ( {
95+ x : - 100 , y : - 100 ,
96+ } ) ;
97+ } ) ;
98+
99+ it ( 'should update position for nodes that are not inside their parents' , ( ) => {
100+ const instance = getInstance ( ) ;
101+ //Here, node 3 is not inside node 1 but node 1 is node 3's parent
102+ //So, node 3 is expected to be moved, but node 1 and node 2 are not
103+ const mockCyRefGetElementByIdNode1 = generateMockCyRefGetElementById ( 0 , 0 , 100 , 100 ) ;
104+ const mockCyRefGetElementByIdNode2 = generateMockCyRefGetElementById ( 150 , 150 , 100 , 100 ) ;
105+ const mockCyRefGetElementByIdNode3 = generateMockCyRefGetElementById ( - 150 , - 150 , 100 , 100 ) ;
106+ mockCyRef = {
107+ getElementById : vi . fn ( ) . mockImplementation ( ( id ) => {
108+ if ( id === "node1" ) return mockCyRefGetElementByIdNode1 ;
109+ if ( id === "node2" ) return mockCyRefGetElementByIdNode2 ;
110+ if ( id === "node3" ) return mockCyRefGetElementByIdNode3 ;
111+ return null ;
112+ } ) ,
113+ nodes : vi . fn ( ) . mockReturnValue ( [ ] ) ,
114+ edges : vi . fn ( ) . mockReturnValue ( [ ] ) ,
115+ } as unknown as cytoscape . Core ;
116+
117+ const nodes : CalmNode [ ] = [
118+ generateMockNodeObj ( 'node1' ) ,
119+ generateMockNodeObj ( 'node2' ) ,
120+ generateMockNodeObj ( 'node3' , 'node1' )
121+ ] ;
122+
123+ instance . calculateAndUpdateNodePositions ( mockCyRef , nodes ) ;
124+ //Once for node 3
125+ expect ( mockCyRefGetElementByIdNode3 . position ) . toHaveBeenCalledWith ( {
126+ x : 50 , y : 50 ,
127+ } ) ;
128+ } ) ;
129+
130+ it ( 'should be able to handle nested parents' , ( ) => {
131+ const instance = getInstance ( ) ;
132+ //Here, node 3 should be inside node 2 and node 2 should be inside node 1
133+ //So, nodes 2 first, then node 3 are expected to be moved, but node 1 is not
134+ const mockCyRefGetElementByIdNode1 = generateMockCyRefGetElementById ( 0 , 0 , 100 , 100 ) ;
135+ const mockCyRefGetElementByIdNode2 = generateMockCyRefGetElementById ( 50 , 50 , 70 , 70 ) ;
136+ const mockCyRefGetElementByIdNode3 = generateMockCyRefGetElementById ( - 50 , - 50 , 50 , 50 ) ;
137+ mockCyRef = {
138+ getElementById : vi . fn ( ) . mockImplementation ( ( id ) => {
139+ if ( id === "node1" ) return mockCyRefGetElementByIdNode1 ;
140+ if ( id === "node2" ) return mockCyRefGetElementByIdNode2 ;
141+ if ( id === "node3" ) return mockCyRefGetElementByIdNode3 ;
142+ return null ;
143+ } ) ,
144+ nodes : vi . fn ( ) . mockReturnValue ( [ ] ) ,
145+ edges : vi . fn ( ) . mockReturnValue ( [ ] ) ,
146+ } as unknown as cytoscape . Core ;
147+
148+ const nodes : CalmNode [ ] = [
149+ generateMockNodeObj ( 'node1' ) ,
150+ generateMockNodeObj ( 'node2' , 'node1' ) ,
151+ generateMockNodeObj ( 'node3' , 'node2' ) ,
152+ ] ;
153+
154+ instance . calculateAndUpdateNodePositions ( mockCyRef , nodes ) ;
155+ //Once for node 2 (move to centre of node 1)
156+ expect ( mockCyRefGetElementByIdNode2 . position ) . toHaveBeenCalledWith ( {
157+ x : 50 , y : 50 ,
158+ } ) ;
159+ //Once for node 3 (move to centre of node 2, which has just moved)
160+ expect ( mockCyRefGetElementByIdNode3 . position ) . toHaveBeenCalledWith ( {
161+ x : 85 , y : 85 ,
162+ } ) ;
163+ } ) ;
164+
165+ it ( 'should be able to place a node to be moved between two nodes if necessary' , ( ) => {
166+ const instance = getInstance ( ) ;
167+ //Here, node 3 should not be inside node 1. It will bemoved so as to not overlap with node 2.
168+ const mockCyRefGetElementByIdNode1 = generateMockCyRefGetElementById ( 0 , 0 , 100 , 100 ) ;
169+ const mockCyRefGetElementByIdNode2 = generateMockCyRefGetElementById ( - 200 , - 200 , 100 , 100 ) ;
170+ const mockCyRefGetElementByIdNode3 = generateMockCyRefGetElementById ( 0 , 0 , 50 , 50 ) ;
171+ const mockCyRefGetElementByIdNode4 = generateMockCyRefGetElementById ( 10 , 10 , 40 , 40 ) ;
172+ mockCyRef = {
173+ getElementById : vi . fn ( ) . mockImplementation ( ( id ) => {
174+ if ( id === "node1" ) return mockCyRefGetElementByIdNode1 ;
175+ if ( id === "node2" ) return mockCyRefGetElementByIdNode2 ;
176+ if ( id === "node3" ) return mockCyRefGetElementByIdNode3 ;
177+ if ( id === "node4" ) return mockCyRefGetElementByIdNode4 ;
178+ return null ;
179+ } ) ,
180+ nodes : vi . fn ( ) . mockReturnValue ( [ ] ) ,
181+ edges : vi . fn ( ) . mockReturnValue ( [ ] ) ,
182+ } as unknown as cytoscape . Core ;
183+
184+ const nodes : CalmNode [ ] = [
185+ generateMockNodeObj ( 'node1' ) ,
186+ generateMockNodeObj ( 'node2' ) ,
187+ generateMockNodeObj ( 'node3' ) ,
188+ generateMockNodeObj ( 'node4' , 'node1' ) ,
189+ ] ;
190+
191+ instance . calculateAndUpdateNodePositions ( mockCyRef , nodes ) ;
192+ //Once for node 3 (move between node 1 and node 2)
193+ expect ( mockCyRefGetElementByIdNode3 . position ) . toHaveBeenCalledWith ( {
194+ x : - 50 , y : - 50 ,
195+ } ) ;
196+ } ) ;
197+ } ) ;
0 commit comments