Merge branch 'master' of github.com:OneUptime/oneuptime

This commit is contained in:
Simon Larsen
2025-06-13 12:53:37 +01:00
39 changed files with 195 additions and 0 deletions

View File

@@ -56,6 +56,7 @@ const ForgotPassword: () => JSX.Element = () => {
title: "Email",
fieldType: FormFieldSchemaType.Email,
required: true,
disableSpellCheck: true,
},
]}
onSuccess={() => {

View File

@@ -122,6 +122,7 @@ const LoginPage: () => JSX.Element = () => {
disabled: Boolean(initialValues && initialValues["email"]),
title: "Email",
dataTestId: "email",
disableSpellCheck: true,
},
{
field: {
@@ -139,6 +140,7 @@ const LoginPage: () => JSX.Element = () => {
openLinkInNewTab: false,
},
dataTestId: "password",
disableSpellCheck: true,
},
]}
createOrUpdateApiUrl={apiUrl}

View File

@@ -196,6 +196,7 @@ const LoginPage: () => JSX.Element = () => {
required: true,
title: "Email",
dataTestId: "email",
disableSpellCheck: true,
},
]}
maxPrimaryButtonWidth={true}

View File

@@ -104,6 +104,7 @@ const RegisterPage: () => JSX.Element = () => {
disabled: Boolean(initialValues && initialValues["email"]),
title: "Email",
dataTestId: "email",
disableSpellCheck: true,
},
{
field: {
@@ -114,6 +115,7 @@ const RegisterPage: () => JSX.Element = () => {
required: true,
title: "Full Name",
dataTestId: "name",
disableSpellCheck: true,
},
];
@@ -128,6 +130,7 @@ const RegisterPage: () => JSX.Element = () => {
required: true,
title: "Company Name",
dataTestId: "companyName",
disableSpellCheck: true,
},
]);
@@ -159,6 +162,7 @@ const RegisterPage: () => JSX.Element = () => {
title: "Password",
required: true,
dataTestId: "password",
disableSpellCheck: true,
},
{
field: {
@@ -175,6 +179,7 @@ const RegisterPage: () => JSX.Element = () => {
required: true,
showEvenIfPermissionDoesNotExist: true,
dataTestId: "confirmPassword",
disableSpellCheck: true,
},
]);

View File

@@ -68,6 +68,7 @@ const RegisterPage: () => JSX.Element = () => {
title: "New Password",
required: true,
showEvenIfPermissionDoesNotExist: true,
disableSpellCheck: true,
},
{
field: {
@@ -83,6 +84,7 @@ const RegisterPage: () => JSX.Element = () => {
overrideFieldKey: "confirmPassword",
required: true,
showEvenIfPermissionDoesNotExist: true,
disableSpellCheck: true,
},
]}
createOrUpdateApiUrl={apiUrl}

View File

