diff --git a/web/src/components/core/EmailInput.test.jsx b/web/src/components/core/EmailInput.test.tsx similarity index 74% rename from web/src/components/core/EmailInput.test.jsx rename to web/src/components/core/EmailInput.test.tsx index b4572709f3..1a050e9f15 100644 --- a/web/src/components/core/EmailInput.test.jsx +++ b/web/src/components/core/EmailInput.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright (c) [2023] SUSE LLC + * Copyright (c) [2023-2024] SUSE LLC * * All Rights Reserved. * @@ -23,9 +23,35 @@ import React, { useState } from "react"; import { screen } from "@testing-library/react"; -import EmailInput from "./EmailInput"; +import EmailInput, { EmailInputProps } from "./EmailInput"; import { plainRender } from "~/test-utils"; +/** + * Controlled component for testing the EmailInputProps + * + * Instead of testing if given callbacks are called, below tests are going to + * check the rendered result to be more aligned with the React Testing Library + * principles, https://testing-library.com/docs/guiding-principles/ + * + */ +const EmailInputTest = (props: EmailInputProps) => { + const [email, setEmail] = useState(""); + const [isValid, setIsValid] = useState(true); + + return ( + <> + setEmail(v)} + onValidate={setIsValid} + /> + {email &&

Email value updated!

} + {isValid === false &&

Email is not valid!

} + + ); +}; + describe("EmailInput component", () => { it("renders an email input", () => { plainRender( @@ -36,27 +62,6 @@ describe("EmailInput component", () => { expect(inputField).toHaveAttribute("type", "email"); }); - // Using a controlled component for testing the rendered result instead of testing if - // the given onChange callback is called. The former is more aligned with the - // React Testing Library principles, https://testing-library.com/docs/guiding-principles/ - const EmailInputTest = (props) => { - const [email, setEmail] = useState(""); - const [isValid, setIsValid] = useState(true); - - return ( - <> - setEmail(v)} - onValidate={setIsValid} - /> - {email &&

Email value updated!

} - {isValid === false &&

Email is not valid!

} - - ); - }; - it("triggers onChange callback", async () => { const { user } = plainRender(); const emailInput = screen.getByRole("textbox", { name: "Test email" }); diff --git a/web/src/components/core/EmailInput.jsx b/web/src/components/core/EmailInput.tsx similarity index 73% rename from web/src/components/core/EmailInput.jsx rename to web/src/components/core/EmailInput.tsx index 6bda2447b5..ec2531de77 100644 --- a/web/src/components/core/EmailInput.jsx +++ b/web/src/components/core/EmailInput.tsx @@ -21,28 +21,25 @@ */ import React, { useEffect, useState } from "react"; -import { InputGroup, TextInput } from "@patternfly/react-core"; -import { noop } from "~/utils"; +import { InputGroup, TextInput, TextInputProps } from "@patternfly/react-core"; +import { isEmpty, noop } from "~/utils"; /** * Email validation. * * Code inspired by https://github.com/manishsaraan/email-validator/blob/master/index.js - * - * @param {string} email - * @returns {boolean} */ -const validateEmail = (email) => { +const validateEmail = (email: string) => { const regexp = /^[-!#$%&'*+/0-9=?A-Z^_a-z`{|}~](\.?[-!#$%&'*+/0-9=?A-Z^_a-z`{|}~])*@[a-zA-Z0-9](-*\.?[a-zA-Z0-9])*\.[a-zA-Z](-?[a-zA-Z0-9])+$/; - const validateFormat = (email) => { + const validateFormat = (email: string) => { const parts = email.split("@"); return parts.length === 2 && regexp.test(email); }; - const validateSizes = (email) => { + const validateSizes = (email: string) => { const [account, address] = email.split("@"); if (account.length > 64) return false; @@ -58,22 +55,25 @@ const validateEmail = (email) => { return validateFormat(email) && validateSizes(email); }; +export type EmailInputProps = TextInputProps & { onValidate?: (isValid: boolean) => void }; + /** * Renders an email input field which validates its value. * @component * - * @param {(boolean) => void} onValidate - Callback to be called every time the input value is + * @param onValidate - Callback to be called every time the input value is * validated. - * @param {Object} props - Props matching the {@link https://www.patternfly.org/components/forms/text-input PF/TextInput}, + * @param props - Props matching the {@link https://www.patternfly.org/components/forms/text-input PF/TextInput}, * except `type` and `validated` which are managed by the component. */ -export default function EmailInput({ onValidate = noop, ...props }) { +export default function EmailInput({ onValidate = noop, ...props }: EmailInputProps) { const [isValid, setIsValid] = useState(true); useEffect(() => { - const isValid = props.value.length === 0 || validateEmail(props.value); + const { value } = props; + const isValid = typeof value === "string" && (isEmpty(value) || validateEmail(value)); setIsValid(isValid); - onValidate(isValid); + typeof onValidate === "function" && onValidate(isValid); }, [onValidate, props.value, setIsValid]); return (