Skip to content

Commit 1767edf

Browse files
committed
chore: document updated cartDeliveryAddressesUpdate empty array behavior
1 parent 2652707 commit 1767edf

File tree

3 files changed

+356
-21
lines changed

3 files changed

+356
-21
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
---
2+
'@shopify/hydrogen': patch
3+
---
4+
5+
Document `cartDeliveryAddressesUpdate` empty array behavior for API 2025-10+
6+
7+
## Breaking Behavior Change in Storefront API 2025-10
8+
9+
The `cartDeliveryAddressesUpdate` mutation now clears all delivery addresses when passed an empty array. This behavior was undefined in previous API versions.
10+
11+
## What Changed
12+
13+
**Before (API ≤ 2025-07):**
14+
Passing an empty array had undefined behavior (likely ignored or caused an error).
15+
16+
**After (API ≥ 2025-10):**
17+
Passing an empty array explicitly clears all delivery addresses from the cart.
18+
19+
## Usage Examples
20+
21+
### Clearing All Delivery Addresses
22+
23+
**New in 2025-10** - Use empty array to clear addresses:
24+
25+
```typescript
26+
import {createCartHandler} from '@shopify/hydrogen';
27+
28+
export async function action({context}) {
29+
const cartHandler = createCartHandler({
30+
storefront: context.storefront,
31+
getCartId: context.cart.getCartId,
32+
});
33+
34+
// Clear all delivery addresses from cart
35+
const result = await cartHandler.updateDeliveryAddresses([]);
36+
37+
return result;
38+
}
39+
```
40+
41+
### Updating Specific Addresses
42+
43+
Standard usage remains unchanged:
44+
45+
```typescript
46+
// Update delivery addresses with new address
47+
const result = await cartHandler.updateDeliveryAddresses([
48+
{
49+
id: 'gid://shopify/CartSelectableAddress/existing-address',
50+
selected: true,
51+
address: {
52+
deliveryAddress: {
53+
address1: '123 Main Street',
54+
city: 'New York',
55+
countryCode: 'US',
56+
zip: '10001',
57+
},
58+
},
59+
},
60+
]);
61+
```
62+
63+
### Migration Guide
64+
65+
**If you're passing empty arrays:**
66+
67+
```typescript
68+
// BEFORE: This had undefined behavior
69+
await cartHandler.updateDeliveryAddresses([]);
70+
71+
// AFTER (2025-10): This now clears all addresses
72+
await cartHandler.updateDeliveryAddresses([]);
73+
// ⚠️ Verify this is your intended behavior!
74+
```
75+
76+
**If you want to clear addresses:**
77+
78+
```typescript
79+
// BEFORE (workaround): Had to remove addresses individually
80+
const addressIds = cart.delivery.addresses.map((addr) => addr.id);
81+
await cartHandler.removeDeliveryAddresses(addressIds);
82+
83+
// AFTER (2025-10): Simply pass empty array
84+
await cartHandler.updateDeliveryAddresses([]);
85+
```
Lines changed: 263 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,277 @@
11
import {describe, it, expect} from 'vitest';
22
import {CART_ID, mockCreateStorefrontClient} from '../cart-test-helper';
3-
import {cartDeliveryAddressesUpdateDefault} from './cartDeliveryAddressesUpdateDefault';
3+
import {
4+
cartDeliveryAddressesUpdateDefault,
5+
CART_DELIVERY_ADDRESSES_UPDATE_MUTATION,
6+
} from './cartDeliveryAddressesUpdateDefault';
7+
import {CartSelectableAddressUpdateInput} from '@shopify/hydrogen-react/storefront-api-types';
48