@@ -106,6 +106,7 @@ const Settings: FunctionComponent = (): ReactElement => {
title: "Admin Notification Email",
fieldType: FormFieldSchemaType.Email,
required: false,
disableSpellCheck: true,
},
]}
modelDetailProps={{
@@ -199,6 +200,7 @@ const Settings: FunctionComponent = (): ReactElement => {
fieldType: FormFieldSchemaType.Hostname,
required: true,
placeholder: "smtp.server.com",
disableSpellCheck: true,
},
{
field: {
@@ -229,6 +231,7 @@ const Settings: FunctionComponent = (): ReactElement => {
fieldType: FormFieldSchemaType.Text,
required: false,
placeholder: "emailuser",
disableSpellCheck: true,
},
{
field: {
@@ -239,6 +242,7 @@ const Settings: FunctionComponent = (): ReactElement => {
fieldType: FormFieldSchemaType.EncryptedText,
required: false,
placeholder: "Password",
disableSpellCheck: true,
},
{
field: {
@@ -251,6 +255,7 @@ const Settings: FunctionComponent = (): ReactElement => {
description:
"Email used to log in to this SMTP Server. This is also the email your customers will see. ",
placeholder: "email@company.com",
disableSpellCheck: true,
},
{
field: {
@@ -263,6 +268,7 @@ const Settings: FunctionComponent = (): ReactElement => {
description:
"This is the display name your team and customers see, when they receive emails from OneUptime.",
placeholder: "Company, Inc.",
disableSpellCheck: true,
},
]}
modelDetailProps={{

View File

@@ -48,6 +48,7 @@ const Users: FunctionComponent = (): ReactElement => {
fieldType: FormFieldSchemaType.Email,
required: true,
placeholder: "email@company.com",
disableSpellCheck: true,
},
{
field: {
@@ -57,6 +58,7 @@ const Users: FunctionComponent = (): ReactElement => {
fieldType: FormFieldSchemaType.Password,
required: true,
placeholder: "Password",
disableSpellCheck: true,
},
{
field: {

View File

@@ -39,6 +39,7 @@ describe("Input", () => {
error: "error",
outerDivClassName: "outerDivClassName",
autoFocus: true,
disableSpellCheck: false,
};
const { getByRole } = render(<Input {...props} />);
@@ -283,4 +284,25 @@ describe("Input", () => {
expect(getByRole("textbox")).toHaveFocus();
});
test("enables spellcheck by default", () => {
const { getByRole } = render(<Input />);
const input: HTMLElement = getByRole("textbox");
expect(input).toHaveAttribute("spellcheck", "true");
});
test("disables spellcheck when disableSpellCheck is true", () => {
const { getByRole } = render(<Input disableSpellCheck={true} />);
const input: HTMLElement = getByRole("textbox");
expect(input).toHaveAttribute("spellcheck", "false");
});
test("enables spellcheck when disableSpellCheck is false", () => {
const { getByRole } = render(<Input disableSpellCheck={false} />);
const input: HTMLElement = getByRole("textbox");
expect(input).toHaveAttribute("spellcheck", "true");
});
});

View File

@@ -0,0 +1,55 @@
import React from "react";
import MarkdownEditor from "../../../UI/Components/Markdown.tsx/MarkdownEditor";
import { render, screen } from "@testing-library/react";
import { describe, expect, test } from "@jest/globals";
describe("MarkdownEditor with SpellCheck", () => {
test("should enable spell check by default", () => {
render(
<MarkdownEditor
initialValue="This is a test with speling errors"
placeholder="Enter markdown here..."
/>
);
const textarea = screen.getByRole("textbox");
expect(textarea.spellcheck).toBe(true);
});
test("should disable spell check when disableSpellCheck is true", () => {
render(
<MarkdownEditor
initialValue="This is a test with speling errors"
placeholder="Enter markdown here..."
disableSpellCheck={true}
/>
);
const textarea = screen.getByRole("textbox");
expect(textarea.spellcheck).toBe(false);
});
test("should handle spell check prop changes", () => {
const { rerender } = render(
<MarkdownEditor
initialValue="This is a test with speling errors"
placeholder="Enter markdown here..."
disableSpellCheck={false}
/>
);
let textarea = screen.getByRole("textbox");
expect(textarea.spellcheck).toBe(true);
rerender(
<MarkdownEditor
initialValue="This is a test with speling errors"
placeholder="Enter markdown here..."
disableSpellCheck={true}
/>
);
textarea = screen.getByRole("textbox");
expect(textarea.spellcheck).toBe(false);
});
});

View File

@@ -25,6 +25,7 @@ describe("TextArea", () => {
tabIndex={1}
error="error"
autoFocus={true}
disableSpellCheck={false}
/>,
);
const textarea: HTMLElement = getByRole("textbox");
@@ -150,4 +151,25 @@ describe("TextArea", () => {
expect(getByDisplayValue("")).toBeInTheDocument();
expect(queryByDisplayValue("\n")).not.toBeInTheDocument();
});
test("enables spellcheck by default", () => {
const { getByRole } = render(<TextArea />);
const textarea: HTMLElement = getByRole("textbox");
expect(textarea).toHaveAttribute("spellcheck", "true");
});
test("disables spellcheck when disableSpellCheck is true", () => {
const { getByRole } = render(<TextArea disableSpellCheck={true} />);
const textarea: HTMLElement = getByRole("textbox");
expect(textarea).toHaveAttribute("spellcheck", "false");
});
test("enables spellcheck when disableSpellCheck is false", () => {
const { getByRole } = render(<TextArea disableSpellCheck={false} />);
const textarea: HTMLElement = getByRole("textbox");
expect(textarea).toHaveAttribute("spellcheck", "true");
});
});

View File

@@ -4,6 +4,7 @@ import React, {
FunctionComponent,
ReactElement,
useEffect,
useRef,
useState,
} from "react";
@@ -22,6 +23,7 @@ export interface ComponentProps {
error?: string | undefined;
value?: string | undefined;
showLineNumbers?: boolean | undefined;
disableSpellCheck?: boolean | undefined;
}
const CodeEditor: FunctionComponent<ComponentProps> = (
@@ -31,6 +33,7 @@ const CodeEditor: FunctionComponent<ComponentProps> = (
const [placeholder, setPlaceholder] = useState<string>("");
const [helpText, setHelpText] = useState<string>("");
const editorRef = useRef<any>(null);
useEffect(() => {
let value: string | undefined = props.value;
@@ -87,6 +90,20 @@ const CodeEditor: FunctionComponent<ComponentProps> = (
const [value, setValue] = useState<string>("");
// Handle spell check configuration for Monaco Editor
useEffect(() => {
if (editorRef.current && props.type === CodeType.Markdown) {
const editor = editorRef.current;
const domNode = editor.getDomNode();
if (domNode) {
const textareaElement = domNode.querySelector('textarea');
if (textareaElement) {
textareaElement.spellcheck = !props.disableSpellCheck;
}
}
}
}, [props.disableSpellCheck, props.type]);
useEffect(() => {
let initialValue: string | undefined = props.initialValue;
@@ -133,6 +150,20 @@ const CodeEditor: FunctionComponent<ComponentProps> = (
props.onChange(code);
}
}}
onMount={(editor, monaco) => {
editorRef.current = editor;
// Configure spell check for Markdown
if (props.type === CodeType.Markdown) {
const domNode = editor.getDomNode();
if (domNode) {
const textareaElement = domNode.querySelector('textarea');
if (textareaElement) {
textareaElement.spellcheck = !props.disableSpellCheck;
}
}
}
}}
defaultValue={value || placeholder || ""}
className={className}
options={{

View File

@@ -424,6 +424,7 @@ const FormField: <T extends GenericObject>(
error={props.touched && props.error ? props.error : undefined}
tabIndex={index}
dataTestId={props.field.dataTestId}
disableSpellCheck={props.field.disableSpellCheck}
onChange={async (value: string) => {
onChange(value);
props.setFieldValue(props.fieldName, value);
@@ -476,6 +477,7 @@ const FormField: <T extends GenericObject>(
dataTestId={props.field.dataTestId}
tabIndex={index}
type={CodeType.Markdown}
disableSpellCheck={props.field.disableSpellCheck}
onChange={async (value: string) => {
onChange(value);
props.setFieldValue(props.fieldName, value);

View File

@@ -114,5 +114,8 @@ export default interface Field<TEntity> {
hideOptionalLabel?: boolean | undefined;
// Spell check configuration (primarily for Markdown and text fields)
disableSpellCheck?: boolean | undefined;
getSummaryElement?: (item: FormValues<TEntity>) => ReactElement | undefined;
}

View File

@@ -38,6 +38,7 @@ export interface ComponentProps {
error?: string | undefined;
outerDivClassName?: string | undefined;
autoFocus?: boolean | undefined;
disableSpellCheck?: boolean | undefined;
}
const Input: FunctionComponent<ComponentProps> = (
@@ -159,6 +160,7 @@ const Input: FunctionComponent<ComponentProps> = (
onFocus={props.onFocus}
onClick={props.onClick}
data-testid={props.dataTestId}
spellCheck={!props.disableSpellCheck && props.type === InputType.TEXT}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
let value: string | Date = e.target.value;

View File

@@ -10,6 +10,7 @@ export interface ComponentProps {
onBlur?: (() => void) | undefined;
tabIndex?: number | undefined;
error?: string | undefined;
disableSpellCheck?: boolean | undefined;
}
const MarkdownEditor: FunctionComponent<ComponentProps> = (
@@ -25,6 +26,7 @@ const MarkdownEditor: FunctionComponent<ComponentProps> = (
onFocus={props.onFocus ? props.onFocus : () => {}}
onBlur={props.onBlur ? props.onBlur : () => {}}
error={props.error}
disableSpellCheck={props.disableSpellCheck}
/>
);
};

View File

@@ -19,6 +19,7 @@ export interface ComponentProps {
error?: string | undefined;
autoFocus?: boolean | undefined;
dataTestId?: string | undefined;
disableSpellCheck?: boolean | undefined;
}
const TextArea: FunctionComponent<ComponentProps> = (
@@ -64,6 +65,7 @@ const TextArea: FunctionComponent<ComponentProps> = (
data-testid={props.dataTestId}
className={`${className || ""}`}
value={text}
spellCheck={!props.disableSpellCheck}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
const value: string = e.target.value;

View File

@@ -129,6 +129,7 @@ const CustomSMTPTable: FunctionComponent = (): ReactElement => {
fieldType: FormFieldSchemaType.Hostname,
required: true,
placeholder: "smtp.server.com",
disableSpellCheck: true,
},
{
field: {
@@ -158,6 +159,7 @@ const CustomSMTPTable: FunctionComponent = (): ReactElement => {
fieldType: FormFieldSchemaType.Text,
required: false,
placeholder: "emailuser",
disableSpellCheck: true,
},
{
field: {
@@ -168,6 +170,7 @@ const CustomSMTPTable: FunctionComponent = (): ReactElement => {
fieldType: FormFieldSchemaType.EncryptedText,
required: false,
placeholder: "Password",
disableSpellCheck: true,
},
{
field: {
@@ -180,6 +183,7 @@ const CustomSMTPTable: FunctionComponent = (): ReactElement => {
description:
"Email used to log in to this SMTP Server. This is also the email your customers will see. ",
placeholder: "email@company.com",
disableSpellCheck: true,
},
{
field: {
@@ -192,6 +196,7 @@ const CustomSMTPTable: FunctionComponent = (): ReactElement => {
description:
"This is the display name your team and customers see, when they receive emails from OneUptime.",
placeholder: "Company, Inc.",
disableSpellCheck: true,
},
]}
showRefreshButton={true}

View File

@@ -323,6 +323,7 @@ return {
/>
<Input
initialValue={destinationInputValue}
disableSpellCheck={true}
onBlur={() => {
setTouched({
...touched,

View File

@@ -136,6 +136,7 @@ const Call: () => JSX.Element = (): ReactElement => {
validation: {
minLength: 2,
},
disableSpellCheck: true,
},
]}
showRefreshButton={true}

View File

@@ -135,6 +135,7 @@ const Email: () => JSX.Element = (): ReactElement => {
validation: {
minLength: 2,
},
disableSpellCheck: true,
},
]}
showRefreshButton={true}

View File

@@ -135,6 +135,7 @@ const SMS: () => JSX.Element = (): ReactElement => {
validation: {
minLength: 2,
},
disableSpellCheck: true,
},
]}
showRefreshButton={true}

View File

@@ -54,6 +54,7 @@ const Home: FunctionComponent<PageComponentProps> = (): ReactElement => {
title: "Email",
description:
"You will have to verify your email again if you change it",
disableSpellCheck: true,
},
{
field: {

View File

@@ -76,6 +76,7 @@ const Home: FunctionComponent<PageComponentProps> = (): ReactElement => {
title: "Password",
required: true,
showEvenIfPermissionDoesNotExist: true,
disableSpellCheck: true,
},
{
field: {
@@ -90,6 +91,7 @@ const Home: FunctionComponent<PageComponentProps> = (): ReactElement => {
title: "Confirm Password",
required: true,
showEvenIfPermissionDoesNotExist: true,
disableSpellCheck: true,
},
]}
formType={FormType.Update}

View File

@@ -100,6 +100,7 @@ const Domains: FunctionComponent<PageComponentProps> = (): ReactElement => {
validation: {
minLength: 2,
},
disableSpellCheck: true,
},
]}
selectMoreFields={{

View File

@@ -94,6 +94,7 @@ const MonitorSecrets: FunctionComponent<
noNumbers: true,
noSpecialCharacters: true,
},
disableSpellCheck: true,
},
{
field: {

View File

@@ -122,6 +122,7 @@ const SSOPage: FunctionComponent<PageComponentProps> = (
"Members will be forwarded here when signing in to your organization",
placeholder: "https://yourapp.example.com/apps/appId",
stepId: "sign-on",
disableSpellCheck: true,
},
{
field: {
@@ -134,6 +135,7 @@ const SSOPage: FunctionComponent<PageComponentProps> = (
required: true,
placeholder: "https://example.com",
stepId: "sign-on",
disableSpellCheck: true,
},
{
field: {

View File

@@ -170,6 +170,7 @@ const StatusPageDelete: FunctionComponent<PageComponentProps> = (
minLength: 2,
},
stepId: "basic",
disableSpellCheck: true,
},
{
field: {
@@ -210,6 +211,7 @@ const StatusPageDelete: FunctionComponent<PageComponentProps> = (
stepId: "more",
placeholder:
"-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
disableSpellCheck: true,
showIf: (item: FormValues<StatusPageDomain>): boolean => {
return Boolean(item.isCustomCertificate);
},
@@ -224,6 +226,7 @@ const StatusPageDelete: FunctionComponent<PageComponentProps> = (
placeholder:
"-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
stepId: "more",
disableSpellCheck: true,
showIf: (item: FormValues<StatusPageDomain>): boolean => {
return Boolean(item.isCustomCertificate);
},

View File

@@ -128,6 +128,7 @@ const StatusPageDelete: FunctionComponent<PageComponentProps> = (
fieldType: FormFieldSchemaType.Email,
required: true,
placeholder: "subscriber@company.com",
disableSpellCheck: true,
},
{
field: {

View File

@@ -106,6 +106,7 @@ const StatusPageDelete: FunctionComponent<PageComponentProps> = (
fieldType: FormFieldSchemaType.URL,
required: true,
placeholder: "https://link.com",
disableSpellCheck: true,
},
]}
showRefreshButton={true}

View File

@@ -129,6 +129,7 @@ const StatusPageDelete: FunctionComponent<PageComponentProps> = (
fieldType: FormFieldSchemaType.URL,
required: true,
placeholder: "https://link.com",
disableSpellCheck: true,
},
]}
showRefreshButton={true}

View File

@@ -51,6 +51,7 @@ const StatusPageDelete: FunctionComponent<
fieldType: FormFieldSchemaType.Email,
required: true,
placeholder: "user@company.com",
disableSpellCheck: true,
},
]}
showRefreshButton={true}

View File

@@ -136,6 +136,7 @@ const SSOPage: FunctionComponent<PageComponentProps> = (
"Members will be forwarded here when signing in to your organization",
placeholder: "https://yourapp.example.com/apps/appId",
stepId: "sign-on",
disableSpellCheck: true,
},
{
field: {
@@ -148,6 +149,7 @@ const SSOPage: FunctionComponent<PageComponentProps> = (
required: true,
placeholder: "https://example.com",
stepId: "sign-on",
disableSpellCheck: true,
},
{
field: {

View File

@@ -139,6 +139,7 @@ const StatusPageSlackSubscribers: FunctionComponent<PageComponentProps> = (
fieldType: FormFieldSchemaType.URL,
required: true,
placeholder: "https://hooks.slack.com/services/...",
disableSpellCheck: true,
},
{
field: {

View File

@@ -58,6 +58,7 @@ const StatusPageDelete: FunctionComponent<PageComponentProps> = (
fieldType: FormFieldSchemaType.URL,
required: true,
placeholder: "URL",
disableSpellCheck: true,
},
]}
showRefreshButton={true}

View File

@@ -119,6 +119,7 @@ const ForgotPassword: FunctionComponent<ComponentProps> = (
showEvenIfPermissionDoesNotExist: true,
fieldType: FormFieldSchemaType.Email,
required: true,
disableSpellCheck: true,
},
]}
onSuccess={() => {

View File

@@ -134,6 +134,7 @@ const LoginPage: FunctionComponent<ComponentProps> = (
title: "Email",
fieldType: FormFieldSchemaType.Email,
required: true,
disableSpellCheck: true,
},
{
field: {

View File

@@ -127,6 +127,7 @@ const ResetPassword: FunctionComponent<ComponentProps> = (
placeholder: "New Password",
title: "New Password",
required: true,
disableSpellCheck: true,
},
{
field: {
@@ -142,6 +143,7 @@ const ResetPassword: FunctionComponent<ComponentProps> = (
title: "Confirm Password",
overrideFieldKey: "confirmPassword",
required: true,
disableSpellCheck: true,
},
]}
createOrUpdateApiUrl={apiUrl}

View File

@@ -92,6 +92,7 @@ const SubscribePage: FunctionComponent<SubscribePageProps> = (
fieldType: FormFieldSchemaType.Email,
required: true,
placeholder: "subscriber@company.com",
disableSpellCheck: true,
},
];
@@ -204,6 +205,7 @@ const SubscribePage: FunctionComponent<SubscribePageProps> = (
fieldType: FormFieldSchemaType.Email,
required: true,
placeholder: "email@yourcompany.com",
disableSpellCheck: true,
},
]}
createOrUpdateApiUrl={URL.fromString(

View File

@@ -92,6 +92,7 @@ const SubscribePage: FunctionComponent<SubscribePageProps> = (
fieldType: FormFieldSchemaType.Phone,
required: true,
placeholder: "+11234567890",
disableSpellCheck: true,
},
];
@@ -204,6 +205,7 @@ const SubscribePage: FunctionComponent<SubscribePageProps> = (
fieldType: FormFieldSchemaType.Phone,
required: true,
placeholder: "+11234567890",
disableSpellCheck: true,
},
]}
createOrUpdateApiUrl={URL.fromString(