@salesforce/retail-react-app 1.0.0-preview.0
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/.eslintignore +7 -0
- package/.eslintrc.js +25 -0
- package/.prettierignore +4 -0
- package/.prettierrc.yaml +7 -0
- package/CHANGELOG.md +173 -0
- package/LICENSE +14 -0
- package/README.md +48 -0
- package/app/assets/svg/account.svg +3 -0
- package/app/assets/svg/alert.svg +3 -0
- package/app/assets/svg/basket.svg +3 -0
- package/app/assets/svg/brand-logo.svg +10 -0
- package/app/assets/svg/cc-amex.svg +7 -0
- package/app/assets/svg/cc-cvv.svg +8 -0
- package/app/assets/svg/cc-discover.svg +14 -0
- package/app/assets/svg/cc-mastercard.svg +8 -0
- package/app/assets/svg/cc-visa.svg +11 -0
- package/app/assets/svg/check-circle.svg +3 -0
- package/app/assets/svg/check.svg +3 -0
- package/app/assets/svg/chevron-down.svg +3 -0
- package/app/assets/svg/chevron-left.svg +3 -0
- package/app/assets/svg/chevron-right.svg +3 -0
- package/app/assets/svg/chevron-up.svg +3 -0
- package/app/assets/svg/close.svg +3 -0
- package/app/assets/svg/dashboard.svg +4 -0
- package/app/assets/svg/figma-logo.svg +14 -0
- package/app/assets/svg/file.svg +3 -0
- package/app/assets/svg/filter.svg +3 -0
- package/app/assets/svg/flag-ca.svg +5 -0
- package/app/assets/svg/flag-cn.svg +19 -0
- package/app/assets/svg/flag-fr.svg +19 -0
- package/app/assets/svg/flag-gb.svg +16 -0
- package/app/assets/svg/flag-it.svg +29 -0
- package/app/assets/svg/flag-jp.svg +10 -0
- package/app/assets/svg/flag-us.svg +7 -0
- package/app/assets/svg/github-logo.svg +40 -0
- package/app/assets/svg/hamburger.svg +8 -0
- package/app/assets/svg/heart-solid.svg +7 -0
- package/app/assets/svg/heart.svg +3 -0
- package/app/assets/svg/info.svg +3 -0
- package/app/assets/svg/like.svg +4 -0
- package/app/assets/svg/location.svg +3 -0
- package/app/assets/svg/lock.svg +3 -0
- package/app/assets/svg/paypal.svg +19 -0
- package/app/assets/svg/plug.svg +3 -0
- package/app/assets/svg/plus.svg +3 -0
- package/app/assets/svg/receipt.svg +3 -0
- package/app/assets/svg/search.svg +8 -0
- package/app/assets/svg/signout.svg +3 -0
- package/app/assets/svg/social-facebook.svg +3 -0
- package/app/assets/svg/social-instagram.svg +3 -0
- package/app/assets/svg/social-pinterest.svg +4 -0
- package/app/assets/svg/social-twitter.svg +3 -0
- package/app/assets/svg/social-youtube.svg +3 -0
- package/app/assets/svg/user.svg +3 -0
- package/app/assets/svg/visibility-off.svg +5 -0
- package/app/assets/svg/visibility.svg +3 -0
- package/app/components/_app/index.jsx +401 -0
- package/app/components/_app/index.test.js +85 -0
- package/app/components/_app/partials/above-header.jsx +10 -0
- package/app/components/_app-config/index.jsx +125 -0
- package/app/components/_app-config/index.test.js +77 -0
- package/app/components/_error/index.jsx +142 -0
- package/app/components/_error/index.test.js +25 -0
- package/app/components/action-card/index.jsx +75 -0
- package/app/components/address-display/index.jsx +30 -0
- package/app/components/basic-tile/index.jsx +65 -0
- package/app/components/basic-tile/index.test.js +23 -0
- package/app/components/breadcrumb/index.jsx +67 -0
- package/app/components/breadcrumb/index.test.js +30 -0
- package/app/components/confirmation-modal/index.jsx +111 -0
- package/app/components/confirmation-modal/index.test.js +98 -0
- package/app/components/drawer-menu/index.jsx +405 -0
- package/app/components/drawer-menu/index.test.js +33 -0
- package/app/components/dynamic-image/index.jsx +56 -0
- package/app/components/field/index.jsx +161 -0
- package/app/components/footer/index.jsx +269 -0
- package/app/components/footer/index.test.js +22 -0
- package/app/components/forms/address-fields.jsx +49 -0
- package/app/components/forms/credit-card-fields.jsx +149 -0
- package/app/components/forms/form-action-buttons.jsx +55 -0
- package/app/components/forms/login-fields.jsx +31 -0
- package/app/components/forms/password-requirements.jsx +99 -0
- package/app/components/forms/post-checkout-registration-fields.jsx +43 -0
- package/app/components/forms/profile-fields.jsx +36 -0
- package/app/components/forms/promo-code-fields.jsx +43 -0
- package/app/components/forms/registration-fields.jsx +42 -0
- package/app/components/forms/reset-password-fields.jsx +31 -0
- package/app/components/forms/state-province-options.jsx +75 -0
- package/app/components/forms/update-password-fields.jsx +49 -0
- package/app/components/forms/useAddressFields.jsx +196 -0
- package/app/components/forms/useCreditCardFields.jsx +146 -0
- package/app/components/forms/useLoginFields.jsx +52 -0
- package/app/components/forms/useProfileFields.jsx +95 -0
- package/app/components/forms/usePromoCodeFields.jsx +39 -0
- package/app/components/forms/useRegistrationFields.jsx +136 -0
- package/app/components/forms/useResetPasswordFields.jsx +40 -0
- package/app/components/forms/useUpdatePasswordFields.jsx +89 -0
- package/app/components/header/index.jsx +290 -0
- package/app/components/header/index.test.js +217 -0
- package/app/components/hero/index.jsx +84 -0
- package/app/components/hero/index.test.js +40 -0
- package/app/components/icons/index.jsx +158 -0
- package/app/components/icons/index.test.js +20 -0
- package/app/components/image-gallery/index.jsx +176 -0
- package/app/components/image-gallery/index.test.js +485 -0
- package/app/components/item-variant/index.jsx +33 -0
- package/app/components/item-variant/item-attributes.jsx +107 -0
- package/app/components/item-variant/item-image.jsx +73 -0
- package/app/components/item-variant/item-name.jsx +28 -0
- package/app/components/item-variant/item-price.jsx +117 -0
- package/app/components/link/index.jsx +32 -0
- package/app/components/link/index.test.js +72 -0
- package/app/components/links-list/index.jsx +89 -0
- package/app/components/links-list/index.test.js +62 -0
- package/app/components/list-menu/index.jsx +280 -0
- package/app/components/list-menu/index.test.js +44 -0
- package/app/components/loading-spinner/index.jsx +46 -0
- package/app/components/locale-selector/index.jsx +124 -0
- package/app/components/locale-selector/index.test.js +37 -0
- package/app/components/locale-text/index.jsx +97 -0
- package/app/components/locale-text/index.test.js +36 -0
- package/app/components/login/index.jsx +96 -0
- package/app/components/nested-accordion/index.jsx +185 -0
- package/app/components/nested-accordion/index.test.js +98 -0
- package/app/components/offline-banner/index.jsx +40 -0
- package/app/components/offline-banner/index.test.js +15 -0
- package/app/components/offline-boundary/index.jsx +104 -0
- package/app/components/offline-boundary/index.test.js +123 -0
- package/app/components/order-summary/index.jsx +331 -0
- package/app/components/page-action-placeholder/index.jsx +50 -0
- package/app/components/pagination/index.jsx +134 -0
- package/app/components/pagination/index.test.js +25 -0
- package/app/components/product-item/index.jsx +146 -0
- package/app/components/product-item/index.test.js +38 -0
- package/app/components/product-scroller/index.jsx +172 -0
- package/app/components/product-scroller/index.test.js +98 -0
- package/app/components/product-tile/index.jsx +195 -0
- package/app/components/product-tile/index.test.js +96 -0
- package/app/components/product-view/index.jsx +538 -0
- package/app/components/product-view/index.test.js +224 -0
- package/app/components/product-view-modal/index.jsx +48 -0
- package/app/components/product-view-modal/index.test.js +72 -0
- package/app/components/promo-code/index.jsx +162 -0
- package/app/components/promo-popover/index.jsx +83 -0
- package/app/components/quantity-picker/index.jsx +58 -0
- package/app/components/radio-card/index.jsx +75 -0
- package/app/components/recommended-products/index.jsx +227 -0
- package/app/components/register/index.jsx +114 -0
- package/app/components/reset-password/index.jsx +87 -0
- package/app/components/responsive/index.jsx +29 -0
- package/app/components/scroll-to-top/index.jsx +24 -0
- package/app/components/scroll-to-top/index.test.js +46 -0
- package/app/components/search/index.jsx +279 -0
- package/app/components/search/index.test.js +127 -0
- package/app/components/search/partials/recent-searches.jsx +76 -0
- package/app/components/search/partials/search-suggestions.jsx +45 -0
- package/app/components/search/partials/suggestions.jsx +43 -0
- package/app/components/section/index.jsx +68 -0
- package/app/components/seo/index.jsx +33 -0
- package/app/components/social-icons/index.jsx +101 -0
- package/app/components/social-icons/index.test.js +30 -0
- package/app/components/swatch-group/index.jsx +77 -0
- package/app/components/swatch-group/index.test.js +136 -0
- package/app/components/swatch-group/swatch.jsx +94 -0
- package/app/components/toggle-card/index.jsx +97 -0
- package/app/components/with-registration/index.jsx +58 -0
- package/app/components/with-registration/index.test.js +85 -0
- package/app/constants.js +109 -0
- package/app/contexts/index.js +92 -0
- package/app/hooks/einstein-mock-data.js +916 -0
- package/app/hooks/index.js +17 -0
- package/app/hooks/use-add-to-cart-modal.js +344 -0
- package/app/hooks/use-add-to-cart-modal.test.js +625 -0
- package/app/hooks/use-auth-modal.js +337 -0
- package/app/hooks/use-auth-modal.test.js +365 -0
- package/app/hooks/use-currency.js +20 -0
- package/app/hooks/use-currency.test.js +41 -0
- package/app/hooks/use-current-basket.js +39 -0
- package/app/hooks/use-current-customer.js +29 -0
- package/app/hooks/use-derived-product.js +77 -0
- package/app/hooks/use-derived-product.test.js +69 -0
- package/app/hooks/use-einstein.js +512 -0
- package/app/hooks/use-einstein.test.js +224 -0
- package/app/hooks/use-intersection-observer.js +64 -0
- package/app/hooks/use-limit-urls.js +31 -0
- package/app/hooks/use-limit-urls.test.js +40 -0
- package/app/hooks/use-multi-site.js +36 -0
- package/app/hooks/use-multi-site.test.js +53 -0
- package/app/hooks/use-navigation.js +37 -0
- package/app/hooks/use-navigation.test.js +109 -0
- package/app/hooks/use-page-urls.js +35 -0
- package/app/hooks/use-page-urls.test.js +39 -0
- package/app/hooks/use-pdp-search-params.js +16 -0
- package/app/hooks/use-pdp-search-params.test.js +52 -0
- package/app/hooks/use-previous.js +17 -0
- package/app/hooks/use-product-view-modal.js +93 -0
- package/app/hooks/use-product-view-modal.test.js +172 -0
- package/app/hooks/use-search-params.js +96 -0
- package/app/hooks/use-search-params.test.js +91 -0
- package/app/hooks/use-sort-urls.js +33 -0
- package/app/hooks/use-sort-urls.test.js +42 -0
- package/app/hooks/use-toast.js +68 -0
- package/app/hooks/use-toast.test.js +58 -0
- package/app/hooks/use-variant.js +32 -0
- package/app/hooks/use-variant.test.js +81 -0
- package/app/hooks/use-variation-attributes.js +138 -0
- package/app/hooks/use-variation-attributes.test.js +119 -0
- package/app/hooks/use-variation-params.js +31 -0
- package/app/hooks/use-variation-params.test.js +73 -0
- package/app/hooks/use-wish-list.js +42 -0
- package/app/main.jsx +14 -0
- package/app/mocks/basket-with-suit.js +146 -0
- package/app/mocks/empty-basket.js +39 -0
- package/app/mocks/mock-data.js +5632 -0
- package/app/mocks/product-set-winter-lookM.js +1224 -0
- package/app/mocks/searchResults.js +144 -0
- package/app/mocks/variant-750518699578M.js +434 -0
- package/app/page-designer/README.md +102 -0
- package/app/page-designer/assets/image-tile/index.jsx +51 -0
- package/app/page-designer/assets/image-tile/index.test.js +30 -0
- package/app/page-designer/assets/image-with-text/index.jsx +140 -0
- package/app/page-designer/assets/image-with-text/index.test.js +38 -0
- package/app/page-designer/assets/index.js +9 -0
- package/app/page-designer/index.js +10 -0
- package/app/page-designer/layouts/carousel/index.jsx +222 -0
- package/app/page-designer/layouts/carousel/index.test.js +43 -0
- package/app/page-designer/layouts/index.js +14 -0
- package/app/page-designer/layouts/mobileGrid1r1c/index.jsx +36 -0
- package/app/page-designer/layouts/mobileGrid1r1c/index.test.js +35 -0
- package/app/page-designer/layouts/mobileGrid2r1c/index.jsx +37 -0
- package/app/page-designer/layouts/mobileGrid2r1c/index.test.js +47 -0
- package/app/page-designer/layouts/mobileGrid2r2c/index.jsx +37 -0
- package/app/page-designer/layouts/mobileGrid2r2c/index.test.js +71 -0
- package/app/page-designer/layouts/mobileGrid2r3c/index.jsx +37 -0
- package/app/page-designer/layouts/mobileGrid2r3c/index.test.js +95 -0
- package/app/page-designer/layouts/mobileGrid3r1c/index.jsx +37 -0
- package/app/page-designer/layouts/mobileGrid3r1c/index.test.js +59 -0
- package/app/page-designer/layouts/mobileGrid3r2c/index.jsx +37 -0
- package/app/page-designer/layouts/mobileGrid3r2c/index.test.js +95 -0
- package/app/page-designer/utils.js +14 -0
- package/app/pages/account/addresses.jsx +382 -0
- package/app/pages/account/addresses.test.js +120 -0
- package/app/pages/account/constant.js +57 -0
- package/app/pages/account/index.jsx +237 -0
- package/app/pages/account/index.test.js +188 -0
- package/app/pages/account/order-detail.jsx +397 -0
- package/app/pages/account/order-history.jsx +264 -0
- package/app/pages/account/orders.jsx +30 -0
- package/app/pages/account/orders.test.js +95 -0
- package/app/pages/account/profile.jsx +357 -0
- package/app/pages/account/wishlist/index.jsx +195 -0
- package/app/pages/account/wishlist/index.mock.js +1481 -0
- package/app/pages/account/wishlist/index.test.js +56 -0
- package/app/pages/account/wishlist/partials/wishlist-primary-action.jsx +170 -0
- package/app/pages/account/wishlist/partials/wishlist-primary-action.mock.js +1623 -0
- package/app/pages/account/wishlist/partials/wishlist-primary-action.test.js +99 -0
- package/app/pages/account/wishlist/partials/wishlist-secondary-button-group.jsx +120 -0
- package/app/pages/account/wishlist/partials/wishlist-secondary-button-group.test.js +391 -0
- package/app/pages/cart/index.jsx +476 -0
- package/app/pages/cart/index.test.js +481 -0
- package/app/pages/cart/partials/cart-cta.jsx +46 -0
- package/app/pages/cart/partials/cart-secondary-button-group.jsx +135 -0
- package/app/pages/cart/partials/cart-secondary-button-group.test.js +103 -0
- package/app/pages/cart/partials/cart-skeleton.jsx +93 -0
- package/app/pages/cart/partials/cart-title.jsx +27 -0
- package/app/pages/cart/partials/empty-cart.jsx +86 -0
- package/app/pages/checkout/confirmation.jsx +541 -0
- package/app/pages/checkout/confirmation.mock.js +450 -0
- package/app/pages/checkout/confirmation.test.js +114 -0
- package/app/pages/checkout/index.jsx +169 -0
- package/app/pages/checkout/index.test.js +582 -0
- package/app/pages/checkout/partials/cc-radio-group.jsx +122 -0
- package/app/pages/checkout/partials/checkout-footer.jsx +140 -0
- package/app/pages/checkout/partials/checkout-footer.test.js +16 -0
- package/app/pages/checkout/partials/checkout-header.jsx +54 -0
- package/app/pages/checkout/partials/checkout-header.test.js +16 -0
- package/app/pages/checkout/partials/checkout-skeleton.jsx +52 -0
- package/app/pages/checkout/partials/contact-info.jsx +251 -0
- package/app/pages/checkout/partials/contact-info.test.js +43 -0
- package/app/pages/checkout/partials/payment-form.jsx +97 -0
- package/app/pages/checkout/partials/payment.jsx +276 -0
- package/app/pages/checkout/partials/shipping-address-selection.jsx +377 -0
- package/app/pages/checkout/partials/shipping-address.jsx +132 -0
- package/app/pages/checkout/partials/shipping-options.jsx +232 -0
- package/app/pages/checkout/util/checkout-context.js +94 -0
- package/app/pages/home/data.js +134 -0
- package/app/pages/home/index.jsx +301 -0
- package/app/pages/home/index.test.js +23 -0
- package/app/pages/login/index.jsx +123 -0
- package/app/pages/login/index.test.js +229 -0
- package/app/pages/login-redirect/index.jsx +23 -0
- package/app/pages/login-redirect/index.test.js +16 -0
- package/app/pages/page-not-found/index.jsx +90 -0
- package/app/pages/page-not-found/index.test.js +31 -0
- package/app/pages/product-detail/index.jsx +394 -0
- package/app/pages/product-detail/index.mock.js +197 -0
- package/app/pages/product-detail/index.test.js +162 -0
- package/app/pages/product-detail/partials/information-accordion.jsx +121 -0
- package/app/pages/product-list/index.jsx +735 -0
- package/app/pages/product-list/index.test.js +180 -0
- package/app/pages/product-list/partials/above-page-header.jsx +10 -0
- package/app/pages/product-list/partials/checkbox-refinements.jsx +41 -0
- package/app/pages/product-list/partials/checkbox-refinements.test.js +53 -0
- package/app/pages/product-list/partials/color-refinements.jsx +88 -0
- package/app/pages/product-list/partials/empty-results.jsx +118 -0
- package/app/pages/product-list/partials/link-refinements.jsx +38 -0
- package/app/pages/product-list/partials/page-header.jsx +42 -0
- package/app/pages/product-list/partials/radio-refinements.jsx +60 -0
- package/app/pages/product-list/partials/refinements.jsx +144 -0
- package/app/pages/product-list/partials/selected-refinements.jsx +100 -0
- package/app/pages/product-list/partials/size-refinements.jsx +55 -0
- package/app/pages/registration/index.jsx +87 -0
- package/app/pages/registration/index.test.jsx +132 -0
- package/app/pages/reset-password/index.jsx +112 -0
- package/app/pages/reset-password/index.test.jsx +141 -0
- package/app/request-processor.js +118 -0
- package/app/request-processor.test.js +23 -0
- package/app/routes.jsx +111 -0
- package/app/routes.test.js +13 -0
- package/app/ssr.js +70 -0
- package/app/static/ico/favicon.ico +0 -0
- package/app/static/img/global/app-icon-192.png +0 -0
- package/app/static/img/global/app-icon-512.png +0 -0
- package/app/static/img/global/apple-touch-icon.png +0 -0
- package/app/static/img/hero.png +0 -0
- package/app/static/manifest.json +19 -0
- package/app/static/robots.txt +2 -0
- package/app/theme/components/base/accordion.js +21 -0
- package/app/theme/components/base/alert.js +17 -0
- package/app/theme/components/base/badge.js +25 -0
- package/app/theme/components/base/button.js +77 -0
- package/app/theme/components/base/checkbox.js +30 -0
- package/app/theme/components/base/container.js +17 -0
- package/app/theme/components/base/drawer.js +26 -0
- package/app/theme/components/base/formLabel.js +13 -0
- package/app/theme/components/base/icon.js +13 -0
- package/app/theme/components/base/input.js +44 -0
- package/app/theme/components/base/modal.js +11 -0
- package/app/theme/components/base/popover.js +61 -0
- package/app/theme/components/base/radio.js +33 -0
- package/app/theme/components/base/select.js +15 -0
- package/app/theme/components/base/skeleton.js +12 -0
- package/app/theme/components/base/tooltip.js +19 -0
- package/app/theme/components/project/_app.js +25 -0
- package/app/theme/components/project/breadcrumb.js +25 -0
- package/app/theme/components/project/checkout-footer.js +35 -0
- package/app/theme/components/project/drawer-menu.js +66 -0
- package/app/theme/components/project/footer.js +84 -0
- package/app/theme/components/project/header.js +84 -0
- package/app/theme/components/project/image-gallery.js +59 -0
- package/app/theme/components/project/links-list.js +43 -0
- package/app/theme/components/project/list-menu.js +91 -0
- package/app/theme/components/project/locale-selector.js +42 -0
- package/app/theme/components/project/nested-accordion.js +26 -0
- package/app/theme/components/project/offline-banner.js +25 -0
- package/app/theme/components/project/pagination.js +22 -0
- package/app/theme/components/project/product-tile.js +32 -0
- package/app/theme/components/project/social-icons.js +52 -0
- package/app/theme/components/project/swatch-group.js +115 -0
- package/app/theme/foundations/colors.js +170 -0
- package/app/theme/foundations/gradients.js +9 -0
- package/app/theme/foundations/layerStyles.js +41 -0
- package/app/theme/foundations/shadows.js +9 -0
- package/app/theme/foundations/sizes.js +18 -0
- package/app/theme/foundations/space.js +9 -0
- package/app/theme/foundations/styles.js +21 -0
- package/app/theme/index.js +104 -0
- package/app/utils/cc-utils.js +112 -0
- package/app/utils/cc-utils.test.js +41 -0
- package/app/utils/image-groups-utils.js +62 -0
- package/app/utils/image-groups-utils.test.js +65 -0
- package/app/utils/locale.js +78 -0
- package/app/utils/locale.test.js +112 -0
- package/app/utils/password-utils.js +21 -0
- package/app/utils/phone-utils.js +22 -0
- package/app/utils/phone-utils.test.js +15 -0
- package/app/utils/product-utils.js +35 -0
- package/app/utils/product-utils.test.js +51 -0
- package/app/utils/responsive-image.js +198 -0
- package/app/utils/responsive-image.test.js +170 -0
- package/app/utils/routes-utils.js +111 -0
- package/app/utils/routes-utils.test.js +291 -0
- package/app/utils/site-utils.js +222 -0
- package/app/utils/site-utils.test.js +376 -0
- package/app/utils/test-utils.js +257 -0
- package/app/utils/url.js +291 -0
- package/app/utils/url.test.js +421 -0
- package/app/utils/utils.js +201 -0
- package/app/utils/utils.test.js +182 -0
- package/babel.config.js +7 -0
- package/cache-hash-config.json +8 -0
- package/config/default.js +64 -0
- package/config/mocks/default.js +131 -0
- package/config/sites.js +78 -0
- package/jest-setup.js +191 -0
- package/jest.config.js +50 -0
- package/jsconfig.json +13 -0
- package/package.json +105 -0
- package/scripts/extract-default-messages.js +92 -0
- package/tests/lighthouserc.js +37 -0
- package/translations/README.md +127 -0
- package/translations/compiled/de-DE.json +3212 -0
- package/translations/compiled/en-GB.json +3212 -0
- package/translations/compiled/en-US.json +3212 -0
- package/translations/compiled/en-XA.json +6948 -0
- package/translations/compiled/es-MX.json +3216 -0
- package/translations/compiled/fr-FR.json +3216 -0
- package/translations/compiled/it-IT.json +3188 -0
- package/translations/compiled/ja-JP.json +3200 -0
- package/translations/compiled/ko-KR.json +3180 -0
- package/translations/compiled/pt-BR.json +3220 -0
- package/translations/compiled/zh-CN.json +3212 -0
- package/translations/compiled/zh-TW.json +3208 -0
- package/translations/de-DE.json +1417 -0
- package/translations/en-GB.json +1417 -0
- package/translations/en-US.json +1417 -0
- package/translations/es-MX.json +1417 -0
- package/translations/fr-FR.json +1417 -0
- package/translations/it-IT.json +1417 -0
- package/translations/ja-JP.json +1417 -0
- package/translations/ko-KR.json +1417 -0
- package/translations/pt-BR.json +1417 -0
- package/translations/zh-CN.json +1417 -0
- package/translations/zh-TW.json +1417 -0
- package/worker/main.js +36 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023, Salesforce, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
import {useLocation} from 'react-router-dom'
|
|
8
|
+
|
|
9
|
+
export const usePDPSearchParams = (productId) => {
|
|
10
|
+
const {search} = useLocation()
|
|
11
|
+
|
|
12
|
+
const allParams = new URLSearchParams(search)
|
|
13
|
+
const productParams = new URLSearchParams(allParams.get(productId) || '')
|
|
14
|
+
|
|
15
|
+
return [allParams, productParams]
|
|
16
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023, Salesforce, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react'
|
|
9
|
+
import {screen} from '@testing-library/react'
|
|
10
|
+
import PropTypes from 'prop-types'
|
|
11
|
+
import {usePDPSearchParams} from '@salesforce/retail-react-app/app/hooks/use-pdp-search-params'
|
|
12
|
+
import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
|
|
13
|
+
|
|
14
|
+
const MockComponent = ({productId} = {}) => {
|
|
15
|
+
const [allParams, productParams] = usePDPSearchParams(productId)
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<>
|
|
19
|
+
<div data-testid="all-params">{allParams.toString()}</div>
|
|
20
|
+
<div data-testid="product-params">{productParams.toString()}</div>{' '}
|
|
21
|
+
</>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
MockComponent.propTypes = {
|
|
25
|
+
productId: PropTypes.string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test('product set', () => {
|
|
29
|
+
const url =
|
|
30
|
+
// The parent's id is `winter-lookM`, while the children's are `25518447M` and `25518704M`
|
|
31
|
+
'/global/en-GB/product/winter-lookM?25518447M=color%3DJJ5FUXX%26size%3D9XL&25518704M=color%3DJJ2XNXX%26size%3D9MD'
|
|
32
|
+
window.history.pushState({}, '', url)
|
|
33
|
+
|
|
34
|
+
renderWithProviders(<MockComponent productId="25518704M" />)
|
|
35
|
+
|
|
36
|
+
expect(screen.getByTestId('all-params')).toHaveTextContent(
|
|
37
|
+
/^25518447M=color%3DJJ5FUXX%26size%3D9XL&25518704M=color%3DJJ2XNXX%26size%3D9MD$/
|
|
38
|
+
)
|
|
39
|
+
expect(screen.getByTestId('product-params')).toHaveTextContent(/^color=JJ2XNXX&size=9MD$/)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('regular product with variant', () => {
|
|
43
|
+
const url = '/global/en-GB/product/25502228M?color=JJ0NLD0&size=9MD&pid=701642889830M'
|
|
44
|
+
window.history.pushState({}, '', url)
|
|
45
|
+
|
|
46
|
+
renderWithProviders(<MockComponent />)
|
|
47
|
+
|
|
48
|
+
expect(screen.getByTestId('all-params')).toHaveTextContent(
|
|
49
|
+
/^color=JJ0NLD0&size=9MD&pid=701642889830M$/
|
|
50
|
+
)
|
|
51
|
+
expect(screen.getByTestId('product-params')).toHaveTextContent(/^$/)
|
|
52
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2022, Salesforce, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {useEffect, useRef} from 'react'
|
|
9
|
+
export const usePrevious = (value) => {
|
|
10
|
+
const ref = useRef()
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
ref.current = value
|
|
14
|
+
}, [value])
|
|
15
|
+
|
|
16
|
+
return ref.current
|
|
17
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2021, salesforce.com, inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {useEffect, useState} from 'react'
|
|
9
|
+
import {
|
|
10
|
+
rebuildPathWithParams,
|
|
11
|
+
removeQueryParamsFromPath
|
|
12
|
+
} from '@salesforce/retail-react-app/app/utils/url'
|
|
13
|
+
import {useHistory, useLocation} from 'react-router-dom'
|
|
14
|
+
import {useVariant} from '@salesforce/retail-react-app/app/hooks/use-variant'
|
|
15
|
+
import {useToast} from '@salesforce/retail-react-app/app/hooks/use-toast'
|
|
16
|
+
import {useIntl} from 'react-intl'
|
|
17
|
+
import {API_ERROR_MESSAGE} from '@salesforce/retail-react-app/app/constants'
|
|
18
|
+
import {useProduct} from '@salesforce/commerce-sdk-react'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* This hook is responsible for fetching a product detail based on the variation selection
|
|
22
|
+
* and managing the variation params on the url when the modal is open/close
|
|
23
|
+
* @param initialProduct - the initial product when the modal is first open
|
|
24
|
+
* @returns object
|
|
25
|
+
*/
|
|
26
|
+
export const useProductViewModal = (initialProduct) => {
|
|
27
|
+
const location = useLocation()
|
|
28
|
+
const history = useHistory()
|
|
29
|
+
const intl = useIntl()
|
|
30
|
+
const toast = useToast()
|
|
31
|
+
const [product, setProduct] = useState(initialProduct)
|
|
32
|
+
const variant = useVariant(product)
|
|
33
|
+
|
|
34
|
+
const {isFetching} = useProduct(
|
|
35
|
+
{parameters: {id: variant?.productId}},
|
|
36
|
+
{
|
|
37
|
+
placeholderData: initialProduct,
|
|
38
|
+
select: (data) => {
|
|
39
|
+
// if the product id is the same as the initial product id,
|
|
40
|
+
// then merge the data with the initial product to be able to show correct quantity in the modal
|
|
41
|
+
if (data.id === initialProduct.productId) {
|
|
42
|
+
return {
|
|
43
|
+
...initialProduct,
|
|
44
|
+
...data
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return data
|
|
48
|
+
},
|
|
49
|
+
onSuccess: (data) => {
|
|
50
|
+
setProduct(data)
|
|
51
|
+
},
|
|
52
|
+
onError: () => {
|
|
53
|
+
toast({
|
|
54
|
+
title: intl.formatMessage(API_ERROR_MESSAGE),
|
|
55
|
+
status: 'error'
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
const cleanUpVariantParams = () => {
|
|
61
|
+
const paramToRemove = [...(product?.variationAttributes?.map(({id}) => id) ?? []), 'pid']
|
|
62
|
+
const updatedParams = removeQueryParamsFromPath(`${location.search}`, paramToRemove)
|
|
63
|
+
|
|
64
|
+
history.replace({search: updatedParams})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
// when the modal is first mounted,
|
|
69
|
+
// clean up the params in case there are variant params not related to current product
|
|
70
|
+
cleanUpVariantParams()
|
|
71
|
+
return () => {
|
|
72
|
+
cleanUpVariantParams()
|
|
73
|
+
}
|
|
74
|
+
}, [])
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (variant) {
|
|
78
|
+
const {variationValues} = variant
|
|
79
|
+
// update the url with the new product id and variation values when the variant changes
|
|
80
|
+
const updatedUrl = rebuildPathWithParams(`${location.pathname}${location.search}`, {
|
|
81
|
+
...variationValues,
|
|
82
|
+
pid: variant.productId
|
|
83
|
+
})
|
|
84
|
+
history.replace(updatedUrl)
|
|
85
|
+
}
|
|
86
|
+
}, [variant])
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
product,
|
|
90
|
+
variant,
|
|
91
|
+
isFetching
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2021, salesforce.com, inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react'
|
|
9
|
+
import {Router} from 'react-router-dom'
|
|
10
|
+
import PropTypes from 'prop-types'
|
|
11
|
+
import {screen, fireEvent, waitFor} from '@testing-library/react'
|
|
12
|
+
import {createMemoryHistory} from 'history'
|
|
13
|
+
import {IntlProvider} from 'react-intl'
|
|
14
|
+
|
|
15
|
+
import mockProductDetail from '@salesforce/retail-react-app/app/mocks/variant-750518699578M'
|
|
16
|
+
import {useProductViewModal} from '@salesforce/retail-react-app/app/hooks/use-product-view-modal'
|
|
17
|
+
import {
|
|
18
|
+
DEFAULT_LOCALE,
|
|
19
|
+
renderWithProviders
|
|
20
|
+
} from '@salesforce/retail-react-app/app/utils/test-utils'
|
|
21
|
+
import messages from '@salesforce/retail-react-app/translations/compiled/en-GB.json'
|
|
22
|
+
import {rest} from 'msw'
|
|
23
|
+
|
|
24
|
+
jest.mock('@salesforce/commerce-sdk-react', () => {
|
|
25
|
+
const originalModule = jest.requireActual('@salesforce/commerce-sdk-react')
|
|
26
|
+
return {
|
|
27
|
+
...originalModule,
|
|
28
|
+
useProduct: jest.fn().mockReturnValue({isFetching: false})
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const mockProduct = {
|
|
33
|
+
...mockProductDetail,
|
|
34
|
+
id: '750518699660M',
|
|
35
|
+
variationValues: {
|
|
36
|
+
color: 'BLACKFB',
|
|
37
|
+
size: '050',
|
|
38
|
+
width: 'V'
|
|
39
|
+
},
|
|
40
|
+
c_color: 'BLACKFB',
|
|
41
|
+
c_isNew: true,
|
|
42
|
+
c_refinementColor: 'black',
|
|
43
|
+
c_size: '050',
|
|
44
|
+
c_width: 'V'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const MockComponent = ({product}) => {
|
|
48
|
+
const productViewModalData = useProductViewModal(product)
|
|
49
|
+
const [isShown, setIsShown] = React.useState(false)
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div>
|
|
53
|
+
<button onClick={() => setIsShown(!isShown)}>Toggle the content</button>
|
|
54
|
+
{isShown && (
|
|
55
|
+
<>
|
|
56
|
+
<div>{productViewModalData.product.id}</div>
|
|
57
|
+
<div data-testid="variant">{JSON.stringify(productViewModalData.variant)}</div>
|
|
58
|
+
<div>{`isFetching: ${productViewModalData.isFetching}`}</div>
|
|
59
|
+
</>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
MockComponent.propTypes = {
|
|
66
|
+
product: PropTypes.object
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
global.server.use(
|
|
71
|
+
rest.get('*/products/:productId', (req, res, ctx) => {
|
|
72
|
+
return res(ctx.delay(0), ctx.json(mockProduct))
|
|
73
|
+
})
|
|
74
|
+
)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('useProductViewModal hook', () => {
|
|
78
|
+
test('return proper data', async () => {
|
|
79
|
+
const history = createMemoryHistory()
|
|
80
|
+
history.push('/test/path')
|
|
81
|
+
renderWithProviders(<MockComponent product={mockProductDetail} />)
|
|
82
|
+
|
|
83
|
+
const toggleButton = screen.getByText(/Toggle the content/)
|
|
84
|
+
fireEvent.click(toggleButton)
|
|
85
|
+
|
|
86
|
+
await waitFor(() => {
|
|
87
|
+
expect(screen.getByText('750518699578M')).toBeInTheDocument()
|
|
88
|
+
expect(screen.getByText(/isFetching: false/i)).toBeInTheDocument()
|
|
89
|
+
expect(screen.getByTestId('variant')).toHaveTextContent(
|
|
90
|
+
'{"orderable":true,"price":299.99,"productId":"750518699578M","variationValues":{"color":"BLACKFB","size":"038","width":"V"}}'
|
|
91
|
+
)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test("update product's related url param when the product content is shown", () => {
|
|
96
|
+
const history = createMemoryHistory()
|
|
97
|
+
history.push('/test/path?color=BLACKFB')
|
|
98
|
+
|
|
99
|
+
renderWithProviders(
|
|
100
|
+
<Router history={history}>
|
|
101
|
+
<IntlProvider
|
|
102
|
+
locale={DEFAULT_LOCALE}
|
|
103
|
+
defaultLocale={DEFAULT_LOCALE}
|
|
104
|
+
messages={messages}
|
|
105
|
+
>
|
|
106
|
+
<MockComponent product={mockProductDetail} />
|
|
107
|
+
</IntlProvider>
|
|
108
|
+
</Router>
|
|
109
|
+
)
|
|
110
|
+
const toggleButton = screen.getByText(/Toggle the content/)
|
|
111
|
+
fireEvent.click(toggleButton)
|
|
112
|
+
expect(history.location.pathname).toBe('/test/path')
|
|
113
|
+
const searchParams = new URLSearchParams(history.location.search)
|
|
114
|
+
expect(searchParams.get('color')).toBe('BLACKFB')
|
|
115
|
+
expect(searchParams.get('width')).toBe('V')
|
|
116
|
+
expect(searchParams.get('pid')).toBe('750518699578M')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test("clean up product's related url param when unmounting product content", () => {
|
|
120
|
+
const history = createMemoryHistory()
|
|
121
|
+
history.push('/test/path')
|
|
122
|
+
|
|
123
|
+
renderWithProviders(
|
|
124
|
+
<Router history={history}>
|
|
125
|
+
<IntlProvider
|
|
126
|
+
locale={DEFAULT_LOCALE}
|
|
127
|
+
defaultLocale={DEFAULT_LOCALE}
|
|
128
|
+
messages={messages}
|
|
129
|
+
>
|
|
130
|
+
<MockComponent product={mockProductDetail} />
|
|
131
|
+
</IntlProvider>
|
|
132
|
+
</Router>
|
|
133
|
+
)
|
|
134
|
+
const toggleButton = screen.getByText(/Toggle the content/)
|
|
135
|
+
// show the content
|
|
136
|
+
fireEvent.click(toggleButton)
|
|
137
|
+
expect(history.location.pathname).toBe('/test/path')
|
|
138
|
+
|
|
139
|
+
// hide the content
|
|
140
|
+
fireEvent.click(toggleButton)
|
|
141
|
+
const searchParams = new URLSearchParams(history.location.search.toString())
|
|
142
|
+
waitFor(() => {
|
|
143
|
+
expect(searchParams.get('color')).toBeUndefined()
|
|
144
|
+
expect(searchParams.get('width')).toBeUndefined()
|
|
145
|
+
expect(searchParams.get('pid')).toBeUndefined()
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('load new variant on variant selection', async () => {
|
|
150
|
+
const history = createMemoryHistory()
|
|
151
|
+
history.push('/test/path')
|
|
152
|
+
|
|
153
|
+
renderWithProviders(
|
|
154
|
+
<Router history={history}>
|
|
155
|
+
<IntlProvider locale={DEFAULT_LOCALE} defaultLocale={DEFAULT_LOCALE}>
|
|
156
|
+
<MockComponent product={mockProductDetail} />
|
|
157
|
+
</IntlProvider>
|
|
158
|
+
</Router>
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
const toggleButton = screen.getByText(/Toggle the content/)
|
|
162
|
+
fireEvent.click(toggleButton)
|
|
163
|
+
expect(screen.getByText('750518699578M')).toBeInTheDocument()
|
|
164
|
+
|
|
165
|
+
history.push('/test/path?color=BLACKFB&size=050&width=V&pid=750518699660M')
|
|
166
|
+
await waitFor(() => {
|
|
167
|
+
expect(screen.getByTestId('variant')).toHaveTextContent(
|
|
168
|
+
'{"orderable":true,"price":299.99,"productId":"750518699660M","variationValues":{"color":"BLACKFB","size":"050","width":"V"}}'
|
|
169
|
+
)
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
})
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2021, salesforce.com, inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {useLocation} from 'react-router-dom'
|
|
9
|
+
import queryString from 'query-string'
|
|
10
|
+
|
|
11
|
+
// Constants
|
|
12
|
+
import {DEFAULT_SEARCH_PARAMS} from '@salesforce/retail-react-app/app/constants'
|
|
13
|
+
|
|
14
|
+
const PARSE_OPTIONS = {
|
|
15
|
+
parseBooleans: true,
|
|
16
|
+
parseNumbers: true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
* This hook will return all the location search params pertinant
|
|
21
|
+
* to the product list page.
|
|
22
|
+
*/
|
|
23
|
+
export const useSearchParams = (searchParams = DEFAULT_SEARCH_PARAMS, parseRefine = true) => {
|
|
24
|
+
const {search} = useLocation()
|
|
25
|
+
|
|
26
|
+
// Encode the search query, including preset values.
|
|
27
|
+
const searchParamsObject = {
|
|
28
|
+
...searchParams,
|
|
29
|
+
...parse(search.substring(1), parseRefine)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return [searchParamsObject, {stringify, parse}]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Encode's the provided search parameters object, paying special attention to ensure
|
|
37
|
+
* that the child `refine` object is alway encoded correctly.
|
|
38
|
+
*
|
|
39
|
+
* @param {Object} searchParamsObj
|
|
40
|
+
* @returns
|
|
41
|
+
*/
|
|
42
|
+
export const stringify = (searchParamsObj) => {
|
|
43
|
+
let searchParamsObjCopy = {...searchParamsObj}
|
|
44
|
+
|
|
45
|
+
// Remove our copy of the original refinement value so it's not stringified.
|
|
46
|
+
delete searchParamsObjCopy._refine
|
|
47
|
+
|
|
48
|
+
// "stringify" the nested refinements
|
|
49
|
+
searchParamsObjCopy.refine = Object.keys(searchParamsObjCopy.refine).map((key) =>
|
|
50
|
+
queryString.stringify(
|
|
51
|
+
{[key]: searchParamsObjCopy.refine[key]},
|
|
52
|
+
{
|
|
53
|
+
arrayFormat: 'separator',
|
|
54
|
+
arrayFormatSeparator: '|',
|
|
55
|
+
encode: false
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
// "stringify" the entire object
|
|
61
|
+
searchParamsObjCopy = queryString.stringify(searchParamsObjCopy)
|
|
62
|
+
return searchParamsObjCopy
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Decode's the provided query string representation of a search parameter object, paying
|
|
67
|
+
* special attention to also decode the 'refine' object.
|
|
68
|
+
*
|
|
69
|
+
* @param {Object} searchParamsStr
|
|
70
|
+
* @param {Boolean} parseRefine - opt out of parsing the inner refine object.
|
|
71
|
+
* @returns
|
|
72
|
+
*/
|
|
73
|
+
export const parse = (searchParamsStr, parseRefine = true) => {
|
|
74
|
+
const params = queryString.parse(searchParamsStr, PARSE_OPTIONS)
|
|
75
|
+
|
|
76
|
+
// Ensure the refinments is an array (make it easier to manipulate).
|
|
77
|
+
params.refine = Array.isArray(params.refine) ? params.refine : [params.refine].filter(Boolean)
|
|
78
|
+
|
|
79
|
+
// Parse the nested refinement entries.
|
|
80
|
+
if (parseRefine) {
|
|
81
|
+
params._refine = params.refine
|
|
82
|
+
params.refine = params.refine.reduce((acc, curr) => {
|
|
83
|
+
return {
|
|
84
|
+
...acc,
|
|
85
|
+
...queryString.parse(curr, {
|
|
86
|
+
...PARSE_OPTIONS,
|
|
87
|
+
parseNumbers: false,
|
|
88
|
+
arrayFormat: 'separator',
|
|
89
|
+
arrayFormatSeparator: '|'
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
}, {})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return params
|
|
96
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2021, salesforce.com, inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react'
|
|
9
|
+
import {Router} from 'react-router'
|
|
10
|
+
|
|
11
|
+
import {render} from '@testing-library/react'
|
|
12
|
+
import {createMemoryHistory} from 'history'
|
|
13
|
+
import {
|
|
14
|
+
useSearchParams,
|
|
15
|
+
stringify,
|
|
16
|
+
parse
|
|
17
|
+
} from '@salesforce/retail-react-app/app/hooks/use-search-params'
|
|
18
|
+
|
|
19
|
+
const MockComponent = () => {
|
|
20
|
+
const [params] = useSearchParams()
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<script data-testid="limits" type="application/json">
|
|
24
|
+
{JSON.stringify(params)}
|
|
25
|
+
</script>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('The useSearchParams', () => {
|
|
30
|
+
test('returns an object with the default search params when none are present in the url.', () => {
|
|
31
|
+
const history = createMemoryHistory()
|
|
32
|
+
history.push('/test/path')
|
|
33
|
+
|
|
34
|
+
const wrapper = render(
|
|
35
|
+
<Router history={history}>
|
|
36
|
+
<MockComponent />
|
|
37
|
+
</Router>
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
expect(wrapper.getByTestId('limits').text).toBe(
|
|
41
|
+
'{"limit":25,"offset":0,"sort":"best-matches","refine":{},"_refine":[]}'
|
|
42
|
+
)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('returns an object with the parsed search params.', () => {
|
|
46
|
+
const history = createMemoryHistory()
|
|
47
|
+
history.push(
|
|
48
|
+
'/test/path?limit=25&offset=0&refine=c_refinementColor%3DBlack%7CPurple&sort=best-matches'
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
const wrapper = render(
|
|
52
|
+
<Router history={history}>
|
|
53
|
+
<MockComponent />
|
|
54
|
+
</Router>
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
expect(wrapper.getByTestId('limits').text).toBe(
|
|
58
|
+
'{"limit":25,"offset":0,"sort":"best-matches","refine":{"c_refinementColor":["Black","Purple"]},"_refine":["c_refinementColor=Black|Purple"]}'
|
|
59
|
+
)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('stringy method', () => {
|
|
63
|
+
const objectToStringify = {
|
|
64
|
+
limit: '25',
|
|
65
|
+
offset: '0',
|
|
66
|
+
refine: {
|
|
67
|
+
c_refinementColor: ['Black', 'Purple']
|
|
68
|
+
},
|
|
69
|
+
sort: 'best-matches'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const stringifiedObject = stringify(objectToStringify)
|
|
73
|
+
expect(stringifiedObject).toBe(
|
|
74
|
+
'limit=25&offset=0&refine=c_refinementColor%3DBlack%7CPurple&sort=best-matches'
|
|
75
|
+
)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('parse method', () => {
|
|
79
|
+
const stringToParse =
|
|
80
|
+
'limit=25&offset=0&refine=c_refinementColor%3DBlack%7CPurple&sort=best-matches'
|
|
81
|
+
|
|
82
|
+
const parsedString = parse(stringToParse)
|
|
83
|
+
expect(parsedString).toEqual({
|
|
84
|
+
_refine: ['c_refinementColor=Black|Purple'],
|
|
85
|
+
limit: 25,
|
|
86
|
+
offset: 0,
|
|
87
|
+
refine: {c_refinementColor: ['Black', 'Purple']},
|
|
88
|
+
sort: 'best-matches'
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2021, salesforce.com, inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {useMemo} from 'react'
|
|
9
|
+
import {useLocation} from 'react-router-dom'
|
|
10
|
+
|
|
11
|
+
// Utils
|
|
12
|
+
import {buildUrlSet} from '@salesforce/retail-react-app/app/utils/url'
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* Generate a memoized list of page size urls. Chaning the page size will reset
|
|
16
|
+
* the offset to zero to simplify things.
|
|
17
|
+
*/
|
|
18
|
+
export const useSortUrls = ({options = []}) => {
|
|
19
|
+
const location = useLocation()
|
|
20
|
+
|
|
21
|
+
return useMemo(
|
|
22
|
+
() =>
|
|
23
|
+
buildUrlSet(
|
|
24
|
+
`${location.pathname}${location.search}`,
|
|
25
|
+
'sort',
|
|
26
|
+
options.map(({id}) => id),
|
|
27
|
+
{
|
|
28
|
+
offset: 0
|
|
29
|
+
}
|
|
30
|
+
),
|
|
31
|
+
[location.pathname, location.search, options]
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2021, salesforce.com, inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react'
|
|
9
|
+
import {Router} from 'react-router'
|
|
10
|
+
|
|
11
|
+
import {render} from '@testing-library/react'
|
|
12
|
+
import {createMemoryHistory} from 'history'
|
|
13
|
+
import {useSortUrls} from '@salesforce/retail-react-app/app/hooks/use-sort-urls'
|
|
14
|
+
|
|
15
|
+
const MOCK_SORT_OPTIONS = [{id: 'high-to-low'}, {id: 'low-to-high'}]
|
|
16
|
+
|
|
17
|
+
const MockComponent = () => {
|
|
18
|
+
const urls = useSortUrls({options: MOCK_SORT_OPTIONS})
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<script data-testid="limits" type="application/json">
|
|
22
|
+
{JSON.stringify(urls)}
|
|
23
|
+
</script>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('The useSortUrls', () => {
|
|
28
|
+
test('returns an array of urls, one values for each sort value.', () => {
|
|
29
|
+
const history = createMemoryHistory()
|
|
30
|
+
history.push('/test/path')
|
|
31
|
+
|
|
32
|
+
const wrapper = render(
|
|
33
|
+
<Router history={history}>
|
|
34
|
+
<MockComponent />
|
|
35
|
+
</Router>
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
expect(wrapper.getByTestId('limits').text).toBe(
|
|
39
|
+
'["/test/path?sort=high-to-low&offset=0","/test/path?sort=low-to-high&offset=0"]'
|
|
40
|
+
)
|
|
41
|
+
})
|
|
42
|
+
})
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2021, salesforce.com, inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react'
|
|
8
|
+
import {
|
|
9
|
+
Alert,
|
|
10
|
+
AlertIcon,
|
|
11
|
+
AlertTitle,
|
|
12
|
+
CloseButton,
|
|
13
|
+
Spacer,
|
|
14
|
+
useToast as useChakraToast
|
|
15
|
+
} from '@chakra-ui/react'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Display a toast message on the screen.
|
|
19
|
+
* This is a custom hook to handle showing toasts in the app, preventing duplicate toasts and to add action elements
|
|
20
|
+
* to toasts when required. It supports all props supported by Chakra toast.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} title Message text to be displayed in toast
|
|
23
|
+
* @param {string} id - id provided to the toast to avoid duplicate toast ids, use it if multiple toasts are needed
|
|
24
|
+
* @param {string} status Semantic state of the toast - success | error | info | warning
|
|
25
|
+
* @param {node} action Optional component to be displayed in the toast (eg. Button to allow user to perform action)
|
|
26
|
+
* @param {string} position The placement of the toast on screen
|
|
27
|
+
* @param {number} duration The delay before the toast hides (in milliseconds)
|
|
28
|
+
*/
|
|
29
|
+
export function useToast() {
|
|
30
|
+
const toast = useChakraToast()
|
|
31
|
+
|
|
32
|
+
return ({
|
|
33
|
+
title,
|
|
34
|
+
status,
|
|
35
|
+
action,
|
|
36
|
+
position = 'top-right',
|
|
37
|
+
duration = 5000,
|
|
38
|
+
variant = 'subtle',
|
|
39
|
+
isClosable = true
|
|
40
|
+
}) => {
|
|
41
|
+
let toastConfig = {
|
|
42
|
+
title,
|
|
43
|
+
status,
|
|
44
|
+
isClosable,
|
|
45
|
+
position,
|
|
46
|
+
duration,
|
|
47
|
+
variant
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (action) {
|
|
51
|
+
toastConfig = {
|
|
52
|
+
...toastConfig,
|
|
53
|
+
|
|
54
|
+
render: ({onClose}) => (
|
|
55
|
+
<Alert status={status} variant="subtle" borderRadius="md" py={3} width="sm">
|
|
56
|
+
<AlertIcon />
|
|
57
|
+
<AlertTitle> {title} </AlertTitle>
|
|
58
|
+
<Spacer />
|
|
59
|
+
{action}
|
|
60
|
+
<Spacer />
|
|
61
|
+
<CloseButton onClick={onClose} />
|
|
62
|
+
</Alert>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
toast(toastConfig)
|
|
67
|
+
}
|
|
68
|
+
}
|