-
Notifications
You must be signed in to change notification settings - Fork 8
Make workflow read only by disabling drag-and-drop and edge handles #165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@serverlessworkflow/diagram-editor": minor | ||
| --- | ||
|
|
||
| Enable read-only mode locking nodes and edges on the canvas. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,9 +21,49 @@ import { DiagramEditorContextProvider } from "../../../src/store/DiagramEditorCo | |
| import { SidebarProvider } from "../../../src/components/ui/sidebar"; | ||
| import { I18nProvider } from "@serverlessworkflow/i18n"; | ||
| import { en } from "../../../src/i18n/locales/en"; | ||
| import { ReactFlowProvider } from "@xyflow/react"; | ||
| import { ReactFlowProvider, ReactFlow } from "@xyflow/react"; | ||
| import * as autoLayoutModule from "../../../src/react-flow/diagram/autoLayout"; | ||
|
|
||
| // Mock ReactFlow to capture props | ||
| vi.mock("@xyflow/react", async () => { | ||
| const actual = await vi.importActual("@xyflow/react"); | ||
| return { | ||
| ...actual, | ||
| ReactFlow: vi.fn((props) => { | ||
| return <div data-testid="react-flow-canvas" />; | ||
| }), | ||
| }; | ||
| }); | ||
|
handreyrc marked this conversation as resolved.
|
||
|
|
||
| /** | ||
| * Helper function to render the Diagram component with all required providers | ||
| * @param options - Configuration options for the diagram | ||
| * @param options.isReadOnly - Whether the diagram should be in read-only mode | ||
| * @param options.content - The workflow content to render | ||
| * @param options.locale - The locale to use for i18n | ||
| */ | ||
| function renderDiagram({ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wondering is it worth moving this to common
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO it may be a bit overkill. I would be more inclined to move it to render-helpers if something more complex was happening there. At the end it is just a render call.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good, yes, we can always move it later if we need to use it anywhere else, thanks |
||
| isReadOnly = true, | ||
| content = "", | ||
| locale = "en", | ||
| }: { | ||
| isReadOnly?: boolean; | ||
| content?: string; | ||
| locale?: string; | ||
| } = {}) { | ||
| return render( | ||
| <ReactFlowProvider> | ||
| <DiagramEditorContextProvider content={content} isReadOnly={isReadOnly} locale={locale}> | ||
| <I18nProvider locale="en" dictionaries={{ en }}> | ||
|
handreyrc marked this conversation as resolved.
|
||
| <SidebarProvider> | ||
| <Diagram /> | ||
| </SidebarProvider> | ||
| </I18nProvider> | ||
| </DiagramEditorContextProvider> | ||
| </ReactFlowProvider>, | ||
| ); | ||
| } | ||
|
handreyrc marked this conversation as resolved.
|
||
|
|
||
| describe("Diagram Component", () => { | ||
| let applyAutoLayoutSpy: ReturnType<typeof vi.spyOn>; | ||
|
|
||
|
|
@@ -33,24 +73,17 @@ describe("Diagram Component", () => { | |
| nodes: [], | ||
| edges: [], | ||
| }); | ||
|
|
||
| // Clear mock calls before each test | ||
| vi.mocked(ReactFlow).mockClear(); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| vi.restoreAllMocks(); | ||
| }); | ||
|
|
||
| it("render Diagram component and canvas", async () => { | ||
| render( | ||
| <ReactFlowProvider> | ||
| <DiagramEditorContextProvider content={""} isReadOnly={true} locale={"en"}> | ||
| <I18nProvider locale="en" dictionaries={{ en }}> | ||
| <SidebarProvider> | ||
| <Diagram /> | ||
| </SidebarProvider> | ||
| </I18nProvider> | ||
| </DiagramEditorContextProvider> | ||
| </ReactFlowProvider>, | ||
| ); | ||
| renderDiagram({ isReadOnly: true }); | ||
|
|
||
| const diagram = screen.getByTestId("diagram-container"); | ||
| const canvas = screen.getByTestId("react-flow-canvas"); | ||
|
|
@@ -63,4 +96,89 @@ describe("Diagram Component", () => { | |
| expect(applyAutoLayoutSpy).toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
|
|
||
| it("should apply read-only class when isReadOnly is true", async () => { | ||
| renderDiagram({ isReadOnly: true }); | ||
|
|
||
| const diagram = screen.getByTestId("diagram-container"); | ||
|
|
||
| // Verify that the read-only class is applied | ||
| expect(diagram).toHaveClass("read-only"); | ||
|
|
||
| await waitFor(() => { | ||
| expect(applyAutoLayoutSpy).toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
|
|
||
| it("should not apply read-only class when isReadOnly is false", async () => { | ||
| renderDiagram({ isReadOnly: false }); | ||
|
|
||
| const diagram = screen.getByTestId("diagram-container"); | ||
|
|
||
| // Verify that the read-only class is not applied | ||
| expect(diagram).not.toHaveClass("read-only"); | ||
|
|
||
| await waitFor(() => { | ||
| expect(applyAutoLayoutSpy).toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
|
|
||
| it("should disable node interaction when isReadOnly is true", async () => { | ||
| renderDiagram({ isReadOnly: true }); | ||
|
|
||
| const diagram = screen.getByTestId("diagram-container"); | ||
|
|
||
| // Verify that the read-only class is applied | ||
| // This class applies CSS rule: .read-only .react-flow__handle { visibility: hidden !important; } | ||
| expect(diagram).toHaveClass("read-only"); | ||
|
|
||
| // Verify ReactFlow canvas is rendered | ||
| const canvas = screen.getByTestId("react-flow-canvas"); | ||
| expect(canvas).toBeInTheDocument(); | ||
|
handreyrc marked this conversation as resolved.
|
||
|
|
||
| // Wait for ReactFlow to be called | ||
| await waitFor(() => { | ||
| expect(ReactFlow).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| // Verify that ReactFlow was called with nodesDraggable={false} and nodesConnectable={false} | ||
| const mockReactFlow = vi.mocked(ReactFlow); | ||
| const lastCall = mockReactFlow.mock.calls.at(-1); | ||
| expect(lastCall).toBeDefined(); | ||
| const reactFlowProps = lastCall![0]; | ||
| expect(reactFlowProps.nodesDraggable).toBe(false); | ||
| expect(reactFlowProps.nodesConnectable).toBe(false); | ||
|
|
||
| await waitFor(() => { | ||
| expect(applyAutoLayoutSpy).toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
|
|
||
| it("should enable node interaction when isReadOnly is false", async () => { | ||
| renderDiagram({ isReadOnly: false }); | ||
|
|
||
| const diagram = screen.getByTestId("diagram-container"); | ||
|
|
||
| // Verify that the read-only class is not applied | ||
| expect(diagram).not.toHaveClass("read-only"); | ||
|
|
||
| // Verify ReactFlow canvas is rendered | ||
| const canvas = screen.getByTestId("react-flow-canvas"); | ||
| expect(canvas).toBeInTheDocument(); | ||
|
|
||
| // Wait for ReactFlow to be called | ||
| await waitFor(() => { | ||
| expect(ReactFlow).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| // Verify that ReactFlow was called with nodesDraggable={true} and nodesConnectable={true} | ||
| const mockReactFlow = vi.mocked(ReactFlow); | ||
| const reactFlowProps = mockReactFlow.mock.calls[mockReactFlow.mock.calls.length - 1][0]; | ||
| expect(reactFlowProps.nodesDraggable).toBe(true); | ||
| expect(reactFlowProps.nodesConnectable).toBe(true); | ||
|
|
||
| await waitFor(() => { | ||
| expect(applyAutoLayoutSpy).toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.