From 293df235c22a30526a468027fd3cba4ffa6d1d1c Mon Sep 17 00:00:00 2001 From: Eason Su Date: Fri, 28 Jul 2023 20:00:16 +0800 Subject: [PATCH 1/2] Add JS tests for the `PhoneNumberCard` component. --- .../phone-number-card.test.js | 262 ++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 js/src/components/contact-information/phone-number-card/phone-number-card.test.js diff --git a/js/src/components/contact-information/phone-number-card/phone-number-card.test.js b/js/src/components/contact-information/phone-number-card/phone-number-card.test.js new file mode 100644 index 0000000000..cfe36f7fb1 --- /dev/null +++ b/js/src/components/contact-information/phone-number-card/phone-number-card.test.js @@ -0,0 +1,262 @@ +/** + * External dependencies + */ +import '@testing-library/jest-dom'; +import { screen, render, act } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { parsePhoneNumberFromString as parsePhoneNumber } from 'libphonenumber-js'; + +/** + * Internal dependencies + */ +import PhoneNumberCard from './phone-number-card'; + +jest.mock( '@woocommerce/components', () => ( { + ...jest.requireActual( '@woocommerce/components' ), + Spinner: jest + .fn( () =>
) + .mockName( 'Spinner' ), +} ) ); + +jest.mock( '.~/hooks/useCountryKeyNameMap', () => + jest + .fn() + .mockReturnValue( { US: 'United States' } ) + .mockName( 'useCountryKeyNameMap' ) +); + +jest.mock( '.~/data', () => ( { + ...jest.requireActual( '.~/data' ), + useAppDispatch() { + return { + requestPhoneVerificationCode: jest + .fn( () => Promise.resolve( { verificationId: 123 } ) ) + .mockName( 'requestPhoneVerificationCode' ), + + verifyPhoneNumber: jest + .fn( () => Promise.resolve( {} ) ) + .mockName( 'verifyPhoneNumber' ), + }; + }, +} ) ); + +describe( 'PhoneNumberCard', () => { + let phoneData; + let phoneNumber; + + function mockPhoneData( fullPhoneNumber ) { + const parsed = parsePhoneNumber( fullPhoneNumber ); + + phoneData = { + ...parsed, + display: parsed.formatInternational(), + isValid: parsed.isValid(), + }; + + phoneNumber = { + ...phoneNumber, + data: phoneData, + }; + } + + function mockVerified( isVerified ) { + phoneNumber = { + ...phoneNumber, + data: { + ...phoneData, + isVerified, + }, + }; + } + + function mockLoaded( loaded ) { + phoneNumber = { + ...phoneNumber, + loaded, + }; + } + + beforeEach( () => { + mockPhoneData( '+12133734253' ); + mockVerified( true ); + mockLoaded( true ); + } ); + + it( 'When not yet loaded, should render a loading spinner', () => { + mockLoaded( false ); + + render( ); + + const spinner = screen.getByRole( 'status', { name: 'spinner' } ); + const display = screen.queryByText( phoneData.display ); + const button = screen.queryByRole( 'button' ); + + expect( spinner ).toBeInTheDocument(); + expect( display ).not.toBeInTheDocument(); + expect( button ).not.toBeInTheDocument(); + } ); + + it( 'When `initEditing` is not specified, should render in display mode after loading a verified phone number', () => { + mockLoaded( false ); + const { rerender } = render( + + ); + + mockLoaded( true ); + rerender( ); + + const button = screen.getByRole( 'button', { name: 'Edit' } ); + + expect( button ).toBeInTheDocument(); + } ); + + it( 'When `initEditing` is not specified, should render in editing mode after loading an unverified phone number', () => { + mockLoaded( false ); + const { rerender } = render( + + ); + + mockVerified( false ); + mockLoaded( true ); + rerender( ); + + const button = screen.getByRole( 'button', { name: /Send/ } ); + + expect( button ).toBeInTheDocument(); + } ); + + it( 'When `initEditing` is true, should directly render in editing mode', () => { + render( ); + + const button = screen.getByRole( 'button', { name: /Send/ } ); + + expect( button ).toBeInTheDocument(); + } ); + + it( 'When `initEditing` is false, should render in display mode regardless of verified or not', () => { + // Start with a verified and valid phone number + const { rerender } = render( + + ); + + expect( screen.getByText( phoneData.display ) ).toBeInTheDocument(); + + // Set to an unverified and invalid phone number + mockPhoneData( '+121' ); + mockVerified( false ); + + rerender( + + ); + + expect( screen.getByText( phoneData.display ) ).toBeInTheDocument(); + } ); + + it( 'When the phone number is loaded but not yet verified, should directly render in editing mode', () => { + mockVerified( false ); + render( ); + + const button = screen.getByRole( 'button', { name: /Send/ } ); + + expect( button ).toBeInTheDocument(); + } ); + + it( 'When `showValidation` is true and the phone number is not yet verified, should show a validation error text', () => { + const text = 'A verified phone number is required.'; + mockVerified( false ); + + const { rerender } = render( + + ); + + expect( screen.queryByText( text ) ).not.toBeInTheDocument(); + + rerender( + + ); + + expect( screen.getByText( text ) ).toBeInTheDocument(); + } ); + + it( 'When `onEditClick` is specified and the Edit button is clicked, should callback `onEditClick`', () => { + const onEditClick = jest.fn(); + render( + + ); + + const button = screen.getByRole( 'button', { name: 'Edit' } ); + + expect( button ).toBeInTheDocument(); + expect( onEditClick ).toHaveBeenCalledTimes( 0 ); + + userEvent.click( button ); + + expect( onEditClick ).toHaveBeenCalledTimes( 1 ); + } ); + + describe( 'Should callback `onPhoneNumberVerified`', () => { + it( 'When `initEditing` is not specified and loaded phone number has been verified', () => { + const onPhoneNumberVerified = jest.fn(); + + render( + + ); + + expect( onPhoneNumberVerified ).toHaveBeenCalledTimes( 1 ); + } ); + + it( 'When `initEditing` is false and loaded phone number has been verified', () => { + const onPhoneNumberVerified = jest.fn(); + render( + + ); + + expect( onPhoneNumberVerified ).toHaveBeenCalledTimes( 1 ); + } ); + + it( 'When an unverified phone number is getting verified', async () => { + const onPhoneNumberVerified = jest.fn(); + mockVerified( false ); + render( + + ); + + expect( onPhoneNumberVerified ).toHaveBeenCalledTimes( 0 ); + + await act( async () => { + const button = screen.getByRole( 'button', { name: /Send/ } ); + userEvent.click( button ); + } ); + + screen.getAllByRole( 'textbox' ).forEach( ( codeInput, i ) => { + userEvent.type( codeInput, i.toString() ); + } ); + + await act( async () => { + const button = screen.getByRole( 'button', { name: /Verify/ } ); + userEvent.click( button ); + } ); + + expect( onPhoneNumberVerified ).toHaveBeenCalledTimes( 1 ); + } ); + } ); +} ); From ca7ce02bbb5aa9e407dfe26ac47ecff3be8344a9 Mon Sep 17 00:00:00 2001 From: Eason Su Date: Mon, 31 Jul 2023 11:42:47 +0800 Subject: [PATCH 2/2] Change "display mode" to "non-editing mode" in the description of `PhoneNumberCard` tests. Address: https://github.com/woocommerce/google-listings-and-ads/pull/2027#discussion_r1277807856 --- .../phone-number-card/phone-number-card.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/src/components/contact-information/phone-number-card/phone-number-card.test.js b/js/src/components/contact-information/phone-number-card/phone-number-card.test.js index cfe36f7fb1..ed8d9d6f29 100644 --- a/js/src/components/contact-information/phone-number-card/phone-number-card.test.js +++ b/js/src/components/contact-information/phone-number-card/phone-number-card.test.js @@ -96,7 +96,7 @@ describe( 'PhoneNumberCard', () => { expect( button ).not.toBeInTheDocument(); } ); - it( 'When `initEditing` is not specified, should render in display mode after loading a verified phone number', () => { + it( 'When `initEditing` is not specified, should render in non-editing mode after loading a verified phone number', () => { mockLoaded( false ); const { rerender } = render( @@ -133,7 +133,7 @@ describe( 'PhoneNumberCard', () => { expect( button ).toBeInTheDocument(); } ); - it( 'When `initEditing` is false, should render in display mode regardless of verified or not', () => { + it( 'When `initEditing` is false, should render in non-editing mode regardless of verified or not', () => { // Start with a verified and valid phone number const { rerender } = render(