@@ -4,6 +4,7 @@ import { DeliveryMethods } from '@/components/checkout/delivery/delivery-methods
44import { checkoutQueryKeys } from '@/components/checkout/utils/query-keys' ;
55import {
66 advanceCheckoutDebounce ,
7+ buildDraftOrder ,
78 buildShippingAddress ,
89 clearOperations ,
910 fillShippingAddress ,
@@ -17,7 +18,26 @@ import {
1718 waitForCheckoutReady ,
1819 waitForOperation ,
1920} from './checkout-test-env' ;
20- import { getLastUpdateInput } from './checkout-test-fixtures' ;
21+ import {
22+ getLastConfirmInput ,
23+ getLastUpdateInput ,
24+ } from './checkout-test-fixtures' ;
25+
26+ function offlinePaymentMethods ( ) {
27+ return {
28+ card : null as never ,
29+ offline : {
30+ processor : 'offline' ,
31+ checkoutTypes : [ 'standard' ] ,
32+ } ,
33+ } ;
34+ }
35+
36+ async function waitForDeliveryMethodEnabled ( name : RegExp ) {
37+ await waitFor ( ( ) => {
38+ expect ( screen . getByRole ( 'radio' , { name } ) ) . not . toBeDisabled ( ) ;
39+ } ) ;
40+ }
2141
2242describe ( 'Checkout draft-order field sync' , ( ) => {
2343 it ( 'syncs contact email to both shipping and billing' , async ( ) => {
@@ -96,6 +116,247 @@ describe('Checkout draft-order field sync', () => {
96116 expect ( getOperations ( 'DraftOrderShippingRates' ) ) . toHaveLength ( 0 ) ;
97117 } ) ;
98118
119+ it ( 'keeps pickup selected when a draft-order refetch still looks like shipping' , async ( ) => {
120+ const { user, queryClient, session } = renderCheckout ( {
121+ draftOrderOverrides : {
122+ lineItems : [ { fulfillmentMode : DeliveryMethods . SHIP } ] ,
123+ } ,
124+ } ) ;
125+ await waitForCheckoutReady ( ) ;
126+ await waitForDeliveryMethodEnabled ( / l o c a l p i c k u p / i) ;
127+
128+ await user . click ( screen . getByRole ( 'radio' , { name : / l o c a l p i c k u p / i } ) ) ;
129+ await waitFor ( ( ) => {
130+ expect (
131+ screen . getByRole ( 'radio' , { name : / l o c a l p i c k u p / i } )
132+ ) . toBeChecked ( ) ;
133+ } ) ;
134+
135+ queryClient . setQueryData ( checkoutQueryKeys . draftOrder ( session . id ) , {
136+ checkoutSession : {
137+ ...session ,
138+ draftOrder : buildDraftOrder ( {
139+ lineItems : [ { fulfillmentMode : DeliveryMethods . SHIP } ] ,
140+ } ) ,
141+ } ,
142+ } ) ;
143+ await flushPromises ( ) ;
144+
145+ await waitFor ( ( ) => {
146+ expect (
147+ screen . getByRole ( 'radio' , { name : / l o c a l p i c k u p / i } )
148+ ) . toBeChecked ( ) ;
149+ expect (
150+ screen . getByRole ( 'radio' , { name : / s h i p p i n g / i } )
151+ ) . not . toBeChecked ( ) ;
152+ } ) ;
153+ } ) ;
154+
155+ it ( 'keeps shipping selected when a stale pickup refetch arrives during delivery-method switching' , async ( ) => {
156+ const stalePickupOrder = buildDraftOrder ( {
157+ lineItems : [ { fulfillmentMode : DeliveryMethods . PICKUP } ] ,
158+ shippingLines : [ ] ,
159+ } ) ;
160+ const { user, queryClient, session } = renderCheckout ( {
161+ draftOrder : stalePickupOrder ,
162+ } ) ;
163+ await waitForCheckoutReady ( ) ;
164+ await waitForDeliveryMethodEnabled ( / s h i p p i n g / i) ;
165+
166+ await user . click ( screen . getByRole ( 'radio' , { name : / s h i p p i n g / i } ) ) ;
167+ queryClient . setQueryData ( checkoutQueryKeys . draftOrder ( session . id ) , {
168+ checkoutSession : {
169+ ...session ,
170+ draftOrder : stalePickupOrder ,
171+ } ,
172+ } ) ;
173+ await flushPromises ( ) ;
174+
175+ await waitFor ( ( ) => {
176+ expect ( screen . getByRole ( 'radio' , { name : / s h i p p i n g / i } ) ) . toBeChecked ( ) ;
177+ expect (
178+ screen . getByRole ( 'radio' , { name : / l o c a l p i c k u p / i } )
179+ ) . not . toBeChecked ( ) ;
180+ } ) ;
181+ } ) ;
182+
183+ it ( 'defaults to pickup when shipping is disabled and the order-derived method is shipping' , async ( ) => {
184+ renderCheckout ( {
185+ draftOrderOverrides : {
186+ lineItems : [ { fulfillmentMode : DeliveryMethods . SHIP } ] ,
187+ } ,
188+ sessionOverrides : {
189+ enableShipping : false ,
190+ enableLocalPickup : true ,
191+ } ,
192+ } ) ;
193+ await waitForCheckoutReady ( ) ;
194+
195+ expect (
196+ screen . queryByRole ( 'radio' , { name : / s h i p p i n g / i } )
197+ ) . not . toBeInTheDocument ( ) ;
198+ expect ( document . body ) . toHaveTextContent ( / l o c a l p i c k u p / i) ;
199+ } ) ;
200+
201+ it ( 'defaults to shipping when pickup is disabled and the order-derived method is pickup' , async ( ) => {
202+ renderCheckout ( {
203+ draftOrderOverrides : {
204+ lineItems : [ { fulfillmentMode : DeliveryMethods . PICKUP } ] ,
205+ } ,
206+ sessionOverrides : {
207+ enableShipping : true ,
208+ enableLocalPickup : false ,
209+ } ,
210+ } ) ;
211+ await waitForCheckoutReady ( ) ;
212+
213+ expect (
214+ screen . queryByRole ( 'radio' , { name : / l o c a l p i c k u p / i } )
215+ ) . not . toBeInTheDocument ( ) ;
216+ expect ( document . body ) . toHaveTextContent ( / s h i p p i n g / i) ;
217+ } ) ;
218+
219+ it ( 'keeps an explicit delivery selection across mixed-fulfillment refetches' , async ( ) => {
220+ const mixedOrder = buildDraftOrder ( {
221+ lineItems : [
222+ { id : 'ship-item' , fulfillmentMode : DeliveryMethods . SHIP } ,
223+ { id : 'pickup-item' , fulfillmentMode : DeliveryMethods . PICKUP } ,
224+ ] ,
225+ } ) ;
226+ const { user, queryClient, session } = renderCheckout ( {
227+ draftOrder : mixedOrder ,
228+ } ) ;
229+ await waitForCheckoutReady ( ) ;
230+ await waitForDeliveryMethodEnabled ( / l o c a l p i c k u p / i) ;
231+
232+ await user . click ( screen . getByRole ( 'radio' , { name : / l o c a l p i c k u p / i } ) ) ;
233+ await waitFor ( ( ) => {
234+ expect (
235+ screen . getByRole ( 'radio' , { name : / l o c a l p i c k u p / i } )
236+ ) . toBeChecked ( ) ;
237+ } ) ;
238+
239+ queryClient . setQueryData ( checkoutQueryKeys . draftOrder ( session . id ) , {
240+ checkoutSession : {
241+ ...session ,
242+ draftOrder : mixedOrder ,
243+ } ,
244+ } ) ;
245+ await flushPromises ( ) ;
246+
247+ await waitFor ( ( ) => {
248+ expect (
249+ screen . getByRole ( 'radio' , { name : / l o c a l p i c k u p / i } )
250+ ) . toBeChecked ( ) ;
251+ expect (
252+ screen . getByRole ( 'radio' , { name : / s h i p p i n g / i } )
253+ ) . not . toBeChecked ( ) ;
254+ } ) ;
255+ } ) ;
256+
257+ it ( 'keeps shipping selected when a shipping address sync refetches a prior pickup order' , async ( ) => {
258+ const { user } = renderCheckout ( {
259+ draftOrderOverrides : {
260+ lineItems : [ { fulfillmentMode : DeliveryMethods . PICKUP } ] ,
261+ shippingLines : [ ] ,
262+ shipping : {
263+ address : buildShippingAddress ( {
264+ addressLine1 : '' ,
265+ addressLine2 : '' ,
266+ adminArea1 : 'GA' ,
267+ adminArea2 : '' ,
268+ postalCode : '' ,
269+ countryCode : 'US' ,
270+ } ) ,
271+ } ,
272+ } ,
273+ } ) ;
274+ await waitForCheckoutReady ( ) ;
275+ await waitForOperation ( 'ApplyCheckoutSessionFulfillmentLocation' ) ;
276+ await waitForOperation ( 'CalculateCheckoutSessionTaxes' ) ;
277+ await waitForOperation ( 'DraftOrder' ) ;
278+ await flushPromises ( ) ;
279+ clearOperations ( ) ;
280+
281+ expect ( screen . getByRole ( 'radio' , { name : / l o c a l p i c k u p / i } ) ) . toBeChecked ( ) ;
282+
283+ await waitFor ( ( ) => {
284+ expect (
285+ screen . getByRole ( 'radio' , { name : / s h i p p i n g / i } )
286+ ) . not . toBeDisabled ( ) ;
287+ } ) ;
288+ await user . click ( screen . getByRole ( 'radio' , { name : / s h i p p i n g / i } ) ) ;
289+ await waitFor ( ( ) => {
290+ expect ( screen . getByRole ( 'radio' , { name : / s h i p p i n g / i } ) ) . toBeChecked ( ) ;
291+ } ) ;
292+
293+ await typeIntoNamedField ( user , 'shippingAddressLine1' , '456 Shipping Ln' ) ;
294+ await typeIntoNamedField ( user , 'shippingAdminArea2' , 'Jasper' ) ;
295+ await typeIntoNamedField ( user , 'shippingPostalCode' , '30143' ) ;
296+ await advanceCheckoutDebounce ( ) ;
297+ await waitForOperation ( 'UpdateCheckoutSessionDraftOrder' ) ;
298+ await flushPromises ( ) ;
299+
300+ await waitFor ( ( ) => {
301+ expect ( screen . getByRole ( 'radio' , { name : / s h i p p i n g / i } ) ) . toBeChecked ( ) ;
302+ expect (
303+ screen . getByRole ( 'radio' , { name : / l o c a l p i c k u p / i } )
304+ ) . not . toBeChecked ( ) ;
305+ } ) ;
306+ } ) ;
307+
308+ it ( 'confirms as shipping after pickup-to-shipping address sync refetches' , async ( ) => {
309+ const { user } = renderCheckout ( {
310+ draftOrderOverrides : {
311+ lineItems : [ { fulfillmentMode : DeliveryMethods . PICKUP } ] ,
312+ shippingLines : [ ] ,
313+ shipping : {
314+ firstName : '' ,
315+ lastName : '' ,
316+ address : buildShippingAddress ( {
317+ addressLine1 : '' ,
318+ addressLine2 : '' ,
319+ adminArea1 : 'GA' ,
320+ adminArea2 : '' ,
321+ postalCode : '' ,
322+ countryCode : 'US' ,
323+ } ) ,
324+ } ,
325+ } ,
326+ sessionOverrides : {
327+ paymentMethods : offlinePaymentMethods ( ) ,
328+ } ,
329+ } ) ;
330+ await waitForCheckoutReady ( ) ;
331+ await waitForOperation ( 'ApplyCheckoutSessionFulfillmentLocation' ) ;
332+ await waitForDeliveryMethodEnabled ( / s h i p p i n g / i) ;
333+ clearOperations ( ) ;
334+
335+ await user . click ( screen . getByRole ( 'radio' , { name : / s h i p p i n g / i } ) ) ;
336+ await waitFor ( ( ) => {
337+ expect ( screen . getByRole ( 'radio' , { name : / s h i p p i n g / i } ) ) . toBeChecked ( ) ;
338+ } ) ;
339+
340+ await typeIntoNamedField ( user , 'shippingFirstName' , 'Ship' ) ;
341+ await typeIntoNamedField ( user , 'shippingLastName' , 'Buyer' ) ;
342+ await typeIntoNamedField ( user , 'shippingAddressLine1' , '456 Shipping Ln' ) ;
343+ await typeIntoNamedField ( user , 'shippingAdminArea2' , 'Jasper' ) ;
344+ await typeIntoNamedField ( user , 'shippingPostalCode' , '30143' ) ;
345+ await advanceCheckoutDebounce ( ) ;
346+ await waitForOperation ( 'UpdateCheckoutSessionDraftOrder' ) ;
347+ await waitForOperation ( 'ApplyCheckoutSessionShippingMethod' ) ;
348+ await flushPromises ( ) ;
349+
350+ await user . click (
351+ await screen . findByRole ( 'button' , { name : / c o m p l e t e y o u r o r d e r / i } )
352+ ) ;
353+ await waitForOperation ( 'ConfirmCheckoutSession' ) ;
354+
355+ expect ( getLastConfirmInput ( ) ) . not . toHaveProperty ( 'fulfillmentLocationId' ) ;
356+ expect ( getLastConfirmInput ( ) ) . not . toHaveProperty ( 'fulfillmentStartAt' ) ;
357+ expect ( getLastConfirmInput ( ) ) . not . toHaveProperty ( 'fulfillmentEndAt' ) ;
358+ } ) ;
359+
99360 it ( 'still sends address and recalculates taxes when only an address field changes' , async ( ) => {
100361 const { user } = renderCheckout ( ) ;
101362 await waitForCheckoutReady ( ) ;
0 commit comments