@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,224 @@
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 {EinsteinAPI} from '@salesforce/retail-react-app/app/hooks/use-einstein'
8
+ import {
9
+ mockAddToCartProduct,
10
+ mockProduct,
11
+ mockCategory,
12
+ mockSearchResults,
13
+ mockBasket,
14
+ mockRecommenderDetails
15
+ } from '@salesforce/retail-react-app/app/hooks/einstein-mock-data'
16
+ import fetchMock from 'jest-fetch-mock'
17
+
18
+ const einsteinApi = new EinsteinAPI({
19
+ host: `http://localhost/test-path`,
20
+ einsteinId: 'test-id',
21
+ siteId: 'test-site-id',
22
+ cookieId: 'test-usid'
23
+ })
24
+
25
+ const fetchOriginal = global.fetch
26
+
27
+ beforeAll(() => {
28
+ global.fetch = fetchMock
29
+ global.fetch.mockResponse(JSON.stringify({}))
30
+ })
31
+
32
+ afterAll(() => {
33
+ global.fetch = fetchOriginal
34
+ })
35
+
36
+ describe('EinsteinAPI', () => {
37
+ test('viewProduct sends expected api request', async () => {
38
+ await einsteinApi.sendViewProduct(mockProduct)
39
+
40
+ expect(fetch).toHaveBeenCalledWith(
41
+ 'http://localhost/test-path/v3/activities/test-site-id/viewProduct',
42
+ {
43
+ method: 'POST',
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ 'x-cq-client-id': 'test-id'
47
+ },
48
+ body: '{"product":{"id":"56736828M","sku":"56736828M","altId":"","altIdType":""},"cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
49
+ }
50
+ )
51
+ })
52
+
53
+ test('viewSearch sends expected api request', async () => {
54
+ const searchTerm = 'tie'
55
+ await einsteinApi.sendViewSearch(searchTerm, mockSearchResults)
56
+ expect(fetch).toHaveBeenCalledWith(
57
+ 'http://localhost/test-path/v3/activities/test-site-id/viewSearch',
58
+ {
59
+ method: 'POST',
60
+ headers: {
61
+ 'Content-Type': 'application/json',
62
+ 'x-cq-client-id': 'test-id'
63
+ },
64
+ body: '{"searchText":"tie","products":[{"id":"25752986M","sku":"25752986M","altId":"","altIdType":""},{"id":"25752235M","sku":"25752235M","altId":"","altIdType":""},{"id":"25752218M","sku":"25752218M","altId":"","altIdType":""},{"id":"25752981M","sku":"25752981M","altId":"","altIdType":""}],"showProducts":true,"cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
65
+ }
66
+ )
67
+ })
68
+
69
+ test('viewCategory sends expected api request', async () => {
70
+ await einsteinApi.sendViewCategory(mockCategory, mockSearchResults)
71
+ expect(fetch).toHaveBeenCalledWith(
72
+ 'http://localhost/test-path/v3/activities/test-site-id/viewCategory',
73
+ {
74
+ method: 'POST',
75
+ headers: {
76
+ 'Content-Type': 'application/json',
77
+ 'x-cq-client-id': 'test-id'
78
+ },
79
+ body: '{"category":{"id":"mens-accessories-ties"},"products":[{"id":"25752986M","sku":"25752986M","altId":"","altIdType":""},{"id":"25752235M","sku":"25752235M","altId":"","altIdType":""},{"id":"25752218M","sku":"25752218M","altId":"","altIdType":""},{"id":"25752981M","sku":"25752981M","altId":"","altIdType":""}],"showProducts":true,"cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
80
+ }
81
+ )
82
+ })
83
+
84
+ test('clickSearch sends expected api request', async () => {
85
+ const searchTerm = 'tie'
86
+ const clickedProduct = mockSearchResults.hits[0]
87
+ await einsteinApi.sendClickSearch(searchTerm, clickedProduct)
88
+ expect(fetch).toHaveBeenCalledWith(
89
+ 'http://localhost/test-path/v3/activities/test-site-id/clickSearch',
90
+ {
91
+ method: 'POST',
92
+ headers: {
93
+ 'Content-Type': 'application/json',
94
+ 'x-cq-client-id': 'test-id'
95
+ },
96
+ body: '{"searchText":"tie","product":{"id":"25752986M","sku":"25752986M","altId":"","altIdType":""},"cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
97
+ }
98
+ )
99
+ })
100
+
101
+ test('clickCategory sends expected api request', async () => {
102
+ const clickedProduct = mockSearchResults.hits[0]
103
+ await einsteinApi.sendClickCategory(mockCategory, clickedProduct)
104
+ expect(fetch).toHaveBeenCalledWith(
105
+ 'http://localhost/test-path/v3/activities/test-site-id/clickCategory',
106
+ {
107
+ method: 'POST',
108
+ headers: {
109
+ 'Content-Type': 'application/json',
110
+ 'x-cq-client-id': 'test-id'
111
+ },
112
+ body: '{"category":{"id":"mens-accessories-ties"},"product":{"id":"25752986M","sku":"25752986M","altId":"","altIdType":""},"cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
113
+ }
114
+ )
115
+ })
116
+
117
+ test('viewPage sends expected api request', async () => {
118
+ const path = '/'
119
+ await einsteinApi.sendViewPage(path)
120
+ expect(fetch).toHaveBeenCalledWith(
121
+ 'http://localhost/test-path/v3/activities/test-site-id/viewPage',
122
+ {
123
+ method: 'POST',
124
+ headers: {
125
+ 'Content-Type': 'application/json',
126
+ 'x-cq-client-id': 'test-id'
127
+ },
128
+ body: '{"currentLocation":"/","cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
129
+ }
130
+ )
131
+ })
132
+
133
+ test('beginCheckout sends expected api request', async () => {
134
+ await einsteinApi.sendBeginCheckout(mockBasket)
135
+ expect(fetch).toHaveBeenCalledWith(
136
+ 'http://localhost/test-path/v3/activities/test-site-id/beginCheckout',
137
+ {
138
+ method: 'POST',
139
+ headers: {
140
+ 'Content-Type': 'application/json',
141
+ 'x-cq-client-id': 'test-id'
142
+ },
143
+ body: '{"products":[{"id":"682875719029M","sku":"","price":29.99,"quantity":1}],"amount":29.99,"cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
144
+ }
145
+ )
146
+ })
147
+
148
+ test('checkouStep sends expected api request', async () => {
149
+ const checkoutStepName = 'CheckoutStep'
150
+ const checkoutStep = 0
151
+ await einsteinApi.sendCheckoutStep(checkoutStepName, checkoutStep, mockBasket)
152
+ expect(fetch).toHaveBeenCalledWith(
153
+ 'http://localhost/test-path/v3/activities/test-site-id/checkoutStep',
154
+ {
155
+ method: 'POST',
156
+ headers: {
157
+ 'Content-Type': 'application/json',
158
+ 'x-cq-client-id': 'test-id'
159
+ },
160
+ body: '{"stepName":"CheckoutStep","stepNumber":0,"basketId":"f6bbeee30fb93c2f94213f60f8","cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
161
+ }
162
+ )
163
+ })
164
+
165
+ test('addToCart sends expected api request', async () => {
166
+ await einsteinApi.sendAddToCart([mockAddToCartProduct])
167
+ expect(fetch).toHaveBeenCalledWith(
168
+ 'http://localhost/test-path/v3/activities/test-site-id/addToCart',
169
+ {
170
+ method: 'POST',
171
+ headers: {
172
+ 'Content-Type': 'application/json',
173
+ 'x-cq-client-id': 'test-id'
174
+ },
175
+ body: '{"products":[{"id":"883360544021M","sku":"","price":155,"quantity":1}],"cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
176
+ }
177
+ )
178
+ })
179
+
180
+ test('clickRecommendation sends expected api request', async () => {
181
+ await einsteinApi.sendClickReco(mockRecommenderDetails, mockProduct)
182
+ expect(fetch).toHaveBeenCalledWith(
183
+ 'http://localhost/test-path/v3/activities/test-site-id/clickReco',
184
+ {
185
+ method: 'POST',
186
+ headers: {
187
+ 'Content-Type': 'application/json',
188
+ 'x-cq-client-id': 'test-id'
189
+ },
190
+ body: '{"recommenderName":"testRecommender","__recoUUID":"883360544021M","product":{"id":"56736828M","sku":"56736828M","altId":"","altIdType":""},"cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
191
+ }
192
+ )
193
+ })
194
+
195
+ test('viewRecommendation sends expected api request', async () => {
196
+ await einsteinApi.sendViewReco(mockRecommenderDetails, {id: 'test-reco'})
197
+ expect(fetch).toHaveBeenCalledWith(
198
+ 'http://localhost/test-path/v3/activities/test-site-id/viewReco',
199
+ {
200
+ method: 'POST',
201
+ headers: {
202
+ 'Content-Type': 'application/json',
203
+ 'x-cq-client-id': 'test-id'
204
+ },
205
+ body: '{"recommenderName":"testRecommender","__recoUUID":"883360544021M","products":{"id":"test-reco"},"cookieId":"test-usid","realm":"test","instanceType":"sbx"}'
206
+ }
207
+ )
208
+ })
209
+
210
+ test('getRecommenders send expected api request', async () => {
211
+ await einsteinApi.getRecommenders()
212
+
213
+ expect(fetch).toHaveBeenCalledWith(
214
+ 'http://localhost/test-path/v3/personalization/recommenders/test-site-id',
215
+ {
216
+ method: 'GET',
217
+ headers: {
218
+ 'Content-Type': 'application/json',
219
+ 'x-cq-client-id': 'test-id'
220
+ }
221
+ }
222
+ )
223
+ })
224
+ })
@@ -0,0 +1,64 @@
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 {useState, useEffect} from 'react'
8
+
9
+ /**
10
+ * Returns a boolean to indicate if an element is visible on the screen. Fall back to `true`
11
+ * if IntersectionObserver is not supported.
12
+ * https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver
13
+ * @param {Object} ref - element ref
14
+ * @param {Object} options
15
+ * @param {Object} options.root - See IntersectionObserver options
16
+ * @param {string} options.rootMargin - See IntersectionObserver options
17
+ * @param {number|Array<number>} options.threshold - See IntersectionObserver options
18
+ * @param {boolean} options.useOnce - Detach the observer after the element appears.
19
+ * @returns {boolean}
20
+ */
21
+ const useIntersectionObserver = (ref, options = {}) => {
22
+ const [isIntersecting, setIntersecting] = useState(false)
23
+
24
+ const {useOnce, ...ioOptions} = options
25
+
26
+ useEffect(() => {
27
+ if (!ref?.current) return
28
+
29
+ // Just set `isIntersecting` true if browser doesn't implement IntersectionObserver. If the use-case
30
+ // is critical and you need to support very old browsers, a polyfill will need to be added.
31
+ if (
32
+ !('IntersectionObserver' in window) ||
33
+ !('IntersectionObserverEntry' in window) ||
34
+ !('intersectionRatio' in window.IntersectionObserverEntry.prototype)
35
+ ) {
36
+ if (!isIntersecting) {
37
+ setIntersecting(true)
38
+ }
39
+
40
+ // We want to return early, but `useEffect` expects a function as the return value,
41
+ // so we just return a noop function.
42
+ return () => null
43
+ }
44
+
45
+ const observer = new IntersectionObserver(([entry]) => {
46
+ const onScreen = entry.isIntersecting
47
+ setIntersecting(onScreen)
48
+ if (useOnce && onScreen) {
49
+ observer.disconnect()
50
+ }
51
+ }, ioOptions)
52
+
53
+ observer.observe(ref?.current)
54
+
55
+ // Remove the observer as soon as the component is unmounted
56
+ return () => {
57
+ observer.disconnect()
58
+ }
59
+ }, [ref?.current])
60
+
61
+ return isIntersecting
62
+ }
63
+
64
+ export default useIntersectionObserver
@@ -0,0 +1,31 @@
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
+ // Constants
12
+ import {DEFAULT_LIMIT_VALUES} from '@salesforce/retail-react-app/app/constants'
13
+
14
+ // Utils
15
+ import {buildUrlSet} from '@salesforce/retail-react-app/app/utils/url'
16
+
17
+ /*
18
+ * Generate a memoized list of page size urls. Chaning the page size will reset
19
+ * the offset to zero to simplify things.
20
+ */
21
+ export const useLimitUrls = () => {
22
+ const location = useLocation()
23
+
24
+ return useMemo(
25
+ () =>
26
+ buildUrlSet(`${location.pathname}${location.search}`, 'limit', DEFAULT_LIMIT_VALUES, {
27
+ offset: 0
28
+ }),
29
+ [location.search, location.pathname]
30
+ )
31
+ }
@@ -0,0 +1,40 @@
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 {useLimitUrls} from '@salesforce/retail-react-app/app/hooks/use-limit-urls'
14
+
15
+ const MockComponent = () => {
16
+ const urls = useLimitUrls()
17
+
18
+ return (
19
+ <script data-testid="limits" type="application/json">
20
+ {JSON.stringify(urls)}
21
+ </script>
22
+ )
23
+ }
24
+
25
+ describe('The useLimitUrls', () => {
26
+ test('returns an array of urls, one values for each limit value.', () => {
27
+ const history = createMemoryHistory()
28
+ history.push('/test/path')
29
+
30
+ const wrapper = render(
31
+ <Router history={history}>
32
+ <MockComponent />
33
+ </Router>
34
+ )
35
+
36
+ expect(wrapper.getByTestId('limits').text).toBe(
37
+ '["/test/path?limit=25&offset=0","/test/path?limit=50&offset=0","/test/path?limit=100&offset=0"]'
38
+ )
39
+ })
40
+ })
@@ -0,0 +1,36 @@
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 {useCallback, useContext} from 'react'
9
+ import {MultiSiteContext} from '@salesforce/retail-react-app/app/contexts'
10
+
11
+ /**
12
+ * Custom React hook to get the function that returns usefule multi-site values, the site, the locale and
13
+ * the funtion used to build URLs following the App configuration.
14
+ * @returns {{site, locale, buildUrl: (function(*, *, *): *)}}
15
+ */
16
+ const useMultiSite = () => {
17
+ const context = useContext(MultiSiteContext)
18
+ if (context === undefined) {
19
+ throw new Error('useMultiSite must be used within MultiSiteProvider')
20
+ }
21
+ const {buildUrl: originalFn, site, locale} = context
22
+
23
+ const buildUrl = useCallback(
24
+ (path, siteRef, localeRef) => {
25
+ return originalFn(
26
+ path,
27
+ siteRef ? siteRef : site?.alias || site?.id,
28
+ localeRef ? localeRef : locale?.alias || locale?.id
29
+ )
30
+ },
31
+ [originalFn, site, locale]
32
+ )
33
+ return {site, locale, buildUrl}
34
+ }
35
+
36
+ export default useMultiSite
@@ -0,0 +1,53 @@
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 useMultiSite from '@salesforce/retail-react-app/app/hooks/use-multi-site'
11
+ import {MultiSiteProvider} from '@salesforce/retail-react-app/app/contexts'
12
+ import mockConfig from '@salesforce/retail-react-app/config/mocks/default'
13
+ import {DEFAULT_LOCALE} from '@salesforce/retail-react-app/app/utils/test-utils'
14
+
15
+ const wrapper = ({children}) => <MultiSiteProvider>{children}</MultiSiteProvider>
16
+
17
+ let resultuseMultiSite = {}
18
+
19
+ beforeEach(() => {
20
+ resultuseMultiSite = {}
21
+ })
22
+
23
+ const site = {
24
+ ...mockConfig.app.sites[0],
25
+ alias: 'uk'
26
+ }
27
+
28
+ const locale = DEFAULT_LOCALE
29
+
30
+ const buildUrl = jest.fn().mockImplementation((href, site, locale) => {
31
+ return `${site ? `/${site}` : ''}${locale ? `/${locale}` : ''}${href}`
32
+ })
33
+
34
+ const mockResultuseMultiSite = {
35
+ site,
36
+ locale,
37
+ buildUrl
38
+ }
39
+
40
+ const mockUseContext = jest.fn().mockImplementation(() => mockResultuseMultiSite)
41
+
42
+ React.useContext = mockUseContext
43
+ describe('useMultiSite', () => {
44
+ it('should set initial values', () => {
45
+ expect(resultuseMultiSite).toMatchObject({})
46
+
47
+ const {result} = renderHook(() => useMultiSite(), {wrapper})
48
+
49
+ expect(mockUseContext).toHaveBeenCalled()
50
+ expect(result.current).toHaveProperty('site')
51
+ expect(result.current).toHaveProperty('locale')
52
+ })
53
+ })
@@ -0,0 +1,37 @@
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 {useCallback} from 'react'
8
+ import {useHistory} from 'react-router'
9
+ import useMultiSite from '@salesforce/retail-react-app/app/hooks/use-multi-site'
10
+ import {removeSiteLocaleFromPath} from '@salesforce/retail-react-app/app/utils/url'
11
+
12
+ /**
13
+ * A convenience hook for programmatic navigation uses history's `push` or `replace`. The proper locale
14
+ * is automatically prepended to the provided path. Additional args are passed through to `history`.
15
+ * @returns {function} - Returns a navigate function that passes args to history methods.
16
+ */
17
+ const useNavigation = () => {
18
+ const history = useHistory()
19
+
20
+ const {site, locale: localeShortCode, buildUrl} = useMultiSite()
21
+
22
+ return useCallback(
23
+ /**
24
+ *
25
+ * @param {string} path - path to navigate to
26
+ * @param {('push'|'replace')} action - which history method to use
27
+ * @param {...any} args - additional args passed to `.push` or `.replace`
28
+ */
29
+ (path, action = 'push', ...args) => {
30
+ const updatedHref = buildUrl(removeSiteLocaleFromPath(path))
31
+ history[action](path === '/' ? '/' : updatedHref, ...args)
32
+ },
33
+ [localeShortCode, site]
34
+ )
35
+ }
36
+
37
+ export default useNavigation
@@ -0,0 +1,109 @@
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 userEvent from '@testing-library/user-event'
9
+ import useNavigation from '@salesforce/retail-react-app/app/hooks/use-navigation'
10
+ import mockConfig from '@salesforce/retail-react-app/config/mocks/default'
11
+ import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
12
+ import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
13
+
14
+ jest.mock('@salesforce/pwa-kit-runtime/utils/ssr-config', () => {
15
+ return {
16
+ getConfig: jest.fn()
17
+ }
18
+ })
19
+
20
+ const mockHistoryPush = jest.fn()
21
+ const mockHistoryReplace = jest.fn()
22
+
23
+ jest.mock('react-router', () => {
24
+ const original = jest.requireActual('react-router')
25
+
26
+ return {
27
+ ...original,
28
+ useHistory: jest.fn().mockImplementation(() => {
29
+ return {
30
+ push: mockHistoryPush,
31
+ replace: mockHistoryReplace
32
+ }
33
+ })
34
+ }
35
+ })
36
+
37
+ afterEach(() => {
38
+ jest.resetModules()
39
+ })
40
+
41
+ const TestComponent = () => {
42
+ const navigate = useNavigation()
43
+
44
+ return (
45
+ <div>
46
+ <button data-testid="page1-link" onClick={() => navigate('/page1')} />
47
+ <button data-testid="page2-link" onClick={() => navigate('/page2', 'replace', {})} />
48
+ <button data-testid="page4-link" onClick={() => navigate('/')} />
49
+ </div>
50
+ )
51
+ }
52
+
53
+ test('prepends locale and site and calls history.push', async () => {
54
+ const user = userEvent.setup()
55
+
56
+ getConfig.mockImplementation(() => mockConfig)
57
+ const {getByTestId} = renderWithProviders(<TestComponent />, {
58
+ wrapperProps: {siteAlias: 'uk', appConfig: mockConfig.app}
59
+ })
60
+ await user.click(getByTestId('page1-link'))
61
+ expect(mockHistoryPush).toHaveBeenCalledWith('/uk/en-GB/page1')
62
+ })
63
+
64
+ test('append locale as path and site as query and calls history.push', async () => {
65
+ const user = userEvent.setup()
66
+
67
+ const newConfig = {
68
+ ...mockConfig,
69
+ app: {
70
+ ...mockConfig.app,
71
+ url: {
72
+ locale: 'path',
73
+ site: 'query_param',
74
+ showDefaults: true
75
+ }
76
+ }
77
+ }
78
+ getConfig.mockImplementation(() => newConfig)
79
+ const {getByTestId} = renderWithProviders(<TestComponent />, {
80
+ wrapperProps: {siteAlias: 'uk', appConfig: newConfig.app}
81
+ })
82
+ await user.click(getByTestId('page1-link'))
83
+ expect(mockHistoryPush).toHaveBeenCalledWith('/en-GB/page1?site=uk')
84
+ })
85
+
86
+ test('works for any history method and args', async () => {
87
+ const user = userEvent.setup()
88
+
89
+ getConfig.mockImplementation(() => mockConfig)
90
+
91
+ const {getByTestId} = renderWithProviders(<TestComponent />, {
92
+ wrapperProps: {siteAlias: 'uk', appConfig: mockConfig.app}
93
+ })
94
+
95
+ await user.click(getByTestId('page2-link'))
96
+ expect(mockHistoryReplace).toHaveBeenCalledWith('/uk/en-GB/page2', {})
97
+ })
98
+
99
+ test('if given the path to root or homepage, will not prepend the locale', async () => {
100
+ const user = userEvent.setup()
101
+
102
+ getConfig.mockImplementation(() => mockConfig)
103
+
104
+ const {getByTestId} = renderWithProviders(<TestComponent />, {
105
+ wrapperProps: {siteAlias: 'us', locale: 'en-US'}
106
+ })
107
+ await user.click(getByTestId('page4-link'))
108
+ expect(mockHistoryPush).toHaveBeenCalledWith('/')
109
+ })
@@ -0,0 +1,35 @@
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
+ // Hooks
15
+ import {useSearchParams} from '@salesforce/retail-react-app/app/hooks/use-search-params'
16
+
17
+ /*
18
+ * Generate a memoized list of page size urls. Chaning the page size will reset
19
+ * the offset to zero to simplify things.
20
+ */
21
+ export const usePageUrls = ({total = 0, limit}) => {
22
+ const location = useLocation()
23
+ const [searchParams] = useSearchParams()
24
+ const _limit = limit || searchParams.limit
25
+
26
+ return useMemo(() => {
27
+ const pageCount = Math.ceil(total / _limit)
28
+
29
+ return buildUrlSet(
30
+ `${location.pathname}${location.search}`,
31
+ 'offset',
32
+ new Array(pageCount).fill(0).map((_, index) => index * _limit)
33
+ )
34
+ }, [location.pathname, location.search, _limit, total])
35
+ }
@@ -0,0 +1,39 @@
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 {Router} from 'react-router'
9
+
10
+ import {render} from '@testing-library/react'
11
+ import {createMemoryHistory} from 'history'
12
+ import {usePageUrls} from '@salesforce/retail-react-app/app/hooks/use-page-urls'
13
+
14
+ const MockComponent = () => {
15
+ const urls = usePageUrls({total: 100})
16
+
17
+ return (
18
+ <script data-testid="limits" type="application/json">
19
+ {JSON.stringify(urls)}
20
+ </script>
21
+ )
22
+ }
23
+
24
+ describe('The usePageUrls', () => {
25
+ test('returns an array of urls, one values for each page with the correct offset value.', () => {
26
+ const history = createMemoryHistory()
27
+ history.push('/test/path?limit=25')
28
+
29
+ const wrapper = render(
30
+ <Router history={history}>
31
+ <MockComponent />
32
+ </Router>
33
+ )
34
+
35
+ expect(wrapper.getByTestId('limits').text).toBe(
36
+ '["/test/path?limit=25&offset=0","/test/path?limit=25&offset=25","/test/path?limit=25&offset=50","/test/path?limit=25&offset=75"]'
37
+ )
38
+ })
39
+ })