fontdue-js 2.22.0 → 2.22.1

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 (370) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/dist/components/ConfigContext.d.ts +3 -0
  3. package/dist/components/ConfigContext.js +2 -1
  4. package/dist/components/FontdueProvider/FontdueProviderClientComponent.js +2 -0
  5. package/dist/corsError.d.ts +1 -0
  6. package/dist/corsError.js +16 -9
  7. package/dist/global-shim.d.ts +1 -0
  8. package/dist/global-shim.js +8 -0
  9. package/dist/relay/environment.js +51 -42
  10. package/dist-cjs/__generated__/AddToCartBannerQuery.graphql.js +194 -0
  11. package/dist-cjs/__generated__/AddToCartBanner_item.graphql.js +107 -0
  12. package/dist-cjs/__generated__/AddToCartBanner_order.graphql.js +131 -0
  13. package/dist-cjs/__generated__/AddressFieldsRefetchQuery.graphql.js +170 -0
  14. package/dist-cjs/__generated__/AddressFields_viewer.graphql.js +125 -0
  15. package/dist-cjs/__generated__/BuyButtonIDQuery.graphql.js +121 -0
  16. package/dist-cjs/__generated__/BuyButtonSlugQuery.graphql.js +146 -0
  17. package/dist-cjs/__generated__/BuyButton_collection.graphql.js +56 -0
  18. package/dist-cjs/__generated__/CartButtonQuery.graphql.js +139 -0
  19. package/dist-cjs/__generated__/CartButton_order.graphql.js +71 -0
  20. package/dist-cjs/__generated__/CartItemAdditionalLicenses_licenses.graphql.js +42 -0
  21. package/dist-cjs/__generated__/CartItemAdditionalLicenses_orderItem.graphql.js +52 -0
  22. package/dist-cjs/__generated__/CartItemLicense_selection.graphql.js +80 -0
  23. package/dist-cjs/__generated__/CartItemProduct_product.graphql.js +113 -0
  24. package/dist-cjs/__generated__/CartItemVariable_selection.graphql.js +107 -0
  25. package/dist-cjs/__generated__/CartItemVariable_variable.graphql.js +58 -0
  26. package/dist-cjs/__generated__/CartItem_node.graphql.js +166 -0
  27. package/dist-cjs/__generated__/CartOrderCompleteOrderMutation.graphql.js +783 -0
  28. package/dist-cjs/__generated__/CartOrderRemoveDiscountMutation.graphql.js +760 -0
  29. package/dist-cjs/__generated__/CartOrderUpdateMutation.graphql.js +795 -0
  30. package/dist-cjs/__generated__/CartOrder_UpdateErrors.graphql.js +43 -0
  31. package/dist-cjs/__generated__/CartOrder_order.graphql.js +264 -0
  32. package/dist-cjs/__generated__/CartOrder_viewer.graphql.js +51 -0
  33. package/dist-cjs/__generated__/CartQuery.graphql.js +887 -0
  34. package/dist-cjs/__generated__/CartStateRemoveDiscountMutation.graphql.js +422 -0
  35. package/dist-cjs/__generated__/CartStateUpdateMutation.graphql.js +790 -0
  36. package/dist-cjs/__generated__/CartState_order.graphql.js +122 -0
  37. package/dist-cjs/__generated__/CartTotals_order.graphql.js +140 -0
  38. package/dist-cjs/__generated__/CharacterViewerIDQuery.graphql.js +332 -0
  39. package/dist-cjs/__generated__/CharacterViewerSlugQuery.graphql.js +362 -0
  40. package/dist-cjs/__generated__/CharacterViewerStyleRefetchQuery.graphql.js +226 -0
  41. package/dist-cjs/__generated__/CharacterViewer_collection.graphql.js +197 -0
  42. package/dist-cjs/__generated__/CharacterViewer_family.graphql.js +101 -0
  43. package/dist-cjs/__generated__/CharacterViewer_style.graphql.js +161 -0
  44. package/dist-cjs/__generated__/CheckoutUpdateCustomerMutation.graphql.js +770 -0
  45. package/dist-cjs/__generated__/CheckoutUpdateOrderMutation.graphql.js +811 -0
  46. package/dist-cjs/__generated__/Checkout_UpdateOrderErrors.graphql.js +97 -0
  47. package/dist-cjs/__generated__/Checkout_identity.graphql.js +94 -0
  48. package/dist-cjs/__generated__/Checkout_order.graphql.js +144 -0
  49. package/dist-cjs/__generated__/Checkout_viewer.graphql.js +74 -0
  50. package/dist-cjs/__generated__/CollectionAa_Query.graphql.js +192 -0
  51. package/dist-cjs/__generated__/CollectionAa_product.graphql.js +69 -0
  52. package/dist-cjs/__generated__/CouponCodeInputApplyCouponMutation.graphql.js +432 -0
  53. package/dist-cjs/__generated__/CouponText_coupon.graphql.js +73 -0
  54. package/dist-cjs/__generated__/CustomerLoginFormLoginMutation.graphql.js +72 -0
  55. package/dist-cjs/__generated__/CustomerLoginFormQuery.graphql.js +91 -0
  56. package/dist-cjs/__generated__/DownloadRefetchQuery.graphql.js +124 -0
  57. package/dist-cjs/__generated__/Download_order.graphql.js +61 -0
  58. package/dist-cjs/__generated__/FamilyList_node.graphql.js +96 -0
  59. package/dist-cjs/__generated__/Family_node.graphql.js +160 -0
  60. package/dist-cjs/__generated__/FontFamiliesQuery.graphql.js +368 -0
  61. package/dist-cjs/__generated__/FontStyle_fontStyle.graphql.js +32 -0
  62. package/dist-cjs/__generated__/IdentityBox_identity.graphql.js +52 -0
  63. package/dist-cjs/__generated__/License_node.graphql.js +97 -0
  64. package/dist-cjs/__generated__/NewsletterSignupQuery.graphql.js +110 -0
  65. package/dist-cjs/__generated__/NewsletterSignupUpdateCustomerMutation.graphql.js +81 -0
  66. package/dist-cjs/__generated__/NodePasswordFormAccessNodeMutation.graphql.js +72 -0
  67. package/dist-cjs/__generated__/NodePasswordFormIDQuery.graphql.js +93 -0
  68. package/dist-cjs/__generated__/NodePasswordFormSlugQuery.graphql.js +115 -0
  69. package/dist-cjs/__generated__/OrderVariableSelectionReduxRefetchQuery.graphql.js +154 -0
  70. package/dist-cjs/__generated__/OrderVariableSelectionRedux_viewer.graphql.js +75 -0
  71. package/dist-cjs/__generated__/OrderVariableSelection_variables.graphql.js +107 -0
  72. package/dist-cjs/__generated__/PrecartAddToCartMutation.graphql.js +793 -0
  73. package/dist-cjs/__generated__/PrecartClearCartMutation.graphql.js +81 -0
  74. package/dist-cjs/__generated__/PrecartQuery.graphql.js +308 -0
  75. package/dist-cjs/__generated__/Precart_collection.graphql.js +81 -0
  76. package/dist-cjs/__generated__/Precart_license.graphql.js +67 -0
  77. package/dist-cjs/__generated__/Precart_viewer.graphql.js +161 -0
  78. package/dist-cjs/__generated__/PriceBarSectionRefetchQuery.graphql.js +184 -0
  79. package/dist-cjs/__generated__/PriceBarSection_node.graphql.js +121 -0
  80. package/dist-cjs/__generated__/PriceBar_node.graphql.js +60 -0
  81. package/dist-cjs/__generated__/Price_price.graphql.js +40 -0
  82. package/dist-cjs/__generated__/SKUPrice_sku.graphql.js +69 -0
  83. package/dist-cjs/__generated__/SelectButton_sku.graphql.js +54 -0
  84. package/{dist-bundle/chunks/index-o29NNufd.js → dist-cjs/__generated__/ServerConfigProviderQuery.graphql.js} +7 -30
  85. package/dist-cjs/__generated__/ShareCartCreateSnapshotMutation.graphql.js +84 -0
  86. package/dist-cjs/__generated__/SpecimenLinkQuery.graphql.js +120 -0
  87. package/dist-cjs/__generated__/StoreModalBundleButton_bundle.graphql.js +85 -0
  88. package/dist-cjs/__generated__/StoreModalCartQuery.graphql.js +1017 -0
  89. package/dist-cjs/__generated__/StoreModalCheckoutQuery.graphql.js +194 -0
  90. package/dist-cjs/__generated__/StoreModalContainerQuery.graphql.js +228 -0
  91. package/dist-cjs/__generated__/StoreModalDownloadRefetchQuery.graphql.js +102 -0
  92. package/dist-cjs/__generated__/StoreModalDownload_order.graphql.js +40 -0
  93. package/dist-cjs/__generated__/StoreModalFamilyButton_collection.graphql.js +135 -0
  94. package/dist-cjs/__generated__/StoreModalFamily_collection.graphql.js +130 -0
  95. package/dist-cjs/__generated__/StoreModalIndexItem_fontCollection.graphql.js +98 -0
  96. package/dist-cjs/__generated__/StoreModalIndexQuery.graphql.js +263 -0
  97. package/dist-cjs/__generated__/StoreModalOrderVariableSelection_order.graphql.js +98 -0
  98. package/dist-cjs/__generated__/StoreModalOrderVariableSelection_viewer.graphql.js +66 -0
  99. package/dist-cjs/__generated__/StoreModalProductContent_collection.graphql.js +92 -0
  100. package/dist-cjs/__generated__/StoreModalProductLicenseSelection_collection.graphql.js +47 -0
  101. package/dist-cjs/__generated__/StoreModalProductLicense_license.graphql.js +104 -0
  102. package/dist-cjs/__generated__/StoreModalProductQuery.graphql.js +702 -0
  103. package/dist-cjs/__generated__/StoreModalProductRefetchQuery.graphql.js +433 -0
  104. package/dist-cjs/__generated__/StoreModalProductSummaryAddToCartMutation.graphql.js +149 -0
  105. package/dist-cjs/__generated__/StoreModalProductSummaryClearCartMutation.graphql.js +81 -0
  106. package/dist-cjs/__generated__/StoreModalProductSummaryRefetchQuery.graphql.js +281 -0
  107. package/dist-cjs/__generated__/StoreModalProductSummary_viewer.graphql.js +243 -0
  108. package/dist-cjs/__generated__/StoreModalReviewIdentity_identity.graphql.js +52 -0
  109. package/dist-cjs/__generated__/StoreModalStyleButton_fontStyle.graphql.js +113 -0
  110. package/dist-cjs/__generated__/StoreModalUnifiedCheckoutCompleteOrderMutation.graphql.js +302 -0
  111. package/dist-cjs/__generated__/StoreModalUnifiedCheckoutUpdateCustomerMutation.graphql.js +323 -0
  112. package/dist-cjs/__generated__/StoreModalUnifiedCheckoutUpdateOrderMutation.graphql.js +473 -0
  113. package/dist-cjs/__generated__/StoreModalUnifiedCheckout_order.graphql.js +237 -0
  114. package/dist-cjs/__generated__/StoreModalUnifiedCheckout_viewer.graphql.js +117 -0
  115. package/dist-cjs/__generated__/StripeProviderCreateCheckoutSessionMutation.graphql.js +105 -0
  116. package/dist-cjs/__generated__/StripeProvider_viewer.graphql.js +139 -0
  117. package/dist-cjs/__generated__/TestFontsFormUpdateCustomerMutation.graphql.js +770 -0
  118. package/dist-cjs/__generated__/TestFontsForm_Query.graphql.js +126 -0
  119. package/dist-cjs/__generated__/TestModeBannerQuery.graphql.js +87 -0
  120. package/dist-cjs/__generated__/ThemeConfigQuery.graphql.js +87 -0
  121. package/dist-cjs/__generated__/TypeTesterFeaturesButton_fontStyle.graphql.js +32 -0
  122. package/dist-cjs/__generated__/TypeTesterFeatures_fontStyle.graphql.js +36 -0
  123. package/dist-cjs/__generated__/TypeTesterFloatingToolbar_testers.graphql.js +53 -0
  124. package/dist-cjs/__generated__/TypeTesterStandaloneChangedStylesQuery.graphql.js +347 -0
  125. package/dist-cjs/__generated__/TypeTesterStandaloneQuery.graphql.js +450 -0
  126. package/dist-cjs/__generated__/TypeTesterStyleSelectData_fontStyle.graphql.js +106 -0
  127. package/dist-cjs/__generated__/TypeTesterStyleSelectData_fontStyleData.graphql.js +88 -0
  128. package/dist-cjs/__generated__/TypeTesterStyleSelectData_viewer.graphql.js +166 -0
  129. package/dist-cjs/__generated__/TypeTesterToolbar_fontStyle.graphql.js +36 -0
  130. package/dist-cjs/__generated__/TypeTesterVariableAxes_fontStyle.graphql.js +61 -0
  131. package/dist-cjs/__generated__/TypeTester_fontStyle.graphql.js +106 -0
  132. package/dist-cjs/__generated__/TypeTester_viewer.graphql.js +40 -0
  133. package/dist-cjs/__generated__/TypeTestersChangedStylesQuery.graphql.js +347 -0
  134. package/dist-cjs/__generated__/TypeTestersIDQuery.graphql.js +637 -0
  135. package/dist-cjs/__generated__/TypeTestersRefetchQuery.graphql.js +554 -0
  136. package/dist-cjs/__generated__/TypeTestersSlugQuery.graphql.js +644 -0
  137. package/dist-cjs/__generated__/TypeTesters_collection.graphql.js +258 -0
  138. package/dist-cjs/__generated__/TypeTesters_viewer.graphql.js +40 -0
  139. package/dist-cjs/__generated__/VariableTableAmounts_option.graphql.js +34 -0
  140. package/dist-cjs/__generated__/VariableTableAmounts_variable.graphql.js +34 -0
  141. package/dist-cjs/__generated__/productState_Query.graphql.js +184 -0
  142. package/dist-cjs/__generated__/productState_bundle.graphql.js +79 -0
  143. package/dist-cjs/__generated__/productState_collection.graphql.js +90 -0
  144. package/dist-cjs/__generated__/productState_collectionBundle.graphql.js +88 -0
  145. package/dist-cjs/__generated__/productState_node.graphql.js +122 -0
  146. package/dist-cjs/__generated__/productState_sku.graphql.js +51 -0
  147. package/dist-cjs/__generated__/useFeaturesData_fontStyle.graphql.js +64 -0
  148. package/dist-cjs/__generated__/useFontStyle_fontStyle.graphql.js +94 -0
  149. package/dist-cjs/__generated__/useTotalStyles_fontCollection.graphql.js +57 -0
  150. package/dist-cjs/__tests__/collectionBundleSelection.test.js +1630 -0
  151. package/dist-cjs/components/AddToCartBanner/index.js +75 -0
  152. package/dist-cjs/components/BuyButton/index.js +124 -0
  153. package/dist-cjs/components/BuyButton/index.server.js +36 -0
  154. package/dist-cjs/components/BuyingOptions/index.js +23 -0
  155. package/dist-cjs/components/Cart/AddressFields.js +162 -0
  156. package/dist-cjs/components/Cart/CartItem/CartItemAdditionalLicenses.js +33 -0
  157. package/dist-cjs/components/Cart/CartItem/CartItemLicense.js +66 -0
  158. package/dist-cjs/components/Cart/CartItem/CartItemProduct.js +36 -0
  159. package/dist-cjs/components/Cart/CartItem/CartItemVariable.js +66 -0
  160. package/dist-cjs/components/Cart/CartItem/VariableTextInput.js +79 -0
  161. package/dist-cjs/components/Cart/CartItem/index.js +95 -0
  162. package/dist-cjs/components/Cart/CartOrder.js +314 -0
  163. package/dist-cjs/components/Cart/CartState.js +93 -0
  164. package/dist-cjs/components/Cart/CartTotals.js +97 -0
  165. package/dist-cjs/components/Cart/Checkout.js +473 -0
  166. package/dist-cjs/components/Cart/CheckoutSteps.js +15 -0
  167. package/dist-cjs/components/Cart/CouponCodeInput.js +90 -0
  168. package/dist-cjs/components/Cart/CustomerFields.js +80 -0
  169. package/dist-cjs/components/Cart/Download.js +62 -0
  170. package/dist-cjs/components/Cart/EmptyCart.js +42 -0
  171. package/dist-cjs/components/Cart/IdentityBox.js +37 -0
  172. package/dist-cjs/components/Cart/ShareCart.js +87 -0
  173. package/dist-cjs/components/Cart/index.js +36 -0
  174. package/dist-cjs/components/Cart/injectRelayEnvironment.js +21 -0
  175. package/dist-cjs/components/Cart/types.js +5 -0
  176. package/dist-cjs/components/Cart/utils.js +8 -0
  177. package/dist-cjs/components/CartButton/index.js +127 -0
  178. package/dist-cjs/components/CartButton/index.server.js +18 -0
  179. package/dist-cjs/components/CharacterViewer/StyleSelect.js +60 -0
  180. package/dist-cjs/components/CharacterViewer/index.js +441 -0
  181. package/dist-cjs/components/CharacterViewer/index.server.js +36 -0
  182. package/dist-cjs/components/Checkbox/index.js +27 -0
  183. package/dist-cjs/components/CollectionAa/index.js +38 -0
  184. package/dist-cjs/components/ComponentsContext.js +12 -0
  185. package/dist-cjs/components/ConfigContext.js +86 -0
  186. package/{dist-bundle/chunks/consent-DMvR5rEh.js → dist-cjs/components/ConsentBanner/consent.js} +12 -4
  187. package/dist-cjs/components/ConsentBanner/index.js +84 -0
  188. package/dist-cjs/components/CookieNotification/index.js +83 -0
  189. package/{dist-bundle/chunks/CorsErrorModal-C1g_-3Re.js → dist-cjs/components/CorsErrorModal.js} +19 -16
  190. package/dist-cjs/components/CouponText/index.js +38 -0
  191. package/dist-cjs/components/CustomerLoginForm/index.js +88 -0
  192. package/dist-cjs/components/Family/FamilyList.js +43 -0
  193. package/dist-cjs/components/Family/index.js +77 -0
  194. package/dist-cjs/components/FontFamilies/index.js +53 -0
  195. package/dist-cjs/components/FontStyle/index.js +37 -0
  196. package/dist-cjs/components/FontdueProvider/FontdueProviderClientComponent.js +74 -0
  197. package/dist-cjs/components/FontdueProvider/index.js +43 -0
  198. package/dist-cjs/components/FontdueProvider/index.server.js +35 -0
  199. package/dist-cjs/components/FontdueProvider/useAuxUIOwner.js +32 -0
  200. package/dist-cjs/components/Icons/Align.js +58 -0
  201. package/dist-cjs/components/Icons/ArrowDownRight.js +19 -0
  202. package/dist-cjs/components/Icons/ArrowLeft.js +22 -0
  203. package/dist-cjs/components/Icons/ArrowRight.js +19 -0
  204. package/dist-cjs/components/Icons/CarrotDown.js +19 -0
  205. package/dist-cjs/components/Icons/CarrotUp.js +19 -0
  206. package/dist-cjs/components/Icons/Cart.js +23 -0
  207. package/dist-cjs/components/Icons/CartNew.js +33 -0
  208. package/dist-cjs/components/Icons/Check.js +19 -0
  209. package/dist-cjs/components/Icons/Checkbox.js +24 -0
  210. package/dist-cjs/components/Icons/CheckboxChecked.js +24 -0
  211. package/dist-cjs/components/Icons/CheckboxCrossed.js +26 -0
  212. package/dist-cjs/components/Icons/CircledCheck.js +21 -0
  213. package/dist-cjs/components/Icons/CircledInfo.js +21 -0
  214. package/dist-cjs/components/Icons/CircledMinus.js +21 -0
  215. package/dist-cjs/components/Icons/ColorPicker.js +19 -0
  216. package/dist-cjs/components/Icons/CreditCard.js +21 -0
  217. package/dist-cjs/components/Icons/DownArrow.js +24 -0
  218. package/dist-cjs/components/Icons/DownloadFonts.js +25 -0
  219. package/dist-cjs/components/Icons/FrowningFace.js +25 -0
  220. package/dist-cjs/components/Icons/License.js +19 -0
  221. package/dist-cjs/components/Icons/Receipt.js +34 -0
  222. package/dist-cjs/components/Icons/User.js +19 -0
  223. package/dist-cjs/components/Icons/X.js +19 -0
  224. package/dist-cjs/components/Icons/index.js +167 -0
  225. package/dist-cjs/components/NewsletterSignup/NewsletterSignupElement.js +15 -0
  226. package/dist-cjs/components/NewsletterSignup/index.js +192 -0
  227. package/dist-cjs/components/NewsletterSignup/index.server.js +18 -0
  228. package/dist-cjs/components/NodePasswordForm/index.js +114 -0
  229. package/dist-cjs/components/OrderVariableSelection/OrderVariableSelectionRedux.js +66 -0
  230. package/dist-cjs/components/OrderVariableSelection/index.js +199 -0
  231. package/dist-cjs/components/Precart/License.js +70 -0
  232. package/dist-cjs/components/Precart/index.js +331 -0
  233. package/dist-cjs/components/Price/index.js +111 -0
  234. package/dist-cjs/components/PriceBar/PriceBarSection.js +61 -0
  235. package/dist-cjs/components/PriceBar/index.js +49 -0
  236. package/dist-cjs/components/Root/index.js +208 -0
  237. package/dist-cjs/components/Root/productState.js +143 -0
  238. package/dist-cjs/components/SKUPrice/index.js +46 -0
  239. package/dist-cjs/components/Select/index.js +36 -0
  240. package/dist-cjs/components/SelectButton/index.js +79 -0
  241. package/dist-cjs/components/ServerConfigProvider/index.js +39 -0
  242. package/dist-cjs/components/SpecimenLink/index.js +37 -0
  243. package/dist-cjs/components/StickyNav/index.js +80 -0
  244. package/dist-cjs/components/StoreModal/StoreModalBundleButton.js +47 -0
  245. package/dist-cjs/components/StoreModal/StoreModalCart.js +79 -0
  246. package/dist-cjs/components/StoreModal/StoreModalCheckout.js +24 -0
  247. package/dist-cjs/components/StoreModal/StoreModalCheckoutContext.js +14 -0
  248. package/dist-cjs/components/StoreModal/StoreModalContainer.js +117 -0
  249. package/dist-cjs/components/StoreModal/StoreModalDownload.js +38 -0
  250. package/dist-cjs/components/StoreModal/StoreModalFamily.js +58 -0
  251. package/dist-cjs/components/StoreModal/StoreModalFamilyButton.js +69 -0
  252. package/dist-cjs/components/StoreModal/StoreModalIndex.js +27 -0
  253. package/dist-cjs/components/StoreModal/StoreModalIndexItem.js +63 -0
  254. package/dist-cjs/components/StoreModal/StoreModalLicenseeIsBillingSelection.js +30 -0
  255. package/dist-cjs/components/StoreModal/StoreModalLoader.js +22 -0
  256. package/dist-cjs/components/StoreModal/StoreModalOrderVariableSelection.js +89 -0
  257. package/dist-cjs/components/StoreModal/StoreModalProduct.js +154 -0
  258. package/dist-cjs/components/StoreModal/StoreModalReviewIdentity.js +18 -0
  259. package/dist-cjs/components/StoreModal/StoreModalRouter.js +28 -0
  260. package/dist-cjs/components/StoreModal/StoreModalStyleButton.js +52 -0
  261. package/dist-cjs/components/StoreModal/index.js +39 -0
  262. package/dist-cjs/components/StoreModal/routes.js +51 -0
  263. package/dist-cjs/components/StoreModal/types.js +5 -0
  264. package/dist-cjs/components/StoreModalProductLicenseSelection/LicenseElement.js +46 -0
  265. package/dist-cjs/components/StoreModalProductLicenseSelection/LicenseVariableElement.js +35 -0
  266. package/dist-cjs/components/StoreModalProductLicenseSelection/LicenseVariableRadioElement.js +47 -0
  267. package/dist-cjs/components/StoreModalProductLicenseSelection/StoreModalProductLicense.js +133 -0
  268. package/dist-cjs/components/StoreModalProductLicenseSelection/index.js +31 -0
  269. package/dist-cjs/components/StoreModalProductSummary/index.js +219 -0
  270. package/dist-cjs/components/StripeLogo.js +17 -0
  271. package/dist-cjs/components/StripeProvider/index.js +172 -0
  272. package/dist-cjs/components/Switch/index.js +7 -0
  273. package/dist-cjs/components/TestFontsForm/TestFontsFormElement.js +15 -0
  274. package/dist-cjs/components/TestFontsForm/index.js +208 -0
  275. package/dist-cjs/components/TestFontsForm/index.server.js +18 -0
  276. package/dist-cjs/components/TestModeBanner/index.js +34 -0
  277. package/dist-cjs/components/TextField/index.js +42 -0
  278. package/dist-cjs/components/ThemeConfig/index.js +41 -0
  279. package/dist-cjs/components/ThemeConfig/index.server.js +17 -0
  280. package/{dist-bundle/chunks/index-DsvF5W13.js → dist-cjs/components/Tracking/index.js} +19 -14
  281. package/dist-cjs/components/TypeTester/TypeTesterAlignButtons.js +34 -0
  282. package/dist-cjs/components/TypeTester/TypeTesterBullet.js +23 -0
  283. package/dist-cjs/components/TypeTester/TypeTesterContent.js +58 -0
  284. package/dist-cjs/components/TypeTester/TypeTesterContext.js +166 -0
  285. package/dist-cjs/components/TypeTester/TypeTesterEditAll.js +36 -0
  286. package/dist-cjs/components/TypeTester/TypeTesterFeatures.js +133 -0
  287. package/dist-cjs/components/TypeTester/TypeTesterFeaturesButton.js +82 -0
  288. package/dist-cjs/components/TypeTester/TypeTesterFitter.js +21 -0
  289. package/dist-cjs/components/TypeTester/TypeTesterFloatingToolbar.js +79 -0
  290. package/dist-cjs/components/TypeTester/TypeTesterInput.js +72 -0
  291. package/dist-cjs/components/TypeTester/TypeTesterSlider.js +76 -0
  292. package/dist-cjs/components/TypeTester/TypeTesterStandalone.js +137 -0
  293. package/dist-cjs/components/TypeTester/TypeTesterStandalone.preload.js +27 -0
  294. package/dist-cjs/components/TypeTester/TypeTesterStandalone.server.js +26 -0
  295. package/dist-cjs/components/TypeTester/TypeTesterStandaloneElement.js +65 -0
  296. package/dist-cjs/components/TypeTester/TypeTesterState.js +133 -0
  297. package/dist-cjs/components/TypeTester/TypeTesterStyleSelect.js +101 -0
  298. package/dist-cjs/components/TypeTester/TypeTesterStyleSelectData.js +102 -0
  299. package/dist-cjs/components/TypeTester/TypeTesterToolbar.js +94 -0
  300. package/dist-cjs/components/TypeTester/TypeTesterVariableAxes.js +65 -0
  301. package/dist-cjs/components/TypeTester/features.js +150 -0
  302. package/dist-cjs/components/TypeTester/index.js +169 -0
  303. package/dist-cjs/components/TypeTester/types.js +14 -0
  304. package/dist-cjs/components/TypeTester/useFeaturesData.js +36 -0
  305. package/dist-cjs/components/TypeTester/useTypeTesterStyler.js +83 -0
  306. package/dist-cjs/components/TypeTesters/TypeTestersElement.js +33 -0
  307. package/dist-cjs/components/TypeTesters/index.js +271 -0
  308. package/dist-cjs/components/TypeTesters/index.server.js +44 -0
  309. package/dist-cjs/components/UrlContext.js +16 -0
  310. package/dist-cjs/components/VariableTableAmounts/index.js +28 -0
  311. package/dist-cjs/components/elements/Button/index.js +21 -0
  312. package/dist-cjs/components/elements/EmptyCart/index.js +30 -0
  313. package/dist-cjs/components/elements/StoreModalCartLayout/index.js +38 -0
  314. package/dist-cjs/components/elements/StoreModalContainer/StoreModalContainerContext.js +11 -0
  315. package/dist-cjs/components/elements/StoreModalContainer/index.js +77 -0
  316. package/dist-cjs/components/elements/StoreModalDownloadLayout/index.js +25 -0
  317. package/dist-cjs/components/elements/StoreModalFamily/index.js +31 -0
  318. package/dist-cjs/components/elements/StoreModalFamilyButton/index.js +45 -0
  319. package/dist-cjs/components/elements/StoreModalIndexItem/index.js +35 -0
  320. package/dist-cjs/components/elements/StoreModalLicenseeIsBillingIdentityElement.js +59 -0
  321. package/dist-cjs/components/elements/StoreModalLoadingScreen/index.js +14 -0
  322. package/dist-cjs/components/elements/StoreModalPageContainer/index.js +22 -0
  323. package/dist-cjs/components/elements/StoreModalStyleButton/index.js +37 -0
  324. package/dist-cjs/components/elements/StoreModalUnifiedCheckout.js +753 -0
  325. package/dist-cjs/components/useConsent.js +26 -0
  326. package/dist-cjs/components/useFont.js +76 -0
  327. package/dist-cjs/components/useFontStyle.js +58 -0
  328. package/dist-cjs/components/useInterval.js +28 -0
  329. package/dist-cjs/components/useTotalStyles.js +26 -0
  330. package/{dist-bundle/index.js → dist-cjs/config.js} +8 -4
  331. package/dist-cjs/corsError.js +89 -0
  332. package/dist-cjs/data/unicodeData.js +1 -0
  333. package/dist-cjs/fontLoader.js +55 -0
  334. package/dist-cjs/global-shim.js +12 -0
  335. package/dist-cjs/hooks/useAutofit.js +114 -0
  336. package/dist-cjs/hooks/useLicenseAndOrderVariables.js +25 -0
  337. package/dist-cjs/hooks/useRefetchOnLicenseChanges.js +60 -0
  338. package/dist-cjs/hooks.js +18 -0
  339. package/dist-cjs/index.js +18 -0
  340. package/dist-cjs/package.json +1 -0
  341. package/dist-cjs/react-ranger.d.js +1 -0
  342. package/dist-cjs/react-ranger.js +301 -0
  343. package/dist-cjs/reducer.js +291 -0
  344. package/dist-cjs/relay/environment.js +148 -0
  345. package/{dist-bundle/TypeTester.preload.js → dist-cjs/relay/loadSerializableQuery.js} +8 -26
  346. package/dist-cjs/relay/useSerializablePreloadedQuery.js +52 -0
  347. package/dist-cjs/retryImport.js +24 -0
  348. package/dist-cjs/scripts/updateUnicodeData.js +49 -0
  349. package/dist-cjs/utils.js +107 -0
  350. package/dist-cjs/vanilla/FontStyle.js +58 -0
  351. package/dist-cjs/vanilla/TruncatedMarkdown.js +28 -0
  352. package/package.json +1 -1
  353. package/dist-bundle/FontdueProvider.js +0 -6
  354. package/dist-bundle/FontdueProvider.js.map +0 -1
  355. package/dist-bundle/TypeTester.js +0 -7
  356. package/dist-bundle/TypeTester.js.map +0 -1
  357. package/dist-bundle/TypeTester.preload.js.map +0 -1
  358. package/dist-bundle/chunks/ComponentsContext-CmkN9J4X.js +0 -20843
  359. package/dist-bundle/chunks/ComponentsContext-CmkN9J4X.js.map +0 -1
  360. package/dist-bundle/chunks/CorsErrorModal-C1g_-3Re.js.map +0 -1
  361. package/dist-bundle/chunks/TypeTesterStandalone-BMWuv8Ca.js +0 -27486
  362. package/dist-bundle/chunks/TypeTesterStandalone-BMWuv8Ca.js.map +0 -1
  363. package/dist-bundle/chunks/consent-DMvR5rEh.js.map +0 -1
  364. package/dist-bundle/chunks/index-BNSbxp7B.js +0 -78
  365. package/dist-bundle/chunks/index-BNSbxp7B.js.map +0 -1
  366. package/dist-bundle/chunks/index-C4ak9qTL.js +0 -423
  367. package/dist-bundle/chunks/index-C4ak9qTL.js.map +0 -1
  368. package/dist-bundle/chunks/index-DsvF5W13.js.map +0 -1
  369. package/dist-bundle/chunks/index-o29NNufd.js.map +0 -1
  370. package/dist-bundle/index.js.map +0 -1
