Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { useDiagramEditorContext } from "../store/DiagramEditorContext";
import { ParsingErrorPage } from "./error-pages/ParsingErrorPage";
import { ColorMode, ResolvedColorMode } from "../types/colorMode";
import { useResolvedColorMode } from "../hooks/useResolvedColorMode";
import { DiagramEditorErrorBoundary } from "./error-pages/DiagramEditorErrorBoundary";
Comment thread
kumaradityaraj marked this conversation as resolved.

/**
* DiagramEditor component API
Expand Down Expand Up @@ -57,7 +58,10 @@ const DiagramEditorContent = ({
};

export const DiagramEditor = (props: DiagramEditorProps) => {
// TODO: ErrorBoundary / fallback
const errorBoundaryProps = {
title: "workflowError.title",
message: "workflowError.default",
};
Comment thread
kumaradityaraj marked this conversation as resolved.

// Refs
const diagramDivRef = React.useRef<HTMLDivElement | null>(null);
Expand Down Expand Up @@ -85,19 +89,21 @@ export const DiagramEditor = (props: DiagramEditorProps) => {
className={`dec-root${resolvedColorMode === "dark" ? " dark" : ""}`}
data-testid={"dec-root"}
>
<DiagramEditorContextProvider
content={props.content}
isReadOnly={props.isReadOnly}
locale={locale}
>
<I18nProvider locale={locale} dictionaries={dictionaries}>
<DiagramEditorContent
diagramRef={diagramRef}
diagramDivRef={diagramDivRef}
colorMode={resolvedColorMode}
/>
</I18nProvider>
</DiagramEditorContextProvider>
<DiagramEditorErrorBoundary {...errorBoundaryProps} resetKey={props.content}>
<DiagramEditorContextProvider
content={props.content}
isReadOnly={props.isReadOnly}
locale={locale}
>
<I18nProvider locale={locale} dictionaries={dictionaries}>
<DiagramEditorContent
diagramRef={diagramRef}
diagramDivRef={diagramDivRef}
colorMode={resolvedColorMode}
/>
</I18nProvider>
</DiagramEditorContextProvider>
</DiagramEditorErrorBoundary>
Comment thread
kumaradityaraj marked this conversation as resolved.
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2021-Present The Serverless Workflow Specification Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as React from "react";
import { ErrorPage } from "./ErrorPage";

type State = {
hasError: boolean;
error?: unknown;
};

type DiagramEditorErrorBoundaryProps = {
children: React.ReactNode;
title?: string;
message?: string;
resetKey?: string;
};

export class DiagramEditorErrorBoundary extends React.Component<
DiagramEditorErrorBoundaryProps,
State
> {
constructor(props: DiagramEditorErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error: unknown): State {
return { hasError: true, error };
}

componentDidUpdate(prevProps: DiagramEditorErrorBoundaryProps) {
if (this.state.hasError && prevProps.resetKey !== this.props.resetKey) {
this.setState({ hasError: false, error: undefined });
}
Comment thread
kumaradityaraj marked this conversation as resolved.
}

render() {
if (this.state.hasError) {
return (
<ErrorPage
title={this.props.title ?? "Something went wrong"}
Comment thread
kumaradityaraj marked this conversation as resolved.
message={this.props.message ?? "An unexpected error occurred"}
snippet={this.state.error instanceof Error ? this.state.error.message : undefined}
Comment thread
kumaradityaraj marked this conversation as resolved.
/>
);
}

return this.props.children;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ const initialEdges: RF.Edge[] = [
{ x: 140, y: 70 },
{ x: 140, y: 85 },
{ x: 190, y: 85 },
{ x: 190, y: 95 },
{ x: 190, y: 95 },
],
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2021-Present The Serverless Workflow Specification Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { Meta, StoryObj } from "@storybook/react-vite";
import { DiagramEditorErrorBoundary } from "../src/diagram-editor/error-pages/DiagramEditorErrorBoundary";
import { ColorMode } from "../src/types/colorMode";

type DiagramEditorErrorBoundaryProps = {
title?: string;
message?: string;
resetKey?: string;
};

type DiagramEditorErrorBoundaryStoryProps = DiagramEditorErrorBoundaryProps & {
Comment thread
kumaradityaraj marked this conversation as resolved.
colorMode?: ColorMode;
};

const ThrowError = ({ message = "Test error message" }: { message?: string }) => {
throw new Error(message);
};

const meta = {
title: "Example/DiagramEditorErrorBoundary",
component: DiagramEditorErrorBoundary,
tags: ["autodocs"],
parameters: {
layout: "fullscreen",
},
args: {},
} satisfies Meta<DiagramEditorErrorBoundaryStoryProps>;

export default meta;
type Story = StoryObj<typeof meta>;

export const WithDefaults: Story = {
args: {
children: <ThrowError />,
},
};
Comment thread
kumaradityaraj marked this conversation as resolved.

export const WithErrorCustomMessage: Story = {
args: {
title: "Custom Error Title",
message: "This is a custom error message",
children: <ThrowError message="Custom error details in snippet" />,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2021-Present The Serverless Workflow Specification Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { render, screen } from "@testing-library/react";
import { DiagramEditorErrorBoundary } from "../../../src/diagram-editor/error-pages/DiagramEditorErrorBoundary";
import { describe, expect, it } from "vitest";

const ThrowError = ({ message = "Test error" }: { message?: string }) => {
throw new Error(message);
};

const SafeComponent = () => <div>Safe Content</div>;

describe("DiagramEditorErrorBoundary", () => {
afterEach(() => {
vi.restoreAllMocks();
});
Comment thread
kumaradityaraj marked this conversation as resolved.

it("renders children when no error occurs", () => {
render(
<DiagramEditorErrorBoundary>
<SafeComponent />
</DiagramEditorErrorBoundary>,
);

expect(screen.getByText("Safe Content")).toBeInTheDocument();
});

it("renders fallback UI when child throws", () => {
const spy = vi.spyOn(console, "error").mockImplementation(() => {});

render(
<DiagramEditorErrorBoundary title="Error Title" message="Error Message">
<ThrowError />
</DiagramEditorErrorBoundary>,
);

expect(screen.getByText("Error Title")).toBeInTheDocument();
expect(screen.getByText("Error Message")).toBeInTheDocument();
expect(screen.getByText("Test error")).toBeInTheDocument();

spy.mockRestore();
});

it("uses default fallback values when props not provided", () => {
const spy = vi.spyOn(console, "error").mockImplementation(() => {});

render(
<DiagramEditorErrorBoundary>
<ThrowError />
</DiagramEditorErrorBoundary>,
);

expect(screen.getByText("Something went wrong")).toBeInTheDocument();

expect(screen.getByText("An unexpected error occurred")).toBeInTheDocument();

spy.mockRestore();
});

it("resets error boundary when resetKey changes", () => {
const spy = vi.spyOn(console, "error").mockImplementation(() => {});

const { rerender } = render(
<DiagramEditorErrorBoundary resetKey="key-1">
<ThrowError />
</DiagramEditorErrorBoundary>,
);

expect(screen.getByText(/Something went wrong/i)).toBeInTheDocument();

rerender(
<DiagramEditorErrorBoundary resetKey="key-2">
<SafeComponent />
</DiagramEditorErrorBoundary>,
);

expect(screen.getByText("Safe Content")).toBeInTheDocument();

spy.mockRestore();
});
});
Loading
Loading