Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/pos-cart-update-line-item-quantity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@shopify/ui-extensions': minor
'@shopify/ui-extensions-tester': patch
---

Add `cart.updateLineItemQuantity` to the POS Cart API for API version 2026-10. The method updates one line item's quantity while preserving its properties, discounts, and selling plans.
22 changes: 20 additions & 2 deletions packages/ui-extensions-tester/src/point-of-sale/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,27 @@ extension.shopify.storage = createStorage({
});
```

## 🔒 Mocking mutation return values
## 🔒 Mocking cart mutations

Replace mutation functions with `vi.fn()` and use `createResult()` to build typed return values. The first argument is the mutation name; the second is an optional result override.
The target API mock includes asynchronous stubs for Cart API mutations, including `updateLineItemQuantity`. Replace a stub with a spy when you need to verify a call:

```ts
const updateLineItemQuantity = vi.spyOn(
extension.shopify.cart,
'updateLineItemQuantity',
);

await extension.shopify.cart.updateLineItemQuantity(
'line-item-uuid',
2,
);

expect(
updateLineItemQuantity,
).toHaveBeenCalledWith('line-item-uuid', 2);
```

For mutations that return data, replace the function with `vi.fn()` and use `createResult()` to build typed return values. The first argument is the mutation name; the second is an optional result override.

```ts
import {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ function createMockCartApi(): CartApi {
removeCustomer: async () => {},
addCustomSale: async () => '',
addLineItem: async () => '',
updateLineItemQuantity: async () => {},
removeLineItem: async () => {},
addCartProperties: async () => {},
removeCartProperties: async () => {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ describe('pos.cart.validations.resolution.render', () => {

expect(api.cart.current).toBeDefined();
expect(typeof api.cart.addLineItem).toBe('function');
expect(typeof api.cart.updateLineItemQuantity).toBe('function');
expect(api.scanner).toBeDefined();
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {render} from 'preact';

export default async () => {
render(<Extension />, document.body);
};

const Extension = () => {
return (
<s-tile
heading="My App"
subheading="Call cart function"
onClick={() => {
const [lineItem] = shopify.cart.current.value.lineItems;
if (lineItem) {
shopify.cart.updateLineItemQuantity(
lineItem.uuid,
lineItem.quantity + 1,
);
}
}}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,20 @@ export interface MutableCartApiContent {
options?: AddLineItemOptions,
): Promise<string>;

/**
* Set the quantity of an existing line item identified by its `UUID`, preserving the line item's properties, discounts, and selling plans. This is equivalent to a merchant adjusting the quantity on the native cart line.
*
* If POS has split the line into multiple allocations that share the same `UUID` (for example, lines split across delivery methods), the target line is ambiguous and the call throws instead of guessing.
*
* Only available on API version `2026-10` and later.
*
* @param uuid the UUID of the line item to update
* @param quantity the new absolute quantity; must be an integer of 1 or greater. To remove a line item, use `removeLineItem` instead.
* @returns A promise that resolves after the cart state reflects the change.
* @throws {Error} if the line item is not found, the `UUID` matches multiple split-line allocations, the quantity is invalid, the cart is not editable because it is a return or exchange, or the POS app version does not support this method.
*/
updateLineItemQuantity(uuid: string, quantity: number): Promise<void>;

/**
* Remove a specific line item from the cart using its `UUID`. The line item will be completely removed from the cart along with any associated discounts, properties, or selling plans.
*
Expand Down
Loading