From 2d39d3684856076c99c01c08d0a350fc0eeab9bb Mon Sep 17 00:00:00 2001 From: "Ricardo M." Date: Fri, 17 Jan 2025 11:53:02 +0100 Subject: [PATCH 1/4] fix(Canvas): Fix CustomNode rerendering without controller When performing D&D, the following occurs: 1. The graph model gets reset 2. A structural change in the VisualEntity 3. The nodes rerenders 4. There are failuers in the console due to nodes being rerendered without having a controller, because it was removed during the node removal process The first solution for this is to set an empty model to the visualization controller so it removes all nodes from the canvas, then due to the recreation of the visual entities, the canvas will be repopulated. While this works in the browser, when running Kaoto through the Multiplying Architecture, it doesn't work, very likely because of the react versions mismatch (web: 18, MA: 17). The second iteration of the solution is to schedule the entities recreation outside of react, by using `requestAnimationFrame`, this way, the canvas will be cleared before recreating the visual entities. Almost everything boils down to the fact that because the vizNodes are being recreated during the visualization, and since they are stored in the data property of the canvas nodes, the CustomNodes and CustomGroups are being rerendered, due to the use of `observer` from `mobx`. A more future-proof solution would be to remove the `vizNode` objects from the data properties, so the rerendering process occurs without taking those into consideration. fix: https://github.com/KaotoIO/kaoto/issues/1888 --- .../Visualization/Canvas/Canvas.tsx | 12 +++- .../Custom/Group/CustomGroup.tsx | 46 ++++++++++---- .../Custom/Group/CustomGroupCollapsible.tsx | 39 ------------ .../Custom/Group/CustomGroupExpanded.tsx | 12 ++-- .../Visualization/Custom/Node/CustomNode.tsx | 60 ++++++++++++------- 5 files changed, 85 insertions(+), 84 deletions(-) delete mode 100644 packages/ui/src/components/Visualization/Custom/Group/CustomGroupCollapsible.tsx diff --git a/packages/ui/src/components/Visualization/Canvas/Canvas.tsx b/packages/ui/src/components/Visualization/Canvas/Canvas.tsx index c044715df..8fd2ba68f 100644 --- a/packages/ui/src/components/Visualization/Canvas/Canvas.tsx +++ b/packages/ui/src/components/Visualization/Canvas/Canvas.tsx @@ -90,8 +90,16 @@ export const Canvas: FunctionComponent> = ({ enti }, }; - controller.fromModel(model, false); - setInitialized(true); + if (!initialized) { + controller.fromModel(model, false); + setInitialized(true); + return; + } + + requestAnimationFrame(() => { + controller.fromModel(model, true); + controller.getGraph().layout(); + }); }, [controller, entities, visibleFlows]); const handleSelection = useCallback((selectedIds: string[]) => { diff --git a/packages/ui/src/components/Visualization/Custom/Group/CustomGroup.tsx b/packages/ui/src/components/Visualization/Custom/Group/CustomGroup.tsx index 9dfec7612..1ae2eedb1 100644 --- a/packages/ui/src/components/Visualization/Custom/Group/CustomGroup.tsx +++ b/packages/ui/src/components/Visualization/Custom/Group/CustomGroup.tsx @@ -7,35 +7,55 @@ import { withSelection, } from '@patternfly/react-topology'; import { FunctionComponent } from 'react'; -import { CanvasDefaults } from '../../Canvas/canvas.defaults'; import { CanvasNode } from '../../Canvas/canvas.models'; import { NodeContextMenuFn } from '../ContextMenu/NodeContextMenu'; -import { CustomGroupCollapsible } from './CustomGroupCollapsible'; +import { CustomNodeObserver } from '../Node/CustomNode'; +import { useCollapseStep } from '../hooks/collapse-step.hook'; +import { CustomGroupExpanded } from './CustomGroupExpanded'; type IDefaultGroup = Parameters[0]; interface ICustomGroup extends IDefaultGroup { element: GraphElement; } -const CustomGroup: FunctionComponent = observer(({ element, ...rest }) => { - const vizNode = element.getData()?.vizNode; - const label = vizNode?.getNodeLabel(); - +const CustomGroupInner: FunctionComponent = observer(({ element, onCollapseChange, ...rest }) => { if (!isNode(element)) { - throw new Error('CustomGroup must be used only on Node elements'); + throw new Error('CustomGroupInner must be used only on Node elements'); + } + + const { onCollapseNode, onExpandNode } = useCollapseStep(element); + + if (element.isCollapsed()) { + return ( + { + onExpandNode(); + onCollapseChange?.(element, true); + }} + /> + ); } return ( - { + onCollapseNode(); + onCollapseChange?.(element, false); + }} /> ); }); +const CustomGroup: FunctionComponent = ({ element, ...rest }: ICustomGroup) => { + if (!isNode(element)) { + throw new Error('CustomGroup must be used only on Node elements'); + } + + return ; +}; + export const CustomGroupWithSelection = withSelection()(withContextMenu(NodeContextMenuFn)(CustomGroup)); diff --git a/packages/ui/src/components/Visualization/Custom/Group/CustomGroupCollapsible.tsx b/packages/ui/src/components/Visualization/Custom/Group/CustomGroupCollapsible.tsx deleted file mode 100644 index b5e78d6c6..000000000 --- a/packages/ui/src/components/Visualization/Custom/Group/CustomGroupCollapsible.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { isNode, observer } from '@patternfly/react-topology'; -import { FunctionComponent } from 'react'; -import { useCollapseStep } from '../hooks/collapse-step.hook'; -import { CustomNodeWithSelection } from '../Node/CustomNode'; -import { CustomGroupExpanded } from './CustomGroupExpanded'; -import { CustomGroupProps } from './Group.models'; - -export const CustomGroupCollapsible: FunctionComponent = observer( - ({ className, element, selected, onCollapseChange, ...rest }) => { - if (!isNode(element)) { - throw new Error('CustomGroupCollapsible must be used only on Node elements'); - } - - const { onCollapseNode, onExpandNode } = useCollapseStep(element); - - if (element.isCollapsed()) { - return ( - { - onExpandNode(); - onCollapseChange?.(element, true); - }} - /> - ); - } - - return ( - { - onCollapseNode(); - onCollapseChange?.(element, false); - }} - /> - ); - }, -); diff --git a/packages/ui/src/components/Visualization/Custom/Group/CustomGroupExpanded.tsx b/packages/ui/src/components/Visualization/Custom/Group/CustomGroupExpanded.tsx index 91f939028..b134b8290 100644 --- a/packages/ui/src/components/Visualization/Custom/Group/CustomGroupExpanded.tsx +++ b/packages/ui/src/components/Visualization/Custom/Group/CustomGroupExpanded.tsx @@ -11,7 +11,6 @@ import { observer, useAnchor, useHover, - useSelection, withDndDrop, } from '@patternfly/react-topology'; import { FunctionComponent, useContext, useRef } from 'react'; @@ -21,13 +20,13 @@ import { LayoutType } from '../../Canvas'; import { StepToolbar } from '../../Canvas/StepToolbar/StepToolbar'; import { CanvasDefaults } from '../../Canvas/canvas.defaults'; import { AddStepIcon } from '../Edge/AddStepIcon'; +import { customGroupExpandedDropTargetSpec } from '../customComponentUtils'; import { TargetAnchor } from '../target-anchor'; import './CustomGroupExpanded.scss'; import { CustomGroupProps } from './Group.models'; -import { customGroupExpandedDropTargetSpec } from '../customComponentUtils'; export const CustomGroupExpandedInner: FunctionComponent = observer( - ({ element, onContextMenu, onCollapseToggle, dndDropRef, droppable }) => { + ({ element, onContextMenu, onCollapseToggle, dndDropRef, droppable, selected, onSelect }) => { if (!isNode(element)) { throw new Error('CustomGroupExpanded must be used only on Node elements'); } @@ -37,7 +36,6 @@ export const CustomGroupExpandedInner: FunctionComponent = obs const label = vizNode?.getNodeLabel(settingsAdapter.getSettings().nodeLabel); const isDisabled = !!vizNode?.getComponentSchema()?.definition?.disabled; const tooltipContent = vizNode?.getTooltipContent(); - const [isSelected, onSelect] = useSelection(); const [isGHover, gHoverRef] = useHover(CanvasDefaults.HOVER_DELAY_IN, CanvasDefaults.HOVER_DELAY_OUT); const [isToolbarHover, toolbarHoverRef] = useHover( CanvasDefaults.HOVER_DELAY_IN, @@ -46,8 +44,8 @@ export const CustomGroupExpandedInner: FunctionComponent = obs const boxRef = useRef(null); const shouldShowToolbar = settingsAdapter.getSettings().nodeToolbarTrigger === NodeToolbarTrigger.onHover - ? isGHover || isToolbarHover || isSelected - : isSelected; + ? isGHover || isToolbarHover || selected + : selected; const shouldShowAddStep = shouldShowToolbar && vizNode?.getNodeInteraction().canHaveNextStep && vizNode.getNextNode() === undefined; const isHorizontal = element.getGraph().getLayout() === LayoutType.DagreHorizontal; @@ -80,7 +78,7 @@ export const CustomGroupExpandedInner: FunctionComponent = obs className="custom-group" data-testid={`custom-group__${vizNode.id}`} data-grouplabel={label} - data-selected={isSelected} + data-selected={selected} data-disabled={isDisabled} data-toolbar-open={shouldShowToolbar} onClick={onSelect} diff --git a/packages/ui/src/components/Visualization/Custom/Node/CustomNode.tsx b/packages/ui/src/components/Visualization/Custom/Node/CustomNode.tsx index 161af8b6e..7ca9142f2 100644 --- a/packages/ui/src/components/Visualization/Custom/Node/CustomNode.tsx +++ b/packages/ui/src/components/Visualization/Custom/Node/CustomNode.tsx @@ -2,8 +2,8 @@ import { Icon } from '@patternfly/react-core'; import { ArrowDownIcon, ArrowRightIcon, BanIcon, ExclamationCircleIcon } from '@patternfly/react-icons'; import { AnchorEnd, - DefaultNode, DEFAULT_LAYER, + DefaultNode, DragObjectWithType, DragSourceSpec, DragSpecOperationType, @@ -20,27 +20,25 @@ import { TOP_LAYER, useAnchor, useCombineRefs, - useHover, useDragNode, - useSelection, + useHover, withContextMenu, withDndDrop, withSelection, - useVisualizationController, } from '@patternfly/react-topology'; import clsx from 'clsx'; import { FunctionComponent, useContext, useRef } from 'react'; +import { useEntityContext } from '../../../../hooks/useEntityContext/useEntityContext'; import { AddStepMode, IVisualizationNode, NodeToolbarTrigger } from '../../../../models'; import { SettingsContext } from '../../../../providers'; import { CanvasDefaults } from '../../Canvas/canvas.defaults'; import { CanvasNode, LayoutType } from '../../Canvas/canvas.models'; import { StepToolbar } from '../../Canvas/StepToolbar/StepToolbar'; import { NodeContextMenuFn } from '../ContextMenu/NodeContextMenu'; +import { customNodeDropTargetSpec } from '../customComponentUtils'; import { AddStepIcon } from '../Edge/AddStepIcon'; import { TargetAnchor } from '../target-anchor'; import './CustomNode.scss'; -import { useEntityContext } from '../../../../hooks/useEntityContext/useEntityContext'; -import { customNodeDropTargetSpec } from '../customComponentUtils'; type DefaultNodeProps = Parameters[0]; @@ -50,22 +48,20 @@ interface CustomNodeProps extends DefaultNodeProps { onCollapseToggle?: () => void; } -const CustomNode: FunctionComponent = observer( - ({ element, onContextMenu, onCollapseToggle, dndDropRef, hover, droppable, canDrop }) => { +const CustomNodeInner: FunctionComponent = observer( + ({ element, onContextMenu, onCollapseToggle, dndDropRef, hover, droppable, canDrop, selected, onSelect }) => { if (!isNode(element)) { - throw new Error('CustomNode must be used only on Node elements'); + throw new Error('CustomNodeInner must be used only on Node elements'); } const vizNode: IVisualizationNode | undefined = element.getData()?.vizNode; const entitiesContext = useEntityContext(); - const controller = useVisualizationController(); const settingsAdapter = useContext(SettingsContext); const label = vizNode?.getNodeLabel(settingsAdapter.getSettings().nodeLabel); const isDisabled = !!vizNode?.getComponentSchema()?.definition?.disabled; const tooltipContent = vizNode?.getTooltipContent(); const validationText = vizNode?.getNodeValidationText(); const doesHaveWarnings = !isDisabled && !!validationText; - const [isSelected, onSelect] = useSelection(); const [isGHover, gHoverRef] = useHover(CanvasDefaults.HOVER_DELAY_IN, CanvasDefaults.HOVER_DELAY_OUT); const [isToolbarHover, toolbarHoverRef] = useHover( CanvasDefaults.HOVER_DELAY_IN, @@ -75,8 +71,8 @@ const CustomNode: FunctionComponent = observer( const boxRef = useRef(null); const shouldShowToolbar = settingsAdapter.getSettings().nodeToolbarTrigger === NodeToolbarTrigger.onHover - ? isGHover || isToolbarHover || isSelected - : isSelected; + ? isGHover || isToolbarHover || selected + : selected; const shouldShowAddStep = shouldShowToolbar && vizNode?.getNodeInteraction().canHaveNextStep && vizNode.getNextNode() === undefined; const isHorizontal = element.getGraph().getLayout() === LayoutType.DagreHorizontal; @@ -94,9 +90,13 @@ const CustomNode: FunctionComponent = observer( > = { item: { type: '#node#' }, begin: () => { - const graph = controller.getGraph(); // Hide all edges when dragging starts - graph.getEdges().forEach((edge) => edge.setVisible(false)); + element + .getGraph() + .getEdges() + .forEach((edge) => { + edge.setVisible(false); + }); }, canDrag: () => { if (settingsAdapter.getSettings().experimentalFeatures.enableDragAndDrop) { @@ -110,18 +110,23 @@ const CustomNode: FunctionComponent = observer( const draggedNodePath = element.getData().vizNode.data.path; dropResult.getData()?.vizNode?.moveNodeTo(draggedNodePath); // Set an empty model to clear the graph - controller.fromModel({ + element.getController().fromModel({ nodes: [], edges: [], }); - entitiesContext.updateEntitiesFromCamelResource(); + + requestAnimationFrame(() => { + entitiesContext.updateEntitiesFromCamelResource(); + }); } else { // Show all edges after dropping - controller + element .getGraph() .getEdges() - .forEach((edge) => edge.setVisible(true)); - controller.getGraph().layout(); + .forEach((edge) => { + edge.setVisible(true); + }); + element.getGraph().layout(); } }, }; @@ -148,7 +153,7 @@ const CustomNode: FunctionComponent = observer( className="custom-node" data-testid={`custom-node__${vizNode.id}`} data-nodelabel={label} - data-selected={isSelected} + data-selected={selected} data-disabled={isDisabled} data-toolbar-open={shouldShowToolbar} data-warning={doesHaveWarnings} @@ -244,6 +249,15 @@ const CustomNode: FunctionComponent = observer( }, ); -export const CustomNodeWithSelection = withDndDrop(customNodeDropTargetSpec)( - withSelection()(withContextMenu(NodeContextMenuFn)(CustomNode)), +const CustomNode: FunctionComponent = ({ element, ...rest }: CustomNodeProps) => { + if (!isNode(element)) { + throw new Error('CustomNode must be used only on Node elements'); + } + return ; +}; + +export const CustomNodeObserver = observer(CustomNode); + +export const CustomNodeWithSelection = withSelection()( + withDndDrop(customNodeDropTargetSpec)(withContextMenu(NodeContextMenuFn)(CustomNode)), ); From 3549ea53fcfa1fd676cb10f24cc55321c534947a Mon Sep 17 00:00:00 2001 From: "Ricardo M." Date: Mon, 20 Jan 2025 11:49:47 +0100 Subject: [PATCH 2/4] chore(test): Add simple tests for CustomNode and PlaceholderNode --- .../expressionStepConfig.cy.ts | 6 ++- .../Canvas/controller.service.ts | 4 +- .../Custom/Node/CustomNode.test.tsx | 45 +++++++++++++++++++ .../Custom/Node/PlaceholderNode.test.tsx | 45 +++++++++++++++++++ .../Custom/Node/PlaceholderNode.tsx | 16 ++++++- .../__snapshots__/CustomNode.test.tsx.snap | 5 +++ .../PlaceholderNode.test.tsx.snap | 3 ++ 7 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 packages/ui/src/components/Visualization/Custom/Node/CustomNode.test.tsx create mode 100644 packages/ui/src/components/Visualization/Custom/Node/PlaceholderNode.test.tsx create mode 100644 packages/ui/src/components/Visualization/Custom/Node/__snapshots__/CustomNode.test.tsx.snap create mode 100644 packages/ui/src/components/Visualization/Custom/Node/__snapshots__/PlaceholderNode.test.tsx.snap diff --git a/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/expressionStepConfig.cy.ts b/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/expressionStepConfig.cy.ts index 596dc95db..d6c4d7271 100644 --- a/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/expressionStepConfig.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/expressionStepConfig.cy.ts @@ -25,13 +25,17 @@ describe('Tests for sidebar expression configuration', () => { cy.uploadFixture('flows/camelRoute/basic.yaml'); cy.openDesignPage(); - cy.openStepConfigurationTab('setHeader'); + cy.openStepConfigurationTab('setHeader', 0); cy.selectFormTab('All'); cy.selectExpression('JQ'); cy.interactWithConfigInputObject('expression', '.id'); cy.addExpressionResultType('java.lang.String'); cy.interactWithConfigInputObject('trim'); + // TODO: Closing the configuration panel because adding a new step keep the selection status, + // but closes the panel. This will be fixed in https://github.com/KaotoIO/kaoto/issues/1923 + cy.closeStepConfigurationTab(); + cy.selectAppendNode('setHeader'); cy.chooseFromCatalog('processor', 'setHeader'); diff --git a/packages/ui/src/components/Visualization/Canvas/controller.service.ts b/packages/ui/src/components/Visualization/Canvas/controller.service.ts index 7b249f0f8..f054745bb 100644 --- a/packages/ui/src/components/Visualization/Canvas/controller.service.ts +++ b/packages/ui/src/components/Visualization/Canvas/controller.service.ts @@ -12,7 +12,7 @@ import { withPanZoom, } from '@patternfly/react-topology'; import { CustomGroupWithSelection, CustomNodeWithSelection, NoBendpointsEdge } from '../Custom'; -import { PlaceholderNode } from '../Custom/Node/PlaceholderNode'; +import { PlaceholderNodeWithDnD } from '../Custom/Node/PlaceholderNode'; import { LayoutType } from './canvas.models'; import { CustomEdge } from '../Custom/Edge/CustomEdge'; @@ -52,7 +52,7 @@ export class ControllerService { case 'group': return CustomGroupWithSelection; case 'node-placeholder': - return PlaceholderNode; + return PlaceholderNodeWithDnD; default: switch (kind) { case ModelKind.graph: diff --git a/packages/ui/src/components/Visualization/Custom/Node/CustomNode.test.tsx b/packages/ui/src/components/Visualization/Custom/Node/CustomNode.test.tsx new file mode 100644 index 000000000..584577ae7 --- /dev/null +++ b/packages/ui/src/components/Visualization/Custom/Node/CustomNode.test.tsx @@ -0,0 +1,45 @@ +import { BaseEdge, BaseGraph, BaseNode, ElementContext, VisualizationProvider } from '@patternfly/react-topology'; +import { act, render } from '@testing-library/react'; +import { TestProvidersWrapper } from '../../../../stubs'; +import { ControllerService } from '../../Canvas/controller.service'; +import { CustomNodeObserver } from './CustomNode'; + +describe('CustomNode', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('should throw an error if not used on Node elements', () => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + const edgeElement = new BaseEdge(); + + expect(() => { + act(() => { + render(); + }); + }).toThrow('CustomNode must be used only on Node elements'); + }); + + it('should render without error', () => { + const parentElement = new BaseGraph(); + const element = new BaseNode(); + const controller = ControllerService.createController(); + parentElement.setController(controller); + element.setController(controller); + element.setParent(parentElement); + + const { Provider } = TestProvidersWrapper(); + + const wrapper = render( + + + + + + + , + ); + + expect(wrapper.asFragment()).toMatchSnapshot(); + }); +}); diff --git a/packages/ui/src/components/Visualization/Custom/Node/PlaceholderNode.test.tsx b/packages/ui/src/components/Visualization/Custom/Node/PlaceholderNode.test.tsx new file mode 100644 index 000000000..440ba5463 --- /dev/null +++ b/packages/ui/src/components/Visualization/Custom/Node/PlaceholderNode.test.tsx @@ -0,0 +1,45 @@ +import { BaseEdge, BaseGraph, BaseNode, ElementContext, VisualizationProvider } from '@patternfly/react-topology'; +import { act, render } from '@testing-library/react'; +import { TestProvidersWrapper } from '../../../../stubs'; +import { ControllerService } from '../../Canvas/controller.service'; +import { PlaceholderNodeObserver } from './PlaceholderNode'; + +describe('PlaceholderNode', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('should throw an error if not used on Node elements', () => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + const edgeElement = new BaseEdge(); + + expect(() => { + act(() => { + render(); + }); + }).toThrow('PlaceholderNode must be used only on Node elements'); + }); + + it('should render without error', () => { + const parentElement = new BaseGraph(); + const element = new BaseNode(); + const controller = ControllerService.createController(); + parentElement.setController(controller); + element.setController(controller); + element.setParent(parentElement); + + const { Provider } = TestProvidersWrapper(); + + const wrapper = render( + + + + + + + , + ); + + expect(wrapper.asFragment()).toMatchSnapshot(); + }); +}); diff --git a/packages/ui/src/components/Visualization/Custom/Node/PlaceholderNode.tsx b/packages/ui/src/components/Visualization/Custom/Node/PlaceholderNode.tsx index 38ff2e6e8..24e97a52e 100644 --- a/packages/ui/src/components/Visualization/Custom/Node/PlaceholderNode.tsx +++ b/packages/ui/src/components/Visualization/Custom/Node/PlaceholderNode.tsx @@ -30,7 +30,7 @@ interface PlaceholderNodeInnerProps extends DefaultNodeProps { const PlaceholderNodeInner: FunctionComponent = observer( ({ element, dndDropRef, hover, canDrop }) => { if (!isNode(element)) { - throw new Error('PlaceholderNode must be used only on Node elements'); + throw new Error('PlaceholderNodeInner must be used only on Node elements'); } const vizNode: IVisualizationNode | undefined = element.getData()?.vizNode; const settingsAdapter = useContext(SettingsContext); @@ -93,4 +93,16 @@ const PlaceholderNodeInner: FunctionComponent = obser }, ); -export const PlaceholderNode = withDndDrop(placeholderNodeDropTargetSpec)(PlaceholderNodeInner); +const PlaceholderNode: FunctionComponent = ({ + element, + ...rest +}: PlaceholderNodeInnerProps) => { + if (!isNode(element)) { + throw new Error('PlaceholderNode must be used only on Node elements'); + } + return ; +}; + +export const PlaceholderNodeObserver = observer(PlaceholderNode); + +export const PlaceholderNodeWithDnD = withDndDrop(placeholderNodeDropTargetSpec)(PlaceholderNode); diff --git a/packages/ui/src/components/Visualization/Custom/Node/__snapshots__/CustomNode.test.tsx.snap b/packages/ui/src/components/Visualization/Custom/Node/__snapshots__/CustomNode.test.tsx.snap new file mode 100644 index 000000000..9e1fb2504 --- /dev/null +++ b/packages/ui/src/components/Visualization/Custom/Node/__snapshots__/CustomNode.test.tsx.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CustomNode should render 1`] = ``; + +exports[`CustomNode should render without error 1`] = ``; diff --git a/packages/ui/src/components/Visualization/Custom/Node/__snapshots__/PlaceholderNode.test.tsx.snap b/packages/ui/src/components/Visualization/Custom/Node/__snapshots__/PlaceholderNode.test.tsx.snap new file mode 100644 index 000000000..bc1a29add --- /dev/null +++ b/packages/ui/src/components/Visualization/Custom/Node/__snapshots__/PlaceholderNode.test.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PlaceholderNode should render without error 1`] = ``; From 06f83ac900879a73152a84b9326afe280b6834e5 Mon Sep 17 00:00:00 2001 From: Tomas Plevko Date: Mon, 20 Jan 2025 13:58:56 +0100 Subject: [PATCH 3/4] (e2e): fix expressionStepConfig test failure --- .../specialStepConfiguration/expressionStepConfig.cy.ts | 8 ++++---- packages/ui-tests/cypress/support/cypress.d.ts | 1 + packages/ui-tests/cypress/support/next-commands/design.ts | 4 ++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/expressionStepConfig.cy.ts b/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/expressionStepConfig.cy.ts index d6c4d7271..2953955d3 100644 --- a/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/expressionStepConfig.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/expressionStepConfig.cy.ts @@ -25,7 +25,7 @@ describe('Tests for sidebar expression configuration', () => { cy.uploadFixture('flows/camelRoute/basic.yaml'); cy.openDesignPage(); - cy.openStepConfigurationTab('setHeader', 0); + cy.openStepConfigurationTabByPath('custom-node__route.from.steps.0.setHeader'); cy.selectFormTab('All'); cy.selectExpression('JQ'); cy.interactWithConfigInputObject('expression', '.id'); @@ -41,14 +41,14 @@ describe('Tests for sidebar expression configuration', () => { cy.checkNodeExist('setHeader', 2); - cy.openStepConfigurationTab('setHeader', 1); + cy.openStepConfigurationTabByPath('custom-node__route.from.steps.1.setHeader'); cy.selectFormTab('All'); cy.selectExpression('JQ'); cy.interactWithConfigInputObject('expression', '.name'); cy.addExpressionResultType('java.lang.String'); cy.interactWithConfigInputObject('trim'); - cy.openStepConfigurationTab('setHeader', 0); + cy.openStepConfigurationTabByPath('custom-node__route.from.steps.0.setHeader'); // Check the configured fields didn't disappear from the first node cy.checkConfigCheckboxObject('trim', true); @@ -56,7 +56,7 @@ describe('Tests for sidebar expression configuration', () => { cy.checkConfigInputObject('expression', '.id'); // Check the configured fields didn't disappear from the second node - cy.openStepConfigurationTab('setHeader', 0); + cy.openStepConfigurationTabByPath('custom-node__route.from.steps.1.setHeader'); cy.checkConfigCheckboxObject('trim', true); cy.addExpressionResultType('java.lang.String'); cy.checkConfigInputObject('expression', '.name'); diff --git a/packages/ui-tests/cypress/support/cypress.d.ts b/packages/ui-tests/cypress/support/cypress.d.ts index c1a693900..162ab7659 100644 --- a/packages/ui-tests/cypress/support/cypress.d.ts +++ b/packages/ui-tests/cypress/support/cypress.d.ts @@ -46,6 +46,7 @@ declare global { // design openGroupConfigurationTab(step: string, stepIndex?: number): Chainable>; openStepConfigurationTab(step: string, stepIndex?: number): Chainable>; + openStepConfigurationTabByPath(path: string): Chainable>; toggleExpandGroup(groupName: string): Chainable>; fitToScreen(): Chainable>; closeStepConfigurationTab(): Chainable>; diff --git a/packages/ui-tests/cypress/support/next-commands/design.ts b/packages/ui-tests/cypress/support/next-commands/design.ts index d25de1f10..b2e3e1c55 100644 --- a/packages/ui-tests/cypress/support/next-commands/design.ts +++ b/packages/ui-tests/cypress/support/next-commands/design.ts @@ -7,6 +7,10 @@ Cypress.Commands.add('openStepConfigurationTab', (step: string, stepIndex?: numb cy.get(`g[data-nodelabel^="${step}"]`).eq(stepIndex).click({ force: true }); }); +Cypress.Commands.add('openStepConfigurationTabByPath', (path: string) => { + cy.get(`g[data-testid="${path}"]`).click({ force: true }); +}); + Cypress.Commands.add('openGroupConfigurationTab', (group: string, groupIndex?: number) => { groupIndex = groupIndex ?? 0; cy.get(`g[data-grouplabel^="${group}"]`).eq(groupIndex).click({ force: true }); From 27406ca1a5a12f62bd8fb41360d177c0f8892b0f Mon Sep 17 00:00:00 2001 From: "Ricardo M." Date: Mon, 20 Jan 2025 14:50:43 +0100 Subject: [PATCH 4/4] fix(test): Update snapshots --- .../Custom/Node/__snapshots__/CustomNode.test.tsx.snap | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/ui/src/components/Visualization/Custom/Node/__snapshots__/CustomNode.test.tsx.snap b/packages/ui/src/components/Visualization/Custom/Node/__snapshots__/CustomNode.test.tsx.snap index 9e1fb2504..a5eaf37ca 100644 --- a/packages/ui/src/components/Visualization/Custom/Node/__snapshots__/CustomNode.test.tsx.snap +++ b/packages/ui/src/components/Visualization/Custom/Node/__snapshots__/CustomNode.test.tsx.snap @@ -1,5 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`CustomNode should render 1`] = ``; - exports[`CustomNode should render without error 1`] = ``;