Commit 7f1dae291d for woocommerce

commit 7f1dae291d0f2c70e683d476699966cd1eae44bb
Author: Francesco <frosso@users.noreply.github.com>
Date:   Thu Jan 15 09:00:43 2026 +0100

    fix: address autocomplete sync (#62796)

diff --git a/plugins/woocommerce/changelog/62796-fix-address-autocomplete-sync b/plugins/woocommerce/changelog/62796-fix-address-autocomplete-sync
new file mode 100644
index 0000000000..73ab0eb70f
--- /dev/null
+++ b/plugins/woocommerce/changelog/62796-fix-address-autocomplete-sync
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+fix: ensure address autocomplete is persisted on both billing and shipping forms
\ No newline at end of file
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/address-autocomplete/address-autocomplete.tsx b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/address-autocomplete/address-autocomplete.tsx
index dfedfbc96b..72413f97f7 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/address-autocomplete/address-autocomplete.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/address-autocomplete/address-autocomplete.tsx
@@ -13,6 +13,7 @@ import { cartStore, checkoutStore } from '@woocommerce/block-data';
 import { useDispatch, useSelect } from '@wordpress/data';
 import { useEffect, useState, useRef } from '@wordpress/element';
 import { AddressFormType, getSettingWithCoercion } from '@woocommerce/settings';
+import { useCheckoutAddress } from '@woocommerce/base-context';

 /**
  * Internal dependencies
@@ -37,6 +38,8 @@ export const AddressAutocomplete = ( {
 }: { addressType: AddressFormType; id: string } & ValidatedTextInputProps ) => {
 	// This hook will monitor for changes in country and update the provider accordingly.
 	useUpdatePreferredAutocompleteProvider( addressType );
+
+	const { useShippingAsBilling, useBillingAsShipping } = useCheckoutAddress();
 	const inputRef = useRef< ValidatedTextInputHandle >( null );
 	const observerRef = useRef< MutationObserver | null >( null );
 	const serverProviders = getSettingWithCoercion<
@@ -279,13 +282,17 @@ export const AddressAutocomplete = ( {
 					provider
 						.select( selected.id, country )
 						.then( ( address ) => {
-							const actionToDispatch =
-								addressType === 'shipping'
-									? setShippingAddress
-									: setBillingAddress;
-							actionToDispatch( {
-								...address,
-							} );
+							if ( addressType === 'shipping' ) {
+								setShippingAddress( address );
+								if ( useShippingAsBilling ) {
+									setBillingAddress( address );
+								}
+							} else {
+								setBillingAddress( address );
+								if ( useBillingAsShipping ) {
+									setShippingAddress( address );
+								}
+							}
 						} )
 						.finally( () => {
 							// Clear suggestions.
@@ -312,13 +319,17 @@ export const AddressAutocomplete = ( {
 			}, 1000 );
 			try {
 				const address = await provider.select( suggestionId, country );
-				const actionToDispatch =
-					addressType === 'shipping'
-						? setShippingAddress
-						: setBillingAddress;
-				actionToDispatch( {
-					...address,
-				} );
+				if ( addressType === 'shipping' ) {
+					setShippingAddress( address );
+					if ( useShippingAsBilling ) {
+						setBillingAddress( address );
+					}
+				} else {
+					setBillingAddress( address );
+					if ( useBillingAsShipping ) {
+						setShippingAddress( address );
+					}
+				}
 			} finally {
 				// Clear suggestions.
 				setIsSettingAddress( false );
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/address-autocomplete/test/integration.tsx b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/address-autocomplete/test/integration.tsx
index 6dc79dfb76..28e435e549 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/address-autocomplete/test/integration.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/address-autocomplete/test/integration.tsx
@@ -12,6 +12,13 @@ import type { StoreDescriptor } from '@wordpress/data';
  * Internal dependencies
  */
 import { AddressAutocomplete } from '../address-autocomplete';
+
+const mockUseCheckoutAddress = jest.fn();
+jest.mock( '@woocommerce/base-context', () => ( {
+	...jest.requireActual( '@woocommerce/base-context' ),
+	useCheckoutAddress: () => mockUseCheckoutAddress(),
+} ) );
+
 jest.mock( '@wordpress/data', () => ( {
 	__esModule: true,
 	...jest.requireActual( '@wordpress/data' ),
@@ -78,6 +85,11 @@ jest.mock( '@woocommerce/settings', () => ( {
 } ) );
 describe( 'Suggestions - when rendered in AddressAutocomplete component', () => {
 	beforeAll( () => {
+		mockUseCheckoutAddress.mockReturnValue( {
+			useShippingAsBilling: false,
+			useBillingAsShipping: false,
+		} );
+
 		const genericProvider = {
 			id: 'generic-provider',
 			// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -124,6 +136,14 @@ describe( 'Suggestions - when rendered in AddressAutocomplete component', () =>
 			},
 		};
 	} );
+
+	afterEach( () => {
+		mockUseCheckoutAddress.mockReturnValue( {
+			useShippingAsBilling: false,
+			useBillingAsShipping: false,
+		} );
+	} );
+
 	it( 'Shows suggestions when provider returns results', async () => {
 		const Component = () => {
 			const [ value, setValue ] = useState( '' );
@@ -1222,4 +1242,236 @@ describe( 'Suggestions - when rendered in AddressAutocomplete component', () =>
 			expect( screen.queryByRole( 'listbox' ) ).not.toBeInTheDocument();
 		} );
 	} );
+
+	describe( 'Address sync behavior', () => {
+		beforeEach( () => {
+			const genericProvider = {
+				id: 'generic-provider',
+				canSearch: () => true,
+				search: async () => [
+					{
+						label: '123 Example St, Berlin, Germany',
+						id: '1',
+						matchedSubstrings: [ { length: 3, offset: 0 } ],
+					},
+					{
+						label: '456 Sample Rd, Munich, Germany',
+						id: '2',
+						matchedSubstrings: [ { length: 3, offset: 0 } ],
+					},
+				],
+				select: async () => ( {
+					address_1: '123 Example St',
+					address_2: 'Address 2',
+					city: 'Berlin',
+					state: 'BE',
+					postcode: '10115',
+					country: 'DE',
+				} ),
+			};
+
+			window.wc.addressAutocomplete.providers[ 'generic-provider' ] =
+				genericProvider;
+			window.wc.addressAutocomplete.activeProvider.shipping =
+				genericProvider;
+			window.wc.addressAutocomplete.activeProvider.billing =
+				genericProvider;
+		} );
+
+		it.each( [ 'Enter key', 'click' ] )(
+			'Shipping autocomplete syncs to billing when useShippingAsBilling is true (%s)',
+			async ( interactionMethod ) => {
+				const mockSetBillingAddress = jest.fn();
+				const mockSetShippingAddress = jest.fn();
+
+				mockUseCheckoutAddress.mockReturnValue( {
+					useShippingAsBilling: true,
+					useBillingAsShipping: false,
+				} );
+
+				wpData.useDispatch.mockImplementation(
+					( store: StoreDescriptor | string ) => {
+						if (
+							store === cartStore ||
+							store === 'wc/store/cart'
+						) {
+							return {
+								...jest
+									.requireActual( '@wordpress/data' )
+									.useDispatch( store ),
+								setShippingAddress: mockSetShippingAddress,
+								setBillingAddress: mockSetBillingAddress,
+							};
+						}
+						return jest
+							.requireActual( '@wordpress/data' )
+							.useDispatch( store );
+					}
+				);
+
+				const Component = () => {
+					const [ value, setValue ] = useState( '' );
+					return (
+						<AddressAutocomplete
+							addressType="shipping"
+							id="shipping-test"
+							label="Address 1"
+							onChange={ setValue }
+							value={ value }
+						/>
+					);
+				};
+				render( <Component /> );
+				const input = screen.getByLabelText( 'Address 1' );
+
+				await act( async () => {
+					await userEvent.type( input, '1234' );
+				} );
+
+				await waitFor(
+					() => {
+						expect(
+							screen.getByRole( 'listbox' )
+						).toBeInTheDocument();
+					},
+					{ timeout: 3000 }
+				);
+
+				if ( interactionMethod === 'Enter key' ) {
+					await act( async () => {
+						await userEvent.type( input, '{arrowdown}' );
+					} );
+					await act( async () => {
+						await userEvent.type( input, '{Enter}' );
+					} );
+				} else {
+					const options = screen.getAllByRole( 'option' );
+					await act( async () => {
+						await userEvent.click( options[ 0 ] );
+					} );
+				}
+
+				await waitFor(
+					() => {
+						expect( mockSetShippingAddress ).toHaveBeenCalled();
+					},
+					{ timeout: 3000 }
+				);
+
+				const expectedAddress = {
+					address_1: '123 Example St',
+					address_2: 'Address 2',
+					city: 'Berlin',
+					state: 'BE',
+					postcode: '10115',
+					country: 'DE',
+				};
+
+				expect( mockSetShippingAddress ).toHaveBeenCalledWith(
+					expectedAddress
+				);
+				expect( mockSetBillingAddress ).toHaveBeenCalledWith(
+					expectedAddress
+				);
+			}
+		);
+
+		it.each( [ 'Enter key', 'click' ] )(
+			'Billing autocomplete syncs to shipping when useBillingAsShipping is true (%s)',
+			async ( interactionMethod ) => {
+				const mockSetBillingAddress = jest.fn();
+				const mockSetShippingAddress = jest.fn();
+
+				mockUseCheckoutAddress.mockReturnValue( {
+					useShippingAsBilling: false,
+					useBillingAsShipping: true,
+				} );
+
+				wpData.useDispatch.mockImplementation(
+					( store: StoreDescriptor | string ) => {
+						if (
+							store === cartStore ||
+							store === 'wc/store/cart'
+						) {
+							return {
+								...jest
+									.requireActual( '@wordpress/data' )
+									.useDispatch( store ),
+								setShippingAddress: mockSetShippingAddress,
+								setBillingAddress: mockSetBillingAddress,
+							};
+						}
+						return jest
+							.requireActual( '@wordpress/data' )
+							.useDispatch( store );
+					}
+				);
+
+				const Component = () => {
+					const [ value, setValue ] = useState( '' );
+					return (
+						<AddressAutocomplete
+							addressType="billing"
+							id="billing-test"
+							label="Address 1"
+							onChange={ setValue }
+							value={ value }
+						/>
+					);
+				};
+				render( <Component /> );
+				const input = screen.getByLabelText( 'Address 1' );
+
+				await act( async () => {
+					await userEvent.type( input, '1234' );
+				} );
+
+				await waitFor(
+					() => {
+						expect(
+							screen.getByRole( 'listbox' )
+						).toBeInTheDocument();
+					},
+					{ timeout: 3000 }
+				);
+
+				if ( interactionMethod === 'Enter key' ) {
+					await act( async () => {
+						await userEvent.type( input, '{arrowdown}' );
+					} );
+					await act( async () => {
+						await userEvent.type( input, '{Enter}' );
+					} );
+				} else {
+					const options = screen.getAllByRole( 'option' );
+					await act( async () => {
+						await userEvent.click( options[ 0 ] );
+					} );
+				}
+
+				await waitFor(
+					() => {
+						expect( mockSetBillingAddress ).toHaveBeenCalled();
+					},
+					{ timeout: 3000 }
+				);
+
+				const expectedAddress = {
+					address_1: '123 Example St',
+					address_2: 'Address 2',
+					city: 'Berlin',
+					state: 'BE',
+					postcode: '10115',
+					country: 'DE',
+				};
+
+				expect( mockSetBillingAddress ).toHaveBeenCalledWith(
+					expectedAddress
+				);
+				expect( mockSetShippingAddress ).toHaveBeenCalledWith(
+					expectedAddress
+				);
+			}
+		);
+	} );
 } );