Skip to content

Commit

Permalink
fix(IT Wallet): [SIW-1834] Refactor date claim parser (#6431)
Browse files Browse the repository at this point in the history
## Short description
In this PR, an attempt was made to fix a problem of misalignment of
dates from when we receive them from the issuer to when we submit them
in the credential claim preview.

## List of changes proposed in this pull request
- Removed `localeDateFormat`
- Replaced the date parser with a new `SimpleDateFormat` type and
`SimpleDateClaim`
- Checked every claim format and aligned with Figma where necessary
- Added tests to the parser

## How to test
Obtain some credentials to check that the dates are correct, formats are
correct, and changing the TZ works as expected.

Thanks @mastro993 for the help and the suggestions.

---------

Co-authored-by: Federico Mastrini <[email protected]>
  • Loading branch information
ale-mazz and mastro993 authored Nov 25, 2024
1 parent 14022a1 commit 62a38b5
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 98 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@
"test:ci": "jest --ci --maxWorkers=4 --silent",
"test:dev": "jest --detectOpenHandles --coverage=false",
"test:tz": "yarn test:tz-eu-rome && yarn test:tz-us-ny && yarn test:tz-us-yt && yarn test:tz-au-syd",
"test:tz-eu-rome": "TZ='Europe/Rome' jest --config='./jest.config.no.timezone.js' --detectOpenHandles --coverage=false -t 'Check fiscal code date IT|removeTimezoneFromDate should remove the timezone from a date' './ts/utils/__tests__/fiscal-code.test.ts' './ts/utils/__tests__/dates.test.ts'",
"test:tz-us-ny": "TZ='America/New_York' jest --config='./jest.config.no.timezone.js' --detectOpenHandles --coverage=false -t 'Check fiscal code date NY|removeTimezoneFromDate should remove the timezone from a date' './ts/utils/__tests__/fiscal-code.test.ts' './ts/utils/__tests__/dates.test.ts'",
"test:tz-us-yt": "TZ='America/Whitehorse' jest --config='./jest.config.no.timezone.js' --detectOpenHandles --coverage=false -t 'Check fiscal code date CA|removeTimezoneFromDate should remove the timezone from a date' './ts/utils/__tests__/fiscal-code.test.ts' './ts/utils/__tests__/dates.test.ts'",
"test:tz-au-syd": "TZ='Australia/Sydney' jest --config='./jest.config.no.timezone.js' --detectOpenHandles --coverage=false -t 'Check fiscal code date AU|removeTimezoneFromDate should remove the timezone from a date' './ts/utils/__tests__/fiscal-code.test.ts' './ts/utils/__tests__/dates.test.ts'",
"test:tz-eu-rome": "TZ='Europe/Rome' jest --config='./jest.config.no.timezone.js' --detectOpenHandles --coverage=false -t 'Check fiscal code date IT|SimpleDateClaim should handle valid, invalid, edge cases, and formatting correctly' './ts/utils/__tests__/fiscal-code.test.ts' './ts/features/itwallet/common/utils/__tests__/itwClaimsUtils.test.ts'",
"test:tz-us-ny": "TZ='America/New_York' jest --config='./jest.config.no.timezone.js' --detectOpenHandles --coverage=false -t 'Check fiscal code date NY|SimpleDateClaim should handle valid, invalid, edge cases, and formatting correctly' './ts/utils/__tests__/fiscal-code.test.ts' './ts/features/itwallet/common/utils/__tests__/itwClaimsUtils.test.ts'",
"test:tz-us-yt": "TZ='America/Whitehorse' jest --config='./jest.config.no.timezone.js' --detectOpenHandles --coverage=false -t 'Check fiscal code date CA|SimpleDateClaim should handle valid, invalid, edge cases, and formatting correctly' './ts/utils/__tests__/fiscal-code.test.ts' './ts/features/itwallet/common/utils/__tests__/itwClaimsUtils.test.ts'",
"test:tz-au-syd": "TZ='Australia/Sydney' jest --config='./jest.config.no.timezone.js' --detectOpenHandles --coverage=false -t 'Check fiscal code date AU|SimpleDateClaim should handle valid, invalid, edge cases, and formatting correctly' './ts/utils/__tests__/fiscal-code.test.ts' './ts/features/itwallet/common/utils/__tests__/itwClaimsUtils.test.ts'",
"prettify": "prettier --write \"ts/**/*.(ts|tsx)\"",
"prettier:check": "prettier --check \"ts/**/*.(ts|tsx)\"",
"packager:clear": "rm -rf $TMPDIR/react-native-packager-cache-* && rm -rf $TMPDIR/metro-bundler-cache-*",
Expand Down
31 changes: 11 additions & 20 deletions ts/features/itwallet/common/components/ItwCredentialClaim.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { Divider, ListItemInfo } from "@pagopa/io-app-design-system";
import { DateFromString } from "@pagopa/ts-commons/lib/dates";
import * as E from "fp-ts/Either";
import * as O from "fp-ts/Option";
import { pipe } from "fp-ts/lib/function";
import React, { useMemo } from "react";
import { Image } from "react-native";
import I18n from "../../../../i18n";
import { useIOBottomSheetAutoresizableModal } from "../../../../utils/hooks/bottomSheet";
import { localeDateFormat } from "../../../../utils/locale";
import { useItwInfoBottomSheet } from "../hooks/useItwInfoBottomSheet";
import {
BoolClaim,
Expand All @@ -17,15 +15,17 @@ import {
DrivingPrivilegesClaim,
EmptyStringClaim,
EvidenceClaim,
extractFiscalCode,
FiscalCodeClaim,
getSafeText,
ImageClaim,
isExpirationDateClaim,
PdfClaim,
PlaceOfBirthClaim,
PlaceOfBirthClaimType,
StringClaim,
extractFiscalCode,
isExpirationDateClaim,
getSafeText
SimpleDate,
SimpleDateClaim,
StringClaim
} from "../utils/itwClaimsUtils";
import { ItwCredentialStatus } from "../utils/itwTypesUtils";

Expand Down Expand Up @@ -108,15 +108,12 @@ const DateClaimItem = ({
status
}: {
label: string;
claim: Date;
claim: SimpleDate;
status?: ItwCredentialStatus;
}) => {
// Remove the timezone offset to display the date in its original format

const value = localeDateFormat(
claim,
I18n.t("global.dateFormats.shortFormat")
);
const value = claim.toString("DD/MM/YYYY");

const endElement: ListItemInfo["endElement"] = useMemo(() => {
const ns = "features.itWallet.presentation.credentialDetails.status";
Expand Down Expand Up @@ -270,14 +267,8 @@ const DrivingPrivilegesClaimItem = ({
claim: DrivingPrivilegeClaimType;
detailsButtonVisible?: boolean;
}) => {
const localExpiryDate = localeDateFormat(
claim.expiry_date,
I18n.t("global.dateFormats.shortFormat")
);
const localIssueDate = localeDateFormat(
claim.issue_date,
I18n.t("global.dateFormats.shortFormat")
);
const localExpiryDate = claim.expiry_date.toString("DD/MM/YYYY");
const localIssueDate = claim.issue_date.toString("DD/MM/YYYY");
const privilegeBottomSheet = useIOBottomSheetAutoresizableModal({
title: I18n.t(
"features.itWallet.verifiableCredentials.claims.mdl.category",
Expand Down Expand Up @@ -371,7 +362,7 @@ export const ItwCredentialClaim = ({
const decoded = hidden ? HIDDEN_CLAIM : _decoded;
if (PlaceOfBirthClaim.is(decoded)) {
return <PlaceOfBirthClaimItem label={claim.label} claim={decoded} />;
} else if (DateFromString.is(decoded)) {
} else if (SimpleDateClaim.is(decoded)) {
return (
<DateClaimItem
label={claim.label}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { WithTestID } from "@pagopa/io-app-design-system";
import { DateFromString } from "@pagopa/ts-commons/lib/dates";
import * as E from "fp-ts/lib/Either";
import * as O from "fp-ts/lib/Option";
import { constNull, pipe } from "fp-ts/lib/function";
import React from "react";
import { Image, StyleSheet, View, ViewStyle } from "react-native";
import { Either, Prettify } from "../../../../../types/helpers";
import { localeDateFormat } from "../../../../../utils/locale";
import {
ClaimValue,
DrivingPrivilegesClaim,
EvidenceClaim,
ImageClaim,
PlaceOfBirthClaim
PlaceOfBirthClaim,
SimpleDateClaim,
SimpleDateFormat
} from "../../utils/itwClaimsUtils";
import { ParsedCredential } from "../../utils/itwTypesUtils";
import { ClaimLabel, ClaimLabelProps } from "./ClaimLabel";
Expand Down Expand Up @@ -48,7 +48,7 @@ export type CardClaimProps = Prettify<
// Claim dimensions
dimensions?: ClaimDimensions;
// Optional format for dates contained in the claim component
dateFormat?: string;
dateFormat?: SimpleDateFormat;
} & ClaimLabelProps
>;

Expand All @@ -61,7 +61,7 @@ const CardClaim = ({
position,
dimensions,
testID,
dateFormat = "%d/%m/%Y",
dateFormat = "DD/MM/YY",
...labelProps
}: WithTestID<CardClaimProps>) => {
const claimContent = React.useMemo(
Expand All @@ -70,8 +70,8 @@ const CardClaim = ({
claim?.value,
ClaimValue.decode,
E.fold(constNull, decoded => {
if (DateFromString.is(decoded)) {
const formattedDate = localeDateFormat(decoded, dateFormat);
if (SimpleDateClaim.is(decoded)) {
const formattedDate = decoded.toString(dateFormat);
return <ClaimLabel {...labelProps}>{formattedDate}</ClaimLabel>;
} else if (EvidenceClaim.is(decoded)) {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
/* eslint-disable dot-notation */
/* eslint-disable @typescript-eslint/dot-notation */
import { parse } from "date-fns";
import { pipe } from "fp-ts/lib/function";
import * as O from "fp-ts/lib/Option";
import { Fragment, default as React } from "react";
import { default as React, Fragment } from "react";
import { StyleSheet, View } from "react-native";
import { QrCodeImage } from "../../../../../components/QrCodeImage";
import { localeDateFormat } from "../../../../../utils/locale";
import {
DrivingPrivilegesClaim,
StringClaim
Expand Down Expand Up @@ -50,7 +48,7 @@ const MdlFrontData = ({ claims }: DataComponentProps) => {
<CardClaim
claim={claims["birth_date"]}
position={{ left: `${cols[0]}%`, top: `${rows[2]}%` }}
dateFormat="%d/%m/%y"
dateFormat="DD/MM/YY"
/>
<CardClaim
claim={claims["place_of_birth"]}
Expand All @@ -60,6 +58,7 @@ const MdlFrontData = ({ claims }: DataComponentProps) => {
claim={claims["issue_date"]}
position={{ left: `${cols[0]}%`, top: `${rows[3]}%` }}
fontWeight={"Bold"}
dateFormat={"DD/MM/YYYY"}
/>
<CardClaim
claim={claims["issuing_authority"]}
Expand All @@ -69,6 +68,7 @@ const MdlFrontData = ({ claims }: DataComponentProps) => {
claim={claims["expiry_date"]}
position={{ left: `${cols[0]}%`, top: `${rows[4]}%` }}
fontWeight={"Bold"}
dateFormat={"DD/MM/YYYY"}
/>
<CardClaim
claim={claims["document_number"]}
Expand Down Expand Up @@ -134,7 +134,7 @@ const MdlBackData = ({ claims }: DataComponentProps) => {
}}
>
<ClaimLabel fontSize={9}>
{localeDateFormat(parse(issue_date), "%d/%m/%y")}
{issue_date.toString("DD/MM/YY")}
</ClaimLabel>
</CardClaimContainer>
<CardClaimContainer
Expand All @@ -145,7 +145,7 @@ const MdlBackData = ({ claims }: DataComponentProps) => {
}}
>
<ClaimLabel fontSize={9}>
{localeDateFormat(parse(expiry_date), "%d/%m/%y")}
{expiry_date.toString("DD/MM/YY")}
</ClaimLabel>
</CardClaimContainer>
{restrictions_conditions && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ exports[`CardData should match snapshot for DC front data 1`] = `
}
weight="Semibold"
>
15/03/1990
15/03/90
</Text>
</View>
<View
Expand Down Expand Up @@ -342,7 +342,7 @@ exports[`CardData should match snapshot for DC front data 1`] = `
}
weight="Semibold"
>
23/03/2032
23/03/32
</Text>
</View>
</View>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
getCredentialExpireDays,
getCredentialStatus,
getFiscalCodeFromCredential,
ImageClaim
ImageClaim,
SimpleDateClaim
} from "../itwClaimsUtils";
import { StoredCredential } from "../itwTypesUtils";
import { ItwStoredCredentialsMocks } from "../itwMocksUtils";
Expand Down Expand Up @@ -468,3 +469,55 @@ describe("getCredentialStatus", () => {
});
});
});

describe("SimpleDateClaim", () => {
it("should handle valid, invalid, edge cases, and formatting correctly", () => {
// Valid date decoding
const validInput = "2024-11-19";
const validResult = SimpleDateClaim.decode(validInput);
expect(E.isRight(validResult)).toBe(true);
if (E.isRight(validResult)) {
const decodedDate = validResult.right;

// Validate date parts
expect(decodedDate.getFullYear()).toBe(2024);
expect(decodedDate.getMonth()).toBe(10); // 0-indexed month
expect(decodedDate.getDate()).toBe(19);

// Validate default and custom formats
expect(decodedDate.toString()).toBe("19/11/2024");
expect(decodedDate.toString("DD/MM/YY")).toBe("19/11/24");

// Validate Date object conversions
expect(decodedDate.toDate()).toEqual(new Date(2024, 10, 19));
expect(decodedDate.toDateWithoutTimezone().toISOString()).toBe(
"2024-11-19T00:00:00.000Z"
);
}

// Invalid date decoding
const invalidInput = "invalid-date";
const invalidResult = SimpleDateClaim.decode(invalidInput);
expect(E.isLeft(invalidResult)).toBe(true);

// Valid leap year date
const leapYearInput = "2024-02-29";
const leapYearResult = SimpleDateClaim.decode(leapYearInput);
expect(E.isRight(leapYearResult)).toBe(true);
if (E.isRight(leapYearResult)) {
const leapYearDate = leapYearResult.right;
expect(leapYearDate.getFullYear()).toBe(2024);
expect(leapYearDate.getMonth()).toBe(1); // 0-indexed month
expect(leapYearDate.getDate()).toBe(29);
}

// Valid date with padded spaces
const paddedInput = " 2024-11-19 ";
const paddedResult = SimpleDateClaim.decode(paddedInput.trim());
expect(E.isRight(paddedResult)).toBe(true);
if (E.isRight(paddedResult)) {
const paddedDate = paddedResult.right;
expect(paddedDate.toString()).toBe("19/11/2024");
}
});
});
Loading

0 comments on commit 62a38b5

Please sign in to comment.