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

magento / pwa-studio / #25422

28 Apr 2025 07:31AM UTC coverage: 85.921%. First build
#25422

Pull #4460

codebuild

del22123
Fixed wishlist error on config product
Pull Request #4460: Fixed wish list error on config product

5245 of 6510 branches covered (80.57%)

Branch coverage included in aggregate %.

9 of 11 new or added lines in 1 file covered. (81.82%)

13247 of 15012 relevant lines covered (88.24%)

29.63 hits per line

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

82.0
/packages/peregrine/lib/talons/WishlistPage/useWishlistItem.js
1
import { useCallback, useMemo, useState } from 'react';
2
import { useMutation } from '@apollo/client';
3

4
import { useCartContext } from '@magento/peregrine/lib/context/cart';
5
import mergeOperations from '../../util/shallowMerge';
6
import defaultOperations from './wishlistItem.gql';
7
import { useEventingContext } from '../../context/eventing';
8

9
const SUPPORTED_PRODUCT_TYPES = ['SimpleProduct', 'ConfigurableProduct'];
1✔
10

11
const mergeSupportedProductTypes = (supportedProductTypes = []) => {
1✔
12
    const newSupportedProductTypes = [...SUPPORTED_PRODUCT_TYPES];
11✔
13

14
    if (supportedProductTypes) {
11!
15
        newSupportedProductTypes.push(...supportedProductTypes);
11✔
16
    }
17

18
    return newSupportedProductTypes;
11✔
19
};
20

21
/**
22
 * @function
23
 *
24
 * @param {String} props.item Wishlist Item data from GraphQL
25
 * @param {WishlistItemOperations} props.operations GraphQL operations for the Wishlist Item component
26
 * @param {String} props.wishlistId The ID of the wishlist this item belongs to
27
 *
28
 * @returns {WishlistItemProps}
29
 */
