• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

graphty-org / graphty-element / 15988747166

01 Jul 2025 03:01AM UTC coverage: 78.121% (+4.3%) from 73.786%
15988747166

push

github

apowers313
test: fix test types and mocks

319 of 434 branches covered (73.5%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 1 file covered. (100.0%)

202 existing lines in 8 files now uncovered.

2284 of 2898 relevant lines covered (78.81%)

886.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.5
/src/Node.ts
1
import {
1✔
2
    AbstractMesh,
3
    ActionManager,
1✔
4
    Color3,
1✔
5
    DynamicTexture,
1✔
6
    ExecuteCodeAction,
1✔
7
    Mesh,
8
    MeshBuilder,
1✔
9
    SixDofDragBehavior,
1✔
10
    StandardMaterial,
1✔
11
} from "@babylonjs/core";
1✔
12
import _ from "lodash";
1!
13

14
import {CalculatedValue} from "./CalculatedValue";
15
import {ChangeManager} from "./ChangeManager";
1✔
16
import {AdHocData, NodeStyleConfig} from "./config";
17
import type {Graph} from "./Graph";
18
import {NodeStyleId, Styles} from "./Styles";
1✔
19

20
const GOLDEN_RATIO = 1.618;
1✔
21

22
export type NodeIdType = string | number;
23

24
interface NodeOpts {
25
    pinOnDrag?: boolean;
26
}
27

28
export class Node {
1✔
29
    parentGraph: Graph;
30
    opts: NodeOpts;
31
    id: NodeIdType;
32
    data: AdHocData<string | number>;
33
    algorithmResults: AdHocData;
34
    styleUpdates: AdHocData;
35
    mesh: AbstractMesh;
36
    label?: Mesh;
37
    meshDragBehavior!: SixDofDragBehavior;
38
    dragging = false;
1✔
39
    styleId: NodeStyleId;
40
    pinOnDrag!: boolean;
41
    size!: number;
42
    changeManager: ChangeManager;
43

44
    constructor(graph: Graph, nodeId: NodeIdType, styleId: NodeStyleId, data: AdHocData<string | number>, opts: NodeOpts = {}) {
1✔
45
        this.parentGraph = graph;
1,255✔
46
        this.id = nodeId;
1,255✔
47
        this.opts = opts;
1,255✔
48
        this.changeManager = new ChangeManager();
1,255✔
49
        this.changeManager.loadCalculatedValues(this.parentGraph.styles.getCalculatedStylesForNode(data));
1,255✔
50
        this.data = this.changeManager.watch("data", data);
1,255✔
51
        this.algorithmResults = this.changeManager.watch("algorithmResults", {} as unknown as AdHocData);
1,255✔
52
        this.styleUpdates = this.changeManager.addData("style", {} as unknown as AdHocData);
1,255✔
53

54
        // copy nodeMeshOpts
55
        this.styleId = styleId;
1,255✔
56

57
        // create graph node
58
        this.parentGraph.layoutEngine.addNode(this);
1,255✔
59

60
        // create mesh
61
        this.mesh = Node.defaultNodeMeshFactory(this, this.parentGraph, styleId);
1,255✔
62
    }
1,255✔
63

64
    addCalculatedStyle(cv: CalculatedValue): void {
1✔
UNCOV
65
        this.changeManager.addCalculatedValue(cv);
×
UNCOV
66
    }
×
67

68
    update(): void {
1✔
69
        const newStyleKeys = Object.keys(this.styleUpdates);
8,069✔
70
        if (newStyleKeys.length > 0) {
8,069✔
71
            let style = Styles.getStyleForNodeStyleId(this.styleId);
154✔
72
            style = _.defaultsDeep(this.styleUpdates, style);
154✔
73
            const styleId = Styles.getNodeIdForStyle(style);
154✔
74
            this.updateStyle(styleId);
154✔
75
            for (const key of newStyleKeys) {
154✔
76
                // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
77
                delete this.styleUpdates[key];
231✔
78
            }
231✔
79
        }
154✔
80

81
        if (this.dragging) {
8,069!
UNCOV
82
            return;
×
UNCOV
83
        }
×
84

85
        const pos = this.parentGraph.layoutEngine.getNodePosition(this);
8,069✔
86
        this.mesh.position.x = pos.x;
8,069✔
87
        this.mesh.position.y = pos.y;
8,069✔
88
        if (pos.z) {
8,069✔
89
            this.mesh.position.z = pos.z;
7,915✔
90
        }
7,915✔
91
    }
8,069✔
92

93
    updateStyle(styleId: NodeStyleId): void {
1✔
94
        if (styleId === this.styleId) {
380✔
95
            return;
257✔
96
        }
257✔
97

98
        this.styleId = styleId;
123✔
99
        this.mesh.dispose();
123✔
100
        this.mesh = Node.defaultNodeMeshFactory(this, this.parentGraph, styleId);
123✔
101
    }
380✔
102

103
    pin(): void {
1✔
UNCOV
104
        this.parentGraph.layoutEngine.pin(this);
×
UNCOV
105
    }
×
106

107
    unpin(): void {
1✔
UNCOV
108
        this.parentGraph.layoutEngine.unpin(this);
×
UNCOV
109
    }
×
110

111
    static defaultNodeMeshFactory(n: Node, g: Graph, styleId: NodeStyleId): AbstractMesh {
1✔
112
        const o = Styles.getStyleForNodeStyleId(styleId);
1,378✔
113
        n.size = o.shape?.size ?? 0;
1,378!
114

115
        n.mesh = g.meshCache.get(`node-style-${styleId}`, () => {
1,378✔
116
            let mesh: Mesh;
117✔
117

118
            if (!o.shape) {
117!
119
                throw new TypeError("shape required to create mesh");
×
120
            }
×
121

122
            // create mesh shape
123
            switch (o.shape.type) {
117✔
124
            case "box":
117✔
125
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/set/box
126
                mesh = Node.createBox(n, g, o);
1✔
127
                break;
1✔
128
            case "sphere":
117!
129
                mesh = Node.createSphere(n, g, o);
×
UNCOV
130
                break;
×
131
            case "cylinder":
117!
132
                mesh = Node.createCylinder(n, g, o);
×
133
                break;
×
134
            case "cone":
117!
UNCOV
135
                mesh = Node.createCone(n, g, o);
×
136
                break;
×
137
            case "capsule":
117!
UNCOV
138
                mesh = Node.createCapsule(n, g, o);
×
UNCOV
139
                break;
×
140
            // Torus disabled because it breaks ray finding with arrowcaps whe
141
            // the ray shoots right through the hole in the center of the torus
142
            // case "torus":
143
            //     mesh = Node.createTorus(n, g, o);
144
            //     break;
145
            case "torus-knot":
117!
146
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/set/torus_knot
UNCOV
147
                mesh = Node.createTorusKnot(n, g, o);
×
148
                break;
×
149
            case "tetrahedron":
117!
150
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/polyhedra_by_numbers
UNCOV
151
                mesh = Node.createPolyhedron(0, n, g, o);
×
152
                break;
×
153
            case "octahedron":
117!
154
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/polyhedra_by_numbers
UNCOV
155
                mesh = Node.createPolyhedron(1, n, g, o);
×
156
                break;
×
157
            case "dodecahedron":
117!
158
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/polyhedra_by_numbers
UNCOV
159
                mesh = Node.createPolyhedron(2, n, g, o);
×
160
                break;
×
161
            case "icosahedron":
117!
162
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/polyhedra_by_numbers
UNCOV
163
                mesh = Node.createPolyhedron(3, n, g, o);
×
164
                break;
×
165
            case "rhombicuboctahedron":
117!
166
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/polyhedra_by_numbers
UNCOV
167
                mesh = Node.createPolyhedron(4, n, g, o);
×
168
                break;
×
169
            case "triangular_prism":
117!
170
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/polyhedra_by_numbers
UNCOV
171
                mesh = Node.createPolyhedron(5, n, g, o);
×
172
                break;
×
173
            case "pentagonal_prism":
117!
174
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/polyhedra_by_numbers
UNCOV
175
                mesh = Node.createPolyhedron(6, n, g, o);
×
176
                break;
×
177
            case "hexagonal_prism":
117!
178
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/polyhedra_by_numbers
UNCOV
179
                mesh = Node.createPolyhedron(7, n, g, o);
×
180
                break;
×
181
            case "square_pyramid":
117!
182
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/polyhedra_by_numbers
UNCOV
183
                mesh = Node.createPolyhedron(8, n, g, o);
×
184
                break;
×
185
            case "pentagonal_pyramid":
117!
186
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/polyhedra_by_numbers
UNCOV
187
                mesh = Node.createPolyhedron(9, n, g, o);
×
188
                break;
×
189
            case "triangular_dipyramid":
117!
190
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/polyhedra_by_numbers
UNCOV
191
                mesh = Node.createPolyhedron(10, n, g, o);
×
192
                break;
×
193
            case "pentagonal_dipyramid":
117!
194
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/polyhedra_by_numbers
UNCOV
195
                mesh = Node.createPolyhedron(11, n, g, o);
×
UNCOV
196
                break;
×
197
            case "elongated_square_dypyramid":
117!
198
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/polyhedra_by_numbers
UNCOV
199
                mesh = Node.createPolyhedron(12, n, g, o);
×
200
                break;
×
201
            case "elongated_pentagonal_dipyramid":
117!
202
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/polyhedra_by_numbers
UNCOV
203
                mesh = Node.createPolyhedron(13, n, g, o);
×
UNCOV
204
                break;
×
205
            case "elongated_pentagonal_cupola":
117!
206
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/polyhedra_by_numbers
UNCOV
207
                mesh = Node.createPolyhedron(14, n, g, o);
×
UNCOV
208
                break;
×
209
            case "goldberg":
117!
210
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/goldberg_poly
UNCOV
211
                mesh = Node.createGoldberg(n, g, o);
×
UNCOV
212
                break;
×
213
            case "icosphere":
117✔
214
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/icosphere
215
                mesh = Node.createIcoSphere(n, g, o);
116✔
216
                break;
116✔
217
            case "geodesic":
117!
218
                // https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/geodesic_poly
UNCOV
219
                mesh = Node.createGeodesic(n, g, o);
×
UNCOV
220
                break;
×
221
                // case "text":
222
                //     var fontData = await (await fetch("https://assets.babylonjs.com/fonts/Droid Sans_Regular.json")).json();
223
                //     mesh = MeshBuilder.CreateText("text", n.id, fontData, {
224
                //         size: 16,
225
                //         resolution: 64,
226
                //         depth: 10
227
                //     });
228
            default:
117!
UNCOV
229
                throw new TypeError(`unknown shape: ${o.shape.type}`);
×
230
            }
117✔
231

232
            // create mesh texture
233
            const mat = new StandardMaterial("defaultMaterial");
117✔
234
            let color3: Color3 | undefined;
117✔
235
            if (typeof o.texture?.color === "string") {
117✔
236
                // const color = Color4.FromHexString(o.texture.color);
237
                // const color3 = new Color3(color.r, color.g, color.b);
238
                color3 = Color3.FromHexString(o.texture.color);
116✔
239
            } else if (typeof o.texture?.color === "object") {
117✔
240
                switch (o.texture.color.colorType) {
1✔
241
                case "solid":
1✔
242
                    color3 = Color3.FromHexString(o.texture.color.value ?? "##FFFFFF");
1!
243
                    mesh.visibility = o.texture.color.opacity ?? 1;
1!
244
                    break;
1✔
245
                // TODO
246
                // case "gradient":
247
                // case "radial-gradient":
248
                default:
1!
UNCOV
249
                    throw new TypeError(`unknown advanced colorType ${o.texture.color.colorType}`);
×
250
                }
1✔
251
            }
1✔
252

253
            mat.wireframe = o.effect?.wireframe ?? false;
117✔
254
            if (color3 && g.config.layout.dimensions === 3) {
117✔
255
                mat.diffuseColor = color3;
117✔
256
            } else if (color3) {
117!
UNCOV
257
                mat.disableLighting = true;
×
UNCOV
258
                mat.emissiveColor = color3;
×
UNCOV
259
            }
×
260

261
            mat.freeze();
117✔
262

263
            mesh.material = mat;
117✔
264

265
            return mesh;
117✔
266
        });
1,378✔
267

268
        // create label
269
        if (o.label?.enabled) {
1,378✔
270
            n.label = Node.createLabel(n.id.toString(), n, n.parentGraph);
6✔
271
            n.label.parent = n.mesh;
6✔
272
            n.label.position.x += 1.25;
6✔
273
        }
6✔
274

275
        Node.addDefaultBehaviors(n, n.opts);
1,378✔
276

277
        return n.mesh;
1,378✔
278
    }
1,378✔
279

280
    static createBox(_n: Node, _g: Graph, o: NodeStyleConfig): Mesh {
1✔
281
        return MeshBuilder.CreateBox("box", {size: o.shape?.size ?? 1});
1!
282
    }
1✔
283

284
    static createSphere(_n: Node, _g: Graph, o: NodeStyleConfig): Mesh {
1✔
285
        return MeshBuilder.CreateSphere("sphere", {diameter: o.shape?.size ?? 1});
×
UNCOV
286
    }
×
287

288
    static createCylinder(_n: Node, _g: Graph, o: NodeStyleConfig): Mesh {
1✔
289
        return MeshBuilder.CreateCylinder("cylinder", {height: o.shape?.size ?? 1 * GOLDEN_RATIO, diameter: o.shape?.size ?? 1});
×
UNCOV
290
    }
×
291

292
    static createCone(_n: Node, _g: Graph, o: NodeStyleConfig): Mesh {
1✔
293
        return MeshBuilder.CreateCylinder("cylinder", {height: o.shape?.size ?? 1 * GOLDEN_RATIO, diameterTop: 0, diameterBottom: o.shape?.size ?? 1});
×
UNCOV
294
    }
×
295

296
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
297
    static createCapsule(_n: Node, _g: Graph, _o: NodeStyleConfig): Mesh {
1✔
UNCOV
298
        return MeshBuilder.CreateCapsule("capsule", {});
×
UNCOV
299
    }
×
300

301
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
302
    static createTorus(_n: Node, _g: Graph, _o: NodeStyleConfig): Mesh {
1✔
UNCOV
303
        return MeshBuilder.CreateTorus("torus", {});
×
304
    }
×
305

306
    static createTorusKnot(_n: Node, _g: Graph, o: NodeStyleConfig): Mesh {
1✔
UNCOV
307
        return MeshBuilder.CreateTorusKnot("tk", {radius: o.shape?.size ?? 1 * 0.3, tube: o.shape?.size ?? 1 * 0.2, radialSegments: 128});
×
UNCOV
308
    }
×
309

310
    static createPolyhedron(type: number, _n: Node, _g: Graph, o: NodeStyleConfig): Mesh {
1✔
UNCOV
311
        return MeshBuilder.CreatePolyhedron("polyhedron", {size: o.shape?.size ?? 1, type});
×
UNCOV
312
    }
×
313

314
    static createGoldberg(_n: Node, _g: Graph, o: NodeStyleConfig): Mesh {
1✔
UNCOV
315
        return MeshBuilder.CreateGoldberg("goldberg", {size: o.shape?.size ?? 1});
×
UNCOV
316
    }
×
317

318
    static createIcoSphere(_n: Node, _g: Graph, o: NodeStyleConfig): Mesh {
1✔
319
        return MeshBuilder.CreateIcoSphere("icosphere", {radius: o.shape?.size ?? 1 * 0.75});
116!
320
    }
116✔
321

322
    static createGeodesic(_n: Node, _g: Graph, o: NodeStyleConfig): Mesh {
1✔
UNCOV
323
        return MeshBuilder.CreateGeodesic("geodesic", {size: o.shape?.size ?? 1});
×
UNCOV
324
    }
×
325

326
    static createLabel(text: string, _n: Node, g: Graph): Mesh {
1✔
327
        // adapted from: https://playground.babylonjs.com/#TMHF80
328

329
        // Set font
330
        const fontSize = 48;
6✔
331
        // var font = "bold " + font_size + "px Arial";
332
        const font = `${fontSize}px Arial`;
6✔
333

334
        // Set height for plane
335
        const planeHeight = 0.5;
6✔
336

337
        // Set height for dynamic texture
338
        const DTHeight = 1.5 * fontSize; // or set as wished
6✔
339

340
        // Calcultae ratio
341
        const ratio = planeHeight / DTHeight;
6✔
342

343
        // Use a temporay dynamic texture to calculate the length of the text on the dynamic texture canvas
344
        const temp = new DynamicTexture("DynamicTexture", 64, g.scene);
6✔
345
        const tmpctx = temp.getContext();
6✔
346
        tmpctx.font = font;
6✔
347
        const DTWidth = tmpctx.measureText(text).width + 8;
6✔
348

349
        // Calculate width the plane has to be
350
        const planeWidth = DTWidth * ratio;
6✔
351

352
        // Create dynamic texture and write the text
353
        const dynamicTexture = new DynamicTexture("DynamicTexture", {width: DTWidth, height: DTHeight}, g.scene, false);
6✔
354
        const mat = new StandardMaterial("mat", g.scene);
6✔
355
        mat.specularColor = Color3.Black();
6✔
356
        dynamicTexture.hasAlpha = true;
6✔
357

358
        // draw rounded rectangle for background
359
        // borrowed from https://github.com/BabylonJS/Babylon.js/blob/2a7bd37ec899846b7a02c0507b1b9e27e4293180/packages/dev/gui/src/2D/controls/rectangle.ts#L209
360
        const context = dynamicTexture.getContext();
6✔
361
        context.fillStyle = "white";
6✔
362
        context.beginPath();
6✔
363
        const x = 0;
6✔
364
        const y = 0;
6✔
365
        const radiusList = [20, 20, 20, 20];
6✔
366
        const width = DTWidth;
6✔
367
        const height = DTHeight;
6✔
368
        context.moveTo(x + radiusList[0], y);
6✔
369
        context.lineTo(x + width - radiusList[1], y);
6✔
370
        context.arc(x + width - radiusList[1], y + radiusList[1], radiusList[1], (3 * Math.PI) / 2, Math.PI * 2);
6✔
371
        context.lineTo(x + width, y + height - radiusList[2]);
6✔
372
        context.arc(x + width - radiusList[2], y + height - radiusList[2], radiusList[2], 0, Math.PI / 2);
6✔
373
        context.lineTo(x + radiusList[3], y + height);
6✔
374
        context.arc(x + radiusList[3], y + height - radiusList[3], radiusList[3], Math.PI / 2, Math.PI);
6✔
375
        context.lineTo(x, y + radiusList[0]);
6✔
376
        context.arc(x + radiusList[0], y + radiusList[0], radiusList[0], Math.PI, (3 * Math.PI) / 2);
6✔
377
        context.closePath();
6✔
378
        context.fill();
6✔
379

380
        // draw label text
381
        dynamicTexture.drawText(text, null, null, font, "#000000", "transparent", true);
6✔
382
        mat.opacityTexture = dynamicTexture; // TODO: might be able to just use a rounded rectangle as the opacity layer rather than a colored background?
6✔
383
        mat.emissiveTexture = dynamicTexture;
6✔
384
        mat.disableLighting = true;
6✔
385

386
        // Create plane and set dynamic texture as material
387
        const plane = MeshBuilder.CreatePlane("plane", {width: planeWidth, height: planeHeight}, g.scene);
6✔
388
        plane.material = mat;
6✔
389

390
        // make text always face the camera
391
        plane.billboardMode = 7;
6✔
392

393
        return plane;
6✔
394
    }
6✔
395

396
    static addDefaultBehaviors(n: Node, opts: NodeOpts): void {
1✔
397
        n.mesh.isPickable = true;
1,378✔
398

399
        // drag behavior
400
        n.pinOnDrag = opts.pinOnDrag ?? true;
1,378✔
401
        n.meshDragBehavior = new SixDofDragBehavior();
1,378✔
402
        n.mesh.addBehavior(n.meshDragBehavior);
1,378✔
403

404
        // drag started
405
        n.meshDragBehavior.onDragStartObservable.add(() => {
1,378✔
406
            // make sure the graph is running
UNCOV
407
            n.parentGraph.running = true;
×
408

409
            // don't let the graph engine update the node -- we are controlling it
UNCOV
410
            n.dragging = true;
×
411
        });
1,378✔
412

413
        // drag ended
414
        n.meshDragBehavior.onDragEndObservable.add(() => {
1,378✔
415
            // make sure the graph is running
UNCOV
416
            n.parentGraph.running = true;
×
417

418
            // pin after dragging is node
UNCOV
419
            if (n.pinOnDrag) {
×
UNCOV
420
                n.pin();
×
UNCOV
421
            }
×
422

423
            // the graph engine can have control of the node again
UNCOV
424
            n.dragging = false;
×
425
        });
1,378✔
426

427
        // position changed
428
        n.meshDragBehavior.onPositionChangedObservable.add((event) => {
1,378✔
429
            // make sure the graph is running
430
            n.parentGraph.running = true;
×
431

432
            // update the node position
UNCOV
433
            n.parentGraph.layoutEngine.setNodePosition(n, event.position);
×
434
        });
1,378✔
435

436
        // TODO: this apparently updates dragging objects faster and more fluidly
437
        // https://playground.babylonjs.com/#YEZPVT%23840
438
        // https://forum.babylonjs.com/t/expandable-lines/24681/12
439

440
        // click behavior
441
        n.mesh.actionManager = n.mesh.actionManager ?? new ActionManager(n.parentGraph.scene);
1,378✔
442
        // ActionManager.OnDoublePickTrigger
443
        // ActionManager.OnRightPickTrigger
444
        // ActionManager.OnCenterPickTrigger
445
        // ActionManager.OnLongPressTrigger
446
        if (n.parentGraph.fetchNodes && n.parentGraph.fetchEdges) {
1,378!
447
            const {fetchNodes, fetchEdges} = n.parentGraph;
×
448
            n.mesh.actionManager.registerAction(
×
449
                new ExecuteCodeAction(
×
UNCOV
450
                    {
×
UNCOV
451
                        trigger: ActionManager.OnDoublePickTrigger,
×
452
                        // trigger: ActionManager.OnLongPressTrigger,
UNCOV
453
                    },
×
UNCOV
454
                    () => {
×
455
                        // make sure the graph is running
456
                        n.parentGraph.running = true;
×
457

458
                        // fetch all edges for current node
459
                        // @ts-expect-error for some reason this is confusing window.Node with our Node
460
                        const edges = fetchEdges(n, n.parentGraph);
×
461

462
                        // create set of unique node ids
UNCOV
463
                        const nodeIds = new Set<NodeIdType>();
×
UNCOV
464
                        edges.forEach((e) => {
×
UNCOV
465
                            nodeIds.add(e.src);
×
UNCOV
466
                            nodeIds.add(e.dst);
×
UNCOV
467
                        });
×
UNCOV
468
                        nodeIds.delete(n.id);
×
469

470
                        // fetch all nodes from associated edges
UNCOV
471
                        const nodes = fetchNodes(nodeIds, n.parentGraph);
×
472

473
                        // add all the nodes and edges we collected
UNCOV
474
                        n.parentGraph.addNodes([... nodes]);
×
UNCOV
475
                        n.parentGraph.addEdges([... edges]);
×
476

477
                        // TODO: fetch and add secondary edges
UNCOV
478
                    },
×
UNCOV
479
                ),
×
UNCOV
480
            );
×
UNCOV
481
        }
×
482
    }
1,378✔
483
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc