Skip to content
16 changes: 10 additions & 6 deletions apps/website/screens/guidelines/localization/LocalizationPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,21 +82,25 @@ const sections = [
</tr>
<tr>
<td>
<Code>lengthErrorMessage</Code>
<Code>maxLengthErrorMessage</Code>
</td>
<td>
Min length <Code>minLength</Code>, max length <Code>maxLength</Code>.
The maximum length is <Code>maxLength</Code>.
</td>
<td>
It is a function that receives two parameters (minlength and maxlength) and returns the text with
those parameters.
It is a function that receives a parameter (maxlength) and returns the text with this parameter.
</td>
</tr>
<tr>
<td>
<Code>logoAlternativeText</Code>
<Code>minLengthErrorMessage</Code>
</td>
<td>
The minimum length is <Code>minLength</Code>.
</td>
<td>
It is a function that receives a parameter (minlength) and returns the text with this parameter.
</td>
<td>Logo</td>
</tr>
</tbody>
</DxcTable>
Expand Down
22 changes: 22 additions & 0 deletions packages/lib/src/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,25 @@ export const getMargin = (marginProp: Space | Margin | undefined, side: Side) =>
: marginProp && typeof marginProp === "string"
? spaces[marginProp]
: "0px";

export const getLengthErrorMessage = ({
value,
minLength,
maxLength,
minLengthErrorMessage,
maxLengthErrorMessage,
}: {
value: string;
minLength?: number;
maxLength?: number;
minLengthErrorMessage: (minLength: number) => string;
maxLengthErrorMessage: (maxLength: number) => string;
}) => {
if (minLength != null && value.length < minLength && minLengthErrorMessage) {
return minLengthErrorMessage(minLength);
}
if (maxLength != null && value.length > maxLength && maxLengthErrorMessage) {
return maxLengthErrorMessage(maxLength);
}
return undefined;
};
4 changes: 2 additions & 2 deletions packages/lib/src/common/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ export const defaultTranslatedComponentLabels = {
requiredSelectionErrorMessage: "This field is required. Please, choose an option.",
requiredValueErrorMessage: "This field is required. Please, enter a value.",
formatRequestedErrorMessage: "Please match the format requested.",
lengthErrorMessage: (minLength?: number, maxLength?: number) => `Min length ${minLength}, max length ${maxLength}.`,
logoAlternativeText: "Logo",
minLengthErrorMessage: (minLength: number) => `The minimum length is ${minLength}.`,
maxLengthErrorMessage: (maxLength: number) => `The maximum length is ${maxLength}.`,
},
header: {
closeIcon: "Close menu",
Expand Down
111 changes: 94 additions & 17 deletions packages/lib/src/text-input/TextInput.test.tsx
Comment thread
Jialecl marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { act, fireEvent, render, waitForElementToBeRemoved } from "@testing-libr
import userEvent from "@testing-library/user-event";
import DxcTextInput from "./TextInput";
import MockDOMRect from "../../test/mocks/domRectMock";
import { HalstackProvider } from "../HalstackContext";

// Mocking DOMRect for Radix Primitive Popover
global.DOMRect = MockDOMRect;
Expand Down Expand Up @@ -157,23 +158,30 @@ describe("TextInput component tests", () => {
);
const input = getByRole("textbox");
fireEvent.change(input, { target: { value: "test" } });
expect(onChange).toHaveBeenCalled();
expect(onChange).toHaveBeenCalledWith({
value: "test",
error: "Min length 5, max length 10.",
error: "The minimum length is 5.",
});
fireEvent.blur(input);
expect(onBlur).toHaveBeenCalled();
expect(onBlur).toHaveBeenCalledWith({
value: "test",
error: "Min length 5, max length 10.",
error: "The minimum length is 5.",
});
userEvent.clear(input);

fireEvent.change(input, { target: { value: "test-maximum-length" } });
expect(onChange).toHaveBeenCalledWith({
value: "test-maximum-length",
error: "The maximum length is 10.",
});
fireEvent.blur(input);
expect(onBlur).toHaveBeenCalledWith({
value: "test-maximum-length",
error: "The maximum length is 10.",
});

fireEvent.change(input, { target: { value: "length" } });
expect(onChange).toHaveBeenCalled();
expect(onChange).toHaveBeenCalledWith({ value: "length" });
fireEvent.blur(input);
expect(onBlur).toHaveBeenCalled();
expect(onBlur).toHaveBeenCalledWith({ value: "length" });
});

Expand All @@ -195,34 +203,38 @@ describe("TextInput component tests", () => {
);
const input = getByRole("textbox");
fireEvent.change(input, { target: { value: "test" } });
expect(onChange).toHaveBeenCalled();
expect(onChange).toHaveBeenCalledWith({
value: "test",
error: "Min length 5, max length 10.",
error: "The minimum length is 5.",
});
fireEvent.blur(input);
expect(onBlur).toHaveBeenCalled();
expect(onBlur).toHaveBeenCalledWith({
value: "test",
error: "Min length 5, max length 10.",
error: "The minimum length is 5.",
});
fireEvent.change(input, { target: { value: "test-maximum-length" } });
expect(onChange).toHaveBeenCalledWith({
value: "test-maximum-length",
error: "The maximum length is 10.",
});
fireEvent.blur(input);
expect(onBlur).toHaveBeenCalledWith({
value: "test-maximum-length",
error: "The maximum length is 10.",
});
fireEvent.change(input, { target: { value: "tests" } });
expect(onChange).toHaveBeenCalled();
expect(onChange).toHaveBeenCalledWith({
value: "tests",
error: "Please match the format requested.",
});
fireEvent.blur(input);
expect(onBlur).toHaveBeenCalled();
expect(onBlur).toHaveBeenCalledWith({
value: "tests",
error: "Please match the format requested.",
});
fireEvent.change(input, { target: { value: "tests4&" } });
expect(onChange).toHaveBeenCalled();
expect(onChange).toHaveBeenCalledWith({ value: "tests4&" });
fireEvent.blur(input);
expect(onBlur).toHaveBeenCalled();
expect(onBlur).toHaveBeenCalledWith({ value: "tests4&" });
});