59
describe('cartDeliveryAddressesUpdateDefault', () => {
6-
it('should return a default cart delivery address update implementation', async () => {
7-
const updateDeliveryAddresses = cartDeliveryAddressesUpdateDefault({
8-
storefront: mockCreateStorefrontClient(),
9-
getCartId: () => CART_ID,
10+
describe('empty array behavior (API 2025-10+)', () => {
11+
it('should document that passing empty array clears all delivery addresses', async () => {
12+
const updateDeliveryAddresses = cartDeliveryAddressesUpdateDefault({
13+
storefront: mockCreateStorefrontClient(),
14+
getCartId: () => CART_ID,
15+
});
16+
17+
const result = await updateDeliveryAddresses([]);
18+
19+
expect(result.cart).toHaveProperty('id', CART_ID);
20+
});
21+
22+
it('should accept empty array as valid input per API 2025-10 semantics', async () => {
23+
const updateDeliveryAddresses = cartDeliveryAddressesUpdateDefault({
24+
storefront: mockCreateStorefrontClient(),
25+
getCartId: () => CART_ID,
26+
});
27+
28+
await expect(updateDeliveryAddresses([])).resolves.toBeDefined();
29+
});
30+
});
31+
32+
describe('with delivery addresses', () => {
33+
it('should update delivery addresses when addresses provided', async () => {
34+
const updateDeliveryAddresses = cartDeliveryAddressesUpdateDefault({
35+
storefront: mockCreateStorefrontClient(),
36+
getCartId: () => CART_ID,
37+
});
38+
39+
const mockAddress: CartSelectableAddressUpdateInput = {
40+
id: 'gid://shopify/CartSelectableAddress/test-123',
41+
selected: true,
42+
address: {
43+
deliveryAddress: {
44+
address1: '123 Test Street',
45+
city: 'Test City',
46+
countryCode: 'US',
47+
zip: '12345',
48+
},
49+
},
50+
};
51+
52+
const result = await updateDeliveryAddresses([mockAddress]);
53+
54+
expect(result.cart).toHaveProperty('id', CART_ID);
1055
});
1156

12-
const result = await updateDeliveryAddresses([]);
57+
it('should handle multiple addresses in single update', async () => {
58+
const updateDeliveryAddresses = cartDeliveryAddressesUpdateDefault({
59+
storefront: mockCreateStorefrontClient(),
60+
getCartId: () => CART_ID,
61+
});
1362

14-
expect(result.cart).toHaveProperty('id', CART_ID);
63+
const addresses: Array<CartSelectableAddressUpdateInput> = [
64+
{
65+
id: 'gid://shopify/CartSelectableAddress/addr-1',
66+
selected: true,
67+
address: {
68+
deliveryAddress: {
69+
address1: '123 Main St',
70+
city: 'NYC',
71+
countryCode: 'US',
72+
zip: '10001',
73+
},
74+
},
75+
},
76+
{
77+
id: 'gid://shopify/CartSelectableAddress/addr-2',
78+
selected: false,
79+
address: {
80+
deliveryAddress: {
81+
address1: '456 Broadway',
82+
city: 'NYC',
83+
countryCode: 'US',
84+
zip: '10002',
85+
},
86+
},
87+
},
88+
];
89+
90+
const result = await updateDeliveryAddresses(addresses);
91+
92+
expect(result.cart).toHaveProperty('id', CART_ID);
93+
});
1594
});
1695

17-
it('can override cartFragment', async () => {
18-
const cartFragment = 'cartFragmentOverride';
19-
const updateDeliveryAddresses = cartDeliveryAddressesUpdateDefault({
20-
storefront: mockCreateStorefrontClient(),
21-
getCartId: () => CART_ID,
22-
cartFragment,
96+
describe('cartFragment override', () => {
97+
it('can override cartFragment for custom query fields', async () => {
98+
const cartFragment = 'cartFragmentOverride';
99+
const updateDeliveryAddresses = cartDeliveryAddressesUpdateDefault({
100+
storefront: mockCreateStorefrontClient(),
101+
getCartId: () => CART_ID,
102+
cartFragment,
103+
});
104+
105+
const result = await updateDeliveryAddresses([]);
106+
107+
expect(result.cart).toHaveProperty('id', CART_ID);
108+
expect(result.userErrors?.[0]).toContain(cartFragment);
109+
});
110+
});
111+
112+
describe('mutation structure', () => {
113+
it('should include required mutation fields for error and warning handling', () => {
114+
const mutation = CART_DELIVERY_ADDRESSES_UPDATE_MUTATION();
115+
116+
expect(mutation).toContain('cartDeliveryAddressesUpdate');
117+
expect(mutation).toContain('userErrors');
118+
expect(mutation).toContain('warnings');
119+
expect(mutation).toContain('CartApiError');
120+
expect(mutation).toContain('CartApiWarning');
121+
});
122+
123+
it('should include @inContext directive for internationalization', () => {
124+
const mutation = CART_DELIVERY_ADDRESSES_UPDATE_MUTATION();
125+
126+
expect(mutation).toContain('@inContext');
127+
expect(mutation).toContain('$country');
128+
expect(mutation).toContain('$language');
129+
});
130+
});
131+
132+
describe('documentation completeness', () => {
133+
it('should document empty array behavior in JSDoc', async () => {
134+
const fs = await import('fs');
135+
const path = await import('path');
136+
const functionFile = fs.readFileSync(
137+
path.join(__dirname, 'cartDeliveryAddressesUpdateDefault.tsx'),
138+
'utf-8',
139+
);
140+
141+
expect(functionFile).toContain('empty array');
142+
expect(functionFile).toContain('clear');
143+
expect(functionFile).toContain('2025-10');
23144
});
24145

25-
const result = await updateDeliveryAddresses([]);
146+
it('should include example of clearing addresses with empty array', async () => {
147+
const fs = await import('fs');
148+
const path = await import('path');
149+
const functionFile = fs.readFileSync(
150+
path.join(__dirname, 'cartDeliveryAddressesUpdateDefault.tsx'),
151+
'utf-8',
152+
);
26153

27-
expect(result.cart).toHaveProperty('id', CART_ID);
28-
expect(result.userErrors?.[0]).toContain(cartFragment);
154+
expect(functionFile).toMatch(/example.*clear/i);
155+
expect(functionFile).toContain('updateAddresses([])');
156+
});
157+
});
158+
159+
describe('edge cases', () => {
160+
it('should handle address with only required fields', async () => {
161+
const updateDeliveryAddresses = cartDeliveryAddressesUpdateDefault({
162+
storefront: mockCreateStorefrontClient(),
163+
getCartId: () => CART_ID,
164+
});
165+
166+
const minimalAddress: CartSelectableAddressUpdateInput = {
167+
id: 'gid://shopify/CartSelectableAddress/minimal',
168+
selected: true,
169+
address: {
170+
deliveryAddress: {
171+
address1: '123 Main',
172+
city: 'NYC',
173+
countryCode: 'US',
174+
zip: '10001',
175+
},
176+
},
177+
};
178+
179+
const result = await updateDeliveryAddresses([minimalAddress]);
180+
181+
expect(result.cart).toHaveProperty('id', CART_ID);
182+
});
183+
184+
it('should handle address with copyFromCustomerAddressId instead of deliveryAddress', async () => {
185+
const updateDeliveryAddresses = cartDeliveryAddressesUpdateDefault({
186+
storefront: mockCreateStorefrontClient(),
187+
getCartId: () => CART_ID,
188+
});
189+
190+
const customerAddress = {
191+
id: 'gid://shopify/CartSelectableAddress/test',
192+
selected: true,
193+
address: {
194+
copyFromCustomerAddressId:
195+
'gid://shopify/MailingAddress/123?model_name=CustomerAddress',
196+
},
197+
};
198+
199+
const result = await updateDeliveryAddresses([customerAddress]);
200+
201+
expect(result.cart).toHaveProperty('id', CART_ID);
202+
});
203+
204+
it('should handle optional parameters like country and language', async () => {
205+
const updateDeliveryAddresses = cartDeliveryAddressesUpdateDefault({
206+
storefront: mockCreateStorefrontClient(),
207+
getCartId: () => CART_ID,
208+
});
209+
210+
const result = await updateDeliveryAddresses([], {
211+
country: 'CA',
212+
language: 'FR',
213+
});
214+
215+
expect(result.cart).toHaveProperty('id', CART_ID);
216+
});
217+
218+
it('should handle single address update', async () => {
219+
const updateDeliveryAddresses = cartDeliveryAddressesUpdateDefault({
220+
storefront: mockCreateStorefrontClient(),
221+
getCartId: () => CART_ID,
222+
});
223+
224+
const singleAddress = [
225+
{
226+
id: 'gid://shopify/CartSelectableAddress/single',
227+
selected: true,
228+
oneTimeUse: false,
229+
address: {
230+
deliveryAddress: {
231+
address1: 'Single Street',
232+
city: 'Solo City',
233+
countryCode: 'US' as const,
234+
zip: '99999',
235+
},
236+
},
237+
},
238+
];
239+
240+
const result = await updateDeliveryAddresses(singleAddress);
241+
242+
expect(result.cart).toHaveProperty('id', CART_ID);
243+
});
244+
245+
it('should handle address with all optional fields', async () => {
246+
const updateDeliveryAddresses = cartDeliveryAddressesUpdateDefault({
247+
storefront: mockCreateStorefrontClient(),
248+
getCartId: () => CART_ID,
249+
});
250+
251+
const fullAddress: CartSelectableAddressUpdateInput = {
252+
id: 'gid://shopify/CartSelectableAddress/full',
253+
selected: true,
254+
oneTimeUse: true,
255+
validationStrategy: 'COUNTRY_CODE_ONLY',
256+
address: {
257+
deliveryAddress: {
258+
firstName: 'John',
259+
lastName: 'Doe',
260+
company: 'Acme Corp',
261+
address1: '123 Main St',
262+
address2: 'Apt 4B',
263+
city: 'New York',
264+
provinceCode: 'NY',
265+
countryCode: 'US',
266+
zip: '10001',
267+
phone: '+1234567890',
268+
},
269+
},
270+
};
271+
272+
const result = await updateDeliveryAddresses([fullAddress]);
273+
274+
expect(result.cart).toHaveProperty('id', CART_ID);
275+
});
29276
});
30277
});

0 commit comments

Comments
 (0)