@@ -0,0 +1,1630 @@
1
+ "use strict";
2
+
3
+ var _vitest = require("vitest");
4
+ var _utils = require("../utils");
5
+ var _reducer = _interopRequireWildcard(require("../reducer"));
6
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
7
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
8
+ // Mock react-relay's graphql tag since productState.ts uses it at import time
9
+ _vitest.vi.mock('react-relay', () => ({
10
+ graphql: () => null,
11
+ fetchQuery: _vitest.vi.fn()
12
+ }));
13
+ // Helper to build a minimal FontdueState for testing
14
+ function makeState(overrides = {}) {
15
+ return {
16
+ cartOpen: false,
17
+ precartOpen: false,
18
+ selectedSkuIds: {},
19
+ licenseOptions: [],
20
+ collectionStyleSkus: {},
21
+ collectionSkus: {},
22
+ skuPrices: {},
23
+ fetchedCollectionIds: [],
24
+ storeModalRoute: {
25
+ name: 'index'
26
+ },
27
+ storeModalHistory: [],
28
+ orderVariableSelections: [],
29
+ licenseeIsBillingIdentity: null,
30
+ ...overrides
31
+ };
32
+ }
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Shared fixtures — reusable across tests that need the full
36
+ // "superfamily + 3 families + 3 bundles" topology
37
+ // ---------------------------------------------------------------------------
38
+ function makeThunderState(overrides = {}) {
39
+ return makeState({
40
+ collectionStyleSkus: {
41
+ // Superfamily
42
+ 'super-sku': {
43
+ fontStyleSkuIds: ['s1', 's2', 'ds1', 'ds2', 'cs1', 'cs2', 'vs1', 'vis1', 'dvs1', 'dvis1', 'cvs1', 'cvis1'],
44
+ fontStyleIds: ['f1', 'f2', 'df1', 'df2', 'cf1', 'cf2', 'vf1', 'vif1', 'dvf1', 'dvif1', 'cvf1', 'cvif1'],
45
+ childrenSkuIds: ['text-sku', 'display-sku', 'caption-sku', 'text-var-sku', 'text-var-italic-sku', 'display-var-sku', 'display-var-italic-sku', 'caption-var-sku', 'caption-var-italic-sku', 'text-bundle-sku', 'display-bundle-sku', 'caption-bundle-sku'],
46
+ name: 'Thunder Text Collection'
47
+ },
48
+ // Static families
49
+ 'text-sku': {
50
+ fontStyleSkuIds: ['s1', 's2'],
51
+ fontStyleIds: ['f1', 'f2'],
52
+ childrenSkuIds: [],
53
+ name: 'Thunder Text'
54
+ },
55
+ 'display-sku': {
56
+ fontStyleSkuIds: ['ds1', 'ds2'],
57
+ fontStyleIds: ['df1', 'df2'],
58
+ childrenSkuIds: [],
59
+ name: 'Thunder Display'
60
+ },
61
+ 'caption-sku': {
62
+ fontStyleSkuIds: ['cs1', 'cs2'],
63
+ fontStyleIds: ['cf1', 'cf2'],
64
+ childrenSkuIds: [],
65
+ name: 'Thunder Caption'
66
+ },
67
+ // Variable families
68
+ 'text-var-sku': {
69
+ fontStyleSkuIds: ['vs1'],
70
+ fontStyleIds: ['vf1'],
71
+ childrenSkuIds: [],
72
+ name: 'Thunder Text Variable'
73
+ },
74
+ 'text-var-italic-sku': {
75
+ fontStyleSkuIds: ['vis1'],
76
+ fontStyleIds: ['vif1'],
77
+ childrenSkuIds: [],
78
+ name: 'Thunder Text Italic Variable'
79
+ },
80
+ 'display-var-sku': {
81
+ fontStyleSkuIds: ['dvs1'],
82
+ fontStyleIds: ['dvf1'],
83
+ childrenSkuIds: [],
84
+ name: 'Thunder Display Variable'
85
+ },
86
+ 'display-var-italic-sku': {
87
+ fontStyleSkuIds: ['dvis1'],
88
+ fontStyleIds: ['dvif1'],
89
+ childrenSkuIds: [],
90
+ name: 'Thunder Display Italic Variable'
91
+ },
92
+ 'caption-var-sku': {
93
+ fontStyleSkuIds: ['cvs1'],
94
+ fontStyleIds: ['cvf1'],
95
+ childrenSkuIds: [],
96
+ name: 'Thunder Caption Variable'
97
+ },
98
+ 'caption-var-italic-sku': {
99
+ fontStyleSkuIds: ['cvis1'],
100
+ fontStyleIds: ['cvif1'],
101
+ childrenSkuIds: [],
102
+ name: 'Thunder Caption Italic Variable'
103
+ },
104
+ // Collection bundles
105
+ 'text-bundle-sku': {
106
+ fontStyleSkuIds: ['s1', 's2', 'vs1', 'vis1'],
107
+ fontStyleIds: ['f1', 'f2', 'vf1', 'vif1'],
108
+ childrenSkuIds: ['text-sku', 'text-var-sku', 'text-var-italic-sku'],
109
+ name: 'Thunder Text Family + Variable'
110
+ },
111
+ 'display-bundle-sku': {
112
+ fontStyleSkuIds: ['ds1', 'ds2', 'dvs1', 'dvis1'],
113
+ fontStyleIds: ['df1', 'df2', 'dvf1', 'dvif1'],
114
+ childrenSkuIds: ['display-sku', 'display-var-sku', 'display-var-italic-sku'],
115
+ name: 'Thunder Display Family + Variable'
116
+ },
117
+ 'caption-bundle-sku': {
118
+ fontStyleSkuIds: ['cs1', 'cs2', 'cvs1', 'cvis1'],
119
+ fontStyleIds: ['cf1', 'cf2', 'cvf1', 'cvif1'],
120
+ childrenSkuIds: ['caption-sku', 'caption-var-sku', 'caption-var-italic-sku'],
121
+ name: 'Thunder Caption Family + Variable'
122
+ }
123
+ },
124
+ skuPrices: {
125
+ 'super-sku': 750,
126
+ 'text-sku': 500,
127
+ 'display-sku': 500,
128
+ 'caption-sku': 500,
129
+ 'text-var-sku': 300,
130
+ 'text-var-italic-sku': 300,
131
+ 'display-var-sku': 300,
132
+ 'display-var-italic-sku': 300,
133
+ 'caption-var-sku': 300,
134
+ 'caption-var-italic-sku': 300,
135
+ 'text-bundle-sku': 500,
136
+ 'display-bundle-sku': 500,
137
+ 'caption-bundle-sku': 500,
138
+ s1: 40,
139
+ s2: 40,
140
+ ds1: 40,
141
+ ds2: 40,
142
+ cs1: 40,
143
+ cs2: 40,
144
+ vs1: 40,
145
+ vis1: 40,
146
+ dvs1: 40,
147
+ dvis1: 40,
148
+ cvs1: 40,
149
+ cvis1: 40
150
+ },
151
+ ...overrides
152
+ });
153
+ }
154
+
155
+ // =========================================================================
156
+ // collContainsSkuId
157
+ // =========================================================================
158
+ (0, _vitest.describe)('collContainsSkuId', () => {
159
+ (0, _vitest.it)('returns true when skuId is in childrenSkuIds', () => {
160
+ const coll = {
161
+ fontStyleSkuIds: [],
162
+ fontStyleIds: [],
163
+ childrenSkuIds: ['child-sku-1', 'child-sku-2'],
164
+ name: 'Test'
165
+ };
166
+ (0, _vitest.expect)((0, _utils.collContainsSkuId)(coll, 'child-sku-1')).toBe(true);
167
+ });
168
+ (0, _vitest.it)('returns true when skuId is in fontStyleSkuIds', () => {
169
+ const coll = {
170
+ fontStyleSkuIds: ['style-sku-1'],
171
+ fontStyleIds: [],
172
+ childrenSkuIds: [],
173
+ name: 'Test'
174
+ };
175
+ (0, _vitest.expect)((0, _utils.collContainsSkuId)(coll, 'style-sku-1')).toBe(true);
176
+ });
177
+ (0, _vitest.it)('returns true when id is in fontStyleIds', () => {
178
+ const coll = {
179
+ fontStyleSkuIds: [],
180
+ fontStyleIds: ['style-id-1'],
181
+ childrenSkuIds: [],
182
+ name: 'Test'
183
+ };
184
+ (0, _vitest.expect)((0, _utils.collContainsSkuId)(coll, 'style-id-1')).toBe(true);
185
+ });
186
+ (0, _vitest.it)('returns false for unknown skuId', () => {
187
+ const coll = {
188
+ fontStyleSkuIds: ['style-sku-1'],
189
+ fontStyleIds: ['style-1'],
190
+ childrenSkuIds: ['child-sku-1'],
191
+ name: 'Test'
192
+ };
193
+ (0, _vitest.expect)((0, _utils.collContainsSkuId)(coll, 'unknown-sku')).toBe(false);
194
+ });
195
+ (0, _vitest.it)('returns false for undefined collection', () => {
196
+ (0, _vitest.expect)((0, _utils.collContainsSkuId)(undefined, 'any-sku')).toBe(false);
197
+ });
198
+ });
199
+
200
+ // =========================================================================
201
+ // isSelected
202
+ // =========================================================================
203
+ (0, _vitest.describe)('isSelected', () => {
204
+ (0, _vitest.it)('returns true when the SKU is directly selected', () => {
205
+ const state = makeState({
206
+ selectedSkuIds: {
207
+ 'sku-1': true
208
+ }
209
+ });
210
+ (0, _vitest.expect)((0, _utils.isSelected)('sku-1')(state)).toBe(true);
211
+ });
212
+ (0, _vitest.it)('returns false when the SKU is explicitly deselected (false)', () => {
213
+ const state = makeState({
214
+ selectedSkuIds: {
215
+ 'sku-1': false
216
+ }
217
+ });
218
+ (0, _vitest.expect)((0, _utils.isSelected)('sku-1')(state)).toBe(false);
219
+ });
220
+ (0, _vitest.it)('returns true when a parent collection is selected', () => {
221
+ const state = makeState({
222
+ selectedSkuIds: {
223
+ 'bundle-sku': true
224
+ },
225
+ collectionStyleSkus: {
226
+ 'bundle-sku': {
227
+ fontStyleSkuIds: ['s1'],
228
+ fontStyleIds: ['f1'],
229
+ childrenSkuIds: ['family-sku'],
230
+ name: 'Bundle'
231
+ }
232
+ }
233
+ });
234
+ // Style SKU contained via fontStyleSkuIds
235
+ (0, _vitest.expect)((0, _utils.isSelected)('s1')(state)).toBe(true);
236
+ // Family SKU contained via childrenSkuIds
237
+ (0, _vitest.expect)((0, _utils.isSelected)('family-sku')(state)).toBe(true);
238
+ // Font style ID contained via fontStyleIds
239
+ (0, _vitest.expect)((0, _utils.isSelected)('f1')(state)).toBe(true);
240
+ });
241
+ (0, _vitest.it)('returns false when a parent collection is deselected', () => {
242
+ const state = makeState({
243
+ selectedSkuIds: {
244
+ 'bundle-sku': false,
245
+ 's1': false
246
+ },
247
+ collectionStyleSkus: {
248
+ 'bundle-sku': {
249
+ fontStyleSkuIds: ['s1'],
250
+ fontStyleIds: ['f1'],
251
+ childrenSkuIds: ['family-sku'],
252
+ name: 'Bundle'
253
+ }
254
+ }
255
+ });
256
+ (0, _vitest.expect)((0, _utils.isSelected)('s1')(state)).toBe(false);
257
+ (0, _vitest.expect)((0, _utils.isSelected)('family-sku')(state)).toBe(false);
258
+ });
259
+ (0, _vitest.it)('returns false for null skuId', () => {
260
+ const state = makeState();
261
+ (0, _vitest.expect)((0, _utils.isSelected)(null)(state)).toBe(false);
262
+ });
263
+ (0, _vitest.it)('returns true for member when bundle is selected, even if member is explicitly false', () => {
264
+ // After auto-selection, members are set to false but should still appear
265
+ // selected because the parent bundle is true
266
+ const state = makeState({
267
+ selectedSkuIds: {
268
+ 'bundle-sku': true,
269
+ 'family-sku': false,
270
+ 'var-sku': false
271
+ },
272
+ collectionStyleSkus: {
273
+ 'bundle-sku': {
274
+ fontStyleSkuIds: ['s1', 'vs1'],
275
+ fontStyleIds: ['f1', 'vf1'],
276
+ childrenSkuIds: ['family-sku', 'var-sku'],
277
+ name: 'Bundle'
278
+ }
279
+ }
280
+ });
281
+ (0, _vitest.expect)((0, _utils.isSelected)('family-sku')(state)).toBe(true);
282
+ (0, _vitest.expect)((0, _utils.isSelected)('var-sku')(state)).toBe(true);
283
+ (0, _vitest.expect)((0, _utils.isSelected)('s1')(state)).toBe(true);
284
+ });
285
+ });
286
+
287
+ // =========================================================================
288
+ // collectionSkuIdsDifferences
289
+ // =========================================================================
290
+ (0, _vitest.describe)('collectionSkuIdsDifferences', () => {
291
+ (0, _vitest.it)('includes self when skuId matches a collection', () => {
292
+ const state = makeState({
293
+ collectionStyleSkus: {
294
+ 'family-sku': {
295
+ fontStyleSkuIds: ['s1', 's2'],
296
+ fontStyleIds: ['f1', 'f2'],
297
+ childrenSkuIds: [],
298
+ name: 'Family'
299
+ }
300
+ },
301
+ skuPrices: {
302
+ 'family-sku': 500,
303
+ s1: 40,
304
+ s2: 40
305
+ }
306
+ });
307
+ const diffs = (0, _utils.collectionSkuIdsDifferences)(state, 'family-sku');
308
+ (0, _vitest.expect)(diffs).toHaveProperty('family-sku');
309
+ (0, _vitest.expect)(diffs['family-sku']).toBe(500);
310
+ });
311
+ (0, _vitest.it)('includes parent collections that contain the skuId', () => {
312
+ const state = makeState({
313
+ collectionStyleSkus: {
314
+ 'family-sku': {
315
+ fontStyleSkuIds: ['s1', 's2'],
316
+ fontStyleIds: ['f1', 'f2'],
317
+ childrenSkuIds: [],
318
+ name: 'Family'
319
+ },
320
+ 'bundle-sku': {
321
+ fontStyleSkuIds: ['s1', 's2', 'vs1'],
322
+ fontStyleIds: ['f1', 'f2', 'vf1'],
323
+ childrenSkuIds: ['family-sku', 'var-family-sku'],
324
+ name: 'Bundle'
325
+ }
326
+ },
327
+ skuPrices: {
328
+ 'family-sku': 500,
329
+ 'bundle-sku': 500,
330
+ 'var-family-sku': 300,
331
+ s1: 40,
332
+ s2: 40,
333
+ vs1: 40
334
+ }
335
+ });
336
+ const diffs = (0, _utils.collectionSkuIdsDifferences)(state, 'family-sku');
337
+ (0, _vitest.expect)(diffs).toHaveProperty('family-sku');
338
+ (0, _vitest.expect)(diffs).toHaveProperty('bundle-sku');
339
+ });
340
+ (0, _vitest.it)('subtracts already-selected items from the difference', () => {
341
+ const state = makeState({
342
+ selectedSkuIds: {
343
+ 'family-sku': true
344
+ },
345
+ collectionStyleSkus: {
346
+ 'family-sku': {
347
+ fontStyleSkuIds: ['s1', 's2'],
348
+ fontStyleIds: ['f1', 'f2'],
349
+ childrenSkuIds: [],
350
+ name: 'Family'
351
+ },
352
+ 'bundle-sku': {
353
+ fontStyleSkuIds: ['s1', 's2', 'vs1'],
354
+ fontStyleIds: ['f1', 'f2', 'vf1'],
355
+ childrenSkuIds: ['family-sku', 'var-family-sku'],
356
+ name: 'Bundle'
357
+ }
358
+ },
359
+ skuPrices: {
360
+ 'family-sku': 500,
361
+ 'bundle-sku': 500,
362
+ 'var-family-sku': 300
363
+ }
364
+ });
365
+ const diffs = (0, _utils.collectionSkuIdsDifferences)(state, 'var-family-sku');
366
+ (0, _vitest.expect)(diffs['bundle-sku']).toBe(0);
367
+ });
368
+ (0, _vitest.it)('treats missing prices as 0 rather than producing NaN', () => {
369
+ const state = makeState({
370
+ selectedSkuIds: {
371
+ 'orphan-sku': true
372
+ },
373
+ collectionStyleSkus: {
374
+ 'family-sku': {
375
+ fontStyleSkuIds: ['orphan-sku'],
376
+ fontStyleIds: [],
377
+ childrenSkuIds: [],
378
+ name: 'Family'
379
+ }
380
+ },
381
+ skuPrices: {
382
+ 'family-sku': 500
383
+ // 'orphan-sku' deliberately missing
384
+ }
385
+ });
386
+ const diffs = (0, _utils.collectionSkuIdsDifferences)(state, 'orphan-sku');
387
+ // Missing price treated as 0, so difference = 500 - 0 = 500
388
+ (0, _vitest.expect)(diffs['family-sku']).toBe(500);
389
+ });
390
+ (0, _vitest.it)('only counts items that are selected true, not false', () => {
391
+ const state = makeState({
392
+ selectedSkuIds: {
393
+ 'family-sku': false
394
+ },
395
+ collectionStyleSkus: {
396
+ 'bundle-sku': {
397
+ fontStyleSkuIds: [],
398
+ fontStyleIds: [],
399
+ childrenSkuIds: ['family-sku'],
400
+ name: 'Bundle'
401
+ },
402
+ 'family-sku': {
403
+ fontStyleSkuIds: [],
404
+ fontStyleIds: [],
405
+ childrenSkuIds: [],
406
+ name: 'Family'
407
+ }
408
+ },
409
+ skuPrices: {
410
+ 'bundle-sku': 500,
411
+ 'family-sku': 300
412
+ }
413
+ });
414
+ const diffs = (0, _utils.collectionSkuIdsDifferences)(state, 'family-sku');
415
+ // family-sku is false, not true, so it shouldn't be subtracted
416
+ (0, _vitest.expect)(diffs['bundle-sku']).toBe(500);
417
+ });
418
+ (0, _vitest.it)('returns empty object when no collections contain the skuId', () => {
419
+ const state = makeState({
420
+ collectionStyleSkus: {
421
+ 'other-sku': {
422
+ fontStyleSkuIds: ['x1'],
423
+ fontStyleIds: ['xf1'],
424
+ childrenSkuIds: [],
425
+ name: 'Other'
426
+ }
427
+ },
428
+ skuPrices: {
429
+ 'other-sku': 100,
430
+ 'lonely-style': 40
431
+ }
432
+ });
433
+ const diffs = (0, _utils.collectionSkuIdsDifferences)(state, 'lonely-style');
434
+ (0, _vitest.expect)(Object.keys(diffs)).toHaveLength(0);
435
+ });
436
+ });
437
+
438
+ // =========================================================================
439
+ // collectionSkuIdWithDiscount
440
+ // =========================================================================
441
+ (0, _vitest.describe)('collectionSkuIdWithDiscount', () => {
442
+ (0, _vitest.it)('returns null when no collection is a better deal', () => {
443
+ const state = makeState({
444
+ collectionStyleSkus: {
445
+ 'family-sku': {
446
+ fontStyleSkuIds: ['s1'],
447
+ fontStyleIds: ['f1'],
448
+ childrenSkuIds: [],
449
+ name: 'Family'
450
+ }
451
+ },
452
+ skuPrices: {
453
+ s1: 40,
454
+ 'family-sku': 500
455
+ }
456
+ });
457
+ const [collId, amount] = (0, _reducer.collectionSkuIdWithDiscount)(state, 's1');
458
+ (0, _vitest.expect)(collId).toBe(null);
459
+ (0, _vitest.expect)(amount).toBe(null);
460
+ });
461
+ (0, _vitest.it)('returns parent collection when it is cheaper for individual style', () => {
462
+ const state = makeState({
463
+ selectedSkuIds: {
464
+ s1: true,
465
+ s2: true,
466
+ s3: true,
467
+ s4: true
468
+ },
469
+ collectionStyleSkus: {
470
+ 'family-sku': {
471
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5'],
472
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5'],
473
+ childrenSkuIds: [],
474
+ name: 'Family'
475
+ }
476
+ },
477
+ skuPrices: {
478
+ 'family-sku': 200,
479
+ s1: 40,
480
+ s2: 40,
481
+ s3: 40,
482
+ s4: 40,
483
+ s5: 40
484
+ }
485
+ });
486
+ const [collId, amount] = (0, _reducer.collectionSkuIdWithDiscount)(state, 's5');
487
+ (0, _vitest.expect)(collId).toBe('family-sku');
488
+ (0, _vitest.expect)(amount).toBe(40);
489
+ });
490
+ (0, _vitest.it)('prefers collection bundle over self when prices are equal', () => {
491
+ const state = makeState({
492
+ collectionStyleSkus: {
493
+ 'family-sku': {
494
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5'],
495
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5'],
496
+ childrenSkuIds: [],
497
+ name: 'Thunder Text'
498
+ },
499
+ 'var-family-sku': {
500
+ fontStyleSkuIds: ['vs1'],
501
+ fontStyleIds: ['vf1'],
502
+ childrenSkuIds: [],
503
+ name: 'Thunder Text Variable'
504
+ },
505
+ 'var-italic-sku': {
506
+ fontStyleSkuIds: ['vis1'],
507
+ fontStyleIds: ['vif1'],
508
+ childrenSkuIds: [],
509
+ name: 'Thunder Text Italic Variable'
510
+ },
511
+ 'bundle-sku': {
512
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'vs1', 'vis1'],
513
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'vf1', 'vif1'],
514
+ childrenSkuIds: ['family-sku', 'var-family-sku', 'var-italic-sku'],
515
+ name: 'Thunder Text Family + Variable'
516
+ }
517
+ },
518
+ skuPrices: {
519
+ 'family-sku': 500,
520
+ 'bundle-sku': 500,
521
+ 'var-family-sku': 300,
522
+ 'var-italic-sku': 300,
523
+ s1: 40,
524
+ s2: 40,
525
+ s3: 40,
526
+ s4: 40,
527
+ s5: 40,
528
+ vs1: 40,
529
+ vis1: 40
530
+ }
531
+ });
532
+ const [collId, amount] = (0, _reducer.collectionSkuIdWithDiscount)(state, 'family-sku');
533
+ (0, _vitest.expect)(collId).toBe('bundle-sku');
534
+ (0, _vitest.expect)(amount).toBe(500);
535
+ });
536
+ (0, _vitest.it)('prefers collection bundle regardless of object key order', () => {
537
+ const state = makeState({
538
+ collectionStyleSkus: {
539
+ 'bundle-sku': {
540
+ fontStyleSkuIds: ['s1', 's2', 'vs1'],
541
+ fontStyleIds: ['f1', 'f2', 'vf1'],
542
+ childrenSkuIds: ['family-sku', 'var-family-sku'],
543
+ name: 'Bundle'
544
+ },
545
+ 'family-sku': {
546
+ fontStyleSkuIds: ['s1', 's2'],
547
+ fontStyleIds: ['f1', 'f2'],
548
+ childrenSkuIds: [],
549
+ name: 'Family'
550
+ }
551
+ },
552
+ skuPrices: {
553
+ 'family-sku': 500,
554
+ 'bundle-sku': 500,
555
+ 'var-family-sku': 300
556
+ }
557
+ });
558
+ const [collId] = (0, _reducer.collectionSkuIdWithDiscount)(state, 'family-sku');
559
+ (0, _vitest.expect)(collId).toBe('bundle-sku');
560
+ });
561
+ (0, _vitest.it)('still returns self for display pricing (superfamily with no parent)', () => {
562
+ const state = makeState({
563
+ selectedSkuIds: {
564
+ 'family-sku': true
565
+ },
566
+ collectionStyleSkus: {
567
+ 'superfamily-sku': {
568
+ fontStyleSkuIds: ['s1', 's2', 'ds1', 'ds2'],
569
+ fontStyleIds: ['f1', 'f2', 'df1', 'df2'],
570
+ childrenSkuIds: ['family-sku', 'display-family-sku'],
571
+ name: 'Superfamily'
572
+ },
573
+ 'family-sku': {
574
+ fontStyleSkuIds: ['s1', 's2'],
575
+ fontStyleIds: ['f1', 'f2'],
576
+ childrenSkuIds: [],
577
+ name: 'Family'
578
+ },
579
+ 'display-family-sku': {
580
+ fontStyleSkuIds: ['ds1', 'ds2'],
581
+ fontStyleIds: ['df1', 'df2'],
582
+ childrenSkuIds: [],
583
+ name: 'Display'
584
+ }
585
+ },
586
+ skuPrices: {
587
+ 'superfamily-sku': 750,
588
+ 'family-sku': 500,
589
+ 'display-family-sku': 500
590
+ }
591
+ });
592
+ const [collId, amount] = (0, _reducer.collectionSkuIdWithDiscount)(state, 'superfamily-sku');
593
+ (0, _vitest.expect)(collId).toBe('superfamily-sku');
594
+ (0, _vitest.expect)(amount).toBe(250);
595
+ });
596
+ (0, _vitest.it)('prefers bundle at $0 when family is already selected', () => {
597
+ const state = makeState({
598
+ selectedSkuIds: {
599
+ 'family-sku': true
600
+ },
601
+ collectionStyleSkus: {
602
+ 'family-sku': {
603
+ fontStyleSkuIds: ['s1', 's2'],
604
+ fontStyleIds: ['f1', 'f2'],
605
+ childrenSkuIds: [],
606
+ name: 'Family'
607
+ },
608
+ 'var-family-sku': {
609
+ fontStyleSkuIds: ['vs1'],
610
+ fontStyleIds: ['vf1'],
611
+ childrenSkuIds: [],
612
+ name: 'Variable'
613
+ },
614
+ 'bundle-sku': {
615
+ fontStyleSkuIds: ['s1', 's2', 'vs1'],
616
+ fontStyleIds: ['f1', 'f2', 'vf1'],
617
+ childrenSkuIds: ['family-sku', 'var-family-sku'],
618
+ name: 'Bundle'
619
+ }
620
+ },
621
+ skuPrices: {
622
+ 'family-sku': 500,
623
+ 'var-family-sku': 300,
624
+ 'bundle-sku': 500
625
+ }
626
+ });
627
+ const [collId, amount] = (0, _reducer.collectionSkuIdWithDiscount)(state, 'var-family-sku');
628
+ (0, _vitest.expect)(collId).toBe('bundle-sku');
629
+ (0, _vitest.expect)(amount).toBe(0);
630
+ });
631
+ (0, _vitest.it)('does not upgrade when collection bundle is more expensive', () => {
632
+ const state = makeState({
633
+ collectionStyleSkus: {
634
+ 'family-sku': {
635
+ fontStyleSkuIds: ['s1', 's2'],
636
+ fontStyleIds: ['f1', 'f2'],
637
+ childrenSkuIds: [],
638
+ name: 'Family'
639
+ },
640
+ 'bundle-sku': {
641
+ fontStyleSkuIds: ['s1', 's2', 'vs1'],
642
+ fontStyleIds: ['f1', 'f2', 'vf1'],
643
+ childrenSkuIds: ['family-sku', 'var-family-sku'],
644
+ name: 'Bundle'
645
+ }
646
+ },
647
+ skuPrices: {
648
+ 'family-sku': 500,
649
+ 'bundle-sku': 600
650
+ }
651
+ });
652
+ const [collId] = (0, _reducer.collectionSkuIdWithDiscount)(state, 'family-sku');
653
+ (0, _vitest.expect)(collId).toBe('family-sku');
654
+ });
655
+ (0, _vitest.it)('returns null when the selected SKU has no price (undefined)', () => {
656
+ // If skuPrices[skuId] is undefined, the filter comparison
657
+ // `difference <= undefined` is always false → no collection found
658
+ const state = makeState({
659
+ collectionStyleSkus: {
660
+ 'family-sku': {
661
+ fontStyleSkuIds: ['s1'],
662
+ fontStyleIds: ['f1'],
663
+ childrenSkuIds: [],
664
+ name: 'Family'
665
+ }
666
+ },
667
+ skuPrices: {
668
+ 'family-sku': 500
669
+ } // s1 deliberately missing
670
+ });
671
+ const [collId, amount] = (0, _reducer.collectionSkuIdWithDiscount)(state, 's1');
672
+ (0, _vitest.expect)(collId).toBe(null);
673
+ (0, _vitest.expect)(amount).toBe(null);
674
+ });
675
+ (0, _vitest.it)('treats collections with missing price as price 0', () => {
676
+ // A collection with no price entry is treated as $0, meaning its
677
+ // difference = 0 - sum_selected. With nothing selected, diff = 0.
678
+ const state = makeState({
679
+ collectionStyleSkus: {
680
+ 'ghost-bundle': {
681
+ fontStyleSkuIds: ['s1'],
682
+ fontStyleIds: ['f1'],
683
+ childrenSkuIds: ['family-sku'],
684
+ name: 'Ghost Bundle'
685
+ },
686
+ 'family-sku': {
687
+ fontStyleSkuIds: ['s1'],
688
+ fontStyleIds: ['f1'],
689
+ childrenSkuIds: [],
690
+ name: 'Family'
691
+ }
692
+ },
693
+ skuPrices: {
694
+ 'family-sku': 500,
695
+ s1: 40
696
+ }
697
+ // 'ghost-bundle' price deliberately missing → treated as 0
698
+ });
699
+ const [collId, amount] = (0, _reducer.collectionSkuIdWithDiscount)(state, 'family-sku');
700
+ // ghost-bundle diff = 0, family diff = 500. ghost-bundle is cheaper.
701
+ // 0 <= 500 ✓ → ghost-bundle wins
702
+ (0, _vitest.expect)(collId).toBe('ghost-bundle');
703
+ (0, _vitest.expect)(amount).toBe(0);
704
+ });
705
+ (0, _vitest.it)('handles zero-price SKUs without errors', () => {
706
+ const state = makeState({
707
+ collectionStyleSkus: {
708
+ 'free-family': {
709
+ fontStyleSkuIds: ['fs1'],
710
+ fontStyleIds: ['ff1'],
711
+ childrenSkuIds: [],
712
+ name: 'Free Family'
713
+ },
714
+ 'free-bundle': {
715
+ fontStyleSkuIds: ['fs1', 'fvs1'],
716
+ fontStyleIds: ['ff1', 'fvf1'],
717
+ childrenSkuIds: ['free-family', 'free-var'],
718
+ name: 'Free Bundle'
719
+ }
720
+ },
721
+ skuPrices: {
722
+ 'free-family': 0,
723
+ 'free-bundle': 0,
724
+ 'free-var': 0,
725
+ fs1: 0,
726
+ fvs1: 0
727
+ }
728
+ });
729
+
730
+ // Both have diff = 0, filter: 0 <= 0 ✓, tie-break prefers bundle
731
+ const [collId, amount] = (0, _reducer.collectionSkuIdWithDiscount)(state, 'free-family');
732
+ (0, _vitest.expect)(collId).toBe('free-bundle');
733
+ (0, _vitest.expect)(amount).toBe(0);
734
+ });
735
+ (0, _vitest.it)('handles negative difference (collection cheaper than sum of selected)', () => {
736
+ // Unusual but possible: if prices change or a collection is deeply discounted
737
+ const state = makeState({
738
+ selectedSkuIds: {
739
+ s1: true,
740
+ s2: true,
741
+ s3: true
742
+ },
743
+ collectionStyleSkus: {
744
+ 'family-sku': {
745
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4'],
746
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4'],
747
+ childrenSkuIds: [],
748
+ name: 'Family'
749
+ }
750
+ },
751
+ skuPrices: {
752
+ 'family-sku': 100,
753
+ s1: 40,
754
+ s2: 40,
755
+ s3: 40,
756
+ s4: 40
757
+ }
758
+ });
759
+
760
+ // Family costs $100, 3 styles selected = $120, diff = $100 - $120 = -$20
761
+ // This means the collection is $20 cheaper than what's already selected
762
+ const [collId, amount] = (0, _reducer.collectionSkuIdWithDiscount)(state, 's4');
763
+ (0, _vitest.expect)(collId).toBe('family-sku');
764
+ (0, _vitest.expect)(amount).toBe(-20);
765
+ });
766
+ });
767
+
768
+ // =========================================================================
769
+ // reducer SELECT_SKU_ID — core auto-selection
770
+ // =========================================================================
771
+ (0, _vitest.describe)('reducer SELECT_SKU_ID with collection bundles', () => {
772
+ (0, _vitest.it)('auto-selects collection bundle when selecting a family at the same price', () => {
773
+ const state = makeThunderState();
774
+ const newState = (0, _reducer.default)(state, {
775
+ type: 'SELECT_SKU_ID',
776
+ skuId: 'text-sku',
777
+ selected: true
778
+ });
779
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBe(true);
780
+ (0, _vitest.expect)(newState.selectedSkuIds['text-sku']).toBe(false);
781
+ (0, _vitest.expect)(newState.selectedSkuIds['text-var-sku']).toBe(false);
782
+ (0, _vitest.expect)(newState.selectedSkuIds['text-var-italic-sku']).toBe(false);
783
+ });
784
+ (0, _vitest.it)('does not auto-select bundle when bundle is more expensive', () => {
785
+ const state = makeThunderState({
786
+ skuPrices: {
787
+ ...makeThunderState().skuPrices,
788
+ 'text-bundle-sku': 600 // more expensive than text-sku ($500)
789
+ }
790
+ });
791
+ const newState = (0, _reducer.default)(state, {
792
+ type: 'SELECT_SKU_ID',
793
+ skuId: 'text-sku',
794
+ selected: true
795
+ });
796
+ (0, _vitest.expect)(newState.selectedSkuIds['text-sku']).toBe(true);
797
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBeUndefined();
798
+ });
799
+ (0, _vitest.it)('auto-selects bundle when clicking variable family with static already selected', () => {
800
+ const state = makeThunderState({
801
+ selectedSkuIds: {
802
+ 'text-sku': true
803
+ }
804
+ });
805
+ const newState = (0, _reducer.default)(state, {
806
+ type: 'SELECT_SKU_ID',
807
+ skuId: 'text-var-sku',
808
+ selected: true
809
+ });
810
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBe(true);
811
+ (0, _vitest.expect)(newState.selectedSkuIds['text-sku']).toBe(false);
812
+ (0, _vitest.expect)(newState.selectedSkuIds['text-var-sku']).toBe(false);
813
+ });
814
+ });
815
+
816
+ // =========================================================================
817
+ // reducer SELECT_SKU_ID — multiple bundles don't interfere
818
+ // =========================================================================
819
+ (0, _vitest.describe)('reducer SELECT_SKU_ID with multiple bundles', () => {
820
+ (0, _vitest.it)('only selects the correct bundle for the family being clicked', () => {
821
+ const state = makeThunderState();
822
+ const newState = (0, _reducer.default)(state, {
823
+ type: 'SELECT_SKU_ID',
824
+ skuId: 'display-sku',
825
+ selected: true
826
+ });
827
+
828
+ // Display bundle should be selected
829
+ (0, _vitest.expect)(newState.selectedSkuIds['display-bundle-sku']).toBe(true);
830
+ (0, _vitest.expect)(newState.selectedSkuIds['display-sku']).toBe(false);
831
+ // Text and caption bundles should be untouched
832
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBeUndefined();
833
+ (0, _vitest.expect)(newState.selectedSkuIds['caption-bundle-sku']).toBeUndefined();
834
+ });
835
+ (0, _vitest.it)('upgrades to superfamily when selecting a second family bundle', () => {
836
+ // Selecting two families ($500+$500) costs more than the superfamily ($750),
837
+ // so the system correctly upgrades to the superfamily on the second click
838
+ const state = makeThunderState();
839
+ let newState = (0, _reducer.default)(state, {
840
+ type: 'SELECT_SKU_ID',
841
+ skuId: 'text-sku',
842
+ selected: true
843
+ });
844
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBe(true);
845
+ newState = (0, _reducer.default)(newState, {
846
+ type: 'SELECT_SKU_ID',
847
+ skuId: 'display-sku',
848
+ selected: true
849
+ });
850
+
851
+ // Superfamily ($750) beats two bundles ($500 + $500 = $1000)
852
+ (0, _vitest.expect)(newState.selectedSkuIds['super-sku']).toBe(true);
853
+ // Previous bundle should be deselected (subsumed by superfamily)
854
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBe(false);
855
+ (0, _vitest.expect)(newState.selectedSkuIds['display-bundle-sku']).toBe(false);
856
+ });
857
+ });
858
+
859
+ // =========================================================================
860
+ // reducer SELECT_SKU_ID — deselection flow
861
+ // =========================================================================
862
+ (0, _vitest.describe)('reducer deselection', () => {
863
+ (0, _vitest.it)('deselecting a bundle member deselects the entire bundle', () => {
864
+ // Start with the text bundle selected (members are false)
865
+ const state = makeThunderState({
866
+ selectedSkuIds: {
867
+ 'text-bundle-sku': true,
868
+ 'text-sku': false,
869
+ 'text-var-sku': false,
870
+ 'text-var-italic-sku': false,
871
+ s1: false,
872
+ s2: false,
873
+ vs1: false,
874
+ vis1: false
875
+ }
876
+ });
877
+
878
+ // User clicks to deselect "Thunder Text Variable"
879
+ const newState = (0, _reducer.default)(state, {
880
+ type: 'SELECT_SKU_ID',
881
+ skuId: 'text-var-sku',
882
+ selected: false
883
+ });
884
+
885
+ // The bundle should be deselected
886
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBe(false);
887
+ });
888
+ (0, _vitest.it)('deselecting a font style deselects the parent bundle', () => {
889
+ const state = makeThunderState({
890
+ selectedSkuIds: {
891
+ 'text-bundle-sku': true,
892
+ 'text-sku': false,
893
+ 'text-var-sku': false,
894
+ 'text-var-italic-sku': false,
895
+ s1: false,
896
+ s2: false,
897
+ vs1: false,
898
+ vis1: false
899
+ }
900
+ });
901
+
902
+ // User clicks to deselect individual style "s1"
903
+ const newState = (0, _reducer.default)(state, {
904
+ type: 'SELECT_SKU_ID',
905
+ skuId: 's1',
906
+ selected: false
907
+ });
908
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBe(false);
909
+ });
910
+ (0, _vitest.it)('deselecting a family bundle deselects the parent collection bundle (transitive)', () => {
911
+ // Collection bundle is selected. A family bundle (style package) within
912
+ // it is shown as selected via transitive isSelected. Clicking to deselect
913
+ // it should find and deselect the collection bundle.
914
+ const state = makeState({
915
+ selectedSkuIds: {
916
+ 'coll-bundle-sku': true,
917
+ 'family-sku': false,
918
+ 'var-sku': false,
919
+ 'pkg-upright-sku': false,
920
+ 'pkg-italic-sku': false,
921
+ s1: false,
922
+ s2: false,
923
+ si1: false,
924
+ si2: false,
925
+ vs1: false
926
+ },
927
+ collectionStyleSkus: {
928
+ 'coll-bundle-sku': {
929
+ fontStyleSkuIds: ['s1', 's2', 'si1', 'si2', 'vs1'],
930
+ fontStyleIds: ['f1', 'f2', 'fi1', 'fi2', 'vf1'],
931
+ childrenSkuIds: ['family-sku', 'var-sku'],
932
+ name: 'Text + Variable'
933
+ },
934
+ 'family-sku': {
935
+ fontStyleSkuIds: ['s1', 's2', 'si1', 'si2'],
936
+ fontStyleIds: ['f1', 'f2', 'fi1', 'fi2'],
937
+ childrenSkuIds: ['pkg-upright-sku', 'pkg-italic-sku'],
938
+ name: 'Text Family'
939
+ },
940
+ 'var-sku': {
941
+ fontStyleSkuIds: ['vs1'],
942
+ fontStyleIds: ['vf1'],
943
+ childrenSkuIds: [],
944
+ name: 'Variable'
945
+ },
946
+ 'pkg-upright-sku': {
947
+ fontStyleSkuIds: ['s1', 's2'],
948
+ fontStyleIds: ['f1', 'f2'],
949
+ childrenSkuIds: [],
950
+ name: 'Package Text'
951
+ },
952
+ 'pkg-italic-sku': {
953
+ fontStyleSkuIds: ['si1', 'si2'],
954
+ fontStyleIds: ['fi1', 'fi2'],
955
+ childrenSkuIds: [],
956
+ name: 'Package Italic'
957
+ }
958
+ },
959
+ skuPrices: {
960
+ 'coll-bundle-sku': 500,
961
+ 'family-sku': 500,
962
+ 'var-sku': 300,
963
+ 'pkg-upright-sku': 250,
964
+ 'pkg-italic-sku': 250,
965
+ s1: 100,
966
+ s2: 100,
967
+ si1: 100,
968
+ si2: 100,
969
+ vs1: 100
970
+ }
971
+ });
972
+
973
+ // Bundle shows as selected via transitive isSelected
974
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-upright-sku')(state)).toBe(true);
975
+
976
+ // Click to deselect the bundle
977
+ const newState = (0, _reducer.default)(state, {
978
+ type: 'SELECT_SKU_ID',
979
+ skuId: 'pkg-upright-sku',
980
+ selected: false
981
+ });
982
+
983
+ // Collection bundle should be deselected
984
+ (0, _vitest.expect)(newState.selectedSkuIds['coll-bundle-sku']).toBe(false);
985
+ // Nothing should be selected anymore
986
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-upright-sku')(newState)).toBe(false);
987
+ (0, _vitest.expect)((0, _utils.isSelected)('family-sku')(newState)).toBe(false);
988
+ (0, _vitest.expect)((0, _utils.isSelected)('s1')(newState)).toBe(false);
989
+ });
990
+ (0, _vitest.it)('deselecting a bundle does not affect other bundles', () => {
991
+ const state = makeThunderState({
992
+ selectedSkuIds: {
993
+ 'text-bundle-sku': true,
994
+ 'text-sku': false,
995
+ 'text-var-sku': false,
996
+ 'text-var-italic-sku': false,
997
+ s1: false,
998
+ s2: false,
999
+ vs1: false,
1000
+ vis1: false,
1001
+ 'display-bundle-sku': true,
1002
+ 'display-sku': false,
1003
+ 'display-var-sku': false,
1004
+ 'display-var-italic-sku': false,
1005
+ ds1: false,
1006
+ ds2: false,
1007
+ dvs1: false,
1008
+ dvis1: false
1009
+ }
1010
+ });
1011
+ const newState = (0, _reducer.default)(state, {
1012
+ type: 'SELECT_SKU_ID',
1013
+ skuId: 'text-var-sku',
1014
+ selected: false
1015
+ });
1016
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBe(false);
1017
+ // Display bundle should be unaffected
1018
+ (0, _vitest.expect)(newState.selectedSkuIds['display-bundle-sku']).toBe(true);
1019
+ });
1020
+ });
1021
+
1022
+ // =========================================================================
1023
+ // reducer SELECT_SKU_ID — re-selection after deselection
1024
+ // =========================================================================
1025
+ (0, _vitest.describe)('reducer re-selection cycle', () => {
1026
+ (0, _vitest.it)('re-selecting family after bundle was deselected re-selects the bundle', () => {
1027
+ // 1. Start with bundle selected
1028
+ const state = makeThunderState({
1029
+ selectedSkuIds: {
1030
+ 'text-bundle-sku': true,
1031
+ 'text-sku': false,
1032
+ 'text-var-sku': false,
1033
+ 'text-var-italic-sku': false,
1034
+ s1: false,
1035
+ s2: false,
1036
+ vs1: false,
1037
+ vis1: false
1038
+ }
1039
+ });
1040
+
1041
+ // 2. Deselect a member → bundle goes away
1042
+ const afterDeselect = (0, _reducer.default)(state, {
1043
+ type: 'SELECT_SKU_ID',
1044
+ skuId: 'text-var-sku',
1045
+ selected: false
1046
+ });
1047
+ (0, _vitest.expect)(afterDeselect.selectedSkuIds['text-bundle-sku']).toBe(false);
1048
+
1049
+ // 3. Re-select the family → bundle should come back
1050
+ const afterReselect = (0, _reducer.default)(afterDeselect, {
1051
+ type: 'SELECT_SKU_ID',
1052
+ skuId: 'text-sku',
1053
+ selected: true
1054
+ });
1055
+ (0, _vitest.expect)(afterReselect.selectedSkuIds['text-bundle-sku']).toBe(true);
1056
+ (0, _vitest.expect)(afterReselect.selectedSkuIds['text-sku']).toBe(false);
1057
+ (0, _vitest.expect)(afterReselect.selectedSkuIds['text-var-sku']).toBe(false);
1058
+ });
1059
+ });
1060
+
1061
+ // =========================================================================
1062
+ // reducer SELECT_SKU_ID — superfamily upgrade
1063
+ // =========================================================================
1064
+ (0, _vitest.describe)('reducer superfamily upgrade', () => {
1065
+ (0, _vitest.it)('upgrades to superfamily when cumulative selections make it cheaper', () => {
1066
+ // With two bundles selected ($500 each = $1000 total), the superfamily
1067
+ // at $750 should be a better deal when selecting the third family
1068
+ const state = makeThunderState({
1069
+ selectedSkuIds: {
1070
+ 'text-bundle-sku': true,
1071
+ 'text-sku': false,
1072
+ 'text-var-sku': false,
1073
+ 'text-var-italic-sku': false,
1074
+ s1: false,
1075
+ s2: false,
1076
+ vs1: false,
1077
+ vis1: false,
1078
+ 'display-bundle-sku': true,
1079
+ 'display-sku': false,
1080
+ 'display-var-sku': false,
1081
+ 'display-var-italic-sku': false,
1082
+ ds1: false,
1083
+ ds2: false,
1084
+ dvs1: false,
1085
+ dvis1: false
1086
+ }
1087
+ });
1088
+ const newState = (0, _reducer.default)(state, {
1089
+ type: 'SELECT_SKU_ID',
1090
+ skuId: 'caption-sku',
1091
+ selected: true
1092
+ });
1093
+
1094
+ // Superfamily ($750) - two bundles ($500+$500) = -$250 difference
1095
+ // Caption-sku costs $500, so -$250 <= $500 → superfamily is preferred
1096
+ (0, _vitest.expect)(newState.selectedSkuIds['super-sku']).toBe(true);
1097
+ });
1098
+ });
1099
+
1100
+ // =========================================================================
1101
+ // reducer SELECT_SKU_ID — individual style to family upgrade
1102
+ // =========================================================================
1103
+ (0, _vitest.describe)('reducer individual style upgrade', () => {
1104
+ (0, _vitest.it)('upgrades to family when enough styles are individually selected', () => {
1105
+ const state = makeState({
1106
+ selectedSkuIds: {
1107
+ s1: true,
1108
+ s2: true,
1109
+ s3: true,
1110
+ s4: true
1111
+ },
1112
+ collectionStyleSkus: {
1113
+ 'family-sku': {
1114
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5'],
1115
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5'],
1116
+ childrenSkuIds: [],
1117
+ name: 'Family'
1118
+ }
1119
+ },
1120
+ skuPrices: {
1121
+ 'family-sku': 200,
1122
+ s1: 40,
1123
+ s2: 40,
1124
+ s3: 40,
1125
+ s4: 40,
1126
+ s5: 40
1127
+ }
1128
+ });
1129
+
1130
+ // Family is $200, 4 selected = $160, diff = $40. Style s5 costs $40.
1131
+ // $40 <= $40 → family is a better deal
1132
+ const newState = (0, _reducer.default)(state, {
1133
+ type: 'SELECT_SKU_ID',
1134
+ skuId: 's5',
1135
+ selected: true
1136
+ });
1137
+ (0, _vitest.expect)(newState.selectedSkuIds['family-sku']).toBe(true);
1138
+ // Individual styles should be deselected (included via family)
1139
+ (0, _vitest.expect)(newState.selectedSkuIds['s1']).toBe(false);
1140
+ (0, _vitest.expect)(newState.selectedSkuIds['s5']).toBe(false);
1141
+ });
1142
+ (0, _vitest.it)('does not upgrade to family when not yet cost-effective', () => {
1143
+ const state = makeState({
1144
+ selectedSkuIds: {
1145
+ s1: true
1146
+ },
1147
+ collectionStyleSkus: {
1148
+ 'family-sku': {
1149
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5'],
1150
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5'],
1151
+ childrenSkuIds: [],
1152
+ name: 'Family'
1153
+ }
1154
+ },
1155
+ skuPrices: {
1156
+ 'family-sku': 200,
1157
+ s1: 40,
1158
+ s2: 40,
1159
+ s3: 40,
1160
+ s4: 40,
1161
+ s5: 40
1162
+ }
1163
+ });
1164
+
1165
+ // Family is $200, 1 selected = $40, diff = $160. Style s2 costs $40.
1166
+ // $160 > $40 → not worth upgrading
1167
+ const newState = (0, _reducer.default)(state, {
1168
+ type: 'SELECT_SKU_ID',
1169
+ skuId: 's2',
1170
+ selected: true
1171
+ });
1172
+ (0, _vitest.expect)(newState.selectedSkuIds['family-sku']).toBeUndefined();
1173
+ (0, _vitest.expect)(newState.selectedSkuIds['s1']).toBe(true);
1174
+ (0, _vitest.expect)(newState.selectedSkuIds['s2']).toBe(true);
1175
+ });
1176
+ });
1177
+
1178
+ // =========================================================================
1179
+ // reducer SELECT_SKU_ID — edge cases with missing data
1180
+ // =========================================================================
1181
+ (0, _vitest.describe)('reducer edge cases', () => {
1182
+ (0, _vitest.it)('handles selection when collectionStyleSkus is empty', () => {
1183
+ const state = makeState({
1184
+ skuPrices: {
1185
+ 'some-sku': 100
1186
+ }
1187
+ });
1188
+ const newState = (0, _reducer.default)(state, {
1189
+ type: 'SELECT_SKU_ID',
1190
+ skuId: 'some-sku',
1191
+ selected: true
1192
+ });
1193
+ (0, _vitest.expect)(newState.selectedSkuIds['some-sku']).toBe(true);
1194
+ });
1195
+ (0, _vitest.it)('handles selection when skuPrices is empty', () => {
1196
+ const state = makeState({
1197
+ collectionStyleSkus: {
1198
+ 'family-sku': {
1199
+ fontStyleSkuIds: ['s1'],
1200
+ fontStyleIds: ['f1'],
1201
+ childrenSkuIds: [],
1202
+ name: 'Family'
1203
+ }
1204
+ }
1205
+ });
1206
+
1207
+ // No prices → no collection upgrade possible, just select directly
1208
+ const newState = (0, _reducer.default)(state, {
1209
+ type: 'SELECT_SKU_ID',
1210
+ skuId: 's1',
1211
+ selected: true
1212
+ });
1213
+ (0, _vitest.expect)(newState.selectedSkuIds['s1']).toBe(true);
1214
+ (0, _vitest.expect)(newState.selectedSkuIds['family-sku']).toBeUndefined();
1215
+ });
1216
+ (0, _vitest.it)('re-selecting a bundle member may upgrade to superfamily', () => {
1217
+ // When the text bundle is selected and the user clicks text-sku (a member),
1218
+ // the system re-evaluates: the superfamily sees the text bundle ($500) as
1219
+ // already selected, making its difference only $250 — cheaper than text-sku
1220
+ // at $500. So it upgrades to the superfamily.
1221
+ //
1222
+ // This is a documented consequence of the greedy price-optimization: every
1223
+ // SELECT_SKU_ID re-evaluates the cheapest collection, even for members
1224
+ // that are already effectively selected.
1225
+ const state = makeThunderState({
1226
+ selectedSkuIds: {
1227
+ 'text-bundle-sku': true,
1228
+ 'text-sku': false,
1229
+ 'text-var-sku': false,
1230
+ 'text-var-italic-sku': false,
1231
+ s1: false,
1232
+ s2: false,
1233
+ vs1: false,
1234
+ vis1: false
1235
+ }
1236
+ });
1237
+ const newState = (0, _reducer.default)(state, {
1238
+ type: 'SELECT_SKU_ID',
1239
+ skuId: 'text-sku',
1240
+ selected: true
1241
+ });
1242
+ (0, _vitest.expect)(newState.selectedSkuIds['super-sku']).toBe(true);
1243
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBe(false);
1244
+ });
1245
+ (0, _vitest.it)('UNSELECT_SKUS clears all selections cleanly', () => {
1246
+ const state = makeThunderState({
1247
+ selectedSkuIds: {
1248
+ 'text-bundle-sku': true,
1249
+ 'text-sku': false,
1250
+ 'text-var-sku': false
1251
+ }
1252
+ });
1253
+ const newState = (0, _reducer.default)(state, {
1254
+ type: 'UNSELECT_SKUS'
1255
+ });
1256
+ (0, _vitest.expect)(Object.keys(newState.selectedSkuIds)).toHaveLength(0);
1257
+ });
1258
+ });
1259
+
1260
+ // =========================================================================
1261
+ // isSelected — family bundles (style packages within a family)
1262
+ // =========================================================================
1263
+ (0, _vitest.describe)('isSelected with family bundles (style packages)', () => {
1264
+ // Helper: a family with two style bundles (upright + italic packages)
1265
+ function makeFamilyWithBundlesState(overrides = {}) {
1266
+ return makeState({
1267
+ collectionStyleSkus: {
1268
+ 'family-sku': {
1269
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5'],
1270
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1271
+ childrenSkuIds: ['pkg-upright-sku', 'pkg-italic-sku'],
1272
+ name: 'Thunder Text Family'
1273
+ },
1274
+ 'pkg-upright-sku': {
1275
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5'],
1276
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5'],
1277
+ childrenSkuIds: [],
1278
+ name: 'Package Text'
1279
+ },
1280
+ 'pkg-italic-sku': {
1281
+ fontStyleSkuIds: ['si1', 'si2', 'si3', 'si4', 'si5'],
1282
+ fontStyleIds: ['fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1283
+ childrenSkuIds: [],
1284
+ name: 'Package Text Italic'
1285
+ }
1286
+ },
1287
+ skuPrices: {
1288
+ 'family-sku': 500,
1289
+ 'pkg-upright-sku': 250,
1290
+ 'pkg-italic-sku': 250,
1291
+ s1: 100,
1292
+ s2: 100,
1293
+ s3: 100,
1294
+ s4: 100,
1295
+ s5: 100,
1296
+ si1: 100,
1297
+ si2: 100,
1298
+ si3: 100,
1299
+ si4: 100,
1300
+ si5: 100
1301
+ },
1302
+ ...overrides
1303
+ });
1304
+ }
1305
+ (0, _vitest.it)('bundles show selected when family is directly selected', () => {
1306
+ const state = makeFamilyWithBundlesState({
1307
+ selectedSkuIds: {
1308
+ 'family-sku': true,
1309
+ 'pkg-upright-sku': false,
1310
+ 'pkg-italic-sku': false,
1311
+ s1: false,
1312
+ s2: false,
1313
+ s3: false,
1314
+ s4: false,
1315
+ s5: false,
1316
+ si1: false,
1317
+ si2: false,
1318
+ si3: false,
1319
+ si4: false,
1320
+ si5: false
1321
+ }
1322
+ });
1323
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-upright-sku')(state)).toBe(true);
1324
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-italic-sku')(state)).toBe(true);
1325
+ (0, _vitest.expect)((0, _utils.isSelected)('s1')(state)).toBe(true);
1326
+ (0, _vitest.expect)((0, _utils.isSelected)('si1')(state)).toBe(true);
1327
+ });
1328
+ (0, _vitest.it)('bundles show selected when collection bundle is selected (transitive)', () => {
1329
+ // Collection bundle is selected → family is false → family bundles should
1330
+ // still show selected because the collection bundle covers all styles
1331
+ const state = makeFamilyWithBundlesState({
1332
+ collectionStyleSkus: {
1333
+ // Collection bundle: groups family + variable family
1334
+ 'coll-bundle-sku': {
1335
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5', 'vs1'],
1336
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5', 'vf1'],
1337
+ childrenSkuIds: ['family-sku', 'var-sku'],
1338
+ name: 'Thunder Text + Variable'
1339
+ },
1340
+ 'family-sku': {
1341
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5'],
1342
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1343
+ childrenSkuIds: ['pkg-upright-sku', 'pkg-italic-sku'],
1344
+ name: 'Thunder Text Family'
1345
+ },
1346
+ 'var-sku': {
1347
+ fontStyleSkuIds: ['vs1'],
1348
+ fontStyleIds: ['vf1'],
1349
+ childrenSkuIds: [],
1350
+ name: 'Thunder Text Variable'
1351
+ },
1352
+ 'pkg-upright-sku': {
1353
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5'],
1354
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5'],
1355
+ childrenSkuIds: [],
1356
+ name: 'Package Text'
1357
+ },
1358
+ 'pkg-italic-sku': {
1359
+ fontStyleSkuIds: ['si1', 'si2', 'si3', 'si4', 'si5'],
1360
+ fontStyleIds: ['fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1361
+ childrenSkuIds: [],
1362
+ name: 'Package Text Italic'
1363
+ }
1364
+ },
1365
+ selectedSkuIds: {
1366
+ 'coll-bundle-sku': true,
1367
+ 'family-sku': false,
1368
+ 'var-sku': false,
1369
+ 'pkg-upright-sku': false,
1370
+ 'pkg-italic-sku': false,
1371
+ s1: false,
1372
+ s2: false,
1373
+ s3: false,
1374
+ s4: false,
1375
+ s5: false,
1376
+ si1: false,
1377
+ si2: false,
1378
+ si3: false,
1379
+ si4: false,
1380
+ si5: false,
1381
+ vs1: false
1382
+ }
1383
+ });
1384
+
1385
+ // Family shows selected (direct child of collection bundle)
1386
+ (0, _vitest.expect)((0, _utils.isSelected)('family-sku')(state)).toBe(true);
1387
+ // Bundles should show selected (grandchildren — family is child of
1388
+ // collection bundle, bundles are children of family)
1389
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-upright-sku')(state)).toBe(true);
1390
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-italic-sku')(state)).toBe(true);
1391
+ // Individual styles should also show selected
1392
+ (0, _vitest.expect)((0, _utils.isSelected)('s1')(state)).toBe(true);
1393
+ });
1394
+ (0, _vitest.it)('selecting one bundle then another upgrades to family via reducer', () => {
1395
+ const state = makeFamilyWithBundlesState();
1396
+
1397
+ // Select first bundle
1398
+ let newState = (0, _reducer.default)(state, {
1399
+ type: 'SELECT_SKU_ID',
1400
+ skuId: 'pkg-upright-sku',
1401
+ selected: true
1402
+ });
1403
+ (0, _vitest.expect)(newState.selectedSkuIds['pkg-upright-sku']).toBe(true);
1404
+
1405
+ // Select second bundle — should upgrade to family
1406
+ newState = (0, _reducer.default)(newState, {
1407
+ type: 'SELECT_SKU_ID',
1408
+ skuId: 'pkg-italic-sku',
1409
+ selected: true
1410
+ });
1411
+
1412
+ // Family should be selected since family ($500) = two bundles ($250+$250)
1413
+ (0, _vitest.expect)(newState.selectedSkuIds['family-sku']).toBe(true);
1414
+ // Both bundles should report as selected
1415
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-upright-sku')(newState)).toBe(true);
1416
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-italic-sku')(newState)).toBe(true);
1417
+ });
1418
+ (0, _vitest.it)('auto-upgrades transitively: bundle → family → collection bundle', () => {
1419
+ // When a collection bundle exists at the same price as the family,
1420
+ // selecting both bundles should upgrade all the way to the collection
1421
+ // bundle (not stop at the family)
1422
+ const state = makeFamilyWithBundlesState({
1423
+ collectionStyleSkus: {
1424
+ 'coll-bundle-sku': {
1425
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5', 'vs1'],
1426
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5', 'vf1'],
1427
+ childrenSkuIds: ['family-sku', 'var-sku'],
1428
+ name: 'Thunder Text + Variable'
1429
+ },
1430
+ 'family-sku': {
1431
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5'],
1432
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1433
+ childrenSkuIds: ['pkg-upright-sku', 'pkg-italic-sku'],
1434
+ name: 'Thunder Text Family'
1435
+ },
1436
+ 'var-sku': {
1437
+ fontStyleSkuIds: ['vs1'],
1438
+ fontStyleIds: ['vf1'],
1439
+ childrenSkuIds: [],
1440
+ name: 'Thunder Text Variable'
1441
+ },
1442
+ 'pkg-upright-sku': {
1443
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5'],
1444
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5'],
1445
+ childrenSkuIds: [],
1446
+ name: 'Package Text'
1447
+ },
1448
+ 'pkg-italic-sku': {
1449
+ fontStyleSkuIds: ['si1', 'si2', 'si3', 'si4', 'si5'],
1450
+ fontStyleIds: ['fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1451
+ childrenSkuIds: [],
1452
+ name: 'Package Text Italic'
1453
+ }
1454
+ },
1455
+ skuPrices: {
1456
+ 'coll-bundle-sku': 500,
1457
+ 'family-sku': 500,
1458
+ 'var-sku': 300,
1459
+ 'pkg-upright-sku': 250,
1460
+ 'pkg-italic-sku': 250,
1461
+ s1: 100,
1462
+ s2: 100,
1463
+ s3: 100,
1464
+ s4: 100,
1465
+ s5: 100,
1466
+ si1: 100,
1467
+ si2: 100,
1468
+ si3: 100,
1469
+ si4: 100,
1470
+ si5: 100,
1471
+ vs1: 100
1472
+ },
1473
+ selectedSkuIds: {
1474
+ 'pkg-upright-sku': true
1475
+ }
1476
+ });
1477
+ const newState = (0, _reducer.default)(state, {
1478
+ type: 'SELECT_SKU_ID',
1479
+ skuId: 'pkg-italic-sku',
1480
+ selected: true
1481
+ });
1482
+
1483
+ // Should transitively upgrade: bundle → family → collection bundle
1484
+ (0, _vitest.expect)(newState.selectedSkuIds['coll-bundle-sku']).toBe(true);
1485
+ (0, _vitest.expect)(newState.selectedSkuIds['family-sku']).toBe(false);
1486
+ (0, _vitest.expect)(newState.selectedSkuIds['pkg-upright-sku']).toBe(false);
1487
+ (0, _vitest.expect)(newState.selectedSkuIds['pkg-italic-sku']).toBe(false);
1488
+
1489
+ // All items should report as selected via isSelected
1490
+ (0, _vitest.expect)((0, _utils.isSelected)('family-sku')(newState)).toBe(true);
1491
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-upright-sku')(newState)).toBe(true);
1492
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-italic-sku')(newState)).toBe(true);
1493
+ (0, _vitest.expect)((0, _utils.isSelected)('s1')(newState)).toBe(true);
1494
+ (0, _vitest.expect)((0, _utils.isSelected)('vs1')(newState)).toBe(true);
1495
+ });
1496
+ (0, _vitest.it)('prefers collection bundle over family when family has duplicate fontStyleSkuIds from bundles', () => {
1497
+ // In production data, flattenSkuData produces a family entry whose
1498
+ // fontStyleSkuIds includes BOTH the family's own styles AND the
1499
+ // overlapping styles from its child bundles, creating duplicates.
1500
+ // The tie-breaker must use non-self preference (not raw array length)
1501
+ // to avoid the family's inflated count beating the collection bundle.
1502
+ const state = makeState({
1503
+ collectionStyleSkus: {
1504
+ 'family-sku': {
1505
+ // 10 unique styles, but listed 20 times (family's own + bundle overlap)
1506
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5', 's1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5'],
1507
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1508
+ childrenSkuIds: ['pkg-upright-sku', 'pkg-italic-sku'],
1509
+ name: 'Thunder Caption Family'
1510
+ },
1511
+ 'coll-bundle-sku': {
1512
+ // 12 unique styles (family 10 + variable 2)
1513
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5', 'vs1', 'vs2'],
1514
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5', 'vf1', 'vf2'],
1515
+ childrenSkuIds: ['family-sku', 'var-sku'],
1516
+ name: 'Thunder Caption Family + Variable'
1517
+ },
1518
+ 'var-sku': {
1519
+ fontStyleSkuIds: ['vs1', 'vs2'],
1520
+ fontStyleIds: ['vf1', 'vf2'],
1521
+ childrenSkuIds: [],
1522
+ name: 'Thunder Caption Variable'
1523
+ },
1524
+ 'pkg-upright-sku': {
1525
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5'],
1526
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5'],
1527
+ childrenSkuIds: [],
1528
+ name: 'Package Caption'
1529
+ },
1530
+ 'pkg-italic-sku': {
1531
+ fontStyleSkuIds: ['si1', 'si2', 'si3', 'si4', 'si5'],
1532
+ fontStyleIds: ['fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1533
+ childrenSkuIds: [],
1534
+ name: 'Package Caption Italic'
1535
+ }
1536
+ },
1537
+ skuPrices: {
1538
+ 'family-sku': 500,
1539
+ 'coll-bundle-sku': 500,
1540
+ 'var-sku': 500,
1541
+ 'pkg-upright-sku': 300,
1542
+ 'pkg-italic-sku': 300,
1543
+ s1: 100,
1544
+ s2: 100,
1545
+ s3: 100,
1546
+ s4: 100,
1547
+ s5: 100,
1548
+ si1: 100,
1549
+ si2: 100,
1550
+ si3: 100,
1551
+ si4: 100,
1552
+ si5: 100,
1553
+ vs1: 100,
1554
+ vs2: 100
1555
+ }
1556
+ });
1557
+
1558
+ // family-sku fontStyleSkuIds.length is 20 (duplicates), coll-bundle is 12.
1559
+ // A naive .length comparison would pick the family. The non-self
1560
+ // preference must win so the collection bundle is auto-selected.
1561
+ const [collId] = (0, _reducer.collectionSkuIdWithDiscount)(state, 'family-sku');
1562
+ (0, _vitest.expect)(collId).toBe('coll-bundle-sku');
1563
+ });
1564
+ (0, _vitest.it)('transitive upgrade prefers the collection with the most font styles', () => {
1565
+ // When two collections have the same price difference, prefer the one
1566
+ // covering more font styles
1567
+ const state = makeFamilyWithBundlesState({
1568
+ collectionStyleSkus: {
1569
+ 'small-bundle-sku': {
1570
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5'],
1571
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1572
+ childrenSkuIds: ['family-sku'],
1573
+ name: 'Small Bundle'
1574
+ },
1575
+ 'big-bundle-sku': {
1576
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5', 'vs1'],
1577
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5', 'vf1'],
1578
+ childrenSkuIds: ['family-sku', 'var-sku'],
1579
+ name: 'Big Bundle'
1580
+ },
1581
+ 'family-sku': {
1582
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5'],
1583
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1584
+ childrenSkuIds: ['pkg-upright-sku', 'pkg-italic-sku'],
1585
+ name: 'Thunder Text Family'
1586
+ },
1587
+ 'var-sku': {
1588
+ fontStyleSkuIds: ['vs1'],
1589
+ fontStyleIds: ['vf1'],
1590
+ childrenSkuIds: [],
1591
+ name: 'Variable'
1592
+ },
1593
+ 'pkg-upright-sku': {
1594
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5'],
1595
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5'],
1596
+ childrenSkuIds: [],
1597
+ name: 'Package Text'
1598
+ },
1599
+ 'pkg-italic-sku': {
1600
+ fontStyleSkuIds: ['si1', 'si2', 'si3', 'si4', 'si5'],
1601
+ fontStyleIds: ['fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1602
+ childrenSkuIds: [],
1603
+ name: 'Package Text Italic'
1604
+ }
1605
+ },
1606
+ skuPrices: {
1607
+ 'small-bundle-sku': 500,
1608
+ 'big-bundle-sku': 500,
1609
+ 'family-sku': 500,
1610
+ 'var-sku': 300,
1611
+ 'pkg-upright-sku': 250,
1612
+ 'pkg-italic-sku': 250,
1613
+ s1: 100,
1614
+ s2: 100,
1615
+ s3: 100,
1616
+ s4: 100,
1617
+ s5: 100,
1618
+ si1: 100,
1619
+ si2: 100,
1620
+ si3: 100,
1621
+ si4: 100,
1622
+ si5: 100,
1623
+ vs1: 100
1624
+ }
1625
+ });
1626
+ const [collId] = (0, _reducer.collectionSkuIdWithDiscount)(state, 'family-sku');
1627
+ // Both bundles have the same price, but big-bundle has more styles
1628
+ (0, _vitest.expect)(collId).toBe('big-bundle-sku');
1629
+ });
1630
+ });