import reducer, { saveFlowsToStorage } from "./flows.reducer" import { createNewFlow, deleteFlow, deleteNode, duplicateFlow, loadFlow, importFlow, setFlowArguments, updateSelectedFlow, addNode, addLink, deleteLink, updateNodeData, createNewWorkspace, deleteWorkspace, loadWorkspace, updateSelectedWorkspace, } from "./flows.actions" import { initialFlowChart } from "./flows.constants" import { testFlowArguments, testFlowState } from "./flows.testData" import { Flow, FlowNode, FlowEdge, NodeDataUpdate, Workspace, WorkspaceUpdate, } from "./flows.types" import { isFlowNode, isFlowEdge } from "../../helpers/flow-chart" import { selectExampleFlowsKeyEntryPairs, selectSelectedFlow, selectSelectedFlowId, } from "./flows.selectors" import { pickBy } from "lodash" function getFlowFromStorage(id: string): Flow | undefined { const userFlowsString = localStorage.getItem("userFlows") if (userFlowsString) { const parsed = JSON.parse(userFlowsString) return parsed[id] } else return undefined } function getWorkspaceFromStorage(id: string): Workspace | undefined { const userWorkspacesString = localStorage.getItem("userWorkspaces") if (userWorkspacesString) { const parsed = JSON.parse(userWorkspacesString) return parsed[id] } else return undefined } describe("flows reducer", () => { beforeEach(() => { saveFlowsToStorage(testFlowState.flows) }) it("should delete a flow from redux and storage", () => { expect(getFlowFromStorage("testFlow2")).toBeDefined() const oldNumberOfFlows = Object.keys(testFlowState.flows).length const flowStateWithoutFlow2 = reducer( testFlowState, deleteFlow("testFlow2") ) const newNumberOfFlows = Object.keys(flowStateWithoutFlow2.flows).length expect(oldNumberOfFlows - newNumberOfFlows).toEqual(1) expect( Object.keys(testFlowState.flows).find((flowId) => flowId === "testFlow2") ).toEqual("testFlow2") expect( Object.keys(flowStateWithoutFlow2.flows).find( (flowId) => flowId === "testFlow2" ) ).toBeUndefined() expect(getFlowFromStorage("testFlow2")).toBeUndefined() }) it("should duplicate the flower flow and save it to storage", () => { const flowerYaml = testFlowState.flows.flower.yaml if (flowerYaml) { const oldNumberOfFlows = Object.keys(testFlowState.flows).length const flowStateWithDuplicatedFlowerFlow = reducer( testFlowState, duplicateFlow(flowerYaml) ) const newNumberOfFlows = Object.keys( flowStateWithDuplicatedFlowerFlow.flows ).length const duplicatedIdAndFlow = Object.entries( flowStateWithDuplicatedFlowerFlow.flows ).find(([flowId, flow]) => flow.name === "Custom Flow 3") expect(newNumberOfFlows - oldNumberOfFlows).toBe(1) expect(duplicatedIdAndFlow).toBeDefined() if (duplicatedIdAndFlow) { const [dupId, dupFlow] = duplicatedIdAndFlow expect(dupFlow.flowChart).toEqual(testFlowState.flows.flower.flowChart) expect(getFlowFromStorage(dupId)?.flowChart).toEqual( testFlowState.flows.flower.flowChart ) } } }) it("should import a new flow from YAML and save it to storage", () => { const flowerYaml = testFlowState.flows.flower.yaml if (flowerYaml) { const oldNumberOfFlows = Object.keys(testFlowState.flows).length const flowStateWithImportedFlowerFlow = reducer( testFlowState, importFlow(flowerYaml) ) const newNumberOfFlows = Object.keys( flowStateWithImportedFlowerFlow.flows ).length const importedFlowerFlowIdAndFlow = Object.entries( flowStateWithImportedFlowerFlow.flows ).find(([flowId, flow]) => flow.name === "Custom Flow 3") expect(newNumberOfFlows - oldNumberOfFlows).toBe(1) expect(importedFlowerFlowIdAndFlow).toBeDefined() if (importedFlowerFlowIdAndFlow) { const [id, flow] = importedFlowerFlowIdAndFlow expect(flow.flowChart).toEqual(testFlowState.flows.flower.flowChart) expect(getFlowFromStorage(id)?.flowChart).toEqual(flow.flowChart) } } }) it("should create a new flow and save it to storage", () => { const oldNumberOfFlows = Object.keys(testFlowState.flows).length const flowStateWithNewFlow = reducer(testFlowState, createNewFlow()) const newNumberOfFlows = Object.keys(flowStateWithNewFlow.flows).length const newFlowIdAndFlow = Object.entries(flowStateWithNewFlow.flows).find( ([flowId, flow]) => flow.name === "Custom Flow 3" ) expect(newNumberOfFlows - oldNumberOfFlows).toBe(1) expect(newFlowIdAndFlow).toBeDefined() if (newFlowIdAndFlow) { const [id, flow] = newFlowIdAndFlow expect(flow.flowChart).toEqual(initialFlowChart) expect(getFlowFromStorage(id)?.flowChart).toEqual(flow.flowChart) } }) it("should load a flow", () => { const flowStateWithLoadedFlow = reducer( testFlowState, loadFlow("testFlow2") ) expect( flowStateWithLoadedFlow.workspaces[ flowStateWithLoadedFlow.selectedWorkspaceId ].selectedFlowId ).toEqual("testFlow2") }) it("should create a node and save it to storage", () => { const newNodeId = "newNodeId" const newPosition = { x: 1000, y: 1000 } const newData = { label: "newLabel", name: "newName", uses: "usesSomething", } const newState = reducer( testFlowState, addNode(newNodeId, newPosition, newData) ) const selectedFlowIdOldState = testFlowState.workspaces[testFlowState.selectedWorkspaceId].selectedFlowId const selectedFlowIdNewState = newState.workspaces[newState.selectedWorkspaceId].selectedFlowId const oldFlowChart = testFlowState.flows[selectedFlowIdOldState].flowChart const newFlowChart = newState.flows[selectedFlowIdNewState].flowChart const oldNodeCount = oldFlowChart.elements.filter((element) => isFlowNode(element) ).length const newNodeCount = newFlowChart.elements.filter((element) => isFlowNode(element) ).length const newNode = newFlowChart.elements.find( (element) => element.id === newNodeId ) as FlowNode expect(newNodeCount - oldNodeCount).toBe(1) expect(newNode.position).toEqual(newPosition) expect(newNode.data).toEqual(newData) const flowChartFromStorage = getFlowFromStorage(selectedFlowIdOldState) ?.flowChart const newNodeFromStorage = flowChartFromStorage?.elements.find( (element) => element.id === newNodeId ) as FlowNode expect(newNodeFromStorage.position).toEqual(newPosition) expect(newNodeFromStorage.data).toEqual(newData) }) it("should update nodes data and save it to storage", () => { const oldNode = testFlowState.flows.testFlow1.flowChart.elements.find( (element) => element.id === "gateway" ) const nodeDataUpdate: NodeDataUpdate = { label: "newLabel", } const flowStateWithUpdatedNode = reducer( testFlowState, updateNodeData("gateway", nodeDataUpdate) ) expect( flowStateWithUpdatedNode.flows.testFlow1.flowChart.elements.find( (element) => element.id === "gateway" )?.data ).toEqual({ ...oldNode?.data, ...nodeDataUpdate }) expect(getFlowFromStorage("testFlow1")).toEqual( flowStateWithUpdatedNode.flows.testFlow1 ) }) it("should delete nodes", () => { expect( testFlowState.flows.testFlow1.flowChart.elements.find( (element) => element.id === "gateway" ) ).toBeDefined() const flowStateWithDeletedNode = reducer( testFlowState, deleteNode("gateway") ) expect( flowStateWithDeletedNode.flows.testFlow1.flowChart.elements.find( (element) => element.id === "gateway" ) ).toBeUndefined() expect(getFlowFromStorage("testFlow1")).toEqual( flowStateWithDeletedNode.flows.testFlow1 ) }) it("should delete links, when deleting nodes", () => { expect( testFlowState.flows.testFlow1.flowChart.elements.find( (element) => element.id === "node0" ) ).toBeDefined() expect( testFlowState.flows.testFlow1.flowChart.elements.filter((element) => isFlowEdge(element) ) ).not.toEqual([]) const flowStateWithDeletedNode = reducer(testFlowState, deleteNode("node0")) expect( flowStateWithDeletedNode.flows.testFlow1.flowChart.elements.find( (element) => element.id === "node0" ) ).toBeUndefined() expect( flowStateWithDeletedNode.flows.testFlow1.flowChart.elements.filter( (element) => isFlowEdge(element) ) ).toEqual([]) expect(getFlowFromStorage("testFlow1")).toEqual( flowStateWithDeletedNode.flows.testFlow1 ) }) it("should add a link and save it to storage", () => { const source = "gateway" const target = "node1" const newState = reducer(testFlowState, addLink(source, target)) const selectedFlowIdOldState = testFlowState.workspaces[testFlowState.selectedWorkspaceId].selectedFlowId const selectedFlowIdNewState = newState.workspaces[newState.selectedWorkspaceId].selectedFlowId const oldFlowChart = testFlowState.flows[selectedFlowIdOldState].flowChart const newFlowChart = newState.flows[selectedFlowIdNewState].flowChart const oldLinkCount = oldFlowChart.elements.filter((element) => isFlowEdge(element) ).length const newLinkCount = newFlowChart.elements.filter((element) => isFlowEdge(element) ).length const newLink = newFlowChart.elements.find( (element) => element.id === `e-${source}-to-${target}` ) as FlowEdge expect(newLinkCount - oldLinkCount).toBe(1) expect(newLink.source).toBe(source) expect(newLink.target).toBe(target) const linkFromStorage = getFlowFromStorage( selectedFlowIdOldState )?.flowChart.elements.find( (element) => element.id === `e-${source}-to-${target}` ) as FlowEdge expect(linkFromStorage.source).toBe(source) expect(linkFromStorage.target).toBe(target) }) it("should delete a link from id and save that to storage", () => { const deletedLinkId = "e-gateway-to-node0" const selectedFlowIdOldstate = testFlowState.workspaces[testFlowState.selectedWorkspaceId].selectedFlowId const oldLinkFromStorage = getFlowFromStorage( selectedFlowIdOldstate )?.flowChart.elements.find( (element) => element.id === deletedLinkId ) as FlowEdge const newState = reducer(testFlowState, deleteLink(deletedLinkId)) const selectedFlowIdNewstate = newState.workspaces[newState.selectedWorkspaceId].selectedFlowId const oldFlowChart = testFlowState.flows[selectedFlowIdOldstate].flowChart const newFlowChart = newState.flows[selectedFlowIdNewstate].flowChart const oldLinkCount = oldFlowChart.elements.filter((element) => isFlowEdge(element) ).length const newLinkCount = newFlowChart.elements.filter((element) => isFlowEdge(element) ).length const newLinkFromStorage = getFlowFromStorage( selectedFlowIdOldstate )?.flowChart.elements.find( (element) => element.id === deletedLinkId ) as FlowEdge expect(oldLinkCount - newLinkCount).toBe(1) expect( oldFlowChart.elements.find((element) => element.id === deletedLinkId) ).toBeDefined() expect( newFlowChart.elements.find((element) => element.id === deletedLinkId) ).not.toBeDefined() expect(oldLinkFromStorage).toBeDefined() expect(newLinkFromStorage).not.toBeDefined() }) it("should delete a link from nodeConnection and save that to storage", () => { const source = "gateway" const target = "node0" const selectedFlowIdOldState = testFlowState.workspaces[testFlowState.selectedWorkspaceId].selectedFlowId const deletedLinkId = `e-${source}-to-${target}` const oldLinkFromStorage = getFlowFromStorage( selectedFlowIdOldState )?.flowChart.elements.find( (element) => element.id === deletedLinkId ) as FlowEdge const newState = reducer(testFlowState, deleteLink({ source, target })) const selectedFlowIdNewState = newState.workspaces[newState.selectedWorkspaceId].selectedFlowId const oldFlowChart = testFlowState.flows[selectedFlowIdOldState].flowChart const newFlowChart = newState.flows[selectedFlowIdNewState].flowChart const oldLinkCount = oldFlowChart.elements.filter((element) => isFlowEdge(element) ).length const newLinkCount = newFlowChart.elements.filter((element) => isFlowEdge(element) ).length const newLinkFromStorage = getFlowFromStorage( selectedFlowIdOldState )?.flowChart.elements.find( (element) => element.id === deletedLinkId ) as FlowEdge expect(oldLinkCount - newLinkCount).toBe(1) expect( oldFlowChart.elements.find((element) => element.id === deletedLinkId) ).toBeDefined() expect( newFlowChart.elements.find((element) => element.id === deletedLinkId) ).not.toBeDefined() expect(oldLinkFromStorage).toBeDefined() expect(newLinkFromStorage).not.toBeDefined() }) it("should set flow arguments", () => { const flowStateWithSetArguments = reducer( testFlowState, setFlowArguments(testFlowArguments) ) expect( flowStateWithSetArguments.workspaces[testFlowState.selectedWorkspaceId] .flowArguments ).toEqual(testFlowArguments) }) it("should update selected flow", () => { const { flowChart } = testFlowState.flows.testFlow1 const updatedFlow = reducer( testFlowState, updateSelectedFlow({ name: "Modified Name", type: "user-generated", isConnected: true, flowChart, }) ) expect(updatedFlow.flows.testFlow1.name).toEqual("Modified Name") expect(updatedFlow.flows.testFlow1.type).toEqual("user-generated") expect(updatedFlow.flows.testFlow1.isConnected).toEqual(true) expect(updatedFlow.flows.testFlow1.flowChart).toEqual(flowChart) }) it("should create a new workspace and save it to storage", () => { const oldNumberOfWorkspaces = Object.keys(testFlowState.workspaces).length const flowStateWithNewWorkspace = reducer( testFlowState, createNewWorkspace() ) const newNumberOfWorkspaces = Object.keys( flowStateWithNewWorkspace.workspaces ).length const newWorkspaceIdAndWorkspace = Object.entries( flowStateWithNewWorkspace.workspaces ).find(([workspaceId, workspace]) => workspace.name === "Workspace 3") expect(newNumberOfWorkspaces - oldNumberOfWorkspaces).toBe(1) expect(newWorkspaceIdAndWorkspace).toBeDefined() }) it("should load a workspace", () => { const flowStateWithLoadedWorkspace = reducer( testFlowState, loadWorkspace("testWorkspace2") ) expect(flowStateWithLoadedWorkspace.selectedWorkspaceId).toEqual( "testWorkspace2" ) }) it("should delete a workspace", () => { const oldNumberOfWorkspaces = Object.keys(testFlowState.workspaces).length const flowStateWithNewWorkspace = reducer( testFlowState, createNewWorkspace() ) const newNumberOfWorkspaces = Object.keys( flowStateWithNewWorkspace.workspaces ).length expect(newNumberOfWorkspaces - oldNumberOfWorkspaces).toBe(1) const newWorkSpaceId = Object.keys(flowStateWithNewWorkspace.workspaces)[ newNumberOfWorkspaces - 1 ] const flowStateWithoutNewWorkspace = reducer( testFlowState, deleteWorkspace(newWorkSpaceId) ) const newerNumberOfWorkspaces = Object.keys( flowStateWithoutNewWorkspace.workspaces ).length expect(newNumberOfWorkspaces - newerNumberOfWorkspaces).toBe(1) expect(flowStateWithNewWorkspace.workspaces[newWorkSpaceId]).toBeDefined() expect( flowStateWithoutNewWorkspace.workspaces[newWorkSpaceId] ).toBeUndefined() }) it("should delete a workspace from redux and storage", () => { expect(getWorkspaceFromStorage("testWorkspace2")).toBeDefined() const oldNumberOfWorkspaces = Object.keys(testFlowState.workspaces).length const flowStateWithoutWorkspace2 = reducer( testFlowState, deleteWorkspace("testWorkspace2") ) const newNumberOfWorkspaces = Object.keys( flowStateWithoutWorkspace2.workspaces ).length expect(oldNumberOfWorkspaces - newNumberOfWorkspaces).toEqual(1) expect( Object.keys(testFlowState.workspaces).find( (workspaceId) => workspaceId === "testWorkspace2" ) ).toEqual("testWorkspace2") expect( Object.keys(flowStateWithoutWorkspace2.workspaces).find( (workspaceId) => workspaceId === "testWorkspace2" ) ).toBeUndefined() expect(getWorkspaceFromStorage("testWorkspace2")).toBeUndefined() }) it("should update selected workspace", () => { const update: WorkspaceUpdate = { name: "newName", type: "user-generated", daemon_endpoint: "newDaemonEndpoint", daemon_id: "newWorkspaceId", isConnected: true, files: ["newFile1", "newFile2"], } const updatedFlowState = reducer( testFlowState, updateSelectedWorkspace(update) ) const selectedWorkspaceOldState = testFlowState.workspaces[testFlowState.selectedWorkspaceId] const selectedWorkspaceNewState = updatedFlowState.workspaces[updatedFlowState.selectedWorkspaceId] expect(selectedWorkspaceOldState).not.toEqual(selectedWorkspaceNewState) Object.entries(update).forEach((entry) => { const key = entry[0] as keyof Workspace const value = entry[1] expect(selectedWorkspaceNewState[key]).toEqual(value) }) }) }) describe("flow selectors", () => { const state = { flowState: testFlowState, } it("should select selected flow", () => { const { selectedFlowId } = testFlowState.workspaces[ state.flowState.selectedWorkspaceId ] expect(selectSelectedFlow(state)).toEqual( testFlowState.flows[selectedFlowId] ) }) it("should select example flows key entry pairs", () => { const { selectedWorkspaceId } = testFlowState const workspaceFlows = pickBy( testFlowState.flows, (flow) => flow.workspaceId === selectedWorkspaceId ) expect(selectExampleFlowsKeyEntryPairs(state)).toEqual( Object.entries(workspaceFlows).filter( (flowKeyEntryPair) => flowKeyEntryPair[1].type === "example" ) ) }) it("should select selected flowId", () => { expect(selectSelectedFlowId(state)).toBe( testFlowState.workspaces[testFlowState.selectedWorkspaceId].selectedFlowId ) }) })