Expand Down Expand Up @@ -732,12 +744,29 @@ describe("TextInput component synchronous autosuggest tests", () => {
});
expect(onChange).toHaveBeenCalledWith({
value: "Cha",
error: "Min length 5, max length 10.",
error: "The minimum length is 5.",
});
fireEvent.blur(input);
expect(onBlur).toHaveBeenCalledWith({
value: "Chad",
error: "Min length 5, max length 10.",
error: "The minimum length is 5.",
});

userEvent.clear(input);
fireEvent.focus(input);
fireEvent.change(input, { target: { value: "Democratic Rep" } });
expect(getByText("Democratic Rep")).toBeTruthy();
act(() => {
userEvent.click(getByRole("option"));
});
expect(onChange).toHaveBeenCalledWith({
value: "Democratic Rep",
error: "The maximum length is 10.",
});
fireEvent.blur(input);
expect(onBlur).toHaveBeenCalledWith({
value: "Democratic Republic of the Congo",
error: "The maximum length is 10.",
});
});

Expand Down Expand Up @@ -1156,4 +1185,52 @@ describe("TextInput component asynchronous autosuggest tests", () => {
await waitForElementToBeRemoved(() => getByText("Searching..."));
expect(getByText("Error fetching data")).toBeTruthy();
});

test("Maximum and minimum error messages change within HalstackProvider", () => {
const onChange = jest.fn();
const onBlur = jest.fn();
const { getByRole } = render(
<HalstackProvider
labels={{
formFields: {
maxLengthErrorMessage: (maxLength: number) => `Please do not enter more than ${maxLength} characters.`,
minLengthErrorMessage: (minLegth: number) => `Please do not enter less than ${minLegth} characters.`,
},
}}
>
<DxcTextInput
label="Input label"
placeholder="Placeholder"
onChange={onChange}
onBlur={onBlur}
margin={{ left: "medium", right: "medium" }}
clearable
minLength={5}
maxLength={10}
/>
</HalstackProvider>
);
const input = getByRole("textbox");
fireEvent.change(input, { target: { value: "test" } });
expect(onChange).toHaveBeenCalledWith({
value: "test",
error: "Please do not enter less than 5 characters.",
});
fireEvent.blur(input);
expect(onBlur).toHaveBeenCalledWith({
value: "test",
error: "Please do not enter less than 5 characters.",
});

fireEvent.change(input, { target: { value: "test-maximum-length" } });
expect(onChange).toHaveBeenCalledWith({
value: "test-maximum-length",
error: "Please do not enter more than 10 characters.",
});
fireEvent.blur(input);
expect(onBlur).toHaveBeenCalledWith({
value: "test-maximum-length",
error: "Please do not enter more than 10 characters.",
});
});
});
26 changes: 21 additions & 5 deletions packages/lib/src/text-input/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import TextInputPropsType, { AutosuggestWrapperProps, RefType } from "./types";
import {
calculateWidth,
hasSuggestions,
isLengthIncorrect,
isNumberIncorrect,
isRequired,
makeCancelable,
Expand All @@ -35,6 +34,7 @@ import HelperText from "../styles/forms/HelperText";
import Label from "../styles/forms/Label";
import ErrorMessage from "../styles/forms/ErrorMessage";
import inputStylesByState from "../styles/forms/inputStylesByState";
import { getLengthErrorMessage } from "../common/utils";

const TextInputContainer = styled.div<{
margin: TextInputPropsType["margin"];
Expand Down Expand Up @@ -232,15 +232,23 @@ const DxcTextInput = forwardRef<RefType, TextInputPropsType>(
setInnerValue(formattedValue);
}

const lengthError = getLengthErrorMessage({
value: formattedValue,
minLength,
maxLength,
minLengthErrorMessage: translatedLabels.formFields.minLengthErrorMessage,
maxLengthErrorMessage: translatedLabels.formFields.maxLengthErrorMessage,
});

if (isRequired(formattedValue, optional)) {
onChange?.({
value: formattedValue,
error: translatedLabels.formFields.requiredValueErrorMessage,
});
} else if (isLengthIncorrect(formattedValue, minLength, maxLength)) {
} else if (lengthError) {
onChange?.({
value: formattedValue,
error: translatedLabels.formFields.lengthErrorMessage?.(minLength, maxLength),
error: lengthError,
});
} else if (patternMismatch(pattern, formattedValue)) {
onChange?.({ value: formattedValue, error: translatedLabels.formFields.formatRequestedErrorMessage });
Expand Down Expand Up @@ -331,12 +339,20 @@ const DxcTextInput = forwardRef<RefType, TextInputPropsType>(
const handleInputOnBlur = (event: FocusEvent<HTMLInputElement>) => {
closeSuggestions();

const lengthError = getLengthErrorMessage({
value: event.target.value,
minLength,
maxLength,
minLengthErrorMessage: translatedLabels.formFields.minLengthErrorMessage,
maxLengthErrorMessage: translatedLabels.formFields.maxLengthErrorMessage,
});

if (isRequired(event.target.value, optional)) {
onBlur?.({ value: event.target.value, error: translatedLabels.formFields.requiredValueErrorMessage });
} else if (isLengthIncorrect(event.target.value, minLength, maxLength)) {
} else if (lengthError) {
onBlur?.({
value: event.target.value,
error: translatedLabels.formFields.lengthErrorMessage?.(minLength, maxLength),
error: lengthError,
});
} else if (patternMismatch(pattern, event.target.value)) {
onBlur?.({ value: event.target.value, error: translatedLabels.formFields.formatRequestedErrorMessage });
Expand Down
7 changes: 0 additions & 7 deletions packages/lib/src/text-input/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,6 @@ export const hasSuggestions = (suggestions: TextInputPropsType["suggestions"]) =

export const isRequired = (value: string, optional: boolean) => value === "" && !optional;

export const isLengthIncorrect = (
value: string,
minLength: TextInputPropsType["minLength"],
maxLength: TextInputPropsType["maxLength"]
) =>
value != null && ((minLength != null && value.length < minLength) || (maxLength != null && value.length > maxLength));

export const isNumberIncorrect = (
value: number,
minNumber: TextInputPropsType["minLength"],
Expand Down
21 changes: 17 additions & 4 deletions packages/lib/src/textarea/Textarea.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,26 @@ describe("Textarea component tests", () => {
expect(onChange).toHaveBeenCalled();
expect(onChange).toHaveBeenCalledWith({
value: "test",
error: "Min length 5, max length 10.",
error: "The minimum length is 5.",
});
fireEvent.blur(textarea);
expect(onBlur).toHaveBeenCalled();
expect(onBlur).toHaveBeenCalledWith({
value: "test",
error: "Min length 5, max length 10.",
error: "The minimum length is 5.",
});
userEvent.clear(textarea);
fireEvent.change(textarea, { target: { value: "this is a longer value" } });
expect(onChange).toHaveBeenCalled();
expect(onChange).toHaveBeenCalledWith({
value: "this is a longer value",
error: "The maximum length is 10.",
});
fireEvent.blur(textarea);
expect(onBlur).toHaveBeenCalled();
expect(onBlur).toHaveBeenCalledWith({
value: "this is a longer value",
error: "The maximum length is 10.",
});
userEvent.clear(textarea);
fireEvent.change(textarea, { target: { value: "length" } });
Expand Down Expand Up @@ -233,13 +246,13 @@ describe("Textarea component tests", () => {
expect(onChange).toHaveBeenCalled();
expect(onChange).toHaveBeenCalledWith({
value: "test",
error: "Min length 5, max length 10.",
error: "The minimum length is 5.",
});
fireEvent.blur(textarea);
expect(onBlur).toHaveBeenCalled();
expect(onBlur).toHaveBeenCalledWith({
value: "test",
error: "Min length 5, max length 10.",
error: "The minimum length is 5.",
});
fireEvent.change(textarea, { target: { value: "tests" } });
expect(onChange).toHaveBeenCalled();
Expand Down
Loading
Loading