From 78ab1418b5c2868f70e0bc59703a35209c62e471 Mon Sep 17 00:00:00 2001 From: joon-at-sri Date: Wed, 17 Jul 2024 14:02:00 -0700 Subject: [PATCH] highlight cyto and sigma --- src/logics/graphImpl/cytoImpl.ts | 1 - src/logics/graphImpl/sigmaImpl.ts | 79 +++---- .../ModalDialog/ModalDialogComponent.test.tsx | 197 ++++++++++++++++++ 3 files changed, 241 insertions(+), 36 deletions(-) create mode 100644 test/components/ModalDialog/ModalDialogComponent.test.tsx diff --git a/src/logics/graphImpl/cytoImpl.ts b/src/logics/graphImpl/cytoImpl.ts index 07641b7..1083c2e 100644 --- a/src/logics/graphImpl/cytoImpl.ts +++ b/src/logics/graphImpl/cytoImpl.ts @@ -31,7 +31,6 @@ cy.use(edgehandles) function toCyNode(n: NodeData): cy.NodeDefinition { let nodeColorMap = store.getState().graph.nodeColorMap let color = n.type !== undefined ? nodeColorMap[n.type] : '#000000'; - console.log(color); return { group: "nodes", data: { ...n, id: n.id!.toString() }, diff --git a/src/logics/graphImpl/sigmaImpl.ts b/src/logics/graphImpl/sigmaImpl.ts index 5654c03..58f67ce 100644 --- a/src/logics/graphImpl/sigmaImpl.ts +++ b/src/logics/graphImpl/sigmaImpl.ts @@ -50,36 +50,34 @@ function createSigmaGraph(container: HTMLElement) { }, }); - let selectedNode: any; - let selectedEdge: any; + let selectedNode: string | undefined; + let selectedEdge: string | undefined; + let nodesOfSelectedEdge: { source: string, target: string } | undefined; + sigma.on("clickEdge", e => { store.dispatch(setSelectedEdge(e.edge)) + selectedNode = undefined; + selectEdge(e.edge); + }) sigma.on("clickNode", (e) => { store.dispatch(setSelectedNode(e.node)) - selectNode(e.node); - - }); sigma.setSetting("nodeReducer", (node, data) => { const res: Partial = { ...data }; - - if (selectedNode && selectedNode !== node) { + if ((selectedNode && selectedNode !== node) || (!selectedNode && (nodesOfSelectedEdge && nodesOfSelectedEdge.source !== node && nodesOfSelectedEdge.target !== node))) { res.color = "#f6f6f6"; } - - if (selectedNode === node) { + if (selectedNode === node || (nodesOfSelectedEdge && (nodesOfSelectedEdge.source === node || nodesOfSelectedEdge.target === node))) { res.highlighted = true; } - return res; }); sigma.setSetting("edgeReducer", (edge, data) => { const res: Partial = { ...data }; - - if (selectedNode && !graph.hasExtremity(edge, selectedNode)) { + if ((selectedEdge && selectedEdge !== edge) || (selectedNode && !graph.hasExtremity(edge, selectedNode))) { res.size = 0.1; res.color = "#f2f5f3"; } @@ -90,8 +88,14 @@ function createSigmaGraph(container: HTMLElement) { if (node) { selectedNode = node; } - if (!node) { - selectedNode = undefined; + sigma.refresh(); + } + + function selectEdge(edge?: string) { + if (edge) { + const [source, target] = graph.extremities(edge); + nodesOfSelectedEdge = { source: source, target: target }; + selectedEdge = edge; } sigma.refresh(); } @@ -119,6 +123,9 @@ function createSigmaGraph(container: HTMLElement) { isDragging = true; draggedNode = e.node; graph!.setNodeAttribute(draggedNode, "highlighted", true); + selectedEdge = undefined; + nodesOfSelectedEdge = undefined; + selectNode(e.node); } sigmaLayout?.stop() store.dispatch(setIsPhysicsEnabled(false)) @@ -170,16 +177,18 @@ function createSigmaGraph(container: HTMLElement) { if (jsEvent.shiftKey && !draggingEdge) { store.dispatch(openNodeDialog({ x: params.event.x, y: params.event.y })); } - else if (selectedNode) { + else if (selectedNode || selectedEdge || nodesOfSelectedEdge) { + nodesOfSelectedEdge = undefined; + selectedEdge = undefined; selectedNode = undefined; sigma.refresh(); } }); document.addEventListener('keydown', function (e) { - if (e.key === 'Shift' && shiftKeyDown !== true) { - shiftKeyDown = true; - } + if (e.key === 'Shift' && shiftKeyDown !== true) { + shiftKeyDown = true; } + } ); document.addEventListener('keyup', function (e) { if (e.key === 'Shift' && shiftKeyDown === true) { @@ -198,23 +207,23 @@ function curveEdges(graph: Graph) { edgeMaxIndexAttribute: "parallelMaxIndex", }); graph.forEachEdge((edge, { parallelIndex, parallelMinIndex, parallelMaxIndex, }: - | { parallelIndex: number; parallelMinIndex?: number; parallelMaxIndex: number } - | { parallelIndex?: null; parallelMinIndex?: null; parallelMaxIndex?: null }, - ) => { - if (typeof parallelMinIndex === "number") { - graph.mergeEdgeAttributes(edge, { - type: parallelIndex ? "curved" : "straight", - curvature: getCurvature(parallelIndex, parallelMaxIndex), - }); - } else if (typeof parallelIndex === "number") { - graph.mergeEdgeAttributes(edge, { - type: "curved", - curvature: getCurvature(parallelIndex, parallelMaxIndex), - }); - } else { - graph.setEdgeAttribute(edge, "type", "straight"); - } - }, + | { parallelIndex: number; parallelMinIndex?: number; parallelMaxIndex: number } + | { parallelIndex?: null; parallelMinIndex?: null; parallelMaxIndex?: null }, + ) => { + if (typeof parallelMinIndex === "number") { + graph.mergeEdgeAttributes(edge, { + type: parallelIndex ? "curved" : "straight", + curvature: getCurvature(parallelIndex, parallelMaxIndex), + }); + } else if (typeof parallelIndex === "number") { + graph.mergeEdgeAttributes(edge, { + type: "curved", + curvature: getCurvature(parallelIndex, parallelMaxIndex), + }); + } else { + graph.setEdgeAttribute(edge, "type", "straight"); + } + }, ); } diff --git a/test/components/ModalDialog/ModalDialogComponent.test.tsx b/test/components/ModalDialog/ModalDialogComponent.test.tsx new file mode 100644 index 0000000..4abc358 --- /dev/null +++ b/test/components/ModalDialog/ModalDialogComponent.test.tsx @@ -0,0 +1,197 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import configureStore from 'redux-mock-store'; +import { render, screen, waitFor, fireEvent, act, within } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { App } from '../../../src/App'; +import userEvent from '@testing-library/user-event'; +import { defaultNodeLabel, EdgeData, NodeData, extractEdgesAndNodes, storeSuggestions } from "../../../src/logics/utils"; +import { setupStore } from "../../../src/app/store"; +import axios from 'axios'; +import { EDGE_ID_APPEND, QUERY_ENDPOINT, QUERY_RAW_ENDPOINT } from '../../../src/constants'; +import { setNodePositions } from '../../../src/logics/graph'; +import { addNodes, addEdges } from '../../../src/reducers/graphReducer' +import { openNodeDialog, setSuggestions } from '../../../src/reducers/dialogReducer'; +import { setNodeLabels } from '../../../src/reducers/optionReducer'; +import { ModalDialogComponent } from '../../../src/components/ModalDialog/ModalDialogComponent'; +// jest.mock('../../../src/logics/graph', () => ({ +// applyLayout: jest.fn(), +// getNodePositions: jest.fn(), +// setNodePositions: jest.fn(), +// layoutOptions: ['force-directed', 'hierarchical'] +// })); +jest.mock('../../../src/logics/graphImpl/sigmaImpl', () => ({ + __esModule: true, // Ensure it's treated as a module + +})); + +jest.mock("axios", () => ({ + ...jest.requireActual("axios"), + post: jest.fn(), +})); + +jest.mock('../../../src/constants', () => ({ + INITIAL_LABEL_MAPPINGS: { + person: 'name' + }, + GRAPH_IMPL: "vis" + +})); + +type State = { + gremlin: { + host: string; + port: string; + query: string; + }; + options: { + nodeLabels: string[]; + nodeLimit: number; + queryHistory: string[]; + }; + graph: { + selectedNode: NodeData | null; + selectedEdge: EdgeData | null; + nodes: NodeData[], + edges: NodeData[], + }; + + +}; + +const initialState: State = { + gremlin: { + host: 'localhost', + port: '8182', + query: 'g.V()' + }, + options: { + nodeLabels: [], + nodeLimit: 50, + queryHistory: [] + }, + + graph: { + selectedNode: null, + selectedEdge: null, + nodes: [], + edges: [], + } +}; + +test("test modalDialog renders", async () => { + let user = userEvent.setup(); + let store = setupStore({}); + jest.spyOn(store, 'dispatch'); + store.dispatch(openNodeDialog({ x: 200, y: 200 })); + render( + + + + ); + expect(screen.queryByRole('dialog')).toBeInTheDocument(); +}) + +test("test modalDialog renders with suggested", async () => { + const mockedAxios = axios as jest.Mocked; + mockedAxios.post.mockResolvedValue({ data:{ + "data": [ + { + "id": 32, + "label": "person", + "properties": { + "name": "Bob", + "age": "21" + }, + "edges": [] + } + ], + "status": 200, + "statusText": "OK", + "headers": { + "content-length": "78", + "content-type": "application/json; charset=utf-8" + }, + "config": { + "transitional": { + "silentJSONParsing": true, + "forcedJSONParsing": true, + "clarifyTimeoutError": false + }, + "adapter": [ + "xhr", + "http", + "fetch" + ], + "transformRequest": [ + null + ], + "transformResponse": [ + null + ], + "timeout": 0, + "xsrfCookieName": "XSRF-TOKEN", + "xsrfHeaderName": "X-XSRF-TOKEN", + "maxContentLength": -1, + "maxBodyLength": -1, + "env": {}, + "headers": { + "Accept": "application/json, text/plain, */*", + "Content-Type": "application/json" + }, + "method": "post", + "url": "http://localhost:3001/query", + "data": "{\"host\":\"localhost\",\"port\":\"8182\",\"query\":\"g.addV('person').property('name', 'Bob').property('age', '21')\",\"nodeLimit\":100}" + }, + "request": {} + }}); + + // const argument0: NodeData[] = [ + // { + // "id": 1, + // "label": "person", + // "properties": { + // "name": "Bob", + // "age": "21" + // }, + // "edges": [ + // { + // "id": "0", + // "from": 1, + // "to": 2, + // "label": "knows", + // "properties": { + // "length": "2" + // } + // } + // ] + // }, + // { + // "id": 2, + // "label": "person", + // "properties": { + // "name": "Max", + // "age": "18" + // }, + // "edges": [] + // }, + // ] as NodeData[] + // const argument1 = []; + let user = userEvent.setup(); + let store = setupStore({}); + jest.spyOn(store, 'dispatch'); + + // store.dispatch(setNodeLabels(nodeLabels)); + store.dispatch(openNodeDialog({ x: 200, y: 200 })); + render( + + + + ); + + expect(screen.queryByRole('dialog')).toBeInTheDocument(); + const input = screen.getByDisplayValue('name'); + + // Additional checks can be performed to ensure the right element is selected, if needed + expect(input).toBeInTheDocument(); +})