Skip to content

Commit

Permalink
refactor(web): migrate core/EmailInput to TypeScript
Browse files Browse the repository at this point in the history
  • Loading branch information
dgdavid committed Dec 23, 2024
1 parent c3d85bb commit 767b8d7
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) [2023] SUSE LLC
* Copyright (c) [2023-2024] SUSE LLC
*
* All Rights Reserved.
*
Expand All @@ -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 (
<>
<EmailInput
{...props}
value={email}
onChange={(_, v) => setEmail(v)}
onValidate={setIsValid}
/>
{email && <p>Email value updated!</p>}
{isValid === false && <p>Email is not valid!</p>}
</>
);
};

describe("EmailInput component", () => {
it("renders an email input", () => {
plainRender(
Expand All @@ -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 (
<>
<EmailInput
{...props}
value={email}
onChange={(_, v) => setEmail(v)}
onValidate={setIsValid}
/>
{email && <p>Email value updated!</p>}
{isValid === false && <p>Email is not valid!</p>}
</>
);
};

it("triggers onChange callback", async () => {
const { user } = plainRender(<EmailInputTest id="test-email" aria-label="Test email" />);
const emailInput = screen.getByRole("textbox", { name: "Test email" });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -58,27 +55,29 @@ 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, value, ...props }: EmailInputProps) {
const [isValid, setIsValid] = useState(true);

useEffect(() => {
const isValid = props.value.length === 0 || validateEmail(props.value);
const isValid = typeof value === "string" && (isEmpty(value) || validateEmail(value));
setIsValid(isValid);
onValidate(isValid);
}, [onValidate, props.value, setIsValid]);
typeof onValidate === "function" && onValidate(isValid);
}, [onValidate, value, setIsValid]);

return (
<InputGroup>
<TextInput {...props} type="email" validated={isValid ? "default" : "error"} />
<TextInput {...props} type="email" value={value} validated={isValid ? "default" : "error"} />
</InputGroup>
);
}

0 comments on commit 767b8d7

Please sign in to comment.