Skip to content

Commit c31432e

Browse files
committed
refactor: Make useBpmnEditor return callback ref
1 parent 6b86ab2 commit c31432e

File tree

4 files changed

+24
-39
lines changed

4 files changed

+24
-39
lines changed

frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.test.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import { BpmnConfigPanelFormContextProvider } from '../../../contexts/BpmnConfig
66
import { textMock } from '@studio/testing/mocks/i18nMock';
77

88
jest.mock('../../../hooks/useBpmnEditor', () => ({
9-
useBpmnEditor: jest.fn().mockReturnValue({
10-
canvasRef: { current: document.createElement('div') },
11-
}),
9+
useBpmnEditor: jest.fn().mockReturnValue(jest.fn()),
1210
}));
1311

1412
describe('BPMNEditor', () => {

frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useBpmnEditor } from '../../../hooks/useBpmnEditor';
44
import './BPMNEditor.css';
55

66
export const BPMNEditor = (): React.ReactElement => {
7-
const { canvasRef } = useBpmnEditor();
7+
const canvasRef = useBpmnEditor();
88

9-
return <div className={classes.editorContainer} ref={canvasRef}></div>;
9+
return <div className={classes.editorContainer} ref={canvasRef} />;
1010
};

frontend/packages/process-editor/src/hooks/useBpmnEditor.test.tsx

+13-20
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import React from 'react';
22
import { renderHook, waitFor } from '@testing-library/react';
33
import { useBpmnEditor } from './useBpmnEditor';
4-
import { BpmnContextProvider, useBpmnContext } from '../contexts/BpmnContext';
4+
import { BpmnContextProvider } from '../contexts/BpmnContext';
55
import { BpmnApiContextProvider } from '../contexts/BpmnApiContext';
66
import { useBpmnModeler } from './useBpmnModeler';
77
import type { BpmnDetails } from '../types/BpmnDetails';
88
import type { LayoutSets } from 'app-shared/types/api/LayoutSetsResponse';
99
import { getMockBpmnElementForTask, mockBpmnDetails } from '../../test/mocks/bpmnDetailsMock';
10-
import { mockModelerRef } from '../../test/mocks/bpmnModelerMock';
1110
import { getBpmnEditorDetailsFromBusinessObject } from '../utils/bpmnObjectBuilders';
1211
import { StudioRecommendedNextActionContextProvider } from '@studio/components';
1312

@@ -64,7 +63,7 @@ jest.mock('../contexts/BpmnContext', () => ({
6463
useBpmnContext: jest.fn(() => ({
6564
getUpdatedXml: jest.fn(),
6665
modelerRef: { current: null },
67-
setBpmnDetails: jest.fn(),
66+
setBpmnDetails: setBpmnDetailsMock,
6867
})),
6968
}));
7069

@@ -76,16 +75,6 @@ const setBpmnDetailsMock = jest.fn();
7675
const onProcessTaskAddMock = jest.fn();
7776
const onProcessTaskRemoveMock = jest.fn();
7877

79-
const overrideUseBpmnContext = () => {
80-
(useBpmnContext as jest.Mock).mockReturnValue({
81-
getUpdatedXml: jest.fn(),
82-
modelerRef: {
83-
...mockModelerRef,
84-
},
85-
setBpmnDetails: setBpmnDetailsMock,
86-
});
87-
};
88-
8978
const overrideUseBpmnModeler = (currentEventName: string, currentEvent: any) => {
9079
(useBpmnModeler as jest.Mock).mockReturnValue({
9180
getModeler: () => new BpmnModelerMockImpl(currentEventName, currentEvent),
@@ -124,29 +113,29 @@ describe('useBpmnEditor', () => {
124113
it('should call saveBpmn when "commandStack.changed" event is triggered on modelerInstance', async () => {
125114
const currentEventName = 'commandStack.changed';
126115
const currentEvent = { element: getMockBpmnElementForTask('data') };
127-
renderUseBpmnEditor(false, currentEventName, currentEvent);
116+
setup(currentEventName, currentEvent);
128117

129118
await waitFor(() => expect(saveBpmnMock).toHaveBeenCalledTimes(1));
130119
});
131120

132121
it('should handle "shape.added" event', async () => {
133122
const currentEvent = { element: getMockBpmnElementForTask('data') };
134-
renderUseBpmnEditor(false, 'shape.added', currentEvent);
123+
setup('shape.added', currentEvent);
135124

136125
await waitFor(() => expect(onProcessTaskAddMock).toHaveBeenCalledTimes(1));
137126
});
138127

139128
it('should handle "shape.remove" event', async () => {
140129
const currentEvent = { element: getMockBpmnElementForTask('data') };
141-
renderUseBpmnEditor(false, 'shape.remove', currentEvent);
130+
setup('shape.remove', currentEvent);
142131

143132
await waitFor(() => expect(onProcessTaskRemoveMock).toHaveBeenCalledTimes(1));
144133
});
145134

146135
it('should call setBpmnDetails with selected object when "selection.changed" event is triggered with new selection', async () => {
147136
const currentEventName = 'selection.changed';
148137
const currentEvent = { newSelection: [getMockBpmnElementForTask('data')], oldSelection: [] };
149-
renderUseBpmnEditor(true, currentEventName, currentEvent);
138+
setup(currentEventName, currentEvent);
150139

151140
await waitFor(() => expect(setBpmnDetailsMock).toHaveBeenCalledTimes(1));
152141
expect(setBpmnDetailsMock).toHaveBeenCalledWith(expect.objectContaining(mockBpmnDetails));
@@ -155,20 +144,24 @@ describe('useBpmnEditor', () => {
155144
it('should call setBpmnDetails with null when "selection.changed" event is triggered with no new selected object', async () => {
156145
const currentEventName = 'selection.changed';
157146
const currentEvent = { oldSelection: [getMockBpmnElementForTask('data')], newSelection: [] };
158-
renderUseBpmnEditor(true, currentEventName, currentEvent);
147+
setup(currentEventName, currentEvent);
159148

160149
await waitFor(() => expect(setBpmnDetailsMock).toHaveBeenCalledTimes(1));
161150
expect(setBpmnDetailsMock).toHaveBeenCalledWith(null);
162151
});
163152
});
164153

154+
function setup(...params: Parameters<typeof renderUseBpmnEditor>): void {
155+
const div = document.createElement('div');
156+
const { result } = renderUseBpmnEditor(...params);
157+
result.current(div);
158+
}
159+
165160
const renderUseBpmnEditor = (
166-
overrideBpmnContext: boolean,
167161
currentEventName: string,
168162
currentEvent: any,
169163
bpmnDetails = mockBpmnDetails,
170164
) => {
171-
overrideBpmnContext && overrideUseBpmnContext();
172165
overrideGetBpmnEditorDetailsFromBusinessObject(bpmnDetails);
173166
overrideUseBpmnModeler(currentEventName, currentEvent);
174167
return renderHook(() => useBpmnEditor(), { wrapper });

frontend/packages/process-editor/src/hooks/useBpmnEditor.ts

+8-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { type MutableRefObject, useEffect, useCallback, useRef } from 'react';
2-
import type BpmnModeler from 'bpmn-js/lib/Modeler';
1+
import { useEffect, useCallback } from 'react';
32
import { useBpmnContext } from '../contexts/BpmnContext';
43
import { useBpmnModeler } from './useBpmnModeler';
54
import { useBpmnConfigPanelFormContext } from '../contexts/BpmnConfigPanelContext';
@@ -11,14 +10,10 @@ import { useStudioRecommendedNextActionContext } from '@studio/components';
1110

1211
// Wrapper around bpmn-js to Reactify it
1312

14-
type UseBpmnViewerResult = {
15-
canvasRef: MutableRefObject<HTMLDivElement>;
16-
modelerRef: MutableRefObject<BpmnModeler>;
17-
};
13+
type UseBpmnEditorResult = (div: HTMLDivElement) => void;
1814

19-
export const useBpmnEditor = (): UseBpmnViewerResult => {
15+
export const useBpmnEditor = (): UseBpmnEditorResult => {
2016
const { getUpdatedXml, bpmnXml, modelerRef, setBpmnDetails } = useBpmnContext();
21-
const canvasRef = useRef<HTMLDivElement | null>(null);
2217
const { metadataFormRef, resetForm } = useBpmnConfigPanelFormContext();
2318
const { getModeler, destroyModeler } = useBpmnModeler();
2419
const { addAction } = useStudioRecommendedNextActionContext();
@@ -98,14 +93,13 @@ export const useBpmnEditor = (): UseBpmnViewerResult => {
9893
});
9994
};
10095

101-
useEffect(() => {
102-
if (!canvasRef.current) {
103-
console.log('Canvas reference is not yet available in the DOM.');
104-
}
96+
const canvasRef = useCallback((div: HTMLDivElement) => {
97+
if (modelerRef.current) return;
98+
10599
// GetModeler can only be fetched from this hook once since the modeler creates a
106100
// new instance and will attach the same canvasRef container to all instances it fetches.
107101
// Set modelerRef.current to the Context so that it can be used in other components
108-
modelerRef.current = getModeler(canvasRef.current);
102+
modelerRef.current = getModeler(div);
109103

110104
initializeEditor().then(() => {
111105
// Wait for the initializeEditor to be initialized before attaching event listeners, to avoid trigger add.shape events on first draw
@@ -123,5 +117,5 @@ export const useBpmnEditor = (): UseBpmnViewerResult => {
123117
// eslint-disable-next-line react-hooks/exhaustive-deps
124118
}, []);
125119

126-
return { canvasRef, modelerRef };
120+
return canvasRef;
127121
};

0 commit comments

Comments
 (0)