mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-28 13:21:42 +00:00
fixed test cases errors
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import * as sinon from "sinon";
|
||||
import { D3ForceGraph, LoadMoreDataAction, D3GraphNodeData } from "./D3ForceGraph";
|
||||
import { D3Node, D3Link, GraphData } from "../GraphExplorerComponent/GraphData";
|
||||
import GraphTab from "../../Tabs/GraphTab";
|
||||
import { D3Link, D3Node, GraphData } from "../GraphExplorerComponent/GraphData";
|
||||
import { D3ForceGraph, D3GraphNodeData, LoadMoreDataAction } from "./D3ForceGraph";
|
||||
|
||||
describe("D3ForceGraph", () => {
|
||||
const v1Id = "v1";
|
||||
@@ -138,18 +138,20 @@ describe("D3ForceGraph", () => {
|
||||
|
||||
it("should call onHighlightedNode callback when mouse hovering over node", () => {
|
||||
forceGraph.params.onGraphUpdated = () => {
|
||||
const mouseoverEvent = document.createEvent("Events");
|
||||
mouseoverEvent.initEvent("mouseover", true, false);
|
||||
$(rootNode).find(".node")[0].dispatchEvent(mouseoverEvent); // [0] is v1 vertex
|
||||
if (document) {
|
||||
const mouseoverEvent = document.createEvent("Events");
|
||||
mouseoverEvent.initEvent("mouseover", true, false);
|
||||
$(rootNode).find(".node")[0].dispatchEvent(mouseoverEvent); // [0] is v1 vertex
|
||||
|
||||
// onHighlightedNode is always called once to clear the selection
|
||||
expect((forceGraph.params.onHighlightedNode as sinon.SinonSpy).calledTwice).toBe(true);
|
||||
// onHighlightedNode is always called once to clear the selection
|
||||
expect((forceGraph.params.onHighlightedNode as sinon.SinonSpy).calledTwice).toBe(true);
|
||||
|
||||
const onHighlightedNode = (forceGraph.params.onHighlightedNode as sinon.SinonSpy).args[1][0] as D3GraphNodeData;
|
||||
expect(onHighlightedNode).not.toBe(null);
|
||||
expect(onHighlightedNode.id).toEqual(v1Id);
|
||||
const onHighlightedNode = (forceGraph.params.onHighlightedNode as sinon.SinonSpy)
|
||||
.args[1][0] as D3GraphNodeData;
|
||||
expect(onHighlightedNode).not.toBe(null);
|
||||
expect(onHighlightedNode.id).toEqual(v1Id);
|
||||
}
|
||||
};
|
||||
|
||||
forceGraph.updateGraph(newGraph);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -626,12 +626,12 @@ export class D3ForceGraph implements GraphRenderer {
|
||||
|
||||
this.addNewLinks();
|
||||
|
||||
const nodes = this.simulation.nodes();
|
||||
const nodes = this.simulation && this.simulation.nodes();
|
||||
this.redrawGraph();
|
||||
|
||||
this.animateBigBang(nodes, newNodes);
|
||||
|
||||
this.simulation.alpha(1).restart();
|
||||
this.simulation && this.simulation.alpha(1).restart();
|
||||
this.params.onGraphUpdated(new Date().getTime());
|
||||
});
|
||||
|
||||
@@ -651,140 +651,143 @@ export class D3ForceGraph implements GraphRenderer {
|
||||
}
|
||||
|
||||
private addNewLinks(): d3.Selection<Element, any, any, any> {
|
||||
const newLinks = this.linkSelection.enter().append("g").attr("class", "markerEndContainer");
|
||||
let newLinks: any = {};
|
||||
if (this.linkSelection) {
|
||||
newLinks = this.linkSelection.enter().append("g").attr("class", "markerEndContainer");
|
||||
const line = newLinks
|
||||
.append("path")
|
||||
.attr("class", "link")
|
||||
.attr("fill", "none")
|
||||
.attr("stroke-width", this.params.graphConfig.linkWidth())
|
||||
.attr("stroke", this.params.graphConfig.linkColor());
|
||||
|
||||
const line = newLinks
|
||||
.append("path")
|
||||
.attr("class", "link")
|
||||
.attr("fill", "none")
|
||||
.attr("stroke-width", this.params.graphConfig.linkWidth())
|
||||
.attr("stroke", this.params.graphConfig.linkColor());
|
||||
if (D3ForceGraph.useSvgMarkerEnd()) {
|
||||
line.attr("marker-end", `url(#${this.getArrowHeadSymbolId()}-marker)`);
|
||||
} else {
|
||||
newLinks
|
||||
.append("g")
|
||||
.append("use")
|
||||
.attr("xlink:href", `#${this.getArrowHeadSymbolId()}-nonMarker`)
|
||||
.attr("class", "markerEnd link")
|
||||
.attr("fill", this.params.graphConfig.linkColor())
|
||||
.classed(`${this.getArrowHeadSymbolId()}`, true);
|
||||
}
|
||||
|
||||
if (D3ForceGraph.useSvgMarkerEnd()) {
|
||||
line.attr("marker-end", `url(#${this.getArrowHeadSymbolId()}-marker)`);
|
||||
} else {
|
||||
newLinks
|
||||
.append("g")
|
||||
.append("use")
|
||||
.attr("xlink:href", `#${this.getArrowHeadSymbolId()}-nonMarker`)
|
||||
.attr("class", "markerEnd link")
|
||||
.attr("fill", this.params.graphConfig.linkColor())
|
||||
.classed(`${this.getArrowHeadSymbolId()}`, true);
|
||||
this.linkSelection = newLinks.merge(this.linkSelection);
|
||||
}
|
||||
|
||||
this.linkSelection = newLinks.merge(this.linkSelection);
|
||||
return newLinks;
|
||||
}
|
||||
|
||||
private addNewNodes(): d3.Selection<Element, any, any, any> {
|
||||
var self = this;
|
||||
let newNodes: any = {};
|
||||
if (this.nodeSelection) {
|
||||
newNodes = this.nodeSelection
|
||||
.enter()
|
||||
.append("g")
|
||||
.attr("class", (d: D3Node) => {
|
||||
return d._isRoot ? "node root" : "node";
|
||||
})
|
||||
.call(
|
||||
drag()
|
||||
.on("start", ((e: D3DragEvent<SVGGElement, D3Node, unknown>, d: D3Node) => {
|
||||
return this.dragstarted(d, e);
|
||||
}) as any)
|
||||
.on("drag", ((e: D3DragEvent<SVGGElement, D3Node, unknown>, d: D3Node) => {
|
||||
return this.dragged(d, e);
|
||||
}) as any)
|
||||
.on("end", ((e: D3DragEvent<SVGGElement, D3Node, unknown>, d: D3Node) => {
|
||||
return this.dragended(d, e);
|
||||
}) as any)
|
||||
)
|
||||
.on("mouseover", (_: MouseEvent, d: D3Node) => {
|
||||
if (this.isHighlightDisabled || this.selectedNode || this.isDragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newNodes = this.nodeSelection
|
||||
.enter()
|
||||
.append("g")
|
||||
.attr("class", (d: D3Node) => {
|
||||
return d._isRoot ? "node root" : "node";
|
||||
})
|
||||
.call(
|
||||
drag()
|
||||
.on("start", ((e: D3DragEvent<SVGGElement, D3Node, unknown>, d: D3Node) => {
|
||||
return this.dragstarted(d, e);
|
||||
}) as any)
|
||||
.on("drag", ((e: D3DragEvent<SVGGElement, D3Node, unknown>, d: D3Node) => {
|
||||
return this.dragged(d, e);
|
||||
}) as any)
|
||||
.on("end", ((e: D3DragEvent<SVGGElement, D3Node, unknown>, d: D3Node) => {
|
||||
return this.dragended(d, e);
|
||||
}) as any)
|
||||
)
|
||||
.on("mouseover", (_: MouseEvent, d: D3Node) => {
|
||||
if (this.isHighlightDisabled || this.selectedNode || this.isDragging) {
|
||||
return;
|
||||
}
|
||||
this.highlightNode(this, d);
|
||||
this.simulation.stop();
|
||||
})
|
||||
.on("mouseout", (_: MouseEvent, d: D3Node) => {
|
||||
if (this.isHighlightDisabled || this.selectedNode || this.isDragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.highlightNode(this, d);
|
||||
this.simulation.stop();
|
||||
})
|
||||
.on("mouseout", (_: MouseEvent, d: D3Node) => {
|
||||
if (this.isHighlightDisabled || this.selectedNode || this.isDragging) {
|
||||
return;
|
||||
}
|
||||
this.unhighlightNode();
|
||||
|
||||
this.unhighlightNode();
|
||||
this.simulation.restart();
|
||||
})
|
||||
.each((d: D3Node) => {
|
||||
// Initial position for nodes. This prevents blinking as following the tween transition doesn't always start right away
|
||||
d.x = self.viewCenter.x;
|
||||
d.y = self.viewCenter.y;
|
||||
});
|
||||
|
||||
this.simulation.restart();
|
||||
})
|
||||
.each((d: D3Node) => {
|
||||
// Initial position for nodes. This prevents blinking as following the tween transition doesn't always start right away
|
||||
d.x = self.viewCenter.x;
|
||||
d.y = self.viewCenter.y;
|
||||
});
|
||||
newNodes
|
||||
.append("circle")
|
||||
.attr("fill", this.getNodeColor.bind(this))
|
||||
.attr("class", "main")
|
||||
.attr("r", this.params.graphConfig.nodeSize());
|
||||
|
||||
newNodes
|
||||
.append("circle")
|
||||
.attr("fill", this.getNodeColor.bind(this))
|
||||
.attr("class", "main")
|
||||
.attr("r", this.params.graphConfig.nodeSize());
|
||||
|
||||
var iconGroup = newNodes
|
||||
.append("g")
|
||||
.attr("class", "iconContainer")
|
||||
.attr("tabindex", 0)
|
||||
.attr("aria-label", (d: D3Node) => {
|
||||
return this.retrieveNodeCaption(d);
|
||||
})
|
||||
.on("dblclick", function (_: MouseEvent, d: D3Node) {
|
||||
// this is the <g> element
|
||||
self.onNodeClicked(this.parentNode, d);
|
||||
})
|
||||
.on("click", function (_: MouseEvent, d: D3Node) {
|
||||
// this is the <g> element
|
||||
self.onNodeClicked(this.parentNode, d);
|
||||
})
|
||||
.on("keypress", function (event: KeyboardEvent, d: D3Node) {
|
||||
if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) {
|
||||
event.stopPropagation();
|
||||
var iconGroup = newNodes
|
||||
.append("g")
|
||||
.attr("class", "iconContainer")
|
||||
.attr("tabindex", 0)
|
||||
.attr("aria-label", (d: D3Node) => {
|
||||
return this.retrieveNodeCaption(d);
|
||||
})
|
||||
.on("dblclick", function (_: MouseEvent, d: D3Node) {
|
||||
// this is the <g> element
|
||||
self.onNodeClicked(this.parentNode, d);
|
||||
}
|
||||
});
|
||||
var nodeSize = this.params.graphConfig.nodeSize();
|
||||
var bgsize = nodeSize + 1;
|
||||
})
|
||||
.on("click", function (_: MouseEvent, d: D3Node) {
|
||||
// this is the <g> element
|
||||
self.onNodeClicked(this.parentNode, d);
|
||||
})
|
||||
.on("keypress", function (event: KeyboardEvent, d: D3Node) {
|
||||
if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) {
|
||||
event.stopPropagation();
|
||||
// this is the <g> element
|
||||
self.onNodeClicked(this.parentNode, d);
|
||||
}
|
||||
});
|
||||
var nodeSize = this.params.graphConfig.nodeSize();
|
||||
var bgsize = nodeSize + 1;
|
||||
|
||||
iconGroup
|
||||
.append("rect")
|
||||
.attr("x", -bgsize)
|
||||
.attr("y", -bgsize)
|
||||
.attr("width", bgsize * 2)
|
||||
.attr("height", bgsize * 2)
|
||||
.attr("fill-opacity", (d: D3Node) => {
|
||||
return this.params.graphConfig.nodeIconKey() ? 1 : 0;
|
||||
})
|
||||
.attr("class", "icon-background");
|
||||
iconGroup
|
||||
.append("rect")
|
||||
.attr("x", -bgsize)
|
||||
.attr("y", -bgsize)
|
||||
.attr("width", bgsize * 2)
|
||||
.attr("height", bgsize * 2)
|
||||
.attr("fill-opacity", (d: D3Node) => {
|
||||
return this.params.graphConfig.nodeIconKey() ? 1 : 0;
|
||||
})
|
||||
.attr("class", "icon-background");
|
||||
|
||||
// Possible icon: if xlink:href is undefined, the image won't show
|
||||
iconGroup
|
||||
.append("svg:image")
|
||||
.attr("xlink:href", (d: D3Node) => {
|
||||
return D3ForceGraph.computeImageData(d, this.params.graphConfig);
|
||||
})
|
||||
.attr("x", -nodeSize)
|
||||
.attr("y", -nodeSize)
|
||||
.attr("height", nodeSize * 2)
|
||||
.attr("width", nodeSize * 2)
|
||||
.attr("class", "icon");
|
||||
// Possible icon: if xlink:href is undefined, the image won't show
|
||||
iconGroup
|
||||
.append("svg:image")
|
||||
.attr("xlink:href", (d: D3Node) => {
|
||||
return D3ForceGraph.computeImageData(d, this.params.graphConfig);
|
||||
})
|
||||
.attr("x", -nodeSize)
|
||||
.attr("y", -nodeSize)
|
||||
.attr("height", nodeSize * 2)
|
||||
.attr("width", nodeSize * 2)
|
||||
.attr("class", "icon");
|
||||
|
||||
newNodes
|
||||
.append("text")
|
||||
.attr("class", "caption")
|
||||
.attr("dx", D3ForceGraph.TEXT_DX)
|
||||
.attr("dy", ".35em")
|
||||
.text((d: D3Node) => {
|
||||
return this.retrieveNodeCaption(d);
|
||||
});
|
||||
|
||||
this.nodeSelection = newNodes.merge(this.nodeSelection);
|
||||
newNodes
|
||||
.append("text")
|
||||
.attr("class", "caption")
|
||||
.attr("dx", D3ForceGraph.TEXT_DX)
|
||||
.attr("dy", ".35em")
|
||||
.text((d: D3Node) => {
|
||||
return this.retrieveNodeCaption(d);
|
||||
});
|
||||
|
||||
this.nodeSelection = newNodes.merge(this.nodeSelection);
|
||||
}
|
||||
return newNodes;
|
||||
}
|
||||
|
||||
@@ -967,34 +970,36 @@ export class D3ForceGraph implements GraphRenderer {
|
||||
* Remove LoadMore subassembly for existing nodes that show all their children in the graph
|
||||
*/
|
||||
private updateLoadMore(nodeSelection: d3.Selection<Element, any, any, any>) {
|
||||
const self = this;
|
||||
nodeSelection.selectAll(".loadmore").remove();
|
||||
if (nodeSelection) {
|
||||
const self = this;
|
||||
nodeSelection.selectAll(".loadmore").remove();
|
||||
|
||||
var nodeSize = this.params.graphConfig.nodeSize();
|
||||
const rootSelectionG = nodeSelection
|
||||
.filter((d: D3Node) => {
|
||||
return !!d._isRoot && !!d._pagination;
|
||||
})
|
||||
.append("g")
|
||||
.attr("class", "loadmore");
|
||||
this.createPaginationControl(rootSelectionG, nodeSize);
|
||||
var nodeSize = this.params.graphConfig.nodeSize();
|
||||
const rootSelectionG = nodeSelection
|
||||
.filter((d: D3Node) => {
|
||||
return !!d._isRoot && !!d._pagination;
|
||||
})
|
||||
.append("g")
|
||||
.attr("class", "loadmore");
|
||||
this.createPaginationControl(rootSelectionG, nodeSize);
|
||||
|
||||
const nodeNeighborMap = D3ForceGraph.countEdges(this.linkSelection.data());
|
||||
const missingNeighborNonRootG = nodeSelection
|
||||
.filter((d: D3Node) => {
|
||||
return !(
|
||||
d._isRoot ||
|
||||
(d._outEAllLoaded &&
|
||||
d._inEAllLoaded &&
|
||||
nodeNeighborMap.get(d.id) >= d._outEdgeIds.length + d._inEdgeIds.length)
|
||||
);
|
||||
})
|
||||
.append("g")
|
||||
.attr("class", "loadmore");
|
||||
this.createLoadMoreControl(missingNeighborNonRootG, nodeSize);
|
||||
const nodeNeighborMap = D3ForceGraph.countEdges(this.linkSelection.data());
|
||||
const missingNeighborNonRootG = nodeSelection
|
||||
.filter((d: D3Node) => {
|
||||
return !(
|
||||
d._isRoot ||
|
||||
(d._outEAllLoaded &&
|
||||
d._inEAllLoaded &&
|
||||
nodeNeighborMap.get(d.id) >= d._outEdgeIds.length + d._inEdgeIds.length)
|
||||
);
|
||||
})
|
||||
.append("g")
|
||||
.attr("class", "loadmore");
|
||||
this.createLoadMoreControl(missingNeighborNonRootG, nodeSize);
|
||||
|
||||
// Don't color icons individually, just the definitions
|
||||
this.svg.selectAll("#loadMoreIcon ellipse").attr("fill", this.params.graphConfig.nodeColor());
|
||||
// Don't color icons individually, just the definitions
|
||||
this.svg.selectAll("#loadMoreIcon ellipse").attr("fill", this.params.graphConfig.nodeColor());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user