Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should consider having tests for max lenght and also custom ones.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

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.",
});
});
});
23 changes: 18 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 Down Expand Up @@ -160,6 +159,16 @@ const DxcTextInput = forwardRef<RefType, TextInputPropsType>(
setPortalContainer(document?.getElementById(`${inputId}-portal`));
}, []);

const getLengthErrorMessage = (value: string) => {
if (minLength != null && value.length < minLength) {
return translatedLabels.formFields.minLengthErrorMessage(minLength);
}
if (maxLength != null && value.length > maxLength) {
return translatedLabels.formFields.maxLengthErrorMessage(maxLength);
}
return undefined;
};

const autosuggestWrapperFunction = (children: ReactNode) => (
<Popover.Root open={isOpen && (filteredSuggestions.length > 0 || isSearching || isAutosuggestError)}>
<Popover.Trigger
Expand Down Expand Up @@ -232,15 +241,17 @@ const DxcTextInput = forwardRef<RefType, TextInputPropsType>(
setInnerValue(formattedValue);
}

const lengthError = getLengthErrorMessage(formattedValue);

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 +342,14 @@ const DxcTextInput = forwardRef<RefType, TextInputPropsType>(
const handleInputOnBlur = (event: FocusEvent<HTMLInputElement>) => {
closeSuggestions();

const lengthError = getLengthErrorMessage(event.target.value);

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
23 changes: 17 additions & 6 deletions packages/lib/src/textarea/Textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,21 +99,30 @@ const DxcTextarea = forwardRef<RefType, TextareaPropsType>(
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
const prevValueRef = useRef<string | null>(null);

const isLengthOutOfRange = (value: string) =>
value !== "" && minLength && maxLength && (value.length < minLength || value.length > maxLength);
const getLengthErrorMessage = (value: string) => {
if (minLength != null && value.length < minLength) {
return translatedLabels.formFields.minLengthErrorMessage?.(minLength);
}
if (maxLength != null && value.length > maxLength) {
return translatedLabels.formFields.maxLengthErrorMessage?.(maxLength);
}
return undefined;
};

const changeValue = (newValue: string) => {
if (value == null) setInnerValue(newValue);

const lengthError = getLengthErrorMessage(newValue);

if (newValue === "" && !optional) {
onChange?.({
value: newValue,
error: translatedLabels.formFields.requiredValueErrorMessage,
});
} else if (isLengthOutOfRange(newValue)) {
} else if (lengthError) {
onChange?.({
value: newValue,
error: translatedLabels.formFields.lengthErrorMessage?.(minLength, maxLength),
error: lengthError,
});
} else if (newValue && pattern && !patternMatch(pattern, newValue)) {
onChange?.({
Expand All @@ -124,15 +133,17 @@ const DxcTextarea = forwardRef<RefType, TextareaPropsType>(
};

const handleOnBlur = (event: FocusEvent<HTMLTextAreaElement>) => {
const lengthError = getLengthErrorMessage(event.target.value);

if (event.target.value === "" && !optional) {
onBlur?.({
value: event.target.value,
error: translatedLabels.formFields.requiredValueErrorMessage,
});
} else if (isLengthOutOfRange(event.target.value)) {
} else if (lengthError) {
onBlur?.({
value: event.target.value,
error: translatedLabels.formFields.lengthErrorMessage?.(minLength, maxLength),
error: lengthError,
});
} else if (event.target.value && pattern && !patternMatch(pattern, event.target.value)) {
onBlur?.({
Expand Down
Loading