diff --git a/cypress.config.ts b/cypress.config.ts index ecf4b918..da5196bf 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -5,4 +5,5 @@ export default defineConfig({ fixturesFolder: false, screenshotOnRunFailure: false, defaultCommandTimeout: 10_000, + allowCypressEnv: false, }); diff --git a/cypress/e2e/links/linkAttributes.cy.ts b/cypress/e2e/links/linkAttributes.cy.ts index e68f8ee3..5a72d6ac 100644 --- a/cypress/e2e/links/linkAttributes.cy.ts +++ b/cypress/e2e/links/linkAttributes.cy.ts @@ -5,11 +5,11 @@ beforeEach(() => { cy.dragNodeInCanvas('ewokscore.tests.examples.tasks.sumlist.SumList'); cy.dragNodeInCanvas('ewokscore.tests.examples.tasks.sumlist.SumList'); cy.waitForStableDOM(); - cy.findByRole('button', { name: 'SumList0' }) + cy.findNode('SumList0') .find('.react-flow__handle-right') .click({ force: true }); - cy.findByRole('button', { name: 'SumList1' }) + cy.findNode('SumList1') .find('.react-flow__handle-left') .click({ force: true }); diff --git a/cypress/e2e/nodes.cy.ts b/cypress/e2e/nodes.cy.ts index c9a69641..710041e9 100644 --- a/cypress/e2e/nodes.cy.ts +++ b/cypress/e2e/nodes.cy.ts @@ -2,10 +2,7 @@ beforeEach(() => { cy.loadApp(); cy.findByRole('button', { name: 'Close task drawer' }).click(); cy.waitForStableDOM(); - cy.findAllByRole('button', { name: 'ewoksweb' }) - .filter('.react-flow__node') - .as('node', { type: 'static' }) - .click(); + cy.findNode('ewoksweb').as('node', { type: 'static' }).click(); }); it('changes label of node', () => { diff --git a/cypress/e2e/specialNodes.cy.ts b/cypress/e2e/specialNodes.cy.ts index 5dc5354d..81aa2fea 100644 --- a/cypress/e2e/specialNodes.cy.ts +++ b/cypress/e2e/specialNodes.cy.ts @@ -74,17 +74,13 @@ it('Saves special nodes', () => { cy.findByRole('button', { name: 'General' }).click(); cy.dragNodeInCanvas('note'); - cy.findByRole('button', { name: 'Note0' }).click({ force: true }); + cy.findNode('Note0').click({ force: true }); cy.dragNodeInCanvas('graphInput'); cy.dragNodeInCanvas('graphOutput'); cy.waitForStableDOM(); - cy.findByRole('button', { name: 'In0' }) - .find('.react-flow__handle-right') - .click({ force: true }); - cy.findByRole('button', { name: 'Out0' }) - .find('.react-flow__handle-left') - .click({ force: true }); + cy.findNode('In0').find('.react-flow__handle-right').click({ force: true }); + cy.findNode('Out0').find('.react-flow__handle-left').click({ force: true }); cy.dragNodeInCanvas('graphInput'); cy.dragNodeInCanvas('graphOutput'); diff --git a/cypress/e2e/tasks/generalTasks.cy.ts b/cypress/e2e/tasks/generalTasks.cy.ts index 6f46cbc6..73ed62ad 100644 --- a/cypress/e2e/tasks/generalTasks.cy.ts +++ b/cypress/e2e/tasks/generalTasks.cy.ts @@ -47,13 +47,13 @@ it('does not allow to edit node inputs or task info for graph input, graph outpu cy.dragNodeInCanvas('graphInput'); cy.waitForStableDOM(); - cy.findByRole('button', { name: 'In0' }).click(); + cy.findNode('In0').click(); cy.findByRole('heading', { name: 'Default Inputs' }).should('not.exist'); cy.findByRole('heading', { name: 'Task Info' }).should('not.exist'); cy.dragNodeInCanvas('graphOutput'); cy.waitForStableDOM(); - cy.findByRole('button', { name: 'Out0' }) + cy.findNode('Out0') // Is under the graphInput node .click({ force: true }); cy.findByRole('heading', { name: 'Default Inputs' }).should('not.exist'); @@ -61,7 +61,7 @@ it('does not allow to edit node inputs or task info for graph input, graph outpu cy.dragNodeInCanvas('note'); cy.waitForStableDOM(); - cy.findByRole('button', { name: 'Note0' }) + cy.findNode('Note0') // Is under the graphInput/graphOutput nodes .click({ force: true }); @@ -85,7 +85,5 @@ it('adds a subworkflow node by dragging the subworkflow task', () => { cy.findByRole('option', { name: 'Adding-Tasks' }).click(); cy.waitForStableDOM(); - cy.findAllByRole('button', { name: /Adding-Tasks/ }) - .filter('.react-flow__node') - .should('contain.text', 'graphInputgraphOutput'); + cy.findNode(/Adding-Tasks/).should('contain.text', 'graphInputgraphOutput'); }); diff --git a/cypress/e2e/workflows/changeDetection.cy.ts b/cypress/e2e/workflows/changeDetection.cy.ts index 0de1d369..4e9b09d0 100644 --- a/cypress/e2e/workflows/changeDetection.cy.ts +++ b/cypress/e2e/workflows/changeDetection.cy.ts @@ -2,9 +2,7 @@ beforeEach(() => { cy.loadApp(); cy.findByRole('button', { name: 'Close task drawer' }).click(); cy.waitForStableDOM(); - cy.findAllByRole('button', { name: 'ewoksweb' }) - .filter('.react-flow__node') - .as('node', { type: 'static' }); + cy.findNode('ewoksweb').as('node', { type: 'static' }); }); it('does not show a red dot on the save button if there are no changes', () => { diff --git a/cypress/e2e/workflows/saveOnServer.cy.ts b/cypress/e2e/workflows/saveOnServer.cy.ts index a06e831a..a3d7476a 100644 --- a/cypress/e2e/workflows/saveOnServer.cy.ts +++ b/cypress/e2e/workflows/saveOnServer.cy.ts @@ -138,10 +138,7 @@ it('saves a workflow with an empty skeleton node, and saves the workflow after p cy.saveNewWorkflow(id); - cy.findAllByRole('button', { name: 'taskSkeleton0' }) - .filter('.react-flow__node') - .as('node', { type: 'static' }) - .click(); + cy.findNode('taskSkeleton0').as('node', { type: 'static' }).click(); cy.findByRole('textbox', { name: 'Edit label' }).clear().type('theNewLabel'); cy.findByRole('textbox', { name: 'Edit comment' }) diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 26df85c1..c11bcbca 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -91,4 +91,8 @@ Cypress.Commands.add('hasVisibleEdges', (expectedNumberOfEdges: number) => { .should('be.visible'); }); +Cypress.Commands.add('findNode', (nodeLabel: RegExp | string) => { + return cy.findByRole('group', { name: nodeLabel }); +}); + addWaitForStableDomCommand({ pollInterval: 300, timeout: 5000 }); diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 534a6baf..9a24a27e 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -15,6 +15,7 @@ declare global { hasNavBarLabel(label: string): void; hasVisibleNodes(expectedNumberOfNodes: number): void; hasVisibleEdges(expectedNumberOfEdges: number): void; + findNode(nodeLabel: string | RegExp): Chainable; } } } diff --git a/package.json b/package.json index 14e95cfe..61001fa5 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@mui/material": "5.14.13", "@react-hookz/web": "25.2.0", "@tanstack/react-query": "4.35.7", - "@xyflow/react": "12.3.2", + "@xyflow/react": "12.11.0", "axios": "1.5.1", "d3-format": "3.1.2", "elkjs": "0.11.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 33beff21..972eabf4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,8 +27,8 @@ importers: specifier: 4.35.7 version: 4.35.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@xyflow/react': - specifier: 12.3.2 - version: 12.3.2(@types/react@18.3.28)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + specifier: 12.11.0 + version: 12.11.0(@types/react-dom@18.2.12)(@types/react@18.3.28)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) axios: specifier: 1.5.1 version: 1.5.1 @@ -1284,14 +1284,21 @@ packages: '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - '@xyflow/react@12.3.2': - resolution: {integrity: sha512-+bK3L61BDIvUX++jMiEqIjy5hIIyVmfeiUavpeOZIYKwg6NW0pR5EnHJM2JFfkVqZisFauzS9EgmI+tvTqx9Qw==} + '@xyflow/react@12.11.0': + resolution: {integrity: sha512-na4IO33FSs2OS72hASgZDmTYwFAkef7Z74uBUVrong3ARmQQHfnRUVaCFn1kTt5LbS6pK03TbYjCPGLjLFfziA==} peerDependencies: + '@types/react': '>=17' + '@types/react-dom': '>=17' react: '>=17' react-dom: '>=17' + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@xyflow/system@0.0.43': - resolution: {integrity: sha512-1zHgad1cWr1mKm2xbFaarK0Jg8WRgaQ8ubSBIo/pRdq3fEgCuqgNkL9NSAP6Rvm8zi3+Lu4JPUMN+EEx5QgX9A==} + '@xyflow/system@0.0.77': + resolution: {integrity: sha512-qCDCMCQAAgUu8yHnhloHG9F5mwPX5E+Wl8McpYIOPSSXfzFJJoZcwOcsDiAjitVKIg2de1WmJbCHfpcvxprsgg==} acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -4746,24 +4753,28 @@ snapshots: loupe: 3.2.1 tinyrainbow: 2.0.0 - '@xyflow/react@12.3.2(@types/react@18.3.28)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@xyflow/react@12.11.0(@types/react-dom@18.2.12)(@types/react@18.3.28)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: - '@xyflow/system': 0.0.43 + '@xyflow/system': 0.0.77 classcat: 5.0.5 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) zustand: 4.4.3(@types/react@18.3.28)(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.2.12 transitivePeerDependencies: - - '@types/react' - immer - '@xyflow/system@0.0.43': + '@xyflow/system@0.0.77': dependencies: '@types/d3-drag': 3.0.7 + '@types/d3-interpolate': 3.0.4 '@types/d3-selection': 3.0.11 '@types/d3-transition': 3.0.9 '@types/d3-zoom': 3.0.8 d3-drag: 3.0.0 + d3-interpolate: 3.0.1 d3-selection: 3.0.0 d3-zoom: 3.0.0 diff --git a/src/edition/Canvas/Canvas.tsx b/src/edition/Canvas/Canvas.tsx index 4bd07fd8..a2141af8 100644 --- a/src/edition/Canvas/Canvas.tsx +++ b/src/edition/Canvas/Canvas.tsx @@ -39,7 +39,7 @@ import NoteNode from '../nodes/NoteNode'; import OutputNode from '../nodes/OutputNode'; import RegularNode from '../nodes/RegularNode'; import AddSubworkflowDialog from '../TaskDrawer/AddSubworkflowDialog'; -import { generateNewNodeId } from '../utils'; +import { getNodeWithLabel, generateNewNodeId } from '../utils'; import styles from './Canvas.module.css'; import CanvasBackground from './CanvasBackground'; import FallbackMessage from './FallbackMessage'; @@ -96,7 +96,12 @@ function Canvas(props: Props) { function onNodesChange(changes: NodeChange[]) { const newNodes = applyNodeChanges(changes, getNodes()); - storeRF.getState().setNodes(newNodes); + const nodeData = getNodesData(); + storeRF + .getState() + .setNodes( + newNodes.map((node) => getNodeWithLabel(node, nodeData.get(node.id))), + ); } function onEdgesChange(changes: EdgeChange[]) { diff --git a/src/edition/Sidebar/edit/models.ts b/src/edition/Sidebar/edit/models.ts index e3f05ec3..ebb8186b 100644 --- a/src/edition/Sidebar/edit/models.ts +++ b/src/edition/Sidebar/edit/models.ts @@ -1,3 +1,3 @@ -import type { MarkerType } from '@xyflow/react'; +import { type EwoksMarkerEnd } from '../../../ewoksTypes'; -export type MarkerEndOption = MarkerType | 'none'; +export type MarkerEndOption = EwoksMarkerEnd; diff --git a/src/edition/Sidebar/edit/utils.ts b/src/edition/Sidebar/edit/utils.ts index 752586a0..7fa96c1b 100644 --- a/src/edition/Sidebar/edit/utils.ts +++ b/src/edition/Sidebar/edit/utils.ts @@ -1,5 +1,7 @@ import type { RFMarkerEnd } from '../../../types'; import type { MarkerEndOption } from './models'; +import { convertRFMarkerEndToEwoks } from '../../../utils/utils'; +import { DEFAULT_LINK_VALUES } from '../../../utils/defaultValues'; export function markerEndOptionToRF(option: MarkerEndOption): RFMarkerEnd { if (option === 'none') { @@ -10,11 +12,10 @@ export function markerEndOptionToRF(option: MarkerEndOption): RFMarkerEnd { } export function rfMarkerEndToOption(markerEnd: RFMarkerEnd): MarkerEndOption { - if (!markerEnd || typeof markerEnd === 'string') { - return 'none'; - } - - return markerEnd.type; + return ( + convertRFMarkerEndToEwoks(markerEnd) || + DEFAULT_LINK_VALUES.uiProps.markerEnd.type + ); } export function colorToRFEdgeStyle(color: string | undefined) { diff --git a/src/edition/utils.ts b/src/edition/utils.ts index 6581dc86..c7f05510 100644 --- a/src/edition/utils.ts +++ b/src/edition/utils.ts @@ -1,4 +1,5 @@ -import type { Task } from '../types'; +import { type Node } from '@xyflow/react'; +import type { NodeData, Task } from '../types'; import { getTaskName } from '../utils'; export const GRAPH_INPUT_ICON = 'graphInput.svg'; @@ -32,3 +33,7 @@ export function generateNewNodeId(task: Task, nodesIds: string[]): string { } return `${prefix}${id}`; } + +export function getNodeWithLabel(node: Node, data: NodeData | undefined): Node { + return { ...node, ariaLabel: data?.ewoks_props.label || node.id }; +} diff --git a/src/store/useWorkflowStore.ts b/src/store/useWorkflowStore.ts index 622c88cb..685b5118 100644 --- a/src/store/useWorkflowStore.ts +++ b/src/store/useWorkflowStore.ts @@ -122,7 +122,10 @@ const useWorkflowStore = create((set, get) => ({ }); const nodesWithoutData = rfNodes.map((node) => { - return { ...node, data: {} }; + return { + ...node, + data: {}, + }; }); const edgesWithoutData = rfLinks.map((edge) => { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 7d32833e..c2c382e8 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -11,6 +11,7 @@ import type { DataMapping, RFMarkerEnd, RowValue } from '../types'; import { RowType } from '../types'; import { DEFAULT_LINK_VALUES } from './defaultValues'; import { isMarkerType } from './typeGuards'; +import { type EdgeMarkerType, MarkerType } from '@xyflow/react'; export function createDataMappingData(pair: EwoksDataMapping): DataMapping { return { @@ -168,5 +169,14 @@ export function convertRFMarkerEndToEwoks( return undefined; } - return markerEnd.type; + const { type } = markerEnd; + if (type === 'arrow') { + return MarkerType.Arrow; + } + + if (type === 'arrowclosed') { + return MarkerType.ArrowClosed; + } + + return type; }