@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.
Files changed (425) hide show
  1. package/.eslintignore +7 -0
  2. package/.eslintrc.js +25 -0
  3. package/.prettierignore +4 -0
  4. package/.prettierrc.yaml +7 -0
  5. package/CHANGELOG.md +173 -0
  6. package/LICENSE +14 -0
  7. package/README.md +48 -0
  8. package/app/assets/svg/account.svg +3 -0
  9. package/app/assets/svg/alert.svg +3 -0
  10. package/app/assets/svg/basket.svg +3 -0
  11. package/app/assets/svg/brand-logo.svg +10 -0
  12. package/app/assets/svg/cc-amex.svg +7 -0
  13. package/app/assets/svg/cc-cvv.svg +8 -0
  14. package/app/assets/svg/cc-discover.svg +14 -0
  15. package/app/assets/svg/cc-mastercard.svg +8 -0
  16. package/app/assets/svg/cc-visa.svg +11 -0
  17. package/app/assets/svg/check-circle.svg +3 -0
  18. package/app/assets/svg/check.svg +3 -0
  19. package/app/assets/svg/chevron-down.svg +3 -0
  20. package/app/assets/svg/chevron-left.svg +3 -0
  21. package/app/assets/svg/chevron-right.svg +3 -0
  22. package/app/assets/svg/chevron-up.svg +3 -0
  23. package/app/assets/svg/close.svg +3 -0
  24. package/app/assets/svg/dashboard.svg +4 -0
  25. package/app/assets/svg/figma-logo.svg +14 -0
  26. package/app/assets/svg/file.svg +3 -0
  27. package/app/assets/svg/filter.svg +3 -0
  28. package/app/assets/svg/flag-ca.svg +5 -0
  29. package/app/assets/svg/flag-cn.svg +19 -0
  30. package/app/assets/svg/flag-fr.svg +19 -0
  31. package/app/assets/svg/flag-gb.svg +16 -0
  32. package/app/assets/svg/flag-it.svg +29 -0
  33. package/app/assets/svg/flag-jp.svg +10 -0
  34. package/app/assets/svg/flag-us.svg +7 -0
  35. package/app/assets/svg/github-logo.svg +40 -0
  36. package/app/assets/svg/hamburger.svg +8 -0
  37. package/app/assets/svg/heart-solid.svg +7 -0
  38. package/app/assets/svg/heart.svg +3 -0
  39. package/app/assets/svg/info.svg +3 -0
  40. package/app/assets/svg/like.svg +4 -0
  41. package/app/assets/svg/location.svg +3 -0
  42. package/app/assets/svg/lock.svg +3 -0
  43. package/app/assets/svg/paypal.svg +19 -0
  44. package/app/assets/svg/plug.svg +3 -0
  45. package/app/assets/svg/plus.svg +3 -0
  46. package/app/assets/svg/receipt.svg +3 -0
  47. package/app/assets/svg/search.svg +8 -0
  48. package/app/assets/svg/signout.svg +3 -0
  49. package/app/assets/svg/social-facebook.svg +3 -0
  50. package/app/assets/svg/social-instagram.svg +3 -0
  51. package/app/assets/svg/social-pinterest.svg +4 -0
  52. package/app/assets/svg/social-twitter.svg +3 -0
  53. package/app/assets/svg/social-youtube.svg +3 -0
  54. package/app/assets/svg/user.svg +3 -0
  55. package/app/assets/svg/visibility-off.svg +5 -0
  56. package/app/assets/svg/visibility.svg +3 -0
  57. package/app/components/_app/index.jsx +401 -0
  58. package/app/components/_app/index.test.js +85 -0
  59. package/app/components/_app/partials/above-header.jsx +10 -0
  60. package/app/components/_app-config/index.jsx +125 -0
  61. package/app/components/_app-config/index.test.js +77 -0
  62. package/app/components/_error/index.jsx +142 -0
  63. package/app/components/_error/index.test.js +25 -0
  64. package/app/components/action-card/index.jsx +75 -0
  65. package/app/components/address-display/index.jsx +30 -0
  66. package/app/components/basic-tile/index.jsx +65 -0
  67. package/app/components/basic-tile/index.test.js +23 -0
  68. package/app/components/breadcrumb/index.jsx +67 -0
  69. package/app/components/breadcrumb/index.test.js +30 -0
  70. package/app/components/confirmation-modal/index.jsx +111 -0
  71. package/app/components/confirmation-modal/index.test.js +98 -0
  72. package/app/components/drawer-menu/index.jsx +405 -0
  73. package/app/components/drawer-menu/index.test.js +33 -0
  74. package/app/components/dynamic-image/index.jsx +56 -0
  75. package/app/components/field/index.jsx +161 -0
  76. package/app/components/footer/index.jsx +269 -0
  77. package/app/components/footer/index.test.js +22 -0
  78. package/app/components/forms/address-fields.jsx +49 -0
  79. package/app/components/forms/credit-card-fields.jsx +149 -0
  80. package/app/components/forms/form-action-buttons.jsx +55 -0
  81. package/app/components/forms/login-fields.jsx +31 -0
  82. package/app/components/forms/password-requirements.jsx +99 -0
  83. package/app/components/forms/post-checkout-registration-fields.jsx +43 -0
  84. package/app/components/forms/profile-fields.jsx +36 -0
  85. package/app/components/forms/promo-code-fields.jsx +43 -0
  86. package/app/components/forms/registration-fields.jsx +42 -0
  87. package/app/components/forms/reset-password-fields.jsx +31 -0
  88. package/app/components/forms/state-province-options.jsx +75 -0
  89. package/app/components/forms/update-password-fields.jsx +49 -0
  90. package/app/components/forms/useAddressFields.jsx +196 -0
  91. package/app/components/forms/useCreditCardFields.jsx +146 -0
  92. package/app/components/forms/useLoginFields.jsx +52 -0
  93. package/app/components/forms/useProfileFields.jsx +95 -0
  94. package/app/components/forms/usePromoCodeFields.jsx +39 -0
  95. package/app/components/forms/useRegistrationFields.jsx +136 -0
  96. package/app/components/forms/useResetPasswordFields.jsx +40 -0
  97. package/app/components/forms/useUpdatePasswordFields.jsx +89 -0
  98. package/app/components/header/index.jsx +290 -0
  99. package/app/components/header/index.test.js +217 -0
  100. package/app/components/hero/index.jsx +84 -0
  101. package/app/components/hero/index.test.js +40 -0
  102. package/app/components/icons/index.jsx +158 -0
  103. package/app/components/icons/index.test.js +20 -0
  104. package/app/components/image-gallery/index.jsx +176 -0
  105. package/app/components/image-gallery/index.test.js +485 -0
  106. package/app/components/item-variant/index.jsx +33 -0
  107. package/app/components/item-variant/item-attributes.jsx +107 -0
  108. package/app/components/item-variant/item-image.jsx +73 -0
  109. package/app/components/item-variant/item-name.jsx +28 -0
  110. package/app/components/item-variant/item-price.jsx +117 -0
  111. package/app/components/link/index.jsx +32 -0
  112. package/app/components/link/index.test.js +72 -0
  113. package/app/components/links-list/index.jsx +89 -0
  114. package/app/components/links-list/index.test.js +62 -0
  115. package/app/components/list-menu/index.jsx +280 -0
  116. package/app/components/list-menu/index.test.js +44 -0
  117. package/app/components/loading-spinner/index.jsx +46 -0
  118. package/app/components/locale-selector/index.jsx +124 -0
  119. package/app/components/locale-selector/index.test.js +37 -0
  120. package/app/components/locale-text/index.jsx +97 -0
  121. package/app/components/locale-text/index.test.js +36 -0
  122. package/app/components/login/index.jsx +96 -0
  123. package/app/components/nested-accordion/index.jsx +185 -0
  124. package/app/components/nested-accordion/index.test.js +98 -0
  125. package/app/components/offline-banner/index.jsx +40 -0
  126. package/app/components/offline-banner/index.test.js +15 -0
  127. package/app/components/offline-boundary/index.jsx +104 -0
  128. package/app/components/offline-boundary/index.test.js +123 -0
  129. package/app/components/order-summary/index.jsx +331 -0
  130. package/app/components/page-action-placeholder/index.jsx +50 -0
  131. package/app/components/pagination/index.jsx +134 -0
  132. package/app/components/pagination/index.test.js +25 -0
  133. package/app/components/product-item/index.jsx +146 -0
  134. package/app/components/product-item/index.test.js +38 -0
  135. package/app/components/product-scroller/index.jsx +172 -0
  136. package/app/components/product-scroller/index.test.js +98 -0
  137. package/app/components/product-tile/index.jsx +195 -0
  138. package/app/components/product-tile/index.test.js +96 -0
  139. package/app/components/product-view/index.jsx +538 -0
  140. package/app/components/product-view/index.test.js +224 -0
  141. package/app/components/product-view-modal/index.jsx +48 -0
  142. package/app/components/product-view-modal/index.test.js +72 -0
  143. package/app/components/promo-code/index.jsx +162 -0
  144. package/app/components/promo-popover/index.jsx +83 -0
  145. package/app/components/quantity-picker/index.jsx +58 -0
  146. package/app/components/radio-card/index.jsx +75 -0
  147. package/app/components/recommended-products/index.jsx +227 -0
  148. package/app/components/register/index.jsx +114 -0
  149. package/app/components/reset-password/index.jsx +87 -0
  150. package/app/components/responsive/index.jsx +29 -0
  151. package/app/components/scroll-to-top/index.jsx +24 -0
  152. package/app/components/scroll-to-top/index.test.js +46 -0
  153. package/app/components/search/index.jsx +279 -0
  154. package/app/components/search/index.test.js +127 -0
  155. package/app/components/search/partials/recent-searches.jsx +76 -0
  156. package/app/components/search/partials/search-suggestions.jsx +45 -0
  157. package/app/components/search/partials/suggestions.jsx +43 -0
  158. package/app/components/section/index.jsx +68 -0
  159. package/app/components/seo/index.jsx +33 -0
  160. package/app/components/social-icons/index.jsx +101 -0
  161. package/app/components/social-icons/index.test.js +30 -0
  162. package/app/components/swatch-group/index.jsx +77 -0
  163. package/app/components/swatch-group/index.test.js +136 -0
  164. package/app/components/swatch-group/swatch.jsx +94 -0
  165. package/app/components/toggle-card/index.jsx +97 -0
  166. package/app/components/with-registration/index.jsx +58 -0
  167. package/app/components/with-registration/index.test.js +85 -0
  168. package/app/constants.js +109 -0
  169. package/app/contexts/index.js +92 -0
  170. package/app/hooks/einstein-mock-data.js +916 -0
  171. package/app/hooks/index.js +17 -0
  172. package/app/hooks/use-add-to-cart-modal.js +344 -0
  173. package/app/hooks/use-add-to-cart-modal.test.js +625 -0
  174. package/app/hooks/use-auth-modal.js +337 -0
  175. package/app/hooks/use-auth-modal.test.js +365 -0
  176. package/app/hooks/use-currency.js +20 -0
  177. package/app/hooks/use-currency.test.js +41 -0
  178. package/app/hooks/use-current-basket.js +39 -0
  179. package/app/hooks/use-current-customer.js +29 -0
  180. package/app/hooks/use-derived-product.js +77 -0
  181. package/app/hooks/use-derived-product.test.js +69 -0
  182. package/app/hooks/use-einstein.js +512 -0
  183. package/app/hooks/use-einstein.test.js +224 -0
  184. package/app/hooks/use-intersection-observer.js +64 -0
  185. package/app/hooks/use-limit-urls.js +31 -0
  186. package/app/hooks/use-limit-urls.test.js +40 -0
  187. package/app/hooks/use-multi-site.js +36 -0
  188. package/app/hooks/use-multi-site.test.js +53 -0
  189. package/app/hooks/use-navigation.js +37 -0
  190. package/app/hooks/use-navigation.test.js +109 -0
  191. package/app/hooks/use-page-urls.js +35 -0
  192. package/app/hooks/use-page-urls.test.js +39 -0
  193. package/app/hooks/use-pdp-search-params.js +16 -0
  194. package/app/hooks/use-pdp-search-params.test.js +52 -0
  195. package/app/hooks/use-previous.js +17 -0
  196. package/app/hooks/use-product-view-modal.js +93 -0
  197. package/app/hooks/use-product-view-modal.test.js +172 -0
  198. package/app/hooks/use-search-params.js +96 -0
  199. package/app/hooks/use-search-params.test.js +91 -0
  200. package/app/hooks/use-sort-urls.js +33 -0
  201. package/app/hooks/use-sort-urls.test.js +42 -0
  202. package/app/hooks/use-toast.js +68 -0
  203. package/app/hooks/use-toast.test.js +58 -0
  204. package/app/hooks/use-variant.js +32 -0
  205. package/app/hooks/use-variant.test.js +81 -0
  206. package/app/hooks/use-variation-attributes.js +138 -0
  207. package/app/hooks/use-variation-attributes.test.js +119 -0
  208. package/app/hooks/use-variation-params.js +31 -0
  209. package/app/hooks/use-variation-params.test.js +73 -0
  210. package/app/hooks/use-wish-list.js +42 -0
  211. package/app/main.jsx +14 -0
  212. package/app/mocks/basket-with-suit.js +146 -0
  213. package/app/mocks/empty-basket.js +39 -0
  214. package/app/mocks/mock-data.js +5632 -0
  215. package/app/mocks/product-set-winter-lookM.js +1224 -0
  216. package/app/mocks/searchResults.js +144 -0
  217. package/app/mocks/variant-750518699578M.js +434 -0
  218. package/app/page-designer/README.md +102 -0
  219. package/app/page-designer/assets/image-tile/index.jsx +51 -0
  220. package/app/page-designer/assets/image-tile/index.test.js +30 -0
  221. package/app/page-designer/assets/image-with-text/index.jsx +140 -0
  222. package/app/page-designer/assets/image-with-text/index.test.js +38 -0
  223. package/app/page-designer/assets/index.js +9 -0
  224. package/app/page-designer/index.js +10 -0
  225. package/app/page-designer/layouts/carousel/index.jsx +222 -0
  226. package/app/page-designer/layouts/carousel/index.test.js +43 -0
  227. package/app/page-designer/layouts/index.js +14 -0
  228. package/app/page-designer/layouts/mobileGrid1r1c/index.jsx +36 -0
  229. package/app/page-designer/layouts/mobileGrid1r1c/index.test.js +35 -0
  230. package/app/page-designer/layouts/mobileGrid2r1c/index.jsx +37 -0
  231. package/app/page-designer/layouts/mobileGrid2r1c/index.test.js +47 -0
  232. package/app/page-designer/layouts/mobileGrid2r2c/index.jsx +37 -0
  233. package/app/page-designer/layouts/mobileGrid2r2c/index.test.js +71 -0
  234. package/app/page-designer/layouts/mobileGrid2r3c/index.jsx +37 -0
  235. package/app/page-designer/layouts/mobileGrid2r3c/index.test.js +95 -0
  236. package/app/page-designer/layouts/mobileGrid3r1c/index.jsx +37 -0
  237. package/app/page-designer/layouts/mobileGrid3r1c/index.test.js +59 -0
  238. package/app/page-designer/layouts/mobileGrid3r2c/index.jsx +37 -0
  239. package/app/page-designer/layouts/mobileGrid3r2c/index.test.js +95 -0
  240. package/app/page-designer/utils.js +14 -0
  241. package/app/pages/account/addresses.jsx +382 -0
  242. package/app/pages/account/addresses.test.js +120 -0
  243. package/app/pages/account/constant.js +57 -0
  244. package/app/pages/account/index.jsx +237 -0
  245. package/app/pages/account/index.test.js +188 -0
  246. package/app/pages/account/order-detail.jsx +397 -0
  247. package/app/pages/account/order-history.jsx +264 -0
  248. package/app/pages/account/orders.jsx +30 -0
  249. package/app/pages/account/orders.test.js +95 -0
  250. package/app/pages/account/profile.jsx +357 -0
  251. package/app/pages/account/wishlist/index.jsx +195 -0
  252. package/app/pages/account/wishlist/index.mock.js +1481 -0
  253. package/app/pages/account/wishlist/index.test.js +56 -0
  254. package/app/pages/account/wishlist/partials/wishlist-primary-action.jsx +170 -0
  255. package/app/pages/account/wishlist/partials/wishlist-primary-action.mock.js +1623 -0
  256. package/app/pages/account/wishlist/partials/wishlist-primary-action.test.js +99 -0
  257. package/app/pages/account/wishlist/partials/wishlist-secondary-button-group.jsx +120 -0
  258. package/app/pages/account/wishlist/partials/wishlist-secondary-button-group.test.js +391 -0
  259. package/app/pages/cart/index.jsx +476 -0
  260. package/app/pages/cart/index.test.js +481 -0
  261. package/app/pages/cart/partials/cart-cta.jsx +46 -0
  262. package/app/pages/cart/partials/cart-secondary-button-group.jsx +135 -0
  263. package/app/pages/cart/partials/cart-secondary-button-group.test.js +103 -0
  264. package/app/pages/cart/partials/cart-skeleton.jsx +93 -0
  265. package/app/pages/cart/partials/cart-title.jsx +27 -0
  266. package/app/pages/cart/partials/empty-cart.jsx +86 -0
  267. package/app/pages/checkout/confirmation.jsx +541 -0
  268. package/app/pages/checkout/confirmation.mock.js +450 -0
  269. package/app/pages/checkout/confirmation.test.js +114 -0
  270. package/app/pages/checkout/index.jsx +169 -0
  271. package/app/pages/checkout/index.test.js +582 -0
  272. package/app/pages/checkout/partials/cc-radio-group.jsx +122 -0
  273. package/app/pages/checkout/partials/checkout-footer.jsx +140 -0
  274. package/app/pages/checkout/partials/checkout-footer.test.js +16 -0
  275. package/app/pages/checkout/partials/checkout-header.jsx +54 -0
  276. package/app/pages/checkout/partials/checkout-header.test.js +16 -0
  277. package/app/pages/checkout/partials/checkout-skeleton.jsx +52 -0
  278. package/app/pages/checkout/partials/contact-info.jsx +251 -0
  279. package/app/pages/checkout/partials/contact-info.test.js +43 -0
  280. package/app/pages/checkout/partials/payment-form.jsx +97 -0
  281. package/app/pages/checkout/partials/payment.jsx +276 -0
  282. package/app/pages/checkout/partials/shipping-address-selection.jsx +377 -0
  283. package/app/pages/checkout/partials/shipping-address.jsx +132 -0
  284. package/app/pages/checkout/partials/shipping-options.jsx +232 -0
  285. package/app/pages/checkout/util/checkout-context.js +94 -0
  286. package/app/pages/home/data.js +134 -0
  287. package/app/pages/home/index.jsx +301 -0
  288. package/app/pages/home/index.test.js +23 -0
  289. package/app/pages/login/index.jsx +123 -0
  290. package/app/pages/login/index.test.js +229 -0
  291. package/app/pages/login-redirect/index.jsx +23 -0
  292. package/app/pages/login-redirect/index.test.js +16 -0
  293. package/app/pages/page-not-found/index.jsx +90 -0
  294. package/app/pages/page-not-found/index.test.js +31 -0
  295. package/app/pages/product-detail/index.jsx +394 -0
  296. package/app/pages/product-detail/index.mock.js +197 -0
  297. package/app/pages/product-detail/index.test.js +162 -0
  298. package/app/pages/product-detail/partials/information-accordion.jsx +121 -0
  299. package/app/pages/product-list/index.jsx +735 -0
  300. package/app/pages/product-list/index.test.js +180 -0
  301. package/app/pages/product-list/partials/above-page-header.jsx +10 -0
  302. package/app/pages/product-list/partials/checkbox-refinements.jsx +41 -0
  303. package/app/pages/product-list/partials/checkbox-refinements.test.js +53 -0
  304. package/app/pages/product-list/partials/color-refinements.jsx +88 -0
  305. package/app/pages/product-list/partials/empty-results.jsx +118 -0
  306. package/app/pages/product-list/partials/link-refinements.jsx +38 -0
  307. package/app/pages/product-list/partials/page-header.jsx +42 -0
  308. package/app/pages/product-list/partials/radio-refinements.jsx +60 -0
  309. package/app/pages/product-list/partials/refinements.jsx +144 -0
  310. package/app/pages/product-list/partials/selected-refinements.jsx +100 -0
  311. package/app/pages/product-list/partials/size-refinements.jsx +55 -0
  312. package/app/pages/registration/index.jsx +87 -0
  313. package/app/pages/registration/index.test.jsx +132 -0
  314. package/app/pages/reset-password/index.jsx +112 -0
  315. package/app/pages/reset-password/index.test.jsx +141 -0
  316. package/app/request-processor.js +118 -0
  317. package/app/request-processor.test.js +23 -0
  318. package/app/routes.jsx +111 -0
  319. package/app/routes.test.js +13 -0
  320. package/app/ssr.js +70 -0
  321. package/app/static/ico/favicon.ico +0 -0
  322. package/app/static/img/global/app-icon-192.png +0 -0
  323. package/app/static/img/global/app-icon-512.png +0 -0
  324. package/app/static/img/global/apple-touch-icon.png +0 -0
  325. package/app/static/img/hero.png +0 -0
  326. package/app/static/manifest.json +19 -0
  327. package/app/static/robots.txt +2 -0
  328. package/app/theme/components/base/accordion.js +21 -0
  329. package/app/theme/components/base/alert.js +17 -0
  330. package/app/theme/components/base/badge.js +25 -0
  331. package/app/theme/components/base/button.js +77 -0
  332. package/app/theme/components/base/checkbox.js +30 -0
  333. package/app/theme/components/base/container.js +17 -0
  334. package/app/theme/components/base/drawer.js +26 -0
  335. package/app/theme/components/base/formLabel.js +13 -0
  336. package/app/theme/components/base/icon.js +13 -0
  337. package/app/theme/components/base/input.js +44 -0
  338. package/app/theme/components/base/modal.js +11 -0
  339. package/app/theme/components/base/popover.js +61 -0
  340. package/app/theme/components/base/radio.js +33 -0
  341. package/app/theme/components/base/select.js +15 -0
  342. package/app/theme/components/base/skeleton.js +12 -0
  343. package/app/theme/components/base/tooltip.js +19 -0
  344. package/app/theme/components/project/_app.js +25 -0
  345. package/app/theme/components/project/breadcrumb.js +25 -0
  346. package/app/theme/components/project/checkout-footer.js +35 -0
  347. package/app/theme/components/project/drawer-menu.js +66 -0
  348. package/app/theme/components/project/footer.js +84 -0
  349. package/app/theme/components/project/header.js +84 -0
  350. package/app/theme/components/project/image-gallery.js +59 -0
  351. package/app/theme/components/project/links-list.js +43 -0
  352. package/app/theme/components/project/list-menu.js +91 -0
  353. package/app/theme/components/project/locale-selector.js +42 -0
  354. package/app/theme/components/project/nested-accordion.js +26 -0
  355. package/app/theme/components/project/offline-banner.js +25 -0
  356. package/app/theme/components/project/pagination.js +22 -0
  357. package/app/theme/components/project/product-tile.js +32 -0
  358. package/app/theme/components/project/social-icons.js +52 -0
  359. package/app/theme/components/project/swatch-group.js +115 -0
  360. package/app/theme/foundations/colors.js +170 -0
  361. package/app/theme/foundations/gradients.js +9 -0
  362. package/app/theme/foundations/layerStyles.js +41 -0
  363. package/app/theme/foundations/shadows.js +9 -0
  364. package/app/theme/foundations/sizes.js +18 -0
  365. package/app/theme/foundations/space.js +9 -0
  366. package/app/theme/foundations/styles.js +21 -0
  367. package/app/theme/index.js +104 -0
  368. package/app/utils/cc-utils.js +112 -0
  369. package/app/utils/cc-utils.test.js +41 -0
  370. package/app/utils/image-groups-utils.js +62 -0
  371. package/app/utils/image-groups-utils.test.js +65 -0
  372. package/app/utils/locale.js +78 -0
  373. package/app/utils/locale.test.js +112 -0
  374. package/app/utils/password-utils.js +21 -0
  375. package/app/utils/phone-utils.js +22 -0
  376. package/app/utils/phone-utils.test.js +15 -0
  377. package/app/utils/product-utils.js +35 -0
  378. package/app/utils/product-utils.test.js +51 -0
  379. package/app/utils/responsive-image.js +198 -0
  380. package/app/utils/responsive-image.test.js +170 -0
  381. package/app/utils/routes-utils.js +111 -0
  382. package/app/utils/routes-utils.test.js +291 -0
  383. package/app/utils/site-utils.js +222 -0
  384. package/app/utils/site-utils.test.js +376 -0
  385. package/app/utils/test-utils.js +257 -0
  386. package/app/utils/url.js +291 -0
  387. package/app/utils/url.test.js +421 -0
  388. package/app/utils/utils.js +201 -0
  389. package/app/utils/utils.test.js +182 -0
  390. package/babel.config.js +7 -0
  391. package/cache-hash-config.json +8 -0
  392. package/config/default.js +64 -0
  393. package/config/mocks/default.js +131 -0
  394. package/config/sites.js +78 -0
  395. package/jest-setup.js +191 -0
  396. package/jest.config.js +50 -0
  397. package/jsconfig.json +13 -0
  398. package/package.json +105 -0
  399. package/scripts/extract-default-messages.js +92 -0
  400. package/tests/lighthouserc.js +37 -0
  401. package/translations/README.md +127 -0
  402. package/translations/compiled/de-DE.json +3212 -0
  403. package/translations/compiled/en-GB.json +3212 -0
  404. package/translations/compiled/en-US.json +3212 -0
  405. package/translations/compiled/en-XA.json +6948 -0
  406. package/translations/compiled/es-MX.json +3216 -0
  407. package/translations/compiled/fr-FR.json +3216 -0
  408. package/translations/compiled/it-IT.json +3188 -0
  409. package/translations/compiled/ja-JP.json +3200 -0
  410. package/translations/compiled/ko-KR.json +3180 -0
  411. package/translations/compiled/pt-BR.json +3220 -0
  412. package/translations/compiled/zh-CN.json +3212 -0
  413. package/translations/compiled/zh-TW.json +3208 -0
  414. package/translations/de-DE.json +1417 -0
  415. package/translations/en-GB.json +1417 -0
  416. package/translations/en-US.json +1417 -0
  417. package/translations/es-MX.json +1417 -0
  418. package/translations/fr-FR.json +1417 -0
  419. package/translations/it-IT.json +1417 -0
  420. package/translations/ja-JP.json +1417 -0
  421. package/translations/ko-KR.json +1417 -0
  422. package/translations/pt-BR.json +1417 -0
  423. package/translations/zh-CN.json +1417 -0
  424. package/translations/zh-TW.json +1417 -0
  425. 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
+ }