• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

magento / pwa-studio / #25627

08 Sep 2025 11:02AM UTC coverage: 85.621%. First build
#25627

Pull #4545

codebuild

del15881
Fixing Failing Test Cases for ADD TO CART Dialog box
Pull Request #4545: Enhancement/pwa 3550

5262 of 6570 branches covered (80.09%)

Branch coverage included in aggregate %.

50 of 80 new or added lines in 5 files covered. (62.5%)

13280 of 15086 relevant lines covered (88.03%)

29.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.91
/packages/peregrine/lib/talons/AddToCartDialog/useAddToCartDialog.js
1
import { useCallback, useEffect, useMemo, useState } from 'react';
2
import { useMutation, useQuery, gql } from '@apollo/client';
3

4
import mergeOperations from '../../util/shallowMerge';
5
import { useCartContext } from '../../context/cart';
6
import defaultOperations from './addToCartDialog.gql';
7
import { useEventingContext } from '../../context/eventing';
8
import { isProductConfigurable } from '@magento/peregrine/lib/util/isProductConfigurable';
9
import { getOutOfStockVariants } from '@magento/peregrine/lib/util/getOutOfStockVariants';
10
import { useAwaitQuery } from '@magento/peregrine/lib/hooks/useAwaitQuery';
11
import BrowserPersistence from '../../util/simplePersistence';
12

