Skip to content

Commit

Permalink
chore: [PE-737] CGN capitalize text apostrophe case (#6293)
Browse files Browse the repository at this point in the history
## Short description
This PR updates the `capitalize` function to correctly handle words with
apostrophes, ensuring that words like _Dall'Ara_ remain _Dall'Ara_ and
not _Dall'ara_.
## List of changes proposed in this pull request
- Added new test cases to ensure the function works as expected with
apostrophes.
- Updated the `capitalize` function to correctly handle words with
apostrophes.
- Used RegExp.exec() to match and preserve leading and trailing spaces.

## How to test

1. Ensure your development environment is set up and the project is
running.
2. Run the test suite to verify the changes
3. Check that all tests pass, including the new test cases for words
with apostrophes and leading/trailing spaces.

Or

1. Modify the CgnOwnershipInformation file with a hardcoded word
containing an apostrophe.
2. Verify that the CgnOwnershipInformation component correctly
capitalizes words with apostrophes when rendered.

##Preview
![Screenshot 2024-10-17 at 09 41
56](https://github.com/user-attachments/assets/637139cb-5dce-4d57-bd4f-10cd0490ed49)

---------

Co-authored-by: Alessandro <[email protected]>
  • Loading branch information
LeleDallas and Hantex9 authored Oct 17, 2024
1 parent d15d93f commit 5552357
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import * as pot from "@pagopa/ts-commons/lib/pot";
import * as React from "react";
import {
Divider,
ListItemHeader,
ListItemInfo
} from "@pagopa/io-app-design-system";
import * as pot from "@pagopa/ts-commons/lib/pot";
import * as React from "react";
import I18n from "../../../../../i18n";
import { profileSelector } from "../../../../../store/reducers/profile";
import { useIOSelector } from "../../../../../store/hooks";
import { capitalize } from "../../../../../utils/strings";
import { profileSelector } from "../../../../../store/reducers/profile";
import { capitalizeTextName } from "../../../../../utils/strings";

/**
* Renders the CGN ownership block for detail screen, including Owner's Fiscal Code (The current user logged in)
Expand All @@ -23,12 +23,12 @@ const CgnOwnershipInformation = (): React.ReactElement => {
<ListItemHeader label={I18n.t("bonus.cgn.detail.ownership")} />
<ListItemInfo
label="Nome"
value={capitalize(currentProfile.value.name)}
value={capitalizeTextName(currentProfile.value.name)}
/>
<Divider />
<ListItemInfo
label="Cognome"
value={capitalize(currentProfile.value.family_name)}
value={capitalizeTextName(currentProfile.value.family_name)}
/>
<Divider />
<ListItemInfo
Expand Down
8 changes: 4 additions & 4 deletions ts/features/bonus/cgn/screens/CgnDetailScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { profileSelector } from "../../../../store/reducers/profile";
import { GlobalState } from "../../../../store/reducers/types";
import { formatDateAsShortFormat } from "../../../../utils/dates";
import { useActionOnFocus } from "../../../../utils/hooks/useOnFocus";
import { capitalizeTextName } from "../../../../utils/strings";
import { openWebUrl } from "../../../../utils/url";
import { availableBonusTypesSelectorFromId } from "../../common/store/selectors";
import { ID_CGN_TYPE } from "../../common/utils";
Expand Down Expand Up @@ -65,7 +66,6 @@ import {
import { cgnUnsubscribeSelector } from "../store/reducers/unsubscribe";
import { EYCA_WEBSITE_DISCOUNTS_PAGE_URL } from "../utils/constants";
import { canEycaCardBeShown } from "../utils/eyca";
import { capitalize } from "../../../../utils/strings";

type Props = ReturnType<typeof mapStateToProps> &
ReturnType<typeof mapDispatchToProps>;
Expand Down Expand Up @@ -198,9 +198,9 @@ const CgnDetailScreen = (props: Props): React.ReactElement => {
}}
>
{pot.isSome(currentProfile)
? `${capitalize(currentProfile.value.name)} ${capitalize(
currentProfile.value.family_name
)}`
? `${capitalizeTextName(
currentProfile.value.name
)} ${capitalizeTextName(currentProfile.value.family_name)}`
: ""}
</H4>
}
Expand Down
29 changes: 28 additions & 1 deletion ts/utils/__tests__/string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
capitalize,
isStringNullyOrEmpty,
maybeNotNullyString,
formatBytesWithUnit
formatBytesWithUnit,
capitalizeTextName
} from "../strings";

describe("capitalize", () => {
Expand Down Expand Up @@ -116,3 +117,29 @@ describe("formatBytesWithUnit", () => {
expect(formatBytesWithUnit(-1234)).toEqual("0 B");
});
});

describe("capitalizeTextName", () => {
it("should return a string where each word has the first char in uppercase", () => {
expect(capitalizeTextName("capitalize")).toEqual("Capitalize");
});

it("should return a string where each word has the first char in uppercase even after an apostrophe", () => {
expect(capitalizeTextName("Capit'Alize")).toEqual("Capit'Alize");
});

it("should return a string where each word has the first char in uppercase even after an apostrophe-2", () => {
expect(capitalizeTextName("capit'alize")).toEqual("Capit'Alize");
});

it("should return a string where each word has the first char in uppercase even after an apostrophe-3", () => {
expect(capitalizeTextName("Capit'alize")).toEqual("Capit'Alize");
});

it("should return a string where each word has the first char in uppercase even after an apostrophe-4", () => {
expect(capitalizeTextName("capit'Alize")).toEqual("Capit'Alize");
});

it("should return a string where each word has the first char in uppercase even after an apostrophe-5", () => {
expect(capitalizeTextName("CAPIT'ALIZE")).toEqual("Capit'Alize");
});
});
44 changes: 44 additions & 0 deletions ts/utils/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,47 @@ export const formatBytesWithUnit = (bytes: number) => {
const unit = units[Math.min(i, units.length - 1)];
return `${value} ${unit}`;
};

/**
* Capitalizes the first letter of each word in the given text, preserving leading and trailing spaces.
* Words are separated by the specified separator.
* Handles words with apostrophes by capitalizing the first letter of each sub-token.
*
* @param {string} text
* @param {string} [separator=" "]
* @returns {string}
*
* @example
* capitalizeTextName(" hello world "); // returns " Hello World "
*
* @example
* capitalizeTextName("d'angelo"); //returns "D'Angelo"
*/

export const capitalizeTextName = (
text: string,
separator: string = " "
): string => {
// Match leading and trailing spaces
const leadingSpacesMatch = /^\s*/.exec(text);
const trailingSpacesMatch = /\s*$/.exec(text);

const leadingSpaces = leadingSpacesMatch ? leadingSpacesMatch[0] : "";
const trailingSpaces = trailingSpacesMatch ? trailingSpacesMatch[0] : "";

// Capitalize the words between the separators
const capitalizedText = text
.trim() // Remove leading/trailing spaces for processing
.split(separator)
.map(token =>
// Handle words with apostrophes
token
.split("'")
.map(subToken => _.upperFirst(subToken.toLowerCase()))
.join("'")
)
.join(separator);

// Re-add the leading and trailing spaces
return `${leadingSpaces}${capitalizedText}${trailingSpaces}`;
};

0 comments on commit 5552357

Please sign in to comment.