Skip to content

Commit

Permalink
fix: [IOBP-1206,IOBP-1209] Payment checkout accessibility input text (#…
Browse files Browse the repository at this point in the history
…6745)

## Short description
This pull request includes multiple changes aimed at improving
accessibility

## List of changes proposed in this pull request
- Updated `@pagopa/io-app-design-system` from version 4.5.6 to 4.6.0.
- Added accessibility error messages for the payment notice code and
fiscal code fields
- Refactored input validation logic (using `onContinue` validation mode)
and removed unused accessibility focus function

## How to test
- Using a real device 📱, start a payment flow and press on `Digita`
- Turn on the TalkBack accessibility tool
- Press on `?` icon and then close the bottom sheet
- Ensure that input text is not in error state
- Insert a wrong value or leave it empty and press `Continua`
- Ensure that error logic is handle corretly

## Preview

https://github.com/user-attachments/assets/4de81ac3-4dc5-4cec-8caf-c1f27d460381
  • Loading branch information
LeleDallas authored Feb 25, 2025
1 parent 92390c8 commit 4f5acaf
Show file tree
Hide file tree
Showing 10 changed files with 2,043 additions and 23 deletions.
6 changes: 4 additions & 2 deletions locales/de/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -2207,13 +2207,15 @@
"title": "Gib den Zahlungsmitteilungskodex ein",
"subtitle": "Er hat 18 Ziffern und ist neben dem QR-Code zu finden.",
"validationError": "18 Ziffern, die mit 0, 1 oder 3 beginnen.",
"placeholder": "Zahlungsmitteilungskodex"
"placeholder": "Zahlungsmitteilungskodex",
"a11y": "Fehler. Falscher Mitteilungscode. Es hat 18 Ziffern, Sie finden es in der Nähe des Code QR."
},
"fiscalCode": {
"title": "Gib die Steuernummer der Körperschaft ein",
"subtitle": "Sie hat 11 Ziffern und ist neben dem QR-Code zu finden.",
"validationError": "11 Ziffern eingeben.",
"placeholder": "Steuernummer der Körperschaft"
"placeholder": "Steuernummer der Körperschaft",
"a11y": "Fehler. Steuergesetzbuch des falschen Gläubigers. Es hat 11 Ziffern, Sie finden es in der Nähe des QR -Code"
}
},
"abortDialog": {
Expand Down
6 changes: 4 additions & 2 deletions locales/en/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -2334,13 +2334,15 @@
"title": "Inserisci il codice avviso",
"subtitle": "Ha 18 cifre, lo trovi vicino al codice QR.",
"validationError": "It has 18 digits, starts with 0, 1 or 3.",
"placeholder": "Codice avviso"
"placeholder": "Codice avviso",
"a11y": "Error. Incorrect notice code. It has 18 digits, you find it near the code QR."
},
"fiscalCode": {
"title": "Inserisci il codice fiscale dell’Ente Creditore",
"subtitle": "Ha 11 cifre, lo trovi vicino al codice QR.",
"validationError": "Enter 11 digits.",
"placeholder": "Codice fiscale Ente Creditore"
"placeholder": "Codice fiscale Ente Creditore",
"a11y": "Error. Tax code of the incorrect creditor. It has 11 digits, you can find it close to the QR code"
}
},
"abortDialog": {
Expand Down
6 changes: 4 additions & 2 deletions locales/it/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -2334,13 +2334,15 @@
"title": "Inserisci il codice avviso",
"subtitle": "Ha 18 cifre, lo trovi vicino al codice QR.",
"validationError": "Ha 18 cifre, inizia con 0, 1 o 3.",
"placeholder": "Codice avviso"
"placeholder": "Codice avviso",
"a11y": "Errore. Codice avviso non corretto. Ha 18 cifre, lo trovi vicino al codice QR."
},
"fiscalCode": {
"title": "Inserisci il codice fiscale dell’Ente Creditore",
"subtitle": "Ha 11 cifre, lo trovi vicino al codice QR.",
"validationError": "Inserisci 11 cifre.",
"placeholder": "Codice fiscale Ente Creditore"
"placeholder": "Codice fiscale Ente Creditore",
"a11y": "Errore. Codice fiscale dell’Ente Creditore non corretto. Ha 11 cifre, lo trovi vicino al codice QR"
}
},
"abortDialog": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
AppParamsList,
IOStackNavigationProp
} from "../../../../navigation/params/AppParamsList";
import { setAccessibilityFocus } from "../../../../utils/accessibility";
import { emptyContextualHelp } from "../../../../utils/emptyContextualHelp";
import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender";
import {
Expand All @@ -26,6 +25,7 @@ import {
import * as analytics from "../analytics";
import { usePagoPaPayment } from "../hooks/usePagoPaPayment";
import { PaymentsCheckoutParamsList } from "../navigation/params";
import { TextInputValidationRefProps } from "../types";

export type WalletPaymentInputFiscalCodeScreenNavigationParams = {
paymentNoticeNumber: O.Option<PaymentNoticeNumberFromString>;
Expand All @@ -52,10 +52,9 @@ const WalletPaymentInputFiscalCodeScreen = () => {
fiscalCode: O.none
});

const textInputWrappperRef = useRef<View>(null);
const focusTextInput = () => {
setAccessibilityFocus(textInputWrappperRef);
};
const textInputWrapperRef = useRef<View>(null);

const textInputRef = useRef<TextInputValidationRefProps>(null);

const navigateToTransactionSummary = () => {
pipe(
Expand All @@ -82,7 +81,7 @@ const WalletPaymentInputFiscalCodeScreen = () => {
inputState.fiscalCode,
O.fold(() => {
Keyboard.dismiss();
focusTextInput();
textInputRef.current?.validateInput();
}, navigateToTransactionSummary)
);

Expand Down Expand Up @@ -112,17 +111,23 @@ const WalletPaymentInputFiscalCodeScreen = () => {
}
: undefined
}
ref={textInputWrappperRef}
ref={textInputWrapperRef}
includeContentMargins
>
<TextInputValidation
testID="fiscalCodeInput"
validationMode="onContinue"
ref={textInputRef}
placeholder={I18n.t("wallet.payment.manual.fiscalCode.placeholder")}
accessibilityLabel={I18n.t(
"wallet.payment.manual.fiscalCode.placeholder"
)}
errorMessage={I18n.t(
"wallet.payment.manual.fiscalCode.validationError"
)}
accessibilityErrorLabel={I18n.t(
"wallet.payment.manual.fiscalCode.a11y"
)}
value={inputState.fiscalCodeText}
icon="fiscalCodeIndividual"
onChangeText={value =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
AppParamsList,
IOStackNavigationProp
} from "../../../../navigation/params/AppParamsList";
import { setAccessibilityFocus } from "../../../../utils/accessibility";
import { emptyContextualHelp } from "../../../../utils/emptyContextualHelp";
import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender";
import {
Expand All @@ -20,6 +19,7 @@ import {
} from "../../common/utils/validation";
import * as analytics from "../analytics";
import { PaymentsCheckoutRoutes } from "../navigation/routes";
import { TextInputValidationRefProps } from "../types";
import { trimAndLimitValue } from "../utils";

type InputState = {
Expand Down Expand Up @@ -50,21 +50,17 @@ const WalletPaymentInputNoticeNumberScreen = () => {
inputState.noticeNumber,
O.fold(() => {
Keyboard.dismiss();
focusTextInput();
textInputRef.current?.validateInput();
}, navigateToFiscalCodeInput)
);

const focusTextInput = () => {
if (O.isNone(inputState.noticeNumber)) {
setAccessibilityFocus(textInputWrappperRef);
}
};

useOnFirstRender(() => {
analytics.trackPaymentNoticeDataEntry();
});

const textInputWrappperRef = useRef<View>(null);
const textInputWrapperRef = useRef<View>(null);

const textInputRef = useRef<TextInputValidationRefProps>(null);

return (
<>
Expand All @@ -89,16 +85,22 @@ const WalletPaymentInputNoticeNumberScreen = () => {
: undefined
}
includeContentMargins
ref={textInputWrappperRef}
ref={textInputWrapperRef}
>
<TextInputValidation
testID="noticeNumberInput"
ref={textInputRef}
validationMode="onContinue"
placeholder={I18n.t("wallet.payment.manual.noticeNumber.placeholder")}
accessibilityLabel={I18n.t(
"wallet.payment.manual.noticeNumber.placeholder"
)}
errorMessage={I18n.t(
"wallet.payment.manual.noticeNumber.validationError"
)}
accessibilityErrorLabel={I18n.t(
"wallet.payment.manual.noticeNumber.a11y"
)}
value={inputState.noticeNumberText}
icon="docPaymentCode"
onChangeText={value => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { fireEvent } from "@testing-library/react-native";
import { default as configureMockStore } from "redux-mock-store";
import I18n from "../../../../../i18n";
import { applicationChangeState } from "../../../../../store/actions/application";
import { appReducer } from "../../../../../store/reducers";
import { GlobalState } from "../../../../../store/reducers/types";
import { renderScreenWithNavigationStoreContext } from "../../../../../utils/testWrapper";
import { PaymentsCheckoutRoutes } from "../../navigation/routes";
import { WalletPaymentInputFiscalCodeScreen } from "../WalletPaymentInputFiscalCodeScreen";

const mockNavigation = {
navigate: jest.fn(),
setOptions: jest.fn()
};

jest.mock("@react-navigation/native", () => ({
...jest.requireActual("@react-navigation/native"),
useNavigation: () => mockNavigation
}));

const renderComponent = () => {
const globalState = appReducer(undefined, applicationChangeState("active"));

const mockStore = configureMockStore<GlobalState>();
const store: ReturnType<typeof mockStore> = mockStore({
...globalState
} as GlobalState);

return renderScreenWithNavigationStoreContext<GlobalState>(
WalletPaymentInputFiscalCodeScreen,
PaymentsCheckoutRoutes.PAYMENT_CHECKOUT_INPUT_FISCAL_CODE,
{},
store
);
};

describe("WalletPaymentInputFiscalCodeScreen", () => {
it(`should render the WalletPaymentInputFiscalCodeScreen`, () => {
const renderedComponent = renderComponent();
expect(renderedComponent.toJSON()).toMatchSnapshot();
});

it("should update the state when input changes", () => {
const { getByTestId } = renderComponent();
const input = getByTestId("fiscalCodeInput");

fireEvent.changeText(input, "12345678901");
expect(input.props.value).toBe("12345678901");
});

it("should show an error when input is invalid", () => {
const { getByText, getByTestId } = renderComponent();
const input = getByTestId("fiscalCodeInput");
const button = getByText(I18n.t("global.buttons.continue"));

fireEvent.changeText(input, "invalid");
fireEvent.press(button);

expect(
getByText(I18n.t("wallet.payment.manual.fiscalCode.validationError"))
).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { fireEvent } from "@testing-library/react-native";
import { default as configureMockStore } from "redux-mock-store";
import I18n from "../../../../../i18n";
import { applicationChangeState } from "../../../../../store/actions/application";
import { appReducer } from "../../../../../store/reducers";
import { GlobalState } from "../../../../../store/reducers/types";
import { renderScreenWithNavigationStoreContext } from "../../../../../utils/testWrapper";
import { PaymentsCheckoutRoutes } from "../../navigation/routes";
import { WalletPaymentInputNoticeNumberScreen } from "../WalletPaymentInputNoticeNumberScreen";

const mockNavigation = {
navigate: jest.fn(),
setOptions: jest.fn()
};

jest.mock("@react-navigation/native", () => ({
...jest.requireActual("@react-navigation/native"),
useNavigation: () => mockNavigation
}));

const renderComponent = () => {
const globalState = appReducer(undefined, applicationChangeState("active"));

const mockStore = configureMockStore<GlobalState>();
const store: ReturnType<typeof mockStore> = mockStore({
...globalState
} as GlobalState);

return renderScreenWithNavigationStoreContext<GlobalState>(
WalletPaymentInputNoticeNumberScreen,
PaymentsCheckoutRoutes.PAYMENT_CHECKOUT_INPUT_NOTICE_NUMBER,
{},
store
);
};

describe("WalletPaymentInputNoticeNumberScreen", () => {
it(`should render the WalletPaymentInputNoticeNumberScreen`, () => {
const renderedComponent = renderComponent();
expect(renderedComponent.toJSON()).toMatchSnapshot();
});

it("should update the state when input changes", () => {
const { getByTestId } = renderComponent();
const input = getByTestId("noticeNumberInput");

fireEvent.changeText(input, "123456789012345678");
expect(input.props.value).toBe("123456789012345678");
});

it("should navigate to the next screen when input is valid", () => {
const { getByText, getByTestId } = renderComponent();
const input = getByTestId("noticeNumberInput");
const button = getByText(I18n.t("global.buttons.continue"));

fireEvent.changeText(input, "123456789012345678");
fireEvent.press(button);

expect(mockNavigation.navigate).toHaveBeenCalledWith(
PaymentsCheckoutRoutes.PAYMENT_CHECKOUT_NAVIGATOR,
{
screen: PaymentsCheckoutRoutes.PAYMENT_CHECKOUT_INPUT_FISCAL_CODE,
params: {
paymentNoticeNumber: expect.any(Object)
}
}
);
});

it("should show an error when input is invalid", () => {
const { getByText, getByTestId } = renderComponent();
const input = getByTestId("noticeNumberInput");
const button = getByText(I18n.t("global.buttons.continue"));

fireEvent.changeText(input, "invalid");
fireEvent.press(button);

expect(
getByText(I18n.t("wallet.payment.manual.noticeNumber.validationError"))
).toBeTruthy();
});
});
Loading

0 comments on commit 4f5acaf

Please sign in to comment.