@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,365 @@
|
|
|
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
|
+
import React from 'react'
|
|
8
|
+
import PropTypes from 'prop-types'
|
|
9
|
+
import {screen, within, waitFor} from '@testing-library/react'
|
|
10
|
+
import userEvent from '@testing-library/user-event'
|
|
11
|
+
import {
|
|
12
|
+
renderWithProviders,
|
|
13
|
+
createPathWithDefaults,
|
|
14
|
+
guestToken
|
|
15
|
+
} from '@salesforce/retail-react-app/app/utils/test-utils'
|
|
16
|
+
import {AuthModal, useAuthModal} from '@salesforce/retail-react-app/app/hooks/use-auth-modal'
|
|
17
|
+
import {BrowserRouter as Router, Route} from 'react-router-dom'
|
|
18
|
+
import Account from '@salesforce/retail-react-app/app/pages/account'
|
|
19
|
+
import {rest} from 'msw'
|
|
20
|
+
import {mockedRegisteredCustomer} from '@salesforce/retail-react-app/app/mocks/mock-data'
|
|
21
|
+
|
|
22
|
+
jest.setTimeout(60000)
|
|
23
|
+
|
|
24
|
+
const mockMergedBasket = {
|
|
25
|
+
basketId: 'a10ff320829cb0eef93ca5310a',
|
|
26
|
+
currency: 'USD',
|
|
27
|
+
customerInfo: {
|
|
28
|
+
customerId: 'registeredCustomerId',
|
|
29
|
+
email: 'customer@test.com'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const mockPasswordToken = {
|
|
33
|
+
email: 'foo@test.com',
|
|
34
|
+
expiresInMinutes: 10,
|
|
35
|
+
login: 'foo@test.com',
|
|
36
|
+
resetToken: 'testresettoken'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const mockRegisteredCustomer = {
|
|
40
|
+
authType: 'registered',
|
|
41
|
+
customerId: 'registeredCustomerId',
|
|
42
|
+
customerNo: 'testno',
|
|
43
|
+
email: 'customer@test.com',
|
|
44
|
+
firstName: 'Tester',
|
|
45
|
+
lastName: 'Testing',
|
|
46
|
+
login: 'customer@test.com'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let authModal = undefined
|
|
50
|
+
const MockedComponent = (props) => {
|
|
51
|
+
const {initialView} = props
|
|
52
|
+
authModal = useAuthModal(initialView || undefined)
|
|
53
|
+
const match = {
|
|
54
|
+
params: {pageName: 'profile'}
|
|
55
|
+
}
|
|
56
|
+
return (
|
|
57
|
+
<Router>
|
|
58
|
+
<button onClick={authModal.onOpen}>Open Modal</button>
|
|
59
|
+
<AuthModal {...authModal} />
|
|
60
|
+
<Route path={createPathWithDefaults('/account')}>
|
|
61
|
+
<Account match={match} />
|
|
62
|
+
</Route>
|
|
63
|
+
</Router>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
MockedComponent.propTypes = {
|
|
67
|
+
initialView: PropTypes.string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Set up and clean up
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
authModal = undefined
|
|
73
|
+
global.server.use(
|
|
74
|
+
rest.post('*/customers', (req, res, ctx) => {
|
|
75
|
+
return res(ctx.delay(0), ctx.status(200), ctx.json(mockRegisteredCustomer))
|
|
76
|
+
}),
|
|
77
|
+
rest.get('*/customers/:customerId', (req, res, ctx) => {
|
|
78
|
+
return res(ctx.delay(0), ctx.status(200), ctx.json(mockRegisteredCustomer))
|
|
79
|
+
}),
|
|
80
|
+
rest.post('*/customers/password/actions/create-reset-token', (req, res, ctx) => {
|
|
81
|
+
return res(ctx.delay(0), ctx.status(200), ctx.json(mockPasswordToken))
|
|
82
|
+
}),
|
|
83
|
+
rest.post('*/oauth2/token', (req, res, ctx) =>
|
|
84
|
+
res(
|
|
85
|
+
ctx.delay(0),
|
|
86
|
+
ctx.json({
|
|
87
|
+
customer_id: 'customerid',
|
|
88
|
+
access_token: guestToken,
|
|
89
|
+
refresh_token: 'testrefeshtoken',
|
|
90
|
+
usid: 'testusid',
|
|
91
|
+
enc_user_id: 'testEncUserId',
|
|
92
|
+
id_token: 'testIdToken'
|
|
93
|
+
})
|
|
94
|
+
)
|
|
95
|
+
),
|
|
96
|
+
rest.post('*/baskets/actions/merge', (req, res, ctx) => {
|
|
97
|
+
return res(ctx.delay(0), ctx.json(mockMergedBasket))
|
|
98
|
+
})
|
|
99
|
+
)
|
|
100
|
+
})
|
|
101
|
+
afterEach(() => {
|
|
102
|
+
localStorage.clear()
|
|
103
|
+
jest.resetModules()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('Renders login modal by default', async () => {
|
|
107
|
+
const user = userEvent.setup()
|
|
108
|
+
|
|
109
|
+
renderWithProviders(<MockedComponent />)
|
|
110
|
+
|
|
111
|
+
// open the modal
|
|
112
|
+
const trigger = screen.getByText(/open modal/i)
|
|
113
|
+
await user.click(trigger)
|
|
114
|
+
|
|
115
|
+
await waitFor(() => {
|
|
116
|
+
expect(screen.getByText(/welcome back/i)).toBeInTheDocument()
|
|
117
|
+
expect(screen.getByLabelText(/email/i)).toBeInTheDocument()
|
|
118
|
+
expect(screen.getByLabelText(/Password/)).toBeInTheDocument()
|
|
119
|
+
expect(screen.getByText(/forgot password/i)).toBeInTheDocument()
|
|
120
|
+
expect(screen.getByText(/sign in/i)).toBeInTheDocument()
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// TODO: Fix flaky/broken test
|
|
125
|
+
// eslint-disable-next-line jest/no-disabled-tests
|
|
126
|
+
test.skip('Renders error when given incorrect log in credentials', async () => {
|
|
127
|
+
const user = userEvent.setup()
|
|
128
|
+
|
|
129
|
+
// render our test component
|
|
130
|
+
renderWithProviders(<MockedComponent />, {
|
|
131
|
+
wrapperProps: {
|
|
132
|
+
bypassAuth: false
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
// open the modal
|
|
137
|
+
const trigger = screen.getByText(/open modal/i)
|
|
138
|
+
await user.click(trigger)
|
|
139
|
+
|
|
140
|
+
// enter credentials and submit
|
|
141
|
+
await user.type(screen.getByLabelText('Email'), 'bad@test.com')
|
|
142
|
+
await user.type(screen.getByLabelText('Password'), 'SomeFakePassword1!')
|
|
143
|
+
|
|
144
|
+
// mock failed auth request
|
|
145
|
+
global.server.use(
|
|
146
|
+
rest.post('*/oauth2/login', (req, res, ctx) =>
|
|
147
|
+
res(ctx.delay(0), ctx.status(401), ctx.json({message: 'Unauthorized Credentials.'}))
|
|
148
|
+
),
|
|
149
|
+
rest.post('*/customers', (req, res, ctx) => {
|
|
150
|
+
return res(ctx.delay(0), ctx.status(404), ctx.json({message: 'Not Found.'}))
|
|
151
|
+
})
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
await user.click(screen.getByText(/sign in/i))
|
|
155
|
+
// give it some time to show the error in the form
|
|
156
|
+
await waitFor(
|
|
157
|
+
() => {
|
|
158
|
+
// wait for login error alert to appear
|
|
159
|
+
expect(
|
|
160
|
+
screen.getByText(/something's not right with your email or password\. try again\./i)
|
|
161
|
+
).toBeInTheDocument()
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
timeout: 10000
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test('Allows customer to create an account', async () => {
|
|
170
|
+
const user = userEvent.setup()
|
|
171
|
+
|
|
172
|
+
// render our test component
|
|
173
|
+
renderWithProviders(<MockedComponent />, {
|
|
174
|
+
wrapperProps: {
|
|
175
|
+
bypassAuth: true
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
// open the modal
|
|
180
|
+
const trigger = screen.getByText('Open Modal')
|
|
181
|
+
|
|
182
|
+
await user.click(trigger)
|
|
183
|
+
let form
|
|
184
|
+
await waitFor(() => {
|
|
185
|
+
form = screen.queryByTestId('sf-auth-modal-form')
|
|
186
|
+
expect(form).toBeInTheDocument()
|
|
187
|
+
})
|
|
188
|
+
const createAccount = screen.getByText(/create account/i)
|
|
189
|
+
await user.click(createAccount)
|
|
190
|
+
let registerForm
|
|
191
|
+
await waitFor(() => {
|
|
192
|
+
registerForm = screen.getByTestId('sf-auth-modal-form-register')
|
|
193
|
+
expect(registerForm).toBeInTheDocument()
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
const withinForm = within(registerForm)
|
|
197
|
+
// fill out form and submit
|
|
198
|
+
await waitFor(() => {
|
|
199
|
+
const firstName = withinForm.getByLabelText(/First Name/i)
|
|
200
|
+
expect(firstName).toBeInTheDocument()
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
await user.type(withinForm.getByLabelText('First Name'), 'Tester')
|
|
204
|
+
await user.type(withinForm.getByLabelText('Last Name'), 'Tester')
|
|
205
|
+
await user.type(withinForm.getByPlaceholderText(/you@email.com/i), 'customer@test.com')
|
|
206
|
+
await user.type(withinForm.getAllByLabelText(/password/i)[0], 'Password!1')
|
|
207
|
+
|
|
208
|
+
// login with credentials
|
|
209
|
+
global.server.use(
|
|
210
|
+
rest.post('*/oauth2/token', (req, res, ctx) => {
|
|
211
|
+
return res(
|
|
212
|
+
ctx.delay(0),
|
|
213
|
+
ctx.json({
|
|
214
|
+
customer_id: 'customerid_1',
|
|
215
|
+
access_token:
|
|
216
|
+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXQiOiJHVUlEIiwic2NwIjoic2ZjYy5zaG9wcGVyLW15YWNjb3VudC5iYXNrZXRzIHNmY2Muc2hvcHBlci1teWFjY291bnQuYWRkcmVzc2VzIHNmY2Muc2hvcHBlci1wcm9kdWN0cyBzZmNjLnNob3BwZXItZGlzY292ZXJ5LXNlYXJjaCBzZmNjLnNob3BwZXItbXlhY2NvdW50LnJ3IHNmY2Muc2hvcHBlci1teWFjY291bnQucGF5bWVudGluc3RydW1lbnRzIHNmY2Muc2hvcHBlci1jdXN0b21lcnMubG9naW4gc2ZjYy5zaG9wcGVyLWV4cGVyaWVuY2Ugc2ZjYy5zaG9wcGVyLW15YWNjb3VudC5vcmRlcnMgc2ZjYy5zaG9wcGVyLWN1c3RvbWVycy5yZWdpc3RlciBzZmNjLnNob3BwZXItYmFza2V0cy1vcmRlcnMgc2ZjYy5zaG9wcGVyLW15YWNjb3VudC5hZGRyZXNzZXMucncgc2ZjYy5zaG9wcGVyLW15YWNjb3VudC5wcm9kdWN0bGlzdHMucncgc2ZjYy5zaG9wcGVyLXByb2R1Y3RsaXN0cyBzZmNjLnNob3BwZXItcHJvbW90aW9ucyBzZmNjLnNob3BwZXItYmFza2V0cy1vcmRlcnMucncgc2ZjYy5zaG9wcGVyLW15YWNjb3VudC5wYXltZW50aW5zdHJ1bWVudHMucncgc2ZjYy5zaG9wcGVyLWdpZnQtY2VydGlmaWNhdGVzIHNmY2Muc2hvcHBlci1wcm9kdWN0LXNlYXJjaCBzZmNjLnNob3BwZXItbXlhY2NvdW50LnByb2R1Y3RsaXN0cyBzZmNjLnNob3BwZXItY2F0ZWdvcmllcyBzZmNjLnNob3BwZXItbXlhY2NvdW50Iiwic3ViIjoiY2Mtc2xhczo6enpyZl8wMDE6OnNjaWQ6YzljNDViZmQtMGVkMy00YWEyLTk5NzEtNDBmODg5NjJiODM2Ojp1c2lkOjhlODgzOTczLTY4ZWItNDFmZS1hM2M1LTc1NjIzMjY1MmZmNSIsImN0eCI6InNsYXMiLCJpc3MiOiJzbGFzL3Byb2QvenpyZl8wMDEiLCJpc3QiOjEsImF1ZCI6ImNvbW1lcmNlY2xvdWQvcHJvZC96enJmXzAwMSIsIm5iZiI6MTY3ODgzNDI3MSwic3R5IjoiVXNlciIsImlzYiI6InVpZG86ZWNvbTo6dXBuOmtldjVAdGVzdC5jb206OnVpZG46a2V2aW4gaGU6OmdjaWQ6YWJtZXMybWJrM2xYa1JsSEZKd0dZWWt1eEo6OnJjaWQ6YWJVTXNhdnBEOVk2alcwMGRpMlNqeEdDTVU6OmNoaWQ6UmVmQXJjaEdsb2JhbCIsImV4cCI6MjY3ODgzNjEwMSwiaWF0IjoxNjc4ODM0MzAxLCJqdGkiOiJDMkM0ODU2MjAxODYwLTE4OTA2Nzg5MDM0ODA1ODMyNTcwNjY2NTQyIn0._tUrxeXdFYPj6ZoY-GILFRd3-aD1RGPkZX6TqHeS494',
|
|
217
|
+
refresh_token: 'testrefeshtoken_1',
|
|
218
|
+
usid: 'testusid_1',
|
|
219
|
+
enc_user_id: 'testEncUserId_1',
|
|
220
|
+
id_token: 'testIdToken_1'
|
|
221
|
+
})
|
|
222
|
+
)
|
|
223
|
+
}),
|
|
224
|
+
rest.post('*/oauth2/login', (req, res, ctx) => {
|
|
225
|
+
return res(ctx.delay(0), ctx.status(200), ctx.json(mockedRegisteredCustomer))
|
|
226
|
+
}),
|
|
227
|
+
rest.get('*/customers/:customerId', (req, res, ctx) => {
|
|
228
|
+
return res(ctx.delay(0), ctx.status(200), ctx.json(mockedRegisteredCustomer))
|
|
229
|
+
})
|
|
230
|
+
)
|
|
231
|
+
const submitButton = withinForm.getByText(/create account/i)
|
|
232
|
+
await user.click(submitButton)
|
|
233
|
+
|
|
234
|
+
await waitFor(() => {
|
|
235
|
+
expect(form).not.toBeInTheDocument()
|
|
236
|
+
})
|
|
237
|
+
// wait for success state to appear
|
|
238
|
+
await waitFor(
|
|
239
|
+
() => {
|
|
240
|
+
expect(window.location.pathname).toBe('/uk/en-GB/account')
|
|
241
|
+
const myAccount = screen.getAllByText(/My Account/)
|
|
242
|
+
expect(myAccount).toHaveLength(2)
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
timeout: 5000
|
|
246
|
+
}
|
|
247
|
+
)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
// TODO: investingate why this test is failing when running with other tests
|
|
251
|
+
// eslint-disable-next-line jest/no-disabled-tests
|
|
252
|
+
test.skip('Allows customer to sign in to their account', async () => {
|
|
253
|
+
const user = userEvent.setup()
|
|
254
|
+
|
|
255
|
+
// render our test component
|
|
256
|
+
renderWithProviders(<MockedComponent />, {
|
|
257
|
+
wrapperProps: {
|
|
258
|
+
bypassAuth: false
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
// open the modal
|
|
263
|
+
const trigger = screen.getByText(/open modal/i)
|
|
264
|
+
await user.click(trigger)
|
|
265
|
+
|
|
266
|
+
// enter credentials and submit
|
|
267
|
+
await user.type(screen.getByLabelText('Email'), 'customer@test.com')
|
|
268
|
+
await user.type(screen.getByLabelText('Password'), 'Password!1')
|
|
269
|
+
|
|
270
|
+
// login with credentials
|
|
271
|
+
global.server.use(
|
|
272
|
+
rest.post('*/oauth2/token', (req, res, ctx) =>
|
|
273
|
+
res(
|
|
274
|
+
ctx.delay(0),
|
|
275
|
+
ctx.json({
|
|
276
|
+
customer_id: 'customerid_1',
|
|
277
|
+
access_token:
|
|
278
|
+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXQiOiJHVUlEIiwic2NwIjoic2ZjYy5zaG9wcGVyLW15YWNjb3VudC5iYXNrZXRzIHNmY2Muc2hvcHBlci1teWFjY291bnQuYWRkcmVzc2VzIHNmY2Muc2hvcHBlci1wcm9kdWN0cyBzZmNjLnNob3BwZXItZGlzY292ZXJ5LXNlYXJjaCBzZmNjLnNob3BwZXItbXlhY2NvdW50LnJ3IHNmY2Muc2hvcHBlci1teWFjY291bnQucGF5bWVudGluc3RydW1lbnRzIHNmY2Muc2hvcHBlci1jdXN0b21lcnMubG9naW4gc2ZjYy5zaG9wcGVyLWV4cGVyaWVuY2Ugc2ZjYy5zaG9wcGVyLW15YWNjb3VudC5vcmRlcnMgc2ZjYy5zaG9wcGVyLWN1c3RvbWVycy5yZWdpc3RlciBzZmNjLnNob3BwZXItYmFza2V0cy1vcmRlcnMgc2ZjYy5zaG9wcGVyLW15YWNjb3VudC5hZGRyZXNzZXMucncgc2ZjYy5zaG9wcGVyLW15YWNjb3VudC5wcm9kdWN0bGlzdHMucncgc2ZjYy5zaG9wcGVyLXByb2R1Y3RsaXN0cyBzZmNjLnNob3BwZXItcHJvbW90aW9ucyBzZmNjLnNob3BwZXItYmFza2V0cy1vcmRlcnMucncgc2ZjYy5zaG9wcGVyLW15YWNjb3VudC5wYXltZW50aW5zdHJ1bWVudHMucncgc2ZjYy5zaG9wcGVyLWdpZnQtY2VydGlmaWNhdGVzIHNmY2Muc2hvcHBlci1wcm9kdWN0LXNlYXJjaCBzZmNjLnNob3BwZXItbXlhY2NvdW50LnByb2R1Y3RsaXN0cyBzZmNjLnNob3BwZXItY2F0ZWdvcmllcyBzZmNjLnNob3BwZXItbXlhY2NvdW50Iiwic3ViIjoiY2Mtc2xhczo6enpyZl8wMDE6OnNjaWQ6YzljNDViZmQtMGVkMy00YWEyLTk5NzEtNDBmODg5NjJiODM2Ojp1c2lkOjhlODgzOTczLTY4ZWItNDFmZS1hM2M1LTc1NjIzMjY1MmZmNSIsImN0eCI6InNsYXMiLCJpc3MiOiJzbGFzL3Byb2QvenpyZl8wMDEiLCJpc3QiOjEsImF1ZCI6ImNvbW1lcmNlY2xvdWQvcHJvZC96enJmXzAwMSIsIm5iZiI6MTY3ODgzNDI3MSwic3R5IjoiVXNlciIsImlzYiI6InVpZG86ZWNvbTo6dXBuOmtldjVAdGVzdC5jb206OnVpZG46a2V2aW4gaGU6OmdjaWQ6YWJtZXMybWJrM2xYa1JsSEZKd0dZWWt1eEo6OnJjaWQ6YWJVTXNhdnBEOVk2alcwMGRpMlNqeEdDTVU6OmNoaWQ6UmVmQXJjaEdsb2JhbCIsImV4cCI6MjY3ODgzNjEwMSwiaWF0IjoxNjc4ODM0MzAxLCJqdGkiOiJDMkM0ODU2MjAxODYwLTE4OTA2Nzg5MDM0ODA1ODMyNTcwNjY2NTQyIn0._tUrxeXdFYPj6ZoY-GILFRd3-aD1RGPkZX6TqHeS494',
|
|
279
|
+
refresh_token: 'testrefeshtoken_1',
|
|
280
|
+
usid: 'testusid_1',
|
|
281
|
+
enc_user_id: 'testEncUserId_1',
|
|
282
|
+
id_token: 'testIdToken_1'
|
|
283
|
+
})
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
await user.click(screen.getByText(/sign in/i))
|
|
288
|
+
|
|
289
|
+
// allow time to transition to account page
|
|
290
|
+
await waitFor(
|
|
291
|
+
() => {
|
|
292
|
+
expect(window.location.pathname).toBe('/uk/en-GB/account')
|
|
293
|
+
expect(screen.getByText(/My Profile/i)).toBeInTheDocument()
|
|
294
|
+
},
|
|
295
|
+
{timeout: 5000}
|
|
296
|
+
)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
describe('Reset password', function () {
|
|
300
|
+
beforeEach(() => {
|
|
301
|
+
global.server.use(
|
|
302
|
+
rest.post('*/customers/password/actions/create-reset-token', (req, res, ctx) =>
|
|
303
|
+
res(ctx.delay(0), ctx.status(200), ctx.json(mockPasswordToken))
|
|
304
|
+
)
|
|
305
|
+
)
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
// TODO: Fix flaky/broken test
|
|
309
|
+
// eslint-disable-next-line jest/no-disabled-tests
|
|
310
|
+
test.skip('Allows customer to generate password token', async () => {
|
|
311
|
+
const user = userEvent.setup()
|
|
312
|
+
|
|
313
|
+
// render our test component
|
|
314
|
+
renderWithProviders(<MockedComponent initialView="password" />, {
|
|
315
|
+
wrapperProps: {
|
|
316
|
+
bypassAuth: false
|
|
317
|
+
}
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
// open the modal
|
|
321
|
+
const trigger = screen.getByText(/open modal/i)
|
|
322
|
+
await user.click(trigger)
|
|
323
|
+
expect(authModal.isOpen).toBe(true)
|
|
324
|
+
|
|
325
|
+
// enter credentials and submit
|
|
326
|
+
// const withinForm = within(screen.getByTestId('sf-auth-modal-form'))
|
|
327
|
+
|
|
328
|
+
let resetPwForm = await screen.findByTestId('sf-auth-modal-form-reset-pw')
|
|
329
|
+
expect(resetPwForm).toBeInTheDocument()
|
|
330
|
+
const withinForm = within(resetPwForm)
|
|
331
|
+
await user.type(withinForm.getByLabelText('Email'), 'foo@test.com')
|
|
332
|
+
await user.click(withinForm.getByText(/reset password/i))
|
|
333
|
+
|
|
334
|
+
// wait for success state
|
|
335
|
+
await waitFor(() => {
|
|
336
|
+
expect(screen.getByText(/password reset/i)).toBeInTheDocument()
|
|
337
|
+
expect(screen.getByText(/foo@test.com/i)).toBeInTheDocument()
|
|
338
|
+
})
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
// TODO: Fix flaky/broken test
|
|
342
|
+
// eslint-disable-next-line jest/no-disabled-tests
|
|
343
|
+
test.skip('Allows customer to open generate password token modal from everywhere', async () => {
|
|
344
|
+
const user = userEvent.setup()
|
|
345
|
+
|
|
346
|
+
// render our test component
|
|
347
|
+
renderWithProviders(<MockedComponent initialView="password" />)
|
|
348
|
+
|
|
349
|
+
// open the modal
|
|
350
|
+
const trigger = screen.getByText(/open modal/i)
|
|
351
|
+
await user.click(trigger)
|
|
352
|
+
expect(authModal.isOpen).toBe(true)
|
|
353
|
+
|
|
354
|
+
const withinForm = within(screen.getByTestId('sf-auth-modal-form'))
|
|
355
|
+
|
|
356
|
+
expect(withinForm.getByText(/Reset Password/i)).toBeInTheDocument()
|
|
357
|
+
|
|
358
|
+
// close the modal
|
|
359
|
+
const switchToSignIn = screen.getByText(/Sign in/i)
|
|
360
|
+
await user.click(switchToSignIn)
|
|
361
|
+
|
|
362
|
+
// check that the modal is closed
|
|
363
|
+
expect(authModal.isOpen).toBe(false)
|
|
364
|
+
})
|
|
365
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
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 {useContext} from 'react'
|
|
8
|
+
import {CurrencyContext} from '@salesforce/retail-react-app/app/contexts'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Custom React hook to get the currency for the active locale and to change it to a different currency
|
|
12
|
+
* @returns {{currency: string, setCurrency: function}[]}
|
|
13
|
+
*/
|
|
14
|
+
export const useCurrency = () => {
|
|
15
|
+
const context = useContext(CurrencyContext)
|
|
16
|
+
if (context === undefined) {
|
|
17
|
+
throw new Error('useCurrency must be used within CurrencyProvider')
|
|
18
|
+
}
|
|
19
|
+
return context
|
|
20
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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 {renderHook} from '@testing-library/react'
|
|
10
|
+
import {useCurrency} from '@salesforce/retail-react-app/app/hooks/use-currency'
|
|
11
|
+
import {CurrencyProvider} from '@salesforce/retail-react-app/app/contexts'
|
|
12
|
+
import {DEFAULT_CURRENCY} from '@salesforce/retail-react-app/app/constants'
|
|
13
|
+
|
|
14
|
+
const wrapper = ({children}) => <CurrencyProvider>{children}</CurrencyProvider>
|
|
15
|
+
|
|
16
|
+
let resultCurrency = {}
|
|
17
|
+
|
|
18
|
+
const mockSetCurrency = jest.fn().mockImplementation((currency) => {
|
|
19
|
+
resultCurrency = {currency}
|
|
20
|
+
|
|
21
|
+
return resultCurrency
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const mockUseContext = jest.fn().mockImplementation(() => ({
|
|
25
|
+
currency: {},
|
|
26
|
+
setCurrency: mockSetCurrency
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
React.useContext = mockUseContext
|
|
30
|
+
describe('useCurrency', () => {
|
|
31
|
+
it('should set initial currency', () => {
|
|
32
|
+
const {result} = renderHook(() => useCurrency(), {wrapper})
|
|
33
|
+
|
|
34
|
+
expect(resultCurrency).toMatchObject({})
|
|
35
|
+
|
|
36
|
+
result.current.setCurrency(DEFAULT_CURRENCY)
|
|
37
|
+
|
|
38
|
+
expect(mockUseContext).toHaveBeenCalled()
|
|
39
|
+
expect(resultCurrency).toMatchObject({currency: DEFAULT_CURRENCY})
|
|
40
|
+
})
|
|
41
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023, 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 {useCustomerBaskets} from '@salesforce/commerce-sdk-react'
|
|
8
|
+
import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer'
|
|
9
|
+
import {isServer} from '@salesforce/retail-react-app/app/utils/utils'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* This hook combine some commerce-react-sdk hooks to provide more derived data for Retail App baskets
|
|
13
|
+
* @param id - basket id to get the current used basket among baskets returned, use first basket in the array if not defined
|
|
14
|
+
* @param shouldFetchProductDetail - boolean to indicate if the baskets should fetch product details based on basket items
|
|
15
|
+
*/
|
|
16
|
+
export const useCurrentBasket = ({id = ''} = {}) => {
|
|
17
|
+
const {data: customer} = useCurrentCustomer()
|
|
18
|
+
const {customerId} = customer
|
|
19
|
+
const {data: basketsData, ...restOfQuery} = useCustomerBaskets(
|
|
20
|
+
{parameters: {customerId}},
|
|
21
|
+
{
|
|
22
|
+
enabled: !!customerId && !isServer
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
// if id is not defined, by default use the first basket in the list
|
|
27
|
+
const currentBasket =
|
|
28
|
+
basketsData?.baskets?.find((basket) => basket.basketId === id) || basketsData?.baskets?.[0]
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
...restOfQuery,
|
|
32
|
+
data: currentBasket,
|
|
33
|
+
derivedData: {
|
|
34
|
+
hasBasket: basketsData?.total > 0,
|
|
35
|
+
totalItems:
|
|
36
|
+
currentBasket?.productItems?.reduce((acc, item) => acc + item.quantity, 0) || 0
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
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 {useCustomer, useCustomerId, useCustomerType} from '@salesforce/commerce-sdk-react'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A hook that returns the current customer.
|
|
12
|
+
*
|
|
13
|
+
*/
|
|
14
|
+
export const useCurrentCustomer = () => {
|
|
15
|
+
const customerId = useCustomerId()
|
|
16
|
+
const {isRegistered, isGuest, customerType} = useCustomerType()
|
|
17
|
+
const query = useCustomer({parameters: {customerId}}, {enabled: !!customerId && isRegistered})
|
|
18
|
+
const value = {
|
|
19
|
+
...query,
|
|
20
|
+
data: {
|
|
21
|
+
...query.data,
|
|
22
|
+
customerType,
|
|
23
|
+
customerId,
|
|
24
|
+
isRegistered,
|
|
25
|
+
isGuest
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return value
|
|
29
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
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 {useVariant} from '@salesforce/retail-react-app/app/hooks/use-variant'
|
|
10
|
+
import {useIntl} from 'react-intl'
|
|
11
|
+
import {useVariationParams} from '@salesforce/retail-react-app/app/hooks/use-variation-params'
|
|
12
|
+
import {useVariationAttributes} from '@salesforce/retail-react-app/app/hooks/use-variation-attributes'
|
|
13
|
+
|
|
14
|
+
const OUT_OF_STOCK = 'OUT_OF_STOCK'
|
|
15
|
+
const UNFULFILLABLE = 'UNFULFILLABLE'
|
|
16
|
+
|
|
17
|
+
// TODO: This needs to be refactored.
|
|
18
|
+
export const useDerivedProduct = (product, isProductPartOfSet = false) => {
|
|
19
|
+
const showLoading = !product
|
|
20
|
+
const stockLevel = product?.inventory?.stockLevel || 0
|
|
21
|
+
const stepQuantity = product?.stepQuantity || 1
|
|
22
|
+
const minOrderQuantity = stockLevel > 0 ? product?.minOrderQuantity || 1 : 0
|
|
23
|
+
const initialQuantity = product?.quantity || product?.minOrderQuantity || 1
|
|
24
|
+
|
|
25
|
+
const intl = useIntl()
|
|
26
|
+
const variant = useVariant(product, isProductPartOfSet)
|
|
27
|
+
const variationParams = useVariationParams(product, isProductPartOfSet)
|
|
28
|
+
const variationAttributes = useVariationAttributes(product, isProductPartOfSet)
|
|
29
|
+
const [quantity, setQuantity] = useState(initialQuantity)
|
|
30
|
+
|
|
31
|
+
// A product is considered out of stock if the stock level is 0 or if we have all our
|
|
32
|
+
// variation attributes selected, but don't have a variant. We do this because the API
|
|
33
|
+
// will sometimes return all the variants even if they are out of stock, but for other
|
|
34
|
+
// products it won't.
|
|
35
|
+
const isOutOfStock =
|
|
36
|
+
!stockLevel ||
|
|
37
|
+
(!variant && Object.keys(variationParams).length === variationAttributes.length)
|
|
38
|
+
const unfulfillable = stockLevel < quantity
|
|
39
|
+
const inventoryMessages = {
|
|
40
|
+
[OUT_OF_STOCK]: intl.formatMessage({
|
|
41
|
+
defaultMessage: 'Out of stock',
|
|
42
|
+
id: 'use_product.message.out_of_stock'
|
|
43
|
+
}),
|
|
44
|
+
[UNFULFILLABLE]: intl.formatMessage(
|
|
45
|
+
{
|
|
46
|
+
defaultMessage: 'Only {stockLevel} left!',
|
|
47
|
+
id: 'use_product.message.inventory_remaining'
|
|
48
|
+
},
|
|
49
|
+
{stockLevel}
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
const showInventoryMessage = variant && (isOutOfStock || unfulfillable)
|
|
53
|
+
const inventoryMessage =
|
|
54
|
+
(isOutOfStock && inventoryMessages[OUT_OF_STOCK]) ||
|
|
55
|
+
(unfulfillable && inventoryMessages[UNFULFILLABLE])
|
|
56
|
+
|
|
57
|
+
// If the `initialQuantity` changes, update the state. This typically happens
|
|
58
|
+
// when either the master product changes, or the inventory of the product changes
|
|
59
|
+
// from out-of-stock to in-stock or vice versa.
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
setQuantity(initialQuantity)
|
|
62
|
+
}, [initialQuantity])
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
showLoading,
|
|
66
|
+
showInventoryMessage,
|
|
67
|
+
inventoryMessage,
|
|
68
|
+
variationAttributes,
|
|
69
|
+
quantity,
|
|
70
|
+
minOrderQuantity,
|
|
71
|
+
stepQuantity,
|
|
72
|
+
variationParams,
|
|
73
|
+
setQuantity,
|
|
74
|
+
variant,
|
|
75
|
+
stockLevel
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
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 PropTypes from 'prop-types'
|
|
10
|
+
|
|
11
|
+
import {screen} from '@testing-library/react'
|
|
12
|
+
import {createMemoryHistory} from 'history'
|
|
13
|
+
import {useDerivedProduct} from '@salesforce/retail-react-app/app/hooks/use-derived-product'
|
|
14
|
+
import mockProductDetail from '@salesforce/retail-react-app/app/mocks/variant-750518699578M'
|
|
15
|
+
import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
|
|
16
|
+
|
|
17
|
+
const MockComponent = ({product}) => {
|
|
18
|
+
const {inventoryMessage, quantity, variationParams, variant} = useDerivedProduct(product)
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div>
|
|
22
|
+
<div>{`Quantity: ${quantity}`}</div>
|
|
23
|
+
<div>{inventoryMessage}</div>
|
|
24
|
+
<div>{JSON.stringify(variant)}</div>
|
|
25
|
+
<div>{JSON.stringify(variationParams)}</div>
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
MockComponent.propTypes = {
|
|
31
|
+
product: PropTypes.object
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('useDerivedProduct hook', () => {
|
|
35
|
+
test('runs properly', () => {
|
|
36
|
+
const history = createMemoryHistory()
|
|
37
|
+
history.push('/test/path?test')
|
|
38
|
+
|
|
39
|
+
renderWithProviders(<MockComponent product={mockProductDetail} />)
|
|
40
|
+
|
|
41
|
+
expect(screen.getByText(/Quantity: 1/)).toBeInTheDocument()
|
|
42
|
+
expect(
|
|
43
|
+
screen.getByText(
|
|
44
|
+
/{"orderable":true,"price":299.99,"productId":"750518699578M","variationValues":{"color":"BLACKFB","size":"038","width":"V"}}/
|
|
45
|
+
)
|
|
46
|
+
).toBeInTheDocument()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('has out of stock message', () => {
|
|
50
|
+
const history = createMemoryHistory()
|
|
51
|
+
history.push('/test/path')
|
|
52
|
+
|
|
53
|
+
const mockData = {
|
|
54
|
+
...mockProductDetail,
|
|
55
|
+
inventory: {
|
|
56
|
+
ats: 0,
|
|
57
|
+
backorderable: false,
|
|
58
|
+
id: 'inventory_m',
|
|
59
|
+
orderable: false,
|
|
60
|
+
preorderable: false,
|
|
61
|
+
stockLevel: 0
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
renderWithProviders(<MockComponent product={mockData} />)
|
|
66
|
+
|
|
67
|
+
expect(screen.getByText(/Out of stock/)).toBeInTheDocument()
|
|
68
|
+
})
|
|
69
|
+
})
|