30
export const useWishlistItem = props => {
1✔
31
    const { item, onOpenAddToCartDialog, wishlistId } = props;
14✔
32

33
    const [, { dispatch }] = useEventingContext();
14✔
34

35
    const {
36
        configurable_options: selectedConfigurableOptions = [],
12✔
37
        id: itemId,
38
        product
39
    } = item;
14✔
40

41
    const {
42
        configurable_options: configurableOptions = [],
12✔
43
        __typename: productType,
44
        image,
45
        sku,
46
        stock_status: stockStatus
47
    } = product;
14✔
48
    const { label: imageLabel, url: imageURL } = image;
14✔
49

50
    const isSupportedProductType = useMemo(
14✔
51
        () =>
52
            mergeSupportedProductTypes(props.supportedProductTypes).includes(
11✔
53
                productType
54
            ),
55
        [props.supportedProductTypes, productType]
56
    );
57

58
    const operations = mergeOperations(defaultOperations, props.operations);
14✔
59
    const {
60
        addWishlistItemToCartMutation,
61
        removeProductsFromWishlistMutation
62
    } = operations;
14✔
63

64
    const [{ cartId }] = useCartContext();
14✔
65

66
    const [isRemovalInProgress, setIsRemovalInProgress] = useState(false);
14✔
67

68
    const [
69
        removeProductFromWishlistError,
70
        setRemoveProductFromWishlistError
71
    ] = useState(null);
14✔
72

73
    const cartItem = useMemo(() => {
14✔
74
        const item = {
14✔
75
            quantity: 1,
76
            sku
77
        };
78

79
        // Merge in additional input variables for configurable items
80
        if (
14✔
81
            selectedConfigurableOptions.length &&
15✔
82
            selectedConfigurableOptions.length === configurableOptions.length
83
        ) {
84
            const selectedOptionsArray = selectedConfigurableOptions
1✔
85
                .map(selectedOption => {
86
                    const {
87
                        id: attributeId,
88
                        value_id: selectedValueId
89
                    } = selectedOption;
1✔
90
                    const configurableOption = configurableOptions.find(
1✔
91
                        option => option.attribute_id_v2 === attributeId
1✔
92
                    );
93
                    if (configurableOption) {
1!
94
                        const configurableOptionValue = configurableOption.values.find(
1✔
95
                            optionValue =>
96
                                optionValue.value_index === selectedValueId
1✔
97
                        );
98

99
                        if (
1!
100
                            configurableOptionValue &&
2✔
101
                            configurableOptionValue.uid
102
                        ) {
103
                            return configurableOptionValue.uid;
1✔
104
                        }
105
                    }
NEW
106
                    return null;
×
107
                })
108
                .filter(uid => uid !== null);
1✔
109

110
            if (selectedOptionsArray.length > 0) {
1!
111
                Object.assign(item, {
1✔
112
                    selected_options: selectedOptionsArray
113
                });
114
            } else {
NEW
115
                return null;
×
116
            }
117
        }
118

119
        return item;
14✔
120
    }, [configurableOptions, selectedConfigurableOptions, sku]);
121

122
    const [
123
        addWishlistItemToCart,
124
        {
125
            error: addWishlistItemToCartError,
126
            loading: addWishlistItemToCartLoading
127
        }
128
    ] = useMutation(addWishlistItemToCartMutation, {
14✔
129
        variables: {
130
            cartId,
131
            cartItem
132
        }
133
    });
134

135
    const [removeProductsFromWishlist] = useMutation(
14✔
136
        removeProductsFromWishlistMutation,
137
        {
138
            update: cache => {
139
                // clean up for cache fav product on category page
140
                cache.modify({
1✔
141
                    id: 'ROOT_QUERY',
142
                    fields: {
143
                        customerWishlistProducts: cachedProducts =>
144
                            cachedProducts.filter(
×
145
                                productSku => productSku !== sku
×
146
                            )
147
                    }
148
                });
149

150
                cache.modify({
1✔
151
                    id: `CustomerWishlist:${wishlistId}`,
152
                    fields: {
153
                        items_v2: (cachedItems, { readField, Remove }) => {
154
                            for (var i = 0; i < cachedItems.items.length; i++) {
×
155
                                if (readField('id', item) === itemId) {
×
156
                                    return Remove;
×
157
                                }
158
                            }
159

160
                            return cachedItems;
×
161
                        }
162
                    }
163
                });
164
            },
165
            variables: {
166
                wishlistId: wishlistId,
167
                wishlistItemsId: [itemId]
168
            }
169
        }
170
    );
171

172
    const handleAddToCart = useCallback(async () => {
14✔
173
        if (
3✔
174
            configurableOptions.length === 0 ||
4✔
175
            selectedConfigurableOptions.length === configurableOptions.length
176
        ) {
177
            try {
2✔
178
                await addWishlistItemToCart();
2✔
179

180
                const selectedOptionsLabels =
181
                    selectedConfigurableOptions?.length > 0
2!
182
                        ? selectedConfigurableOptions?.map(
183
                              ({ option_label, value_label }) => ({
×
184
                                  attribute: option_label,
185
                                  value: value_label
186
                              })
187
                          )
188
                        : null;
189

190
                dispatch({
2✔
191
                    type: 'CART_ADD_ITEM',
192
                    payload: {
193
                        cartId,
194
                        sku: item.product.sku,
195
                        name: item.product.name,
196
                        pricing: item.product.price,
197
                        priceTotal:
198
                            item.product.price_range.maximum_price.final_price
199
                                .value,
200
                        currencyCode:
201
                            item.product.price_range.maximum_price.final_price
202
                                .currency,
203
                        discountAmount:
204
                            item.product.price_range.maximum_price.discount
205
                                .amount_off,
206
                        selectedOptions: selectedOptionsLabels,
207
                        quantity: 1
208
                    }
209
                });
210
            } catch (error) {
211
                console.error(error);
×
212
            }
213
        } else {
214
            onOpenAddToCartDialog(item);
1✔
215
        }
216
    }, [
217
        addWishlistItemToCart,
218
        cartId,
219
        configurableOptions.length,
220
        dispatch,
221
        item,
222
        onOpenAddToCartDialog,
223
        selectedConfigurableOptions
224
    ]);
225

226
    const handleRemoveProductFromWishlist = useCallback(async () => {
14✔
227
        try {
3✔
228
            setIsRemovalInProgress(true);
3✔
229
            await removeProductsFromWishlist();
3✔
230
        } catch (e) {
231
            setIsRemovalInProgress(false);
1✔
232
            console.error(e);
1✔
233
            setRemoveProductFromWishlistError(e);
1✔
234
            if (process.env.NODE_ENV !== 'production') {
1!
235
                console.error(e);
1✔
236
            }
237
        }
238
    }, [removeProductsFromWishlist, setRemoveProductFromWishlistError]);
239

240
    const isInStock = stockStatus !== 'OUT_OF_STOCK';
14✔
241
    const addToCartButtonProps = useMemo(() => {
14✔
242
        return {
14✔
243
            disabled: addWishlistItemToCartLoading || !isInStock,
25✔
244
            onClick: handleAddToCart
245
        };
246
    }, [addWishlistItemToCartLoading, handleAddToCart, isInStock]);
247

248
    const imageProps = useMemo(() => {
14✔
249
        return {
11✔
250
            alt: imageLabel,
251
            src: imageURL,
252
            width: 400
253
        };
254
    }, [imageLabel, imageURL]);
255

256
    return {
14✔
257
        addToCartButtonProps,
258
        isRemovalInProgress,
259
        handleRemoveProductFromWishlist,
260
        hasError: !!addWishlistItemToCartError,
261
        hasRemoveProductFromWishlistError: !!removeProductFromWishlistError,
262
        imageProps,
263
        isSupportedProductType,
264
        isInStock
265
    };
266
};
267

268
/**
269
 * JSDoc type definitions
270
 */
271

272
/**
273
 * GraphQL operations for the Wishlist Item component
274
 *
275
 * @typedef {Object} WishlistItemOperations
276
 *
277
 * @property {GraphQLDocument} addWishlistItemToCartMutation Mutation to add item to the cart
278
 * @property {GraphQLDocument} removeProductsFromWishlistMutation Mutation to remove a product from a wishlist
279
 *
280
 * @see [`wishlistItem.gql.js`]{@link https://github.com/magento/pwa-studio/blob/develop/packages/venia-ui/lib/components/WishlistPage/wishlistItem.gql.js}
281
 * for queries used in Venia
282
 */
283

284
/**
285
 * Props data to use when rendering the Wishlist Item component
286
 *
287
 * @typedef {Object} WishlistItemProps
288
 *
289
 * @property {Function} handleRemoveProductFromWishlist Callback to actually remove product from wishlist
290
 * @property {Boolean} hasError Boolean which represents if there was an error adding the wishlist item to cart
291
 * @property {Boolean} hasRemoveProductFromWishlistError If there was an error removing a product from the wishlist
292
 * @property {Boolean} isRemovalInProgress Whether the remove product from wishlist operation is in progress
293
 * @property {Boolean} isSupportedProductType is this product type suported
294
 * @property {Boolean} isInStock is product in stock
295
 */
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