13
export const useAddToCartDialog = props => {
1✔
14
    const { item, onClose } = props;
66✔
15
    const sku = item && item.product?.sku;
66✔
16

17
    const [, { dispatch }] = useEventingContext();
66✔
18

19
    const operations = mergeOperations(defaultOperations, props.operations);
66✔
20

21
    const [userSelectedOptions, setUserSelectedOptions] = useState(new Map());
66✔
22
    const [currentImage, setCurrentImage] = useState();
66✔
23
    const [currentPrice, setCurrentPrice] = useState();
66✔
24
    const [currentDiscount, setCurrentDiscount] = useState();
66✔
25
    const [singleOptionSelection, setSingleOptionSelection] = useState();
66✔
26
    const [multipleOptionSelections, setMultipleOptionSelections] = useState(
66✔
27
        new Map()
28
    );
29

30
    //const [{ cartId }] = useCartContext();
31

32
    const [cartState, cartApi] = useCartContext();
66✔
33

34
    const { cartId } = cartState;
66✔
35

36
    // cart creation logic
37

38
    const CREATE_CART_MUTATION = gql`
66✔
39
        mutation createCart {
40
            cartId: createEmptyCart
41
        }
42
    `;
43

44
    const CART_DETAILS_QUERY = gql`
66✔
45
        query checkUserIsAuthed($cartId: String!) {
46
            cart(cart_id: $cartId) {
47
                id
48
            }
49
        }
50
    `;
51

52
    const [fetchCartId] = useMutation(CREATE_CART_MUTATION);
66✔
53

54
    const fetchCartDetails = useAwaitQuery(CART_DETAILS_QUERY);
66✔
55

56
    const ensureCartId = useCallback(async () => {
66✔
57
        let newCartId = cartId;
3✔
58

59
        if (!newCartId) {
3!
NEW
60
            await cartApi.getCartDetails({
×
61
                fetchCartId,
62

63
                fetchCartDetails
64
            });
65

NEW
66
            newCartId = new BrowserPersistence().getItem('cartId');
×
67

NEW
68
            if (!newCartId) {
×
NEW
69
                throw new Error('Failed to create a new cart');
×
70
            }
71
        }
72

73
        return newCartId;
3✔
74
    }, [cartId, cartApi, fetchCartId, fetchCartDetails]);
75

76
    const optionCodes = useMemo(() => {
66✔
77
        const optionCodeMap = new Map();
10✔
78
        if (item) {
10✔
79
            item.product?.configurable_options.forEach(option => {
9✔
80
                optionCodeMap.set(option.attribute_id, option.attribute_code);
14✔
81
            });
82
        }
83
        return optionCodeMap;
10✔
84
    }, [item]);
85

86
    // Check if display out of stock products option is selected in the Admin Dashboard
87
    const isOutOfStockProductDisplayed = useMemo(() => {
66✔
88
        if (item) {
10✔
89
            let totalVariants = 1;
9✔
90
            const { product } = item;
9✔
91
            const isConfigurable = isProductConfigurable(product);
9✔
92
            if (product?.configurable_options && isConfigurable) {
9!
93
                for (const option of product.configurable_options) {
×
94
                    const length = option.values.length;
×
95
                    totalVariants = totalVariants * length;
×
96
                }
97
                return product.variants.length === totalVariants;
×
98
            }
99
        }
100
    }, [item]);
101

102
    const outOfStockVariants = useMemo(() => {
66✔
103
        if (item) {
21✔
104
            const product = item.product;
20✔
105
            return getOutOfStockVariants(
20✔
106
                product,
107
                optionCodes,
108
                singleOptionSelection,
109
                multipleOptionSelections,
110
                isOutOfStockProductDisplayed
111
            );
112
        }
113
    }, [
114
        item,
115
        optionCodes,
116
        singleOptionSelection,
117
        multipleOptionSelections,
118
        isOutOfStockProductDisplayed
119
    ]);
120

121
    const selectedOptionsArray = useMemo(() => {
66✔
122
        if (item) {
20✔
123
            const existingOptionsMap = item.configurable_options.reduce(
19✔
124
                (optionsMap, selectedOption) => {
125
                    return optionsMap.set(
23✔
126
                        selectedOption.id,
127
                        selectedOption.value_id
128
                    );
129
                },
130
                new Map()
131
            );
132
            const mergedOptionsMap = new Map([
19✔
133
                ...existingOptionsMap,
134
                ...userSelectedOptions
135
            ]);
136

137
            const selectedOptions = [];
19✔
138
            mergedOptionsMap.forEach((selectedValueId, attributeId) => {
19✔
139
                const configurableOption = item.product?.configurable_options.find(
31✔
140
                    option => option.attribute_id_v2 === attributeId
25✔
141
                );
142
                const configurableOptionValue = configurableOption?.values.find(
31✔
143
                    optionValue => optionValue.value_index === selectedValueId
25✔
144
                );
145

146
                selectedOptions.push(configurableOptionValue?.uid);
31✔
147
            });
148

149
            return selectedOptions;
19✔
150
        }
151

152
        return [];
1✔
153
    }, [item, userSelectedOptions]);
154

155
    const { data, loading: isFetchingProductDetail } = useQuery(
66✔
156
        operations.getProductDetailQuery,
157
        {
158
            fetchPolicy: 'cache-and-network',
159
            nextFetchPolicy: 'cache-first',
160
            variables: {
161
                configurableOptionValues: selectedOptionsArray.length
66✔
162
                    ? selectedOptionsArray
163
                    : null,
164
                sku
165
            },
166
            skip: !sku
167
        }
168
    );
169

170
    const [
171
        addProductToCart,
172
        { error: addProductToCartError, loading: isAddingToCart }
173
    ] = useMutation(operations.addProductToCartMutation);
66✔
174

175
    useEffect(() => {
66✔
176
        if (data) {
38✔
177
            const product = data.products.items[0];
21✔
178
            console.log('useAddToCartDialog.js - data', data);
21✔
179
            const {
180
                media_gallery: selectedProductMediaGallery,
181
                variant: selectedVariant
182
            } = product.configurable_product_options_selection;
21✔
183

184
            const currentImage =
185
                selectedProductMediaGallery.length &&
21✔
186
                selectedOptionsArray.length
187
                    ? selectedProductMediaGallery[0]
188
                    : product.image;
189

190
            setCurrentImage(currentImage);
21✔
191

192
            const finalPrice = selectedVariant
21✔
193
                ? selectedVariant.price_range.maximum_price.final_price
194
                : product.price_range.maximum_price.final_price;
195

196
            const discount = selectedVariant
21✔
197
                ? selectedVariant.price_range.maximum_price.discount
198
                : product.price_range.maximum_price.discount;
199

200
            setCurrentDiscount(discount);
21✔
201
            setCurrentPrice(finalPrice);
21✔
202
        }
203
    }, [data, selectedOptionsArray.length]);
204

205
    const handleOnClose = useCallback(() => {
66✔
206
        onClose();
3✔
207
        setCurrentImage();
3✔
208
        setCurrentPrice();
3✔
209
        setUserSelectedOptions(new Map());
3✔
210
        setMultipleOptionSelections(new Map());
3✔
211
    }, [onClose]);
212

213
    const handleOptionSelection = useCallback(
66✔
214
        (optionId, value) => {
215
            setUserSelectedOptions(existing =>
7✔
216
                new Map(existing).set(parseInt(optionId), value)
7✔
217
            );
218
            // Create a new Map to keep track of user single selection with key as String
219
            const nextSingleOptionSelection = new Map();
7✔
220
            nextSingleOptionSelection.set(optionId, value);
7✔
221
            setSingleOptionSelection(nextSingleOptionSelection);
7✔
222
            // Create a new Map to keep track of multiple selections with key as String
223
            const nextMultipleOptionSelections = new Map([
7✔
224
                ...multipleOptionSelections
225
            ]);
226
            nextMultipleOptionSelections.set(optionId, value);
7✔
227
            setMultipleOptionSelections(nextMultipleOptionSelections);
7✔
228
        },
229
        [multipleOptionSelections]
230
    );
231

232
    const handleAddToCart = useCallback(async () => {
66✔
233
        //console.log("useAddToCartDialog.js handleAddToCart is called for ",cartId);
234
        try {
3✔
235
            const ensuredCartId = await ensureCartId();
3✔
236
            const quantity = 1;
3✔
237

238
            await addProductToCart({
3✔
239
                variables: {
240
                    cartId: ensuredCartId,
241
                    cartItem: {
242
                        quantity,
243
                        selected_options: selectedOptionsArray,
244
                        sku
245
                    }
246
                }
247
            });
248

249
            const selectedOptionsLabels =
250
                selectedOptionsArray?.map((value, i) => ({
4!
251
                    attribute: item.product.configurable_options[i].label,
252
                    value:
253
                        item.product.configurable_options[i].values.find(
4!
254
                            x => x.uid === value
6✔
255
                        )?.label || null
256
                })) || null;
257

258
            dispatch({
2✔
259
                type: 'CART_ADD_ITEM',
260
                payload: {
261
                    cartId: ensuredCartId,
262
                    sku: item.product.sku,
263
                    name: item.product.name,
264
                    pricing: item.product.price,
265
                    priceTotal: currentPrice.value,
266
                    currencyCode: currentPrice.currency,
267
                    discountAmount: currentDiscount.amount_off,
268
                    selectedOptions: selectedOptionsLabels,
269
                    quantity
270
                }
271
            });
272

273
            handleOnClose();
2✔
274
        } catch (error) {
275
            console.error(error);
1✔
276
        }
277
    }, [
278
        addProductToCart,
279
        ensureCartId,
280
        currentDiscount,
281
        currentPrice,
282
        dispatch,
283
        handleOnClose,
284
        item,
285
        selectedOptionsArray,
286
        sku
287
    ]);
288

289
    const imageProps = useMemo(() => {
66✔
290
        if (currentImage) {
29✔
291
            return {
16✔
292
                alt: currentImage.label,
293
                src: currentImage.url,
294
                width: 400
295
            };
296
        }
297
    }, [currentImage]);
298

299
    const priceProps = useMemo(() => {
66✔
300
        if (currentPrice) {
29✔
301
            return {
16✔
302
                currencyCode: currentPrice.currency,
303
                value: currentPrice.value
304
            };
305
        }
306
    }, [currentPrice]);
307

308
    const configurableOptionProps = useMemo(() => {
66✔
309
        if (item) {
20✔
310
            return {
19✔
311
                onSelectionChange: handleOptionSelection,
312
                options: item.product?.configurable_options,
313
                selectedValues: item.configurable_options
314
            };
315
        }
316
    }, [handleOptionSelection, item]);
317

318
    const buttonProps = useMemo(() => {
66✔
319
        if (item) {
42✔
320
            return {
41✔
321
                disabled:
322
                    item.product?.configurable_options.length !==
58✔
323
                        selectedOptionsArray.length || isAddingToCart,
324
                onClick: handleAddToCart,
325
                priority: 'high'
326
            };
327
        }
328
    }, [handleAddToCart, isAddingToCart, item, selectedOptionsArray.length]);
329

330
    return {
66✔
331
        buttonProps,
332
        configurableOptionProps,
333
        formErrors: [addProductToCartError],
334
        handleOnClose,
335
        outOfStockVariants,
336
        imageProps,
337
        isFetchingProductDetail,
338
        priceProps
339
    };
340
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc