@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,75 @@
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 PropTypes from 'prop-types'
9
+ import {Box, useRadio, useRadioGroup} from '@chakra-ui/react'
10
+ import {CheckIcon} from '@salesforce/retail-react-app/app/components/icons'
11
+
12
+ const RadioCardGroupContext = React.createContext()
13
+
14
+ export const RadioCard = (props) => {
15
+ const getRadioProps = React.useContext(RadioCardGroupContext)
16
+ const {getInputProps, getCheckboxProps} = useRadio(getRadioProps(props))
17
+
18
+ const input = getInputProps()
19
+ const checkbox = getCheckboxProps()
20
+ return (
21
+ <Box as="label">
22
+ <input {...input} />
23
+ <Box
24
+ {...checkbox}
25
+ aria-hidden={false}
26
+ position="relative"
27
+ cursor="pointer"
28
+ border="1px solid"
29
+ borderColor="gray.200"
30
+ borderRadius="base"
31
+ height="full"
32
+ _checked={{
33
+ borderColor: 'blue.600'
34
+ }}
35
+ _focus={{
36
+ boxShadow: 'outline'
37
+ }}
38
+ px={4}
39
+ py={4}
40
+ >
41
+ {input.checked && (
42
+ <Box
43
+ position="absolute"
44
+ top={0}
45
+ right={0}
46
+ w={0}
47
+ h={0}
48
+ borderStyle="solid"
49
+ borderWidth="0 38px 38px 0"
50
+ borderColor="transparent"
51
+ borderRightColor="blue.600"
52
+ >
53
+ <CheckIcon color="white" position="absolute" right="-40px" top="1px" />
54
+ </Box>
55
+ )}
56
+
57
+ {props.children}
58
+ </Box>
59
+ </Box>
60
+ )
61
+ }
62
+
63
+ export const RadioCardGroup = (props) => {
64
+ const {getRootProps, getRadioProps} = useRadioGroup(props)
65
+ const group = getRootProps()
66
+
67
+ return (
68
+ <RadioCardGroupContext.Provider value={getRadioProps}>
69
+ <Box {...group}>{props.children}</Box>
70
+ </RadioCardGroupContext.Provider>
71
+ )
72
+ }
73
+
74
+ RadioCard.propTypes = {children: PropTypes.any}
75
+ RadioCardGroup.propTypes = {children: PropTypes.any}
@@ -0,0 +1,227 @@
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 React, {useEffect, useRef, useState} from 'react'
9
+ import PropTypes from 'prop-types'
10
+ import {useIntl} from 'react-intl'
11
+ import {Button} from '@chakra-ui/react'
12
+ import ProductScroller from '@salesforce/retail-react-app/app/components/product-scroller'
13
+ import useEinstein from '@salesforce/retail-react-app/app/hooks/use-einstein'
14
+ import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer'
15
+ import useIntersectionObserver from '@salesforce/retail-react-app/app/hooks/use-intersection-observer'
16
+ import {useWishList} from '@salesforce/retail-react-app/app/hooks/use-wish-list'
17
+
18
+ import {useToast} from '@salesforce/retail-react-app/app/hooks/use-toast'
19
+ import useNavigation from '@salesforce/retail-react-app/app/hooks/use-navigation'
20
+ import {
21
+ API_ERROR_MESSAGE,
22
+ TOAST_ACTION_VIEW_WISHLIST,
23
+ TOAST_MESSAGE_ADDED_TO_WISHLIST,
24
+ TOAST_MESSAGE_REMOVED_FROM_WISHLIST
25
+ } from '@salesforce/retail-react-app/app/constants'
26
+ import {useShopperCustomersMutation} from '@salesforce/commerce-sdk-react'
27
+
28
+ /**
29
+ * A component for fetching and rendering product recommendations from the Einstein API
30
+ * by a zone or a recommender name.
31
+ */
32
+ const RecommendedProducts = ({zone, recommender, products, title, shouldFetch, ...props}) => {
33
+ const {
34
+ isLoading,
35
+ recommendations,
36
+ getZoneRecommendations,
37
+ getRecommendations,
38
+ sendClickReco,
39
+ sendViewReco
40
+ } = useEinstein()
41
+ const {data: customer} = useCurrentCustomer()
42
+ const {customerId} = customer
43
+ const {data: wishlist} = useWishList()
44
+
45
+ const createCustomerProductListItem = useShopperCustomersMutation(
46
+ 'createCustomerProductListItem'
47
+ )
48
+ const deleteCustomerProductListItem = useShopperCustomersMutation(
49
+ 'deleteCustomerProductListItem'
50
+ )
51
+ const toast = useToast()
52
+ const navigate = useNavigation()
53
+ const {formatMessage} = useIntl()
54
+
55
+ const ref = useRef()
56
+ const isOnScreen = useIntersectionObserver(ref, {useOnce: true})
57
+ const [_products, setProducts] = useState(products)
58
+
59
+ useEffect(() => {
60
+ // Check if the component should fetch results or not. This is useful
61
+ // when you are still waiting on additional data, like `products`.
62
+ if (typeof shouldFetch === 'function' && !shouldFetch()) {
63
+ return
64
+ }
65
+
66
+ // Fetch either zone or recommender, but not both. If a zone and recommender
67
+ // name are both provided, `zone` takes precendence.
68
+ if (zone) {
69
+ getZoneRecommendations(zone, _products)
70
+ return
71
+ }
72
+ if (recommender) {
73
+ getRecommendations(recommender, _products)
74
+ return
75
+ }
76
+ }, [zone, recommender, _products])
77
+
78
+ useEffect(() => {
79
+ // This is an optimization that eliminates superfluous rerenders/fetching by
80
+ // keeping a copy of the `products` array prop in state for shallow comparison.
81
+ if (!Array.isArray(products)) {
82
+ return
83
+ }
84
+ if (
85
+ products.length !== _products?.length ||
86
+ !products.every((val, index) => val === _products?.[index])
87
+ ) {
88
+ setProducts(products)
89
+ }
90
+ }, [products])
91
+
92
+ useEffect(() => {
93
+ if (isOnScreen && recommendations?.recs) {
94
+ sendViewReco(
95
+ {
96
+ recommenderName: recommendations.recommenderName,
97
+ __recoUUID: recommendations.recoUUID
98
+ },
99
+ recommendations.recs.map((rec) => ({id: rec.id}))
100
+ )
101
+ }
102
+ }, [isOnScreen, recommendations])
103
+
104
+ // The component should remove itself altogether if it has no recommendations
105
+ // and we aren't loading any.
106
+ if (!isLoading && (!recommendations || recommendations.length < 1)) {
107
+ return null
108
+ }
109
+
110
+ // TODO: DRY this handler when intl provider is available globally
111
+ const addItemToWishlist = async (product) => {
112
+ try {
113
+ if (!wishlist || !customerId) {
114
+ return
115
+ }
116
+ await createCustomerProductListItem.mutateAsync({
117
+ parameters: {
118
+ listId: wishlist.id,
119
+ customerId
120
+ },
121
+ body: {
122
+ quantity: 1,
123
+ productId: product.productId,
124
+ public: false,
125
+ priority: 1,
126
+ type: 'product'
127
+ }
128
+ })
129
+
130
+ toast({
131
+ title: formatMessage(TOAST_MESSAGE_ADDED_TO_WISHLIST, {quantity: 1}),
132
+ status: 'success',
133
+ action: (
134
+ // it would be better if we could use <Button as={Link}>
135
+ // but unfortunately the Link component is not compatible
136
+ // with Chakra Toast, since the ToastManager is rendered via portal
137
+ // and the toast doesn't have access to intl provider, which is a
138
+ // requirement of the Link component.
139
+ <Button variant="link" onClick={() => navigate('/account/wishlist')}>
140
+ {formatMessage(TOAST_ACTION_VIEW_WISHLIST)}
141
+ </Button>
142
+ )
143
+ })
144
+ } catch {
145
+ toast({
146
+ title: formatMessage(API_ERROR_MESSAGE),
147
+ status: 'error'
148
+ })
149
+ }
150
+ }
151
+
152
+ const removeItemFromWishlist = async (product) => {
153
+ try {
154
+ const wishlistItem = wishlist?.customerProductListItems?.find(
155
+ (item) => item.productId === product.productId
156
+ )
157
+ if (!wishlistItem || !wishlist || !customerId) {
158
+ return
159
+ }
160
+ await deleteCustomerProductListItem.mutateAsync({
161
+ parameters: {
162
+ customerId,
163
+ itemId: wishlistItem.id,
164
+ listId: wishlist.id
165
+ }
166
+ })
167
+ toast({
168
+ title: formatMessage(TOAST_MESSAGE_REMOVED_FROM_WISHLIST),
169
+ status: 'success',
170
+ id: product.productId
171
+ })
172
+ } catch {
173
+ toast({
174
+ title: formatMessage(API_ERROR_MESSAGE),
175
+ status: 'error'
176
+ })
177
+ }
178
+ }
179
+
180
+ return (
181
+ <ProductScroller
182
+ ref={ref}
183
+ title={title || recommendations?.displayMessage}
184
+ products={recommendations.recs}
185
+ isLoading={isLoading}
186
+ productTileProps={(product) => ({
187
+ onClick: () => {
188
+ sendClickReco(
189
+ {
190
+ recommenderName: recommendations.recommenderName,
191
+ __recoUUID: recommendations.recoUUID
192
+ },
193
+ product
194
+ )
195
+ },
196
+ enableFavourite: true,
197
+ isFavourite: wishlist?.customerProductListItems?.some(
198
+ (item) => item.productId === product?.productId
199
+ ),
200
+ onFavouriteToggle: (isFavourite) => {
201
+ const action = isFavourite ? removeItemFromWishlist : addItemToWishlist
202
+ return action(product)
203
+ }
204
+ })}
205
+ {...props}
206
+ />
207
+ )
208
+ }
209
+
210
+ RecommendedProducts.propTypes = {
211
+ /* The title to appear above the product scroller */
212
+ title: PropTypes.any,
213
+
214
+ /* The zone to request */
215
+ zone: PropTypes.string,
216
+
217
+ /* The recommender to request */
218
+ recommender: PropTypes.string,
219
+
220
+ /* The products to use for recommendation context */
221
+ products: PropTypes.arrayOf(PropTypes.object),
222
+
223
+ /* Callback to determine if the component should fetch results */
224
+ shouldFetch: PropTypes.func
225
+ }
226
+
227
+ export default RecommendedProducts
@@ -0,0 +1,114 @@
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, {Fragment} from 'react'
9
+ import PropTypes from 'prop-types'
10
+ import {FormattedMessage} from 'react-intl'
11
+ import {Alert, Button, Stack, Text, Link as ChakraLink} from '@chakra-ui/react'
12
+ import {AlertIcon, BrandLogo} from '@salesforce/retail-react-app/app/components/icons'
13
+ import {noop} from '@salesforce/retail-react-app/app/utils/utils'
14
+ import RegistrationFields from '@salesforce/retail-react-app/app/components/forms/registration-fields'
15
+ import Link from '@salesforce/retail-react-app/app/components/link'
16
+
17
+ const RegisterForm = ({submitForm, clickSignIn = noop, form}) => {
18
+ return (
19
+ <Fragment>
20
+ <Stack justify="center" align="center" spacing={8}>
21
+ <BrandLogo width="60px" height="auto" />
22
+ <Stack spacing={2}>
23
+ <Text align="center" fontSize="xl" fontWeight="semibold">
24
+ <FormattedMessage
25
+ defaultMessage="Let's get started!"
26
+ id="register_form.heading.lets_get_started"
27
+ />
28
+ </Text>
29
+ <Text fontSize="sm" align="center" color="gray.700">
30
+ <FormattedMessage
31
+ defaultMessage="Create an account and get first access to the very best products, inspiration and community."
32
+ id="register_form.message.create_an_account"
33
+ />
34
+ </Text>
35
+ </Stack>
36
+ </Stack>
37
+ <form
38
+ onSubmit={form.handleSubmit(submitForm)}
39
+ data-testid="sf-auth-modal-form-register"
40
+ >
41
+ <Stack paddingTop={8} spacing={8} paddingLeft={4} paddingRight={4}>
42
+ {form.formState.errors?.global && (
43
+ <Alert status="error">
44
+ <AlertIcon color="red.500" boxSize={4} />
45
+ <Text fontSize="sm" ml={3}>
46
+ {form.formState.errors.global.message}
47
+ </Text>
48
+ </Alert>
49
+ )}
50
+ <RegistrationFields form={form} />
51
+ <Stack spacing={6}>
52
+ <Button
53
+ type="submit"
54
+ onClick={() => form.clearErrors('global')}
55
+ isLoading={form.formState.isSubmitting}
56
+ >
57
+ <FormattedMessage
58
+ defaultMessage="Create Account"
59
+ id="register_form.button.create_account"
60
+ />
61
+ </Button>
62
+
63
+ <Stack direction="row" spacing={1} justify="center">
64
+ <Text fontSize="sm">
65
+ <FormattedMessage
66
+ defaultMessage="Already have an account?"
67
+ id="register_form.message.already_have_account"
68
+ />
69
+ </Text>
70
+ <Button variant="link" size="sm" onClick={clickSignIn}>
71
+ <FormattedMessage
72
+ defaultMessage="Sign in"
73
+ id="register_form.action.sign_in"
74
+ />
75
+ </Button>
76
+ </Stack>
77
+
78
+ <Text fontSize="sm" align="center">
79
+ <FormattedMessage
80
+ id="register_form.message.agree_to_policy_terms"
81
+ defaultMessage="By creating an account, you agree to Salesforce <policy>Privacy Policy</policy> and <terms>Terms & Conditions</terms>"
82
+ values={{
83
+ policy: (chunks) => (
84
+ <ChakraLink as={Link} to="/privacy-policy" color="blue.600">
85
+ {chunks}
86
+ </ChakraLink>
87
+ ),
88
+
89
+ terms: (chunks) => (
90
+ <ChakraLink
91
+ as={Link}
92
+ to="/terms-conditions"
93
+ color="blue.600"
94
+ >
95
+ {chunks}
96
+ </ChakraLink>
97
+ )
98
+ }}
99
+ />
100
+ </Text>
101
+ </Stack>
102
+ </Stack>
103
+ </form>
104
+ </Fragment>
105
+ )
106
+ }
107
+
108
+ RegisterForm.propTypes = {
109
+ submitForm: PropTypes.func,
110
+ clickSignIn: PropTypes.func,
111
+ form: PropTypes.object
112
+ }
113
+
114
+ export default RegisterForm
@@ -0,0 +1,87 @@
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, {Fragment} from 'react'
9
+ import PropTypes from 'prop-types'
10
+ import {FormattedMessage} from 'react-intl'
11
+ import {Alert, Button, Stack, Text} from '@chakra-ui/react'
12
+ import {AlertIcon, BrandLogo} from '@salesforce/retail-react-app/app/components/icons'
13
+ import {noop} from '@salesforce/retail-react-app/app/utils/utils'
14
+ import ResetPasswordFields from '@salesforce/retail-react-app/app/components/forms/reset-password-fields'
15
+
16
+ const ResetPasswordForm = ({submitForm, clickSignIn = noop, form}) => {
17
+ return (
18
+ <Fragment>
19
+ <Stack justify="center" align="center" spacing={8}>
20
+ <BrandLogo width="60px" height="auto" />
21
+ <Stack spacing={2}>
22
+ <Text align="center" fontSize="xl" fontWeight="semibold">
23
+ <FormattedMessage
24
+ defaultMessage="Reset Password"
25
+ id="reset_password_form.title.reset_password"
26
+ />
27
+ </Text>
28
+ <Text fontSize="sm" align="center" color="gray.700">
29
+ <FormattedMessage
30
+ defaultMessage="Enter your email to receive instructions on how to reset your password"
31
+ id="reset_password_form.message.enter_your_email"
32
+ />
33
+ </Text>
34
+ </Stack>
35
+ </Stack>
36
+ <form onSubmit={form.handleSubmit(submitForm)} data-testid="sf-auth-modal-form">
37
+ <Stack paddingTop={8} spacing={8} paddingLeft={4} paddingRight={4}>
38
+ {form.formState.errors?.global && (
39
+ <Alert status="error">
40
+ <AlertIcon color="red.500" boxSize={4} />
41
+ <Text fontSize="sm" ml={3}>
42
+ {form.formState.errors.global.message}
43
+ </Text>
44
+ </Alert>
45
+ )}
46
+ <ResetPasswordFields form={form} />
47
+ <Stack spacing={6}>
48
+ <Button
49
+ type="submit"
50
+ onClick={() => form.clearErrors('global')}
51
+ isLoading={form.formState.isSubmitting}
52
+ >
53
+ <FormattedMessage
54
+ defaultMessage="Reset Password"
55
+ id="reset_password_form.button.reset_password"
56
+ />
57
+ </Button>
58
+
59
+ <Stack direction="row" spacing={1} justify="center">
60
+ <Text fontSize="sm">
61
+ <FormattedMessage
62
+ defaultMessage="Or return to"
63
+ id="reset_password_form.message.return_to_sign_in"
64
+ description="Precedes link to return to sign in"
65
+ />
66
+ </Text>
67
+ <Button variant="link" size="sm" onClick={clickSignIn}>
68
+ <FormattedMessage
69
+ defaultMessage="Sign in"
70
+ id="reset_password_form.action.sign_in"
71
+ />
72
+ </Button>
73
+ </Stack>
74
+ </Stack>
75
+ </Stack>
76
+ </form>
77
+ </Fragment>
78
+ )
79
+ }
80
+
81
+ ResetPasswordForm.propTypes = {
82
+ submitForm: PropTypes.func,
83
+ clickSignIn: PropTypes.func,
84
+ form: PropTypes.object
85
+ }
86
+
87
+ export default ResetPasswordForm
@@ -0,0 +1,29 @@
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 PropTypes from 'prop-types'
9
+ import {Box} from '@chakra-ui/react'
10
+
11
+ /**
12
+ * Render the children in the DOM but visually hide them on desktop
13
+ * @param children - isomorphic components used within a responsive design
14
+ */
15
+ export const HideOnDesktop = ({children}) => (
16
+ <Box display={{base: 'block', lg: 'none'}}>{children}</Box>
17
+ )
18
+ HideOnDesktop.propTypes = {children: PropTypes.node}
19
+
20
+ /**
21
+ * Render the children in the DOM but visually hide them on mobile
22
+ * @param children - isomorphic components used within a responsive design
23
+ */
24
+ export const HideOnMobile = ({children}) => (
25
+ <Box display={{base: 'none', lg: 'block'}}>{children}</Box>
26
+ )
27
+ HideOnMobile.propTypes = {children: PropTypes.node}
28
+
29
+ export default {HideOnMobile, HideOnDesktop}
@@ -0,0 +1,24 @@
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 {useEffect} from 'react'
8
+ import {useLocation} from 'react-router-dom'
9
+
10
+ /**
11
+ * ScrollToTop will scroll the viewport to the top whenever the current URL
12
+ * changes. If this component doesn't meet the needs of your layout, take a look
13
+ * at this [guide]{@link https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/docs/guides/scroll-restoration.md}.
14
+ *
15
+ */
16
+ export default function ScrollToTop() {
17
+ const {pathname} = useLocation()
18
+
19
+ useEffect(() => {
20
+ window.scrollTo(0, 0)
21
+ }, [pathname])
22
+
23
+ return null
24
+ }
@@ -0,0 +1,46 @@
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 {render, waitFor} from '@testing-library/react'
9
+ import ScrollToTop from '@salesforce/retail-react-app/app/components/scroll-to-top/index'
10
+ import {Router} from 'react-router-dom'
11
+ import {createMemoryHistory} from 'history'
12
+
13
+ global.scrollTo = jest.fn()
14
+
15
+ describe('ScrollToTop', () => {
16
+ let history = createMemoryHistory({initialEntries: ['/']})
17
+
18
+ beforeEach(() => {
19
+ render(
20
+ <Router history={history}>
21
+ <ScrollToTop />
22
+ </Router>
23
+ )
24
+ })
25
+
26
+ afterEach(() => {
27
+ jest.clearAllMocks()
28
+ })
29
+
30
+ test('calls window.scrollTo when route changes', async () => {
31
+ expect(global.scrollTo).toHaveBeenCalledTimes(1)
32
+ expect(global.scrollTo).toHaveBeenCalledWith(0, 0)
33
+
34
+ history.push('/new-url')
35
+ await waitFor(() => {
36
+ expect(global.scrollTo).toHaveBeenCalledTimes(2)
37
+ })
38
+ expect(global.scrollTo).toHaveBeenCalledWith(0, 0)
39
+
40
+ history.push('/new-url2')
41
+ await waitFor(() => {
42
+ expect(global.scrollTo).toHaveBeenCalledTimes(3)
43
+ })
44
+ expect(global.scrollTo).toHaveBeenCalledWith(0, 0)
45
+ })
46
+ })