Add alert to show validation errors
This commit is contained in:
@@ -69,3 +69,35 @@ MultipleModelingsModeledMultiple.args = {
|
||||
showMultipleModels: true,
|
||||
modelingStatus: "saved",
|
||||
};
|
||||
|
||||
export const MultipleModelingsValidationFailedNeutral = Template.bind({});
|
||||
MultipleModelingsValidationFailedNeutral.args = {
|
||||
method,
|
||||
modeledMethods: [
|
||||
createModeledMethod(method),
|
||||
createModeledMethod({
|
||||
...method,
|
||||
type: "neutral",
|
||||
}),
|
||||
],
|
||||
showMultipleModels: true,
|
||||
modelingStatus: "unsaved",
|
||||
};
|
||||
|
||||
export const MultipleModelingsValidationFailedDuplicate = Template.bind({});
|
||||
MultipleModelingsValidationFailedDuplicate.args = {
|
||||
method,
|
||||
modeledMethods: [
|
||||
createModeledMethod(method),
|
||||
createModeledMethod({
|
||||
...method,
|
||||
type: "source",
|
||||
input: "",
|
||||
output: "ReturnValue",
|
||||
kind: "remote",
|
||||
}),
|
||||
createModeledMethod(method),
|
||||
],
|
||||
showMultipleModels: true,
|
||||
modelingStatus: "unsaved",
|
||||
};
|
||||
|
||||
@@ -85,11 +85,28 @@ type Props = {
|
||||
|
||||
// Inverse the color scheme
|
||||
inverse?: boolean;
|
||||
|
||||
/**
|
||||
* Role is used as the ARIA role. "alert" should only be set if the alert requires
|
||||
* the user's immediate attention. "status" should be set if the alert is not
|
||||
* important enough to require the user's immediate attention.
|
||||
*
|
||||
* Can be left out if the alert is not important enough to require the user's
|
||||
* immediate attention.
|
||||
*/
|
||||
role?: "alert" | "status";
|
||||
};
|
||||
|
||||
export const Alert = ({ type, title, message, actions, inverse }: Props) => {
|
||||
export const Alert = ({
|
||||
type,
|
||||
title,
|
||||
message,
|
||||
actions,
|
||||
inverse,
|
||||
role,
|
||||
}: Props) => {
|
||||
return (
|
||||
<Container type={type} inverse={inverse}>
|
||||
<Container type={type} inverse={inverse} role={role}>
|
||||
<Title>
|
||||
{getTypeText(type)}: {title}
|
||||
</Title>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ModeledMethodValidationError } from "../../model-editor/shared/validation";
|
||||
import TextButton from "../common/TextButton";
|
||||
import { Alert } from "../common";
|
||||
import * as React from "react";
|
||||
import { useCallback } from "react";
|
||||
|
||||
type Props = {
|
||||
error: ModeledMethodValidationError;
|
||||
setSelectedIndex: (index: number) => void;
|
||||
};
|
||||
|
||||
export const ModeledMethodAlert = ({ error, setSelectedIndex }: Props) => {
|
||||
const handleClick = useCallback(() => {
|
||||
setSelectedIndex(error.index);
|
||||
}, [error.index, setSelectedIndex]);
|
||||
|
||||
return (
|
||||
<Alert
|
||||
role="alert"
|
||||
type="error"
|
||||
title={error.title}
|
||||
message={
|
||||
<>
|
||||
{error.message}{" "}
|
||||
<TextButton onClick={handleClick}>{error.actionText}</TextButton>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,11 +1,13 @@
|
||||
import * as React from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { Method } from "../../model-editor/method";
|
||||
import { ModeledMethod } from "../../model-editor/modeled-method";
|
||||
import { styled } from "styled-components";
|
||||
import { MethodModelingInputs } from "./MethodModelingInputs";
|
||||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react";
|
||||
import { Codicon } from "../common";
|
||||
import { validateModeledMethods } from "../../model-editor/shared/validation";
|
||||
import { ModeledMethodAlert } from "./ModeledMethodAlert";
|
||||
|
||||
export type MultipleModeledMethodsPanelProps = {
|
||||
method: Method;
|
||||
@@ -19,9 +21,14 @@ const Container = styled.div`
|
||||
gap: 0.25rem;
|
||||
|
||||
padding-bottom: 0.5rem;
|
||||
border-top: 0.05rem solid var(--vscode-panelSection-border);
|
||||
border-bottom: 0.05rem solid var(--vscode-panelSection-border);
|
||||
`;
|
||||
|
||||
const AlertContainer = styled.div`
|
||||
margin-top: 0.5rem;
|
||||
`;
|
||||
|
||||
const Footer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -47,8 +54,24 @@ export const MultipleModeledMethodsPanel = ({
|
||||
setSelectedIndex((previousIndex) => previousIndex + 1);
|
||||
}, []);
|
||||
|
||||
const validationErrors = useMemo(
|
||||
() => validateModeledMethods(modeledMethods),
|
||||
[modeledMethods],
|
||||
);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{validationErrors.length > 0 && (
|
||||
<AlertContainer>
|
||||
{validationErrors.map((error, index) => (
|
||||
<ModeledMethodAlert
|
||||
key={index}
|
||||
error={error}
|
||||
setSelectedIndex={setSelectedIndex}
|
||||
/>
|
||||
))}
|
||||
</AlertContainer>
|
||||
)}
|
||||
{modeledMethods.length > 0 ? (
|
||||
<MethodModelingInputs
|
||||
method={method}
|
||||
|
||||
@@ -194,6 +194,16 @@ describe(MultipleModeledMethodsPanel.name, () => {
|
||||
}),
|
||||
).toHaveValue("source");
|
||||
});
|
||||
|
||||
it("does not show errors", () => {
|
||||
render({
|
||||
method,
|
||||
modeledMethods,
|
||||
onChange,
|
||||
});
|
||||
|
||||
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("with three modeled methods", () => {
|
||||
@@ -309,4 +319,56 @@ describe(MultipleModeledMethodsPanel.name, () => {
|
||||
).toHaveValue("remote");
|
||||
});
|
||||
});
|
||||
|
||||
describe("with duplicate modeled methods", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
...method,
|
||||
}),
|
||||
createModeledMethod({
|
||||
...method,
|
||||
}),
|
||||
];
|
||||
|
||||
it("shows errors", () => {
|
||||
render({
|
||||
method,
|
||||
modeledMethods,
|
||||
onChange,
|
||||
});
|
||||
|
||||
expect(screen.getByRole("alert")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows the correct error message", async () => {
|
||||
render({
|
||||
method,
|
||||
modeledMethods,
|
||||
onChange,
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.getByText("Error: Duplicated classification"),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
"This method has two identical or conflicting classifications.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText("1/2")).toBeInTheDocument();
|
||||
|
||||
const button = screen.getByText(
|
||||
"Modify or remove the duplicated classification.",
|
||||
);
|
||||
|
||||
await userEvent.click(button);
|
||||
|
||||
expect(screen.getByText("2/2")).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.getByText("Modify or remove the duplicated classification."),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user