@tapcart/mobile-components 0.16.1 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/hooks/use-order-details.d.ts +23 -0
- package/dist/components/hooks/use-order-details.d.ts.map +1 -1
- package/dist/components/hooks/use-order-details.js +59 -11
- package/dist/components/ui/drawer.d.ts +3 -2
- package/dist/components/ui/drawer.d.ts.map +1 -1
- package/dist/components/ui/drawer.js +3 -3
- package/dist/tests/use-order-details.test.js +84 -1
- package/package.json +1 -1
|
@@ -9,6 +9,29 @@ type UseOrderDetailsProps = {
|
|
|
9
9
|
type UseProductsReturn = {
|
|
10
10
|
orderDetails: Record<string, any>;
|
|
11
11
|
};
|
|
12
|
+
/**
|
|
13
|
+
* Effective (post-edit) quantity for an order line item, correct across BOTH
|
|
14
|
+
* Shopify order APIs the app consumes:
|
|
15
|
+
*
|
|
16
|
+
* - Storefront API exposes `currentQuantity` (ordered minus removed); it is 0
|
|
17
|
+
* for a line removed via Order Editing. Authoritative when present.
|
|
18
|
+
* - New Customer Account API has NO `currentQuantity`. There the only
|
|
19
|
+
* per-line signal is `refundableQuantity` (= quantity − refunded), which is
|
|
20
|
+
* also driven down to the post-edit count by an Order Editing removal
|
|
21
|
+
* (verified against real prod data: a removed line has refundableQuantity 0
|
|
22
|
+
* with no refund record). We trust it ONLY when the order was `edited` AND
|
|
23
|
+
* has no refund activity — because a refund/return ALSO lowers
|
|
24
|
+
* `refundableQuantity`, and `edited` is sticky, so on an edited-then-refunded
|
|
25
|
+
* order we cannot tell an edit-removal from a return. In that case we fall
|
|
26
|
+
* back to the ordered `quantity` (show the item) rather than risk hiding a
|
|
27
|
+
* line the customer actually bought / returned, or blanking a fully-refunded
|
|
28
|
+
* order. Trade-off: an item removed via an edit that DID issue a refund is
|
|
29
|
+
* not hidden (needs a per-line signal the API doesn't expose).
|
|
30
|
+
*
|
|
31
|
+
* Exported so blocks (PurchaseOverview, OrderHistory, OrderStatus) that build
|
|
32
|
+
* their own order objects from raw `customer.orders` share one implementation.
|
|
33
|
+
*/
|
|
34
|
+
export declare const getEffectiveLineQuantity: (order: Record<string, any>, lineItem: Record<string, any>) => number;
|
|
12
35
|
export declare const transformOrderDetails: (order: Record<string, any>) => {
|
|
13
36
|
orderDetails: Record<string, any>;
|
|
14
37
|
checkoutData: Record<string, any>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-order-details.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-order-details.ts"],"names":[],"mappings":"AAKA,KAAK,oBAAoB,GAAG;IAC1B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,OAAO,CAAA;CACf,CAAA;AAED,KAAK,iBAAiB,GAAG;IACvB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAClC,CAAA;
|
|
1
|
+
{"version":3,"file":"use-order-details.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-order-details.ts"],"names":[],"mappings":"AAKA,KAAK,oBAAoB,GAAG;IAC1B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,OAAO,CAAA;CACf,CAAA;AAED,KAAK,iBAAiB,GAAG;IACvB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAClC,CAAA;AAwMD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,wBAAwB,UAC5B,OAAO,MAAM,EAAE,GAAG,CAAC,YAChB,OAAO,MAAM,EAAE,GAAG,CAAC,KAC5B,MAUF,CAAA;AAED,eAAO,MAAM,qBAAqB,UACzB,OAAO,MAAM,EAAE,GAAG,CAAC;kBACT,OAAO,MAAM,EAAE,GAAG,CAAC;kBAAgB,OAAO,MAAM,EAAE,GAAG,CAAC;CAoOxE,CAAA;AAED,wBAAgB,eAAe,CAAC,EAC9B,SAAS,EACT,MAAM,EACN,KAAK,EACL,QAAQ,EACR,OAAO,EACP,IAAY,GACb,EAAE,oBAAoB,GAAG,iBAAiB,CAiE1C"}
|
|
@@ -138,6 +138,54 @@ const updateLineDetails = (line, product) => {
|
|
|
138
138
|
line.sellingPlan = (_k = (_j = product.sellingPlanAllocation) === null || _j === void 0 ? void 0 : _j.sellingPlan) === null || _k === void 0 ? void 0 : _k.name;
|
|
139
139
|
line.variantName = (_l = product.variant) === null || _l === void 0 ? void 0 : _l.title;
|
|
140
140
|
};
|
|
141
|
+
/**
|
|
142
|
+
* True when an order has any refund/return activity. Used to keep the
|
|
143
|
+
* Customer-Account-API `refundableQuantity` heuristic (below) away from orders
|
|
144
|
+
* whose `refundableQuantity` was lowered by a refund rather than an order edit.
|
|
145
|
+
*/
|
|
146
|
+
const hasRefundActivity = (order) => {
|
|
147
|
+
var _a, _b, _c;
|
|
148
|
+
if (Array.isArray(order === null || order === void 0 ? void 0 : order.refunds) && order.refunds.length > 0)
|
|
149
|
+
return true;
|
|
150
|
+
const refunded = (_c = (_b = (_a = order === null || order === void 0 ? void 0 : order.totalRefunded) === null || _a === void 0 ? void 0 : _a.amount) !== null && _b !== void 0 ? _b : order === null || order === void 0 ? void 0 : order.totalRefunded) !== null && _c !== void 0 ? _c : order === null || order === void 0 ? void 0 : order.totalRefund;
|
|
151
|
+
if (refunded != null && parseFloat(String(refunded)) > 0)
|
|
152
|
+
return true;
|
|
153
|
+
const status = String((order === null || order === void 0 ? void 0 : order.financialStatus) || "").toUpperCase();
|
|
154
|
+
return status.includes("REFUND"); // REFUNDED / PARTIALLY_REFUNDED
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Effective (post-edit) quantity for an order line item, correct across BOTH
|
|
158
|
+
* Shopify order APIs the app consumes:
|
|
159
|
+
*
|
|
160
|
+
* - Storefront API exposes `currentQuantity` (ordered minus removed); it is 0
|
|
161
|
+
* for a line removed via Order Editing. Authoritative when present.
|
|
162
|
+
* - New Customer Account API has NO `currentQuantity`. There the only
|
|
163
|
+
* per-line signal is `refundableQuantity` (= quantity − refunded), which is
|
|
164
|
+
* also driven down to the post-edit count by an Order Editing removal
|
|
165
|
+
* (verified against real prod data: a removed line has refundableQuantity 0
|
|
166
|
+
* with no refund record). We trust it ONLY when the order was `edited` AND
|
|
167
|
+
* has no refund activity — because a refund/return ALSO lowers
|
|
168
|
+
* `refundableQuantity`, and `edited` is sticky, so on an edited-then-refunded
|
|
169
|
+
* order we cannot tell an edit-removal from a return. In that case we fall
|
|
170
|
+
* back to the ordered `quantity` (show the item) rather than risk hiding a
|
|
171
|
+
* line the customer actually bought / returned, or blanking a fully-refunded
|
|
172
|
+
* order. Trade-off: an item removed via an edit that DID issue a refund is
|
|
173
|
+
* not hidden (needs a per-line signal the API doesn't expose).
|
|
174
|
+
*
|
|
175
|
+
* Exported so blocks (PurchaseOverview, OrderHistory, OrderStatus) that build
|
|
176
|
+
* their own order objects from raw `customer.orders` share one implementation.
|
|
177
|
+
*/
|
|
178
|
+
export const getEffectiveLineQuantity = (order, lineItem) => {
|
|
179
|
+
var _a;
|
|
180
|
+
if (lineItem.currentQuantity != null)
|
|
181
|
+
return lineItem.currentQuantity;
|
|
182
|
+
if ((order === null || order === void 0 ? void 0 : order.edited) === true &&
|
|
183
|
+
lineItem.refundableQuantity != null &&
|
|
184
|
+
!hasRefundActivity(order)) {
|
|
185
|
+
return lineItem.refundableQuantity;
|
|
186
|
+
}
|
|
187
|
+
return (_a = lineItem.quantity) !== null && _a !== void 0 ? _a : 1;
|
|
188
|
+
};
|
|
141
189
|
export const transformOrderDetails = (order) => {
|
|
142
190
|
var _a;
|
|
143
191
|
// Helper function to safely parse amounts
|
|
@@ -148,12 +196,12 @@ export const transformOrderDetails = (order) => {
|
|
|
148
196
|
return parseFloat(amount) || 0;
|
|
149
197
|
return 0;
|
|
150
198
|
};
|
|
151
|
-
|
|
152
|
-
//
|
|
153
|
-
const activeLineItems = (order.lineItems || []).filter((lineItem) =>
|
|
199
|
+
const effectiveQuantity = (lineItem) => getEffectiveLineQuantity(order, lineItem);
|
|
200
|
+
// Line items removed via Order Editing must not be shown to the customer
|
|
201
|
+
const activeLineItems = (order.lineItems || []).filter((lineItem) => effectiveQuantity(lineItem) > 0);
|
|
154
202
|
// Transform line items to cart lines
|
|
155
203
|
const cartLines = activeLineItems.map((lineItem) => {
|
|
156
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p
|
|
204
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
157
205
|
const variant = lineItem.variant || {};
|
|
158
206
|
const product = variant.product || {};
|
|
159
207
|
return {
|
|
@@ -188,13 +236,13 @@ export const transformOrderDetails = (order) => {
|
|
|
188
236
|
productId: ((_k = variant.product) === null || _k === void 0 ? void 0 : _k.id) || lineItem.productId,
|
|
189
237
|
merchandiseId: variant.id || lineItem.merchandiseId,
|
|
190
238
|
title: lineItem.title || product.title || "Unknown Product",
|
|
191
|
-
quantity: (
|
|
239
|
+
quantity: effectiveQuantity(lineItem),
|
|
192
240
|
uniqueId: crypto.randomUUID(),
|
|
193
241
|
vendor: product.vendor || lineItem.vendor || "Unknown",
|
|
194
242
|
compareAtPrice: {
|
|
195
|
-
amount: parseAmount(((
|
|
196
|
-
currencyCode: ((
|
|
197
|
-
((
|
|
243
|
+
amount: parseAmount(((_l = lineItem.originalTotalPrice) === null || _l === void 0 ? void 0 : _l.amount) || ((_m = lineItem.compareAtPrice) === null || _m === void 0 ? void 0 : _m.amount)),
|
|
244
|
+
currencyCode: ((_o = lineItem.originalTotalPrice) === null || _o === void 0 ? void 0 : _o.currencyCode) ||
|
|
245
|
+
((_p = lineItem.compareAtPrice) === null || _p === void 0 ? void 0 : _p.currencyCode) ||
|
|
198
246
|
order.currency ||
|
|
199
247
|
"USD",
|
|
200
248
|
},
|
|
@@ -265,7 +313,7 @@ export const transformOrderDetails = (order) => {
|
|
|
265
313
|
})),
|
|
266
314
|
subtotalPrice: wrapMoney(subtotal, currencyCode),
|
|
267
315
|
lineItems: activeLineItems.map((lineItem) => {
|
|
268
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t
|
|
316
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
269
317
|
const variant = lineItem.variant || {};
|
|
270
318
|
const product = variant.product || {};
|
|
271
319
|
const currencyCode = ((_a = lineItem.discountedTotalPrice) === null || _a === void 0 ? void 0 : _a.currencyCode) ||
|
|
@@ -315,9 +363,9 @@ export const transformOrderDetails = (order) => {
|
|
|
315
363
|
discountApplication: allocation.discountApplication || {},
|
|
316
364
|
});
|
|
317
365
|
}),
|
|
318
|
-
quantity: (
|
|
366
|
+
quantity: effectiveQuantity(lineItem),
|
|
319
367
|
title: lineItem.title || product.title,
|
|
320
|
-
id: ((
|
|
368
|
+
id: ((_s = variant.id) === null || _s === void 0 ? void 0 : _s.split("/").pop()) || ((_t = lineItem.variantId) === null || _t === void 0 ? void 0 : _t.split("/").pop()),
|
|
321
369
|
};
|
|
322
370
|
}),
|
|
323
371
|
totalPrice: wrapMoney(total, currencyCode),
|
|
@@ -33,11 +33,12 @@ declare const DrawerContentBase: React.ForwardRefExoticComponent<Omit<React.Deta
|
|
|
33
33
|
containerRef?: HTMLElement | undefined;
|
|
34
34
|
maxHeight?: string | undefined;
|
|
35
35
|
} & React.RefAttributes<HTMLDivElement>>;
|
|
36
|
+
declare const DrawerTitle: React.ForwardRefExoticComponent<Omit<import("@radix-ui/react-dialog").DialogTitleProps & React.RefAttributes<HTMLHeadingElement>, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
|
|
36
37
|
declare const DrawerFooter: {
|
|
37
38
|
({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): import("react/jsx-runtime").JSX.Element;
|
|
38
39
|
displayName: string;
|
|
39
40
|
};
|
|
40
|
-
declare const DrawerDescription: React.ForwardRefExoticComponent<Omit<
|
|
41
|
+
declare const DrawerDescription: React.ForwardRefExoticComponent<Omit<import("@radix-ui/react-dialog").DialogDescriptionProps & React.RefAttributes<HTMLParagraphElement>, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
|
|
41
42
|
type DrawerHeaderProps = {
|
|
42
43
|
iconLeftName?: string;
|
|
43
44
|
iconRightName?: string;
|
|
@@ -46,5 +47,5 @@ type DrawerHeaderProps = {
|
|
|
46
47
|
};
|
|
47
48
|
declare const DrawerHeader: ({ title, iconLeftName, iconRightName, onCloseClick, }: DrawerHeaderProps) => import("react/jsx-runtime").JSX.Element;
|
|
48
49
|
declare const DrawerContent: ({ children, className, ...props }: React.HTMLAttributes<HTMLDivElement>) => import("react/jsx-runtime").JSX.Element;
|
|
49
|
-
export { Drawer, DrawerPortal, DrawerOverlay, DrawerTrigger, DrawerClose, DrawerContentBase, DrawerContent, DrawerFooter, DrawerDescription, DrawerHeader, };
|
|
50
|
+
export { Drawer, DrawerPortal, DrawerOverlay, DrawerTrigger, DrawerClose, DrawerContentBase, DrawerContent, DrawerFooter, DrawerTitle, DrawerDescription, DrawerHeader, };
|
|
50
51
|
//# sourceMappingURL=drawer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drawer.d.ts","sourceRoot":"","sources":["../../../components/ui/drawer.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,MAAM,CAAA;AAKhD,KAAK,kBAAkB,GAAG,KAAK,CAAC,wBAAwB,CAAC,QAAQ,CAAC,GAAG;IACnE,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,KAAK,iBAAiB,GAAG;IACvB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,YAAY,CAAC,EAAE,WAAW,CAAA;CAC3B,CAAA;AAED,KAAK,gBAAgB,GAAG,KAAK,CAAC,wBAAwB,CAAC,QAAQ,CAAC,GAAG;IACjE,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,QAAA,MAAM,aAAa,8BAA0D,CAAA;AAG7E,QAAA,MAAM,YAAY;iCAAgC,iBAAiB;;CAgBlE,CAAA;AAGD,QAAA,MAAM,WAAW,4BAAsD,CAAA;AAGvE,KAAK,WAAW,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,GAAG;IACrE,YAAY,CAAC,EAAE,WAAW,CAAA;CAC3B,CAAA;AAED,QAAA,MAAM,MAAM;0CAAyC,WAAW;;CAS/D,CAAA;AAOD,QAAA,MAAM,aAAa;;wCAYlB,CAAA;AAWD,QAAA,MAAM,iBAAiB;;;;;;wCA2CtB,CAAA;
|
|
1
|
+
{"version":3,"file":"drawer.d.ts","sourceRoot":"","sources":["../../../components/ui/drawer.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,MAAM,CAAA;AAKhD,KAAK,kBAAkB,GAAG,KAAK,CAAC,wBAAwB,CAAC,QAAQ,CAAC,GAAG;IACnE,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,KAAK,iBAAiB,GAAG;IACvB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,YAAY,CAAC,EAAE,WAAW,CAAA;CAC3B,CAAA;AAED,KAAK,gBAAgB,GAAG,KAAK,CAAC,wBAAwB,CAAC,QAAQ,CAAC,GAAG;IACjE,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,QAAA,MAAM,aAAa,8BAA0D,CAAA;AAG7E,QAAA,MAAM,YAAY;iCAAgC,iBAAiB;;CAgBlE,CAAA;AAGD,QAAA,MAAM,WAAW,4BAAsD,CAAA;AAGvE,KAAK,WAAW,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,GAAG;IACrE,YAAY,CAAC,EAAE,WAAW,CAAA;CAC3B,CAAA;AAED,QAAA,MAAM,MAAM;0CAAyC,WAAW;;CAS/D,CAAA;AAOD,QAAA,MAAM,aAAa;;wCAYlB,CAAA;AAWD,QAAA,MAAM,iBAAiB;;;;;;wCA2CtB,CAAA;AAWD,QAAA,MAAM,WAAW,qLAYf,CAAA;AAGF,QAAA,MAAM,YAAY;8BAGf,MAAM,cAAc,CAAC,cAAc,CAAC;;CAQtC,CAAA;AAGD,QAAA,MAAM,iBAAiB,+LASrB,CAAA;AAGF,KAAK,iBAAiB,GAAG;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAA;CACjD,CAAA;AAED,QAAA,MAAM,YAAY,0DAKf,iBAAiB,4CA8BnB,CAAA;AAED,QAAA,MAAM,aAAa,sCAIhB,MAAM,cAAc,CAAC,cAAc,CAAC,4CAMtC,CAAA;AAED,OAAO,EACL,MAAM,EACN,YAAY,EACZ,aAAa,EACb,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,YAAY,EACZ,WAAW,EACX,iBAAiB,EACjB,YAAY,GACb,CAAA"}
|
|
@@ -71,9 +71,9 @@ const DrawerFooter = (_a) => {
|
|
|
71
71
|
DrawerFooter.displayName = "DrawerFooter";
|
|
72
72
|
const DrawerDescription = React.forwardRef((_a, ref) => {
|
|
73
73
|
var { className } = _a, props = __rest(_a, ["className"]);
|
|
74
|
-
return (_jsx(
|
|
74
|
+
return (_jsx(DrawerPrimitive.Description, Object.assign({ ref: ref, className: cn("text-sm text-muted-foreground", className) }, props)));
|
|
75
75
|
});
|
|
76
|
-
DrawerDescription.displayName =
|
|
76
|
+
DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
|
|
77
77
|
const DrawerHeader = ({ title, iconLeftName, iconRightName, onCloseClick, }) => {
|
|
78
78
|
return (_jsxs(DrawerHeaderBase, Object.assign({ className: "p-4 text-center flex justify-between" }, { children: [iconLeftName ? (_jsx(DrawerClose, Object.assign({ className: "h-6 w-6 p-0", onClick: onCloseClick }, { children: _jsx(Icon, { name: iconLeftName, color: "coreColors-secondaryIcon", className: "h-6 w-6 p-0", size: "md" }) }))) : (_jsx("div", { className: "w-6" })), _jsx(DrawerTitle, { children: title }), iconRightName ? (_jsx(DrawerClose, Object.assign({ className: "h-6 w-6 p-0", onClick: onCloseClick }, { children: _jsx(Icon, { name: iconRightName, color: "coreColors-secondaryIcon", className: "h-6 w-6 p-0", size: "md" }) }))) : (_jsx("div", { className: "w-6" }))] })));
|
|
79
79
|
};
|
|
@@ -81,4 +81,4 @@ const DrawerContent = (_a) => {
|
|
|
81
81
|
var { children, className } = _a, props = __rest(_a, ["children", "className"]);
|
|
82
82
|
return (_jsx("div", Object.assign({ className: cn("h-full overflow-scroll", className) }, props, { children: children })));
|
|
83
83
|
};
|
|
84
|
-
export { Drawer, DrawerPortal, DrawerOverlay, DrawerTrigger, DrawerClose, DrawerContentBase, DrawerContent, DrawerFooter, DrawerDescription, DrawerHeader, };
|
|
84
|
+
export { Drawer, DrawerPortal, DrawerOverlay, DrawerTrigger, DrawerClose, DrawerContentBase, DrawerContent, DrawerFooter, DrawerTitle, DrawerDescription, DrawerHeader, };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { transformOrderDetails } from "../components/hooks/use-order-details";
|
|
1
|
+
import { transformOrderDetails, getEffectiveLineQuantity, } from "../components/hooks/use-order-details";
|
|
2
2
|
// jsdom does not implement crypto.randomUUID, which transformOrderDetails
|
|
3
3
|
// uses for line uniqueIds
|
|
4
4
|
beforeAll(() => {
|
|
@@ -63,3 +63,86 @@ describe("transformOrderDetails — Shopify Order Editing (currentQuantity)", ()
|
|
|
63
63
|
expect(orderDetails.cart.lines[0].quantity).toBe(1);
|
|
64
64
|
});
|
|
65
65
|
});
|
|
66
|
+
describe("transformOrderDetails — New Customer Account API (no currentQuantity)", () => {
|
|
67
|
+
// Modeled on real prod data (Pink Palm Puff order #PPP95701331): the
|
|
68
|
+
// Customer Account API LineItem has NO `currentQuantity` — a line removed via
|
|
69
|
+
// Order Editing keeps `quantity: 1` and signals removal only via
|
|
70
|
+
// `refundableQuantity: 0`. The kept line has `refundableQuantity: 1`.
|
|
71
|
+
const editedOrder = {
|
|
72
|
+
id: "gid://shopify/Order/10929524212006",
|
|
73
|
+
currency: "USD",
|
|
74
|
+
edited: true,
|
|
75
|
+
financialStatus: "PAID",
|
|
76
|
+
refunds: [],
|
|
77
|
+
totalRefunded: { amount: "0.0", currencyCode: "USD" },
|
|
78
|
+
totalAmount: "63.88",
|
|
79
|
+
lineItems: [
|
|
80
|
+
{
|
|
81
|
+
title: "Couture Carryall in Blush Bay (Reversible)",
|
|
82
|
+
quantity: 1,
|
|
83
|
+
refundableQuantity: 0,
|
|
84
|
+
price: { amount: "99.0", currencyCode: "USD" },
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
title: "Malibu Slippers in Blush Bay",
|
|
88
|
+
quantity: 1,
|
|
89
|
+
refundableQuantity: 1,
|
|
90
|
+
price: { amount: "59.0", currencyCode: "USD" },
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
it("hides items removed via order editing on an edited order", () => {
|
|
95
|
+
const { orderDetails, checkoutData } = transformOrderDetails(editedOrder);
|
|
96
|
+
expect(orderDetails.cart.lines).toHaveLength(1);
|
|
97
|
+
expect(orderDetails.cart.lines[0].title).toBe("Malibu Slippers in Blush Bay");
|
|
98
|
+
expect(checkoutData.lineItems).toHaveLength(1);
|
|
99
|
+
expect(checkoutData.lineItems[0].title).toBe("Malibu Slippers in Blush Bay");
|
|
100
|
+
});
|
|
101
|
+
it("does NOT hide items on an UN-edited fully-refunded order", () => {
|
|
102
|
+
// Real prod data: cancelled+refunded order #PPP95700931 — edited:false,
|
|
103
|
+
// both lines refundableQuantity:0. These must still display in full.
|
|
104
|
+
const refundedOrder = Object.assign(Object.assign({}, editedOrder), { id: "gid://shopify/Order/10929514119462", edited: false, lineItems: editedOrder.lineItems.map((li) => (Object.assign(Object.assign({}, li), { refundableQuantity: 0 }))) });
|
|
105
|
+
const { orderDetails } = transformOrderDetails(refundedOrder);
|
|
106
|
+
expect(orderDetails.cart.lines).toHaveLength(2);
|
|
107
|
+
});
|
|
108
|
+
it("does NOT hide items on an EDITED order that also has a refund/return", () => {
|
|
109
|
+
// `edited` is sticky in Shopify and `refundableQuantity` drops on refunds
|
|
110
|
+
// too, so an edited-then-returned order must NOT collapse: fall back to
|
|
111
|
+
// ordered quantity rather than blank the order / hide bought items.
|
|
112
|
+
const editedAndRefunded = Object.assign(Object.assign({}, editedOrder), { financialStatus: "REFUNDED", totalRefunded: { amount: "171.06", currencyCode: "USD" }, refunds: [{ id: "gid://shopify/Refund/1", createdAt: "2026-07-08" }], lineItems: editedOrder.lineItems.map((li) => (Object.assign(Object.assign({}, li), { refundableQuantity: 0 }))) });
|
|
113
|
+
const { orderDetails } = transformOrderDetails(editedAndRefunded);
|
|
114
|
+
expect(orderDetails.cart.lines).toHaveLength(2);
|
|
115
|
+
});
|
|
116
|
+
it("reduces (not hides) a quantity edited down on an edited order", () => {
|
|
117
|
+
const { orderDetails } = transformOrderDetails(Object.assign(Object.assign({}, editedOrder), { lineItems: [
|
|
118
|
+
{
|
|
119
|
+
title: "Pink Palm Pajamas",
|
|
120
|
+
quantity: 3,
|
|
121
|
+
refundableQuantity: 2,
|
|
122
|
+
price: { amount: "95.0", currencyCode: "USD" },
|
|
123
|
+
},
|
|
124
|
+
] }));
|
|
125
|
+
expect(orderDetails.cart.lines).toHaveLength(1);
|
|
126
|
+
expect(orderDetails.cart.lines[0].quantity).toBe(2);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
describe("getEffectiveLineQuantity", () => {
|
|
130
|
+
it("prefers currentQuantity when present (Storefront API)", () => {
|
|
131
|
+
expect(getEffectiveLineQuantity({ edited: true }, { currentQuantity: 0, quantity: 1, refundableQuantity: 1 })).toBe(0);
|
|
132
|
+
});
|
|
133
|
+
it("uses refundableQuantity on edited orders without currentQuantity", () => {
|
|
134
|
+
expect(getEffectiveLineQuantity({ edited: true }, { quantity: 1, refundableQuantity: 0 })).toBe(0);
|
|
135
|
+
});
|
|
136
|
+
it("uses quantity on un-edited orders even when refundableQuantity differs", () => {
|
|
137
|
+
expect(getEffectiveLineQuantity({ edited: false }, { quantity: 3, refundableQuantity: 0 })).toBe(3);
|
|
138
|
+
});
|
|
139
|
+
it("ignores refundableQuantity on an edited order with a refund (refunds array)", () => {
|
|
140
|
+
expect(getEffectiveLineQuantity({ edited: true, refunds: [{ id: "r1" }] }, { quantity: 1, refundableQuantity: 0 })).toBe(1);
|
|
141
|
+
});
|
|
142
|
+
it("ignores refundableQuantity on an edited order with a refund (financialStatus)", () => {
|
|
143
|
+
expect(getEffectiveLineQuantity({ edited: true, financialStatus: "PARTIALLY_REFUNDED" }, { quantity: 3, refundableQuantity: 1 })).toBe(3);
|
|
144
|
+
});
|
|
145
|
+
it("ignores refundableQuantity on an edited order with totalRefunded > 0", () => {
|
|
146
|
+
expect(getEffectiveLineQuantity({ edited: true, totalRefunded: { amount: "50.0" } }, { quantity: 2, refundableQuantity: 0 })).toBe(2);
|
|
147
|
+
});
|
|
148
|
+
});
|