@@ -22,6 +22,8 @@ const mockAuthHelperFunctions = {
2222
2323const mockUpdateCustomerForBasket = { mutateAsync : jest . fn ( ) }
2424const mockTransferBasket = { mutate : jest . fn ( ) , mutateAsync : jest . fn ( ) }
25+ const mockUpdateBillingAddressForBasket = { mutateAsync : jest . fn ( ) }
26+ const mockUpdateCustomer = { mutateAsync : jest . fn ( ) }
2527
2628jest . mock ( '@salesforce/commerce-sdk-react' , ( ) => {
2729 const originalModule = jest . requireActual ( '@salesforce/commerce-sdk-react' )
@@ -34,10 +36,12 @@ jest.mock('@salesforce/commerce-sdk-react', () => {
3436 useShopperBasketsMutation : jest . fn ( ) . mockImplementation ( ( mutationType ) => {
3537 if ( mutationType === 'updateCustomerForBasket' ) return mockUpdateCustomerForBasket
3638 if ( mutationType === 'transferBasket' ) return mockTransferBasket
39+ if ( mutationType === 'updateBillingAddressForBasket' )
40+ return mockUpdateBillingAddressForBasket
3741 return { mutate : jest . fn ( ) }
3842 } ) ,
3943 useShopperCustomersMutation : jest . fn ( ) . mockImplementation ( ( mutationType ) => {
40- if ( mutationType === 'updateCustomer' ) return { mutateAsync : jest . fn ( ) }
44+ if ( mutationType === 'updateCustomer' ) return mockUpdateCustomer
4145 return { mutateAsync : jest . fn ( ) }
4246 } )
4347 }
@@ -62,13 +66,14 @@ jest.mock('@salesforce/retail-react-app/app/hooks/use-current-basket', () => ({
6266 useCurrentBasket : ( ...args ) => mockUseCurrentBasket ( ...args )
6367} ) )
6468
69+ const mockUseCurrentCustomer = jest . fn ( ( ) => ( {
70+ data : {
71+ email : null ,
72+ isRegistered : false
73+ }
74+ } ) )
6575jest . mock ( '@salesforce/retail-react-app/app/hooks/use-current-customer' , ( ) => ( {
66- useCurrentCustomer : ( ) => ( {
67- data : {
68- email : null ,
69- isRegistered : false
70- }
71- } )
76+ useCurrentCustomer : ( ...args ) => mockUseCurrentCustomer ( ...args )
7277} ) )
7378
7479const mockSetContactPhone = jest . fn ( )
@@ -134,11 +139,25 @@ beforeEach(() => {
134139 basketId : 'test-basket-id' ,
135140 customerInfo : { email : null } ,
136141 shipments : [ { shipmentId : 'shipment-1' , shipmentType : 'delivery' } ] ,
137- productItems : [ { productId : 'product-1' , shipmentId : 'shipment-1' } ]
142+ productItems : [ { productId : 'product-1' , shipmentId : 'shipment-1' } ] ,
143+ billingAddress : null
138144 } ,
139145 derivedData : { hasBasket : true , totalItems : 1 } ,
140146 refetch : jest . fn ( )
141147 } )
148+ // Reset billing address mutation mock
149+ mockUpdateBillingAddressForBasket . mutateAsync . mockResolvedValue ( { } )
150+ // Reset customer mock to default
151+ mockUseCurrentCustomer . mockReturnValue ( {
152+ data : {
153+ email : null ,
154+ isRegistered : false
155+ }
156+ } )
157+ // Reset update customer mock
158+ mockUpdateCustomer . mutateAsync . mockResolvedValue ( { } )
159+ // Reset useCustomerType mock to ensure phone input is not disabled
160+ useCustomerType . mockReturnValue ( { isRegistered : false } )
142161} )
143162
144163afterEach ( ( ) => { } )
@@ -327,12 +346,11 @@ describe('ContactInfo Component', () => {
327346 await user . type ( emailInput , '{enter}' )
328347
329348 // The validation should prevent submission and show error
330- // Since the form doesn't have a visible submit button in this state,
331- // we test that the email field validation works on blur
332- await user . click ( emailInput )
333- await user . tab ( )
334-
335- expect ( screen . getAllByText ( 'Please enter your email address.' ) . length ) . toBeGreaterThan ( 0 )
349+ await waitFor ( ( ) => {
350+ expect ( screen . getAllByText ( 'Please enter your email address.' ) . length ) . toBeGreaterThan (
351+ 0
352+ )
353+ } )
336354 } )
337355
338356 test ( 'validates email format on form submission' , async ( ) => {
@@ -699,4 +717,115 @@ describe('ContactInfo Component', () => {
699717 } )
700718 // Updating basket email may occur asynchronously or be skipped if unchanged; don't hard-require it here
701719 } )
720+
721+ test ( 'defaults phone number from basket billing address when customer phone is not available' , ( ) => {
722+ // Mock basket with billing address phone
723+ mockUseCurrentBasket . mockReturnValue ( {
724+ data : {
725+ basketId : 'test-basket-id' ,
726+ customerInfo : { email : null } ,
727+ shipments : [ { shipmentId : 'shipment-1' , shipmentType : 'delivery' } ] ,
728+ productItems : [ { productId : 'product-1' , shipmentId : 'shipment-1' } ] ,
729+ billingAddress : { phone : '(555) 123-4567' }
730+ } ,
731+ derivedData : { hasBasket : true , totalItems : 1 } ,
732+ refetch : jest . fn ( )
733+ } )
734+
735+ renderWithProviders ( < ContactInfo /> )
736+
737+ const phoneInput = screen . getByLabelText ( 'Phone' )
738+ expect ( phoneInput . value ) . toBe ( '(555) 123-4567' )
739+ } )
740+
741+ test ( 'saves phone number to billing address when guest checks out via "Checkout as Guest" button' , async ( ) => {
742+ // Mock successful OTP authorization to open modal
743+ mockAuthHelperFunctions [ AuthHelpers . AuthorizePasswordless ] . mutateAsync . mockResolvedValue ( { } )
744+ mockUpdateCustomerForBasket . mutateAsync . mockResolvedValue ( { } )
745+ mockUpdateBillingAddressForBasket . mutateAsync . mockResolvedValue ( { } )
746+
747+ const { user} = renderWithProviders ( < ContactInfo /> )
748+
749+ const emailInput = screen . getByLabelText ( 'Email' )
750+ const phoneInput = screen . getByLabelText ( 'Phone' )
751+
752+ // Enter phone first - use fireEvent to ensure value is set
753+ fireEvent . change ( phoneInput , { target : { value : '(727) 555-1234' } } )
754+
755+ // Enter email and wait for OTP modal to open
756+ await user . type ( emailInput , validEmail )
757+ fireEvent . change ( emailInput , { target : { value : validEmail } } )
758+ fireEvent . blur ( emailInput )
759+
760+ // Wait for OTP modal to open
761+ await screen . findByTestId ( 'otp-verify' )
762+
763+ // Click "Checkout as a guest" button
764+ await user . click ( screen . getByText ( / C h e c k o u t a s a g u e s t / i) )
765+
766+ await waitFor ( ( ) => {
767+ expect ( mockUpdateBillingAddressForBasket . mutateAsync ) . toHaveBeenCalled ( )
768+ const callArgs = mockUpdateBillingAddressForBasket . mutateAsync . mock . calls [ 0 ] ?. [ 0 ]
769+ expect ( callArgs ?. parameters ) . toMatchObject ( { basketId : 'test-basket-id' } )
770+ expect ( callArgs ?. body ?. phone ) . toMatch ( / 7 2 7 / )
771+ } )
772+ } )
773+
774+ test ( 'uses phone from billing address when persisting to customer profile after OTP verification' , async ( ) => {
775+ // Mock basket with billing address phone
776+ const billingPhone = '(555) 123-4567'
777+ mockUseCurrentBasket . mockReturnValue ( {
778+ data : {
779+ basketId : 'test-basket-id' ,
780+ customerInfo : { email : null } ,
781+ shipments : [ { shipmentId : 'shipment-1' , shipmentType : 'delivery' } ] ,
782+ productItems : [ { productId : 'product-1' , shipmentId : 'shipment-1' } ] ,
783+ billingAddress : { phone : billingPhone }
784+ } ,
785+ derivedData : { hasBasket : true , totalItems : 1 } ,
786+ refetch : jest . fn ( ) . mockResolvedValue ( {
787+ data : {
788+ basketId : 'test-basket-id' ,
789+ billingAddress : { phone : billingPhone }
790+ }
791+ } )
792+ } )
793+
794+ // Mock OTP verification flow
795+ mockAuthHelperFunctions [ AuthHelpers . AuthorizePasswordless ] . mutateAsync . mockResolvedValue ( { } )
796+ mockAuthHelperFunctions [ AuthHelpers . LoginPasswordlessUser ] . mutateAsync . mockResolvedValue ( { } )
797+ mockTransferBasket . mutateAsync . mockResolvedValue ( { basketId : 'test-basket-id' } )
798+ mockUpdateCustomerForBasket . mutateAsync . mockResolvedValue ( { } )
799+
800+ // Mock customer with customerId after login - update mock to return customer with ID
801+ mockUseCurrentCustomer . mockReturnValue ( {
802+ data : {
803+ email : validEmail ,
804+ isRegistered : true ,
805+ customerId : 'customer-123'
806+ }
807+ } )
808+
809+ const { user} = renderWithProviders ( < ContactInfo /> )
810+
811+ const emailInput = screen . getByLabelText ( 'Email' )
812+ await user . type ( emailInput , validEmail )
813+ fireEvent . change ( emailInput , { target : { value : validEmail } } )
814+ fireEvent . blur ( emailInput )
815+
816+ // Wait for OTP modal and verify
817+ await screen . findByTestId ( 'otp-verify' )
818+ await user . click ( screen . getByTestId ( 'otp-verify' ) )
819+
820+ // Simulate auth state change to registered
821+ useCustomerType . mockReturnValue ( { isRegistered : true } )
822+
823+ await waitFor ( ( ) => {
824+ // Verify updateCustomer was called with phone from billing address
825+ expect ( mockUpdateCustomer . mutateAsync ) . toHaveBeenCalledWith ( {
826+ parameters : { customerId : 'customer-123' } ,
827+ body : { phoneHome : billingPhone }
828+ } )
829+ } )
830+ } )
702831} )
0 commit comments