-
Notifications
You must be signed in to change notification settings - Fork 380
feat(cart): add cartDeliveryAddressesReplace mutation support #3406
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| --- | ||
| '@shopify/hydrogen': minor | ||
| --- | ||
|
|
||
| Add `cartDeliveryAddressesReplaceDefault` to handle the new `cartDeliveryAddressesReplace` Storefront API mutation (2025-10) | ||
|
|
||
| This new mutation replaces all delivery addresses on a cart in a single operation. | ||
|
|
||
| **Usage via cart handler:** | ||
| ```typescript | ||
| const result = await cart.replaceDeliveryAddresses([ | ||
| { | ||
| address: { | ||
| deliveryAddress: { | ||
| address1: '123 Main St', | ||
| city: 'Anytown', | ||
| countryCode: 'US' | ||
| } | ||
| }, | ||
| selected: true | ||
| } | ||
| ]); | ||
| ``` | ||
|
|
||
| **Usage via CartForm:** | ||
| ```tsx | ||
| <CartForm action={CartForm.ACTIONS.DeliveryAddressesReplace}> | ||
| {/* form inputs */} | ||
| </CartForm> | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs'; | ||
|
|
||
| const data: ReferenceEntityTemplateSchema = { | ||
| name: 'cartDeliveryAddressesReplace', | ||
| category: 'utilities', | ||
| subCategory: 'cart', | ||
| isVisualComponent: false, | ||
| related: [], | ||
| description: | ||
| 'Creates a function that accepts an array of [CartSelectableAddressInput](/docs/api/storefront/2025-10/input-objects/CartSelectableAddressInput) to replace all delivery addresses on a cart', | ||
| type: 'utility', | ||
| defaultExample: { | ||
| description: | ||
| 'Replace all delivery addresses on a cart with a new set of addresses', | ||
| codeblock: { | ||
| tabs: [ | ||
| { | ||
| title: 'JavaScript', | ||
| code: './cartDeliveryAddressesReplaceDefault.example.js', | ||
| language: 'js', | ||
| }, | ||
| ], | ||
| title: 'example', | ||
| }, | ||
| }, | ||
| definitions: [ | ||
| { | ||
| title: 'cartDeliveryAddressesReplaceDefault', | ||
| type: 'CartDeliveryAddressesReplaceDefaultGeneratedType', | ||
| description: '', | ||
| }, | ||
| ], | ||
| }; | ||
|
|
||
| export default data; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import {cartDeliveryAddressesReplaceDefault} from '@shopify/hydrogen'; | ||
|
|
||
| const replaceDeliveryAddresses = cartDeliveryAddressesReplaceDefault({ | ||
| storefront, | ||
| getCartId, | ||
| }); | ||
|
|
||
| const result = await replaceDeliveryAddresses( | ||
| [ | ||
| { | ||
| address: { | ||
| deliveryAddress: { | ||
| address1: '<your-address1>', | ||
| address2: '<your-address2>', | ||
| city: '<your-city>', | ||
| company: '<your-company>', | ||
| countryCode: 'AC', | ||
| firstName: '<your-firstName>', | ||
| lastName: '<your-lastName>', | ||
| phone: '<your-phone>', | ||
| provinceCode: '<your-provinceCode>', | ||
| zip: '<your-zip>', | ||
| }, | ||
| }, | ||
| selected: true, | ||
| }, | ||
| ], | ||
| {someOptionalParam: 'value'}, | ||
| ); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import {describe, it, expect} from 'vitest'; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: these tests are bad. there's existing precedent, we're going to fix these after 2025-10 release
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the ones we don’t fix should be removed, because this is leading LLMs to generate more dud tests like these when prompted
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agreed! (for the future, once we overhaul the tests). I would love for this codebase to be a perfect example of fabulous tests! |
||
| import {CART_ID, mockCreateStorefrontClient} from '../cart-test-helper'; | ||
| import {cartDeliveryAddressesReplaceDefault} from './cartDeliveryAddressesReplaceDefault'; | ||
|
|
||
| describe('cartDeliveryAddressesReplaceDefault', () => { | ||
| it('should return a default cart delivery address replace implementation', async () => { | ||
| const replaceDeliveryAddresses = cartDeliveryAddressesReplaceDefault({ | ||
| storefront: mockCreateStorefrontClient(), | ||
| getCartId: () => CART_ID, | ||
| }); | ||
|
|
||
| const result = await replaceDeliveryAddresses([]); | ||
|
|
||
| expect(result.cart).toHaveProperty('id', CART_ID); | ||
| }); | ||
|
|
||
| it('can override cartFragment', async () => { | ||
| const cartFragment = 'cartFragmentOverride'; | ||
| const replaceDeliveryAddresses = cartDeliveryAddressesReplaceDefault({ | ||
| storefront: mockCreateStorefrontClient(), | ||
| getCartId: () => CART_ID, | ||
| cartFragment, | ||
| }); | ||
|
|
||
| const result = await replaceDeliveryAddresses([]); | ||
|
|
||
| expect(result.cart).toHaveProperty('id', CART_ID); | ||
| expect(result.userErrors?.[0]).toContain(cartFragment); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| import {StorefrontApiErrors, formatAPIResult} from '../../storefront'; | ||
| import type {CartSelectableAddressInput} from '@shopify/hydrogen-react/storefront-api-types'; | ||
| import { | ||
| CART_WARNING_FRAGMENT, | ||
| MINIMAL_CART_FRAGMENT, | ||
| USER_ERROR_FRAGMENT, | ||
| } from './cart-fragments'; | ||
| import type { | ||
| CartOptionalInput, | ||
| CartQueryData, | ||
| CartQueryDataReturn, | ||
| CartQueryOptions, | ||
| } from './cart-types'; | ||
|
|
||
| export type CartDeliveryAddressesReplaceFunction = ( | ||
| addresses: Array<CartSelectableAddressInput>, | ||
| optionalParams?: CartOptionalInput, | ||
| ) => Promise<CartQueryDataReturn>; | ||
|
|
||
| /** | ||
| * Replaces all delivery addresses on the cart. | ||
| * | ||
| * This function sends a mutation to the storefront API to replace all delivery addresses on the cart | ||
| * with the provided addresses. It returns the result of the mutation, including any errors that occurred. | ||
| * | ||
| * @param {CartQueryOptions} options - The options for the cart query, including the storefront API client and cart fragment. | ||
| * @returns {CartDeliveryAddressesReplaceFunction} - A function that takes an array of addresses and optional parameters, and returns the result of the API call. | ||
| * | ||
| * @example | ||
| * const replaceDeliveryAddresses = cartDeliveryAddressesReplaceDefault({ storefront, getCartId }); | ||
| * const result = await replaceDeliveryAddresses([ | ||
| * { | ||
| * address: { | ||
| * deliveryAddress: { | ||
| * address1: '123 Main St', | ||
| * city: 'Anytown', | ||
| * countryCode: 'US' | ||
| * } | ||
| * }, | ||
| * selected: true | ||
| * } | ||
| * ], { someOptionalParam: 'value' } | ||
| * ); | ||
| */ | ||
| export function cartDeliveryAddressesReplaceDefault( | ||
| options: CartQueryOptions, | ||
| ): CartDeliveryAddressesReplaceFunction { | ||
| return async ( | ||
| addresses: Array<CartSelectableAddressInput>, | ||
| optionalParams, | ||
| ) => { | ||
| const {cartDeliveryAddressesReplace, errors} = | ||
| await options.storefront.mutate<{ | ||
| cartDeliveryAddressesReplace: CartQueryData; | ||
| errors: StorefrontApiErrors; | ||
| }>(CART_DELIVERY_ADDRESSES_REPLACE_MUTATION(options.cartFragment), { | ||
| variables: { | ||
| cartId: options.getCartId(), | ||
| addresses, | ||
| ...optionalParams, | ||
| }, | ||
| }); | ||
|
|
||
| return formatAPIResult(cartDeliveryAddressesReplace, errors); | ||
| }; | ||
| } | ||
|
|
||
| //! @see: https://shopify.dev/docs/api/storefront/2025-10/mutations/cartDeliveryAddressesReplace | ||
| export const CART_DELIVERY_ADDRESSES_REPLACE_MUTATION = ( | ||
| cartFragment = MINIMAL_CART_FRAGMENT, | ||
| ) => `#graphql | ||
| mutation cartDeliveryAddressesReplace( | ||
| $cartId: ID! | ||
| $addresses: [CartSelectableAddressInput!]!, | ||
| $country: CountryCode = ZZ | ||
| $language: LanguageCode | ||
| ) @inContext(country: $country, language: $language) { | ||
| cartDeliveryAddressesReplace(addresses: $addresses, cartId: $cartId) { | ||
| cart { | ||
| ...CartApiMutation | ||
| } | ||
| userErrors { | ||
| ...CartApiError | ||
| } | ||
| warnings { | ||
| ...CartApiWarning | ||
| } | ||
| } | ||
| } | ||
| ${cartFragment} | ||
| ${USER_ERROR_FRAGMENT} | ||
| ${CART_WARNING_FRAGMENT} | ||
| `; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Re: lines +80 to +100]
this is a very weird test, it doesn’t verify anything, only makes it so we have to copy paste this twice
i’d recommend doing the
satisfiesthing i did in the other PRcorrect me if im wrong but this test is asserting "all the cart actions are present in the
CartForm.ACTIONSobject"See this comment inline on Graphite.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I agree. Since we have plans to overhaul all these tests after the release we can get to it then