@vtex/faststore-plugin-buyer-portal 2.0.12 → 2.0.14

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 (229) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/cms/b2b/components/.gitkeep +0 -0
  3. package/cms/b2b/pages/cms_content_type__i18n.jsonc +36 -0
  4. package/cypress/integration/localization.test.ts +482 -0
  5. package/cypress/support/commands.ts +8 -0
  6. package/docs/i18n/i18n-messages-format.md +391 -0
  7. package/docs/i18n/locales.json +701 -0
  8. package/docs/i18n/migration-inventory.md +959 -0
  9. package/package.json +3 -3
  10. package/specs/custom-localization-system.md +711 -0
  11. package/specs/multi-locale-e2e-validation.md +277 -0
  12. package/src/__mocks__/discovery.config.ts +12 -0
  13. package/src/features/accounting-fields/components/AccountingConfirmDelete/AccountingConfirmDelete.tsx +14 -4
  14. package/src/features/accounting-fields/components/AccountingFieldConfirmEdit/AccountingFieldConfirmEdit.tsx +11 -6
  15. package/src/features/accounting-fields/components/AccountingFieldDropdown/AccountingFieldDropdown.tsx +5 -3
  16. package/src/features/accounting-fields/components/AccountingFieldForm/AccountingFieldForm.tsx +33 -23
  17. package/src/features/accounting-fields/components/AccountingValueConfirmEdit/AccountingValueConfirmEdit.tsx +10 -6
  18. package/src/features/accounting-fields/components/AccountingValueDropdown/AccountingValueDropdown.tsx +19 -9
  19. package/src/features/accounting-fields/components/AccountingValueForm/AccountingValueForm.tsx +21 -10
  20. package/src/features/accounting-fields/components/CreateAccountingFieldDrawer/CreateAccountingFieldDrawer.tsx +11 -8
  21. package/src/features/accounting-fields/components/CreateAccountingValueDrawer/CreateAccountingValueDrawer.tsx +19 -7
  22. package/src/features/accounting-fields/components/DeleteAccountingFieldDrawer/DeleteAccountingFieldDrawer.tsx +14 -12
  23. package/src/features/accounting-fields/components/DeleteAccountingValueDrawer/DeleteAccountingValueDrawer.tsx +20 -10
  24. package/src/features/accounting-fields/components/EditAccountingFieldDrawer/EditAccountingFieldDrawer.tsx +5 -3
  25. package/src/features/accounting-fields/components/EditAccountingValueDrawer/EditAccountingValueDrawer.tsx +13 -6
  26. package/src/features/accounting-fields/components/RemoveValueFromUnitDrawer/RemoveValueFromUnitDrawer.tsx +19 -8
  27. package/src/features/accounting-fields/hooks/useDetailsAccountingField.ts +5 -4
  28. package/src/features/accounting-fields/hooks/useDetailsAccountingValue.ts +5 -4
  29. package/src/features/accounting-fields/hooks/useListAccountingFields.ts +4 -4
  30. package/src/features/accounting-fields/hooks/useListAccountingValues.ts +5 -3
  31. package/src/features/accounting-fields/layouts/AccountingFieldLayout/AccountingFieldLayout.tsx +12 -9
  32. package/src/features/addresses/components/AddRecipientsDrawer/AddRecipientsDrawer.tsx +13 -7
  33. package/src/features/addresses/components/AddressDropdownMenu/AddressDropdownMenu.tsx +22 -20
  34. package/src/features/addresses/components/AddressForm/AddressForm.tsx +33 -22
  35. package/src/features/addresses/components/AddressLine/AddressLine.tsx +29 -25
  36. package/src/features/addresses/components/AddressLocationsList/AddressLocationsList.tsx +15 -6
  37. package/src/features/addresses/components/AddressRecipientsList/AddressRecipientsList.tsx +9 -4
  38. package/src/features/addresses/components/AddressesCard/AddressesCard.tsx +8 -5
  39. package/src/features/addresses/components/CreateAddressDrawer/CreateAddressDrawer.tsx +27 -14
  40. package/src/features/addresses/components/CreateAddressSettingsDrawer/CreateAddressSettingsDrawer.tsx +26 -21
  41. package/src/features/addresses/components/DeleteAddressDrawer/DeleteAddressDrawer.tsx +20 -16
  42. package/src/features/addresses/components/DeleteAddressLocationDrawer/DeleteAddressLocationDrawer.tsx +26 -14
  43. package/src/features/addresses/components/DeleteRecipientAddressDrawer/DeleteRecipientAddressDrawer.tsx +20 -17
  44. package/src/features/addresses/components/EditAddressDrawer/EditAddressDrawer.tsx +12 -6
  45. package/src/features/addresses/components/EditAddressLocationDrawer/EditAddressLocationDrawer.tsx +24 -17
  46. package/src/features/addresses/components/EditRecipientAddressDrawer/EditRecipientAddressDrawer.tsx +17 -15
  47. package/src/features/addresses/components/LocationForm/LocationField/LocationField.tsx +8 -5
  48. package/src/features/addresses/components/LocationForm/LocationForm.tsx +9 -3
  49. package/src/features/addresses/components/LocationsDrawer/LocationsDrawer.tsx +15 -9
  50. package/src/features/addresses/components/RecipientsForm/RecipientItem/RecipientItem.tsx +55 -46
  51. package/src/features/addresses/components/RecipientsForm/RecipientsForm.tsx +4 -1
  52. package/src/features/addresses/components/RemoveAddressDrawer/RemoveAddressDrawer.tsx +21 -14
  53. package/src/features/addresses/layouts/AddressDetailsLayout/AddressDetailsLayout.tsx +16 -9
  54. package/src/features/addresses/layouts/AddressesLayout/AddressesLayout.tsx +11 -8
  55. package/src/features/b2b-agent/layouts/B2BAgentLayout/B2BAgentLayout.tsx +3 -1
  56. package/src/features/budgets/components/BudgetAddForm/BudgetAddForm.tsx +14 -12
  57. package/src/features/budgets/components/BudgetAddSuccess/BudgetAddSuccess.tsx +16 -14
  58. package/src/features/budgets/components/BudgetAllocationsSelection/BudgetAllocationsSelection.tsx +8 -5
  59. package/src/features/budgets/components/BudgetAllocationsTable/BudgetAllocationsTable.tsx +9 -6
  60. package/src/features/budgets/components/BudgetDeleteDrawer/BudgetDeleteDrawer.tsx +23 -17
  61. package/src/features/budgets/components/BudgetEditNotificationDrawer/BudgetEditNotificationDrawer.tsx +13 -6
  62. package/src/features/budgets/components/BudgetNotificationForm/BudgetNotificationForm.tsx +31 -24
  63. package/src/features/budgets/components/BudgetNotificationsInfo/BudgetNotificationsInfo.tsx +15 -9
  64. package/src/features/budgets/components/BudgetRemainingBalance/BudgetRemainingBalance.tsx +6 -2
  65. package/src/features/budgets/components/BudgetSettingsInfo/BudgetSettingsInfo.tsx +27 -10
  66. package/src/features/budgets/components/BudgetUsersTable/BudgetUsersTable.tsx +14 -7
  67. package/src/features/budgets/components/BudgetsTable/BudgetsTable.tsx +11 -8
  68. package/src/features/budgets/components/CreateBudgetAllocationDrawer/CreateBudgetAllocationDrawer.tsx +9 -6
  69. package/src/features/budgets/components/CreateBudgetDrawer/CreateBudgetDrawer.tsx +32 -19
  70. package/src/features/budgets/components/DeleteBudgetAllocationDrawer/DeleteBudgetAllocationDrawer.tsx +17 -12
  71. package/src/features/budgets/components/EditBudgetDrawer/EditBudgetDrawer.tsx +14 -8
  72. package/src/features/budgets/components/EmptyState.tsx/EmptyState.tsx +4 -1
  73. package/src/features/budgets/hooks/useGetAllocations.ts +1 -0
  74. package/src/features/budgets/layouts/BudgetsDetailsLayout/BudgetsDetailsLayout.tsx +7 -5
  75. package/src/features/budgets/layouts/BudgetsLayout/BudgetsLayout.tsx +5 -3
  76. package/src/features/budgets/messages/budgetNotifications.ts +5 -5
  77. package/src/features/budgets/utils/handleBudgetError.tsx +5 -8
  78. package/src/features/budgets/utils/handleBudgetError.types.ts +3 -0
  79. package/src/features/buying-policies/components/AccountingFieldCriteriaSelector/AccountingFieldCriteriaSelector.tsx +10 -5
  80. package/src/features/buying-policies/components/AccountingFieldCriteriaSelector/AccountingValueCriteriaSelector.tsx +27 -11
  81. package/src/features/buying-policies/components/AddBuyingPolicyDrawer/AddBuyingPolicyDrawer.tsx +8 -5
  82. package/src/features/buying-policies/components/BasicBuyingPolicyDrawer/BasicBuyingPolicyDrawer.tsx +49 -33
  83. package/src/features/buying-policies/components/BudgetCriteriaSelector/BudgetCriteriaSelector.tsx +8 -2
  84. package/src/features/buying-policies/components/BuyingPolicyDropdownMenu/BuyingPolicyDropdownMenu.tsx +5 -3
  85. package/src/features/buying-policies/components/DeleteBuyingPolicyDrawer/DeleteBuyingPolicyDrawer.tsx +28 -24
  86. package/src/features/buying-policies/components/UpdateBuyingPolicyDrawer/UpdateBuyingPolicyDrawer.tsx +10 -7
  87. package/src/features/buying-policies/layouts/BuyingPoliciesLayout/BuyingPoliciesLayout.tsx +11 -4
  88. package/src/features/buying-policies/layouts/BuyingPolicyDetailsLayout/BuyingPolicyDetailsLayout.tsx +28 -9
  89. package/src/features/buying-policies/utils/accountingFieldCriteriaHelpers.ts +2 -2
  90. package/src/features/buying-policies/utils/buyingPoliciesWorkflowTypes.ts +3 -3
  91. package/src/features/buying-policies/utils/orderFieldsCriteriaOptions.ts +8 -8
  92. package/src/features/buying-policies/utils/spendingLimitsCriteriaOptions.ts +6 -6
  93. package/src/features/contracts/components/ContractsCard/ContractsCard.tsx +7 -5
  94. package/src/features/contracts/layouts/ContractListingLayout/ContractListingLayout.tsx +9 -3
  95. package/src/features/contracts/layouts/ContractListingLayout/__tests__/utils.test.ts +40 -0
  96. package/src/features/contracts/layouts/ContractListingLayout/utils.ts +13 -0
  97. package/src/features/contracts/layouts/ContractsLayout/ContractsLayout.tsx +9 -4
  98. package/src/features/credit-cards/components/CreateCreditCardDrawer/CreateCreditCardDrawer.tsx +17 -11
  99. package/src/features/credit-cards/components/CreditCardDropdownMenu/CreditCardDropdownMenu.tsx +17 -11
  100. package/src/features/credit-cards/components/CreditCardForm/CreditCardForm.tsx +8 -5
  101. package/src/features/credit-cards/components/CreditCardSettingsDrawer/CreditCardSettingsDrawer.tsx +10 -8
  102. package/src/features/credit-cards/components/DeleteCreditCardDrawer/DeleteCreditCardDrawer.tsx +19 -14
  103. package/src/features/credit-cards/components/EditCreditCardDrawer/EditCreditCardDrawer.tsx +14 -13
  104. package/src/features/credit-cards/components/RemoveCreditCardDrawer/RemoveCreditCardDrawer.tsx +18 -15
  105. package/src/features/credit-cards/layouts/CreditCardsLayout/CreditCardLayout.tsx +10 -4
  106. package/src/features/order-entry/layouts/OrderEntryLayout.tsx +3 -1
  107. package/src/features/org-units/components/AddAllToOrgUnitDropdown/AddAllToOrgUnitDropdown.tsx +11 -9
  108. package/src/features/org-units/components/AuthSetupDrawer/AuthSetupDrawer.tsx +28 -16
  109. package/src/features/org-units/components/CreateOrgUnitDrawer/CreateOrgUnitDrawer.tsx +27 -13
  110. package/src/features/org-units/components/DeleteOrgUnitDrawer/DeleteOrgUnitDrawer.tsx +36 -30
  111. package/src/features/org-units/components/OrgUnitBreadcrumb/OrgUnitBreadcrumb.tsx +13 -3
  112. package/src/features/org-units/components/OrgUnitBreadcrumb/OrgUnitBreadcrumbLink.tsx +4 -4
  113. package/src/features/org-units/components/OrgUnitDetailsDropdownMenu/OrgUnitDetailsDropdownMenu.tsx +4 -2
  114. package/src/features/org-units/components/OrgUnitDetailsNavbar/OrgUnitDetailsNavbar.tsx +23 -5
  115. package/src/features/org-units/components/OrgUnitDetailsNavbar/org-unit-details-navbar.scss +18 -2
  116. package/src/features/org-units/components/OrgUnitsDropdownMenu/OrgUnitsDropdownMenu.tsx +6 -4
  117. package/src/features/org-units/components/OrgUnitsHierarchyTree/OrgUnitsHierarchyTree.tsx +19 -3
  118. package/src/features/org-units/components/OrganizationalUnitsCard/OrganizationalUnitsCard.tsx +15 -7
  119. package/src/features/org-units/components/UpdateOrgUnitDrawer/UpdateOrgUnitDrawer.tsx +24 -10
  120. package/src/features/org-units/layouts/OrgUnitDetailsLayout/OrgUnitDetailsLayout.tsx +31 -27
  121. package/src/features/org-units/layouts/OrgUnitsLayout/OrgUnitsLayout.tsx +8 -3
  122. package/src/features/payment-methods/components/AddPaymentMethodsDrawer/AddPaymentMethodsDrawer.tsx +29 -18
  123. package/src/features/payment-methods/components/PaymentMethodSettingsDrawer/PaymentMethodSettingsDrawer.tsx +13 -9
  124. package/src/features/payment-methods/components/RemoveMethodButton/RemoveMethodButton.tsx +5 -2
  125. package/src/features/payment-methods/components/RemovePaymentMethodsDrawer/RemovePaymentMethodsDrawer.tsx +10 -7
  126. package/src/features/payment-methods/hooks/useRemovePaymentMethodSubmit.ts +6 -4
  127. package/src/features/payment-methods/layouts/PaymentMethodsLayout/PaymentMethodsLayout.tsx +21 -15
  128. package/src/features/product-assortment/components/ProductAssortmentTable/ProductAssortmentTable.tsx +11 -7
  129. package/src/features/product-assortment/layouts/ProductAssortmentLayout/ProductAssortmentLayout.tsx +9 -6
  130. package/src/features/profile/components/ProfileCard/ProfileCard.tsx +8 -5
  131. package/src/features/profile/components/ProfileSummary/ProfileSummary.tsx +13 -6
  132. package/src/features/profile/layouts/ProfileLayout/ProfileLayout.tsx +16 -6
  133. package/src/features/roles/layout/RoleDetailsLayout/RoleDetailsLayout.tsx +4 -2
  134. package/src/features/roles/layout/RolesLayout/RolesLayout.tsx +5 -3
  135. package/src/features/shared/components/BasicCard/BasicCard.tsx +6 -4
  136. package/src/features/shared/components/BasicCard/BasicCardLine.tsx +5 -4
  137. package/src/features/shared/components/CustomerSwitch/CustomerSwitchDrawer.tsx +10 -2
  138. package/src/features/shared/components/CustomerSwitch/CustomerSwitchSearch.tsx +4 -1
  139. package/src/features/shared/components/DropdownFilter/DropdownFilter.tsx +11 -7
  140. package/src/features/shared/components/Error/Error.tsx +13 -6
  141. package/src/features/shared/components/HeaderInside/HeaderInside.tsx +5 -2
  142. package/src/features/shared/components/InternalSearch/InternalSearch.tsx +4 -1
  143. package/src/features/shared/components/InternalTopbar/InternalTopbar.tsx +7 -5
  144. package/src/features/shared/components/MainLinksDropdownMenu/MainLinksDropdownMenu.tsx +23 -8
  145. package/src/features/shared/components/OrgUnitInputSearch/OrgUnitInputSearch.tsx +7 -2
  146. package/src/features/shared/components/Pagination/Pagination.tsx +6 -2
  147. package/src/features/shared/components/Paginator/Counter.tsx +11 -5
  148. package/src/features/shared/components/Paginator/Interval.tsx +4 -3
  149. package/src/features/shared/components/Paginator/NextPageButton.tsx +6 -2
  150. package/src/features/shared/components/SettingsDrawer/SettingsDrawer.tsx +6 -4
  151. package/src/features/shared/components/SettingsDrawer/SettingsDrawerListType.tsx +11 -10
  152. package/src/features/shared/components/SettingsDrawer/listTypeOptions.ts +10 -7
  153. package/src/features/shared/components/SortFilter/SortFilter.tsx +15 -8
  154. package/src/features/shared/components/Table/TableRow/TableRow.tsx +5 -1
  155. package/src/features/shared/components/VerticalNav/VerticalNavLink.tsx +7 -3
  156. package/src/features/shared/layouts/BaseTabsLayout/Navbar.tsx +28 -4
  157. package/src/features/shared/layouts/BaseTabsLayout/about-drawer/AboutDrawer.tsx +15 -5
  158. package/src/features/shared/layouts/BaseTabsLayout/base-tabs-layout.scss +24 -9
  159. package/src/features/shared/layouts/BaseTabsLayout/sidebar-drawer/SidebarDrawer.tsx +5 -2
  160. package/src/features/shared/layouts/ContractTabsLayout/ContractTabsLayout.tsx +10 -5
  161. package/src/features/shared/layouts/ErrorTabsLayout/ErrorTabsLayout.tsx +7 -4
  162. package/src/features/shared/layouts/FinanceTabsLayout/FinanceTabsLayout.tsx +9 -4
  163. package/src/features/shared/layouts/LoadingTabsLayout/LoadingTabsLayout.tsx +7 -4
  164. package/src/features/shared/layouts/OrgUnitTabsLayout/OrgUnitTabLayout.tsx +3 -0
  165. package/src/features/shared/layouts/SumaPageLayout/FullSidebarNav.tsx +14 -8
  166. package/src/features/shared/layouts/SumaPageLayout/SumaSidebar.tsx +9 -5
  167. package/src/features/shared/localization/LocalizationContext.tsx +35 -0
  168. package/src/features/shared/localization/__tests__/createLocalize.test.ts +166 -0
  169. package/src/features/shared/localization/__tests__/get-localization-labels.service.test.ts +202 -0
  170. package/src/features/shared/localization/clients/LocalizationClient.ts +19 -0
  171. package/src/features/shared/localization/constants.ts +31 -0
  172. package/src/features/shared/localization/createLocalize.ts +78 -0
  173. package/src/features/shared/localization/services/get-localization-labels.service.ts +76 -0
  174. package/src/features/shared/localization/types.ts +66 -0
  175. package/src/features/shared/localization/withLocalization.tsx +22 -0
  176. package/src/features/shared/types/AuthRouteProps.ts +5 -0
  177. package/src/features/shared/types/LoaderData.ts +1 -0
  178. package/src/features/shared/utils/addresLabelToPropMapping.ts +10 -10
  179. package/src/features/shared/utils/buyerPortalRoutes.ts +14 -4
  180. package/src/features/shared/utils/constants.ts +1 -4
  181. package/src/features/shared/utils/getContractSettingsLinks.ts +17 -12
  182. package/src/features/shared/utils/getFinanceSettingsLinks.ts +14 -9
  183. package/src/features/shared/utils/getKeyByValue.ts +2 -2
  184. package/src/features/shared/utils/getOrganizationSettingsLinks.ts +13 -3
  185. package/src/features/shared/utils/getSumaListingSidebarLinks.ts +15 -10
  186. package/src/features/shared/utils/index.ts +2 -0
  187. package/src/features/shared/utils/routeLayoutMapping.ts +30 -30
  188. package/src/features/shared/utils/search.tsx +10 -0
  189. package/src/features/shared/utils/withAuth.tsx +6 -0
  190. package/src/features/shared/utils/withAuthLoader.ts +11 -0
  191. package/src/features/shared/utils/withLoaderErrorBoundary.ts +9 -0
  192. package/src/features/shared/utils/withProviders.tsx +13 -2
  193. package/src/features/users/components/CreateUserDrawer/CreateUserDrawer.tsx +39 -27
  194. package/src/features/users/components/CreateUserDrawerWithUsername/CreateUserDrawerWithUsername.tsx +41 -39
  195. package/src/features/users/components/DeleteUserDrawer/DeleteUserDrawer.tsx +27 -23
  196. package/src/features/users/components/EmailTransactionalSection/EmailTransactionalSection.tsx +6 -8
  197. package/src/features/users/components/LoginField/LoginField.tsx +4 -4
  198. package/src/features/users/components/ReassignOrgUnitDrawer/ReassignOrgUnitDrawer.tsx +22 -19
  199. package/src/features/users/components/ResetPasswordDrawer/ResetPasswordDrawer.tsx +16 -21
  200. package/src/features/users/components/UpdateUserDrawer/UpdateUserDrawer.tsx +26 -15
  201. package/src/features/users/components/UpdateUserDrawerWithUsername/UpdateUserDrawerWithUsername.tsx +22 -8
  202. package/src/features/users/components/UserDropdownMenu/UserDropdownMenu.tsx +7 -5
  203. package/src/features/users/components/UserFormFields/UserFormFields.tsx +9 -8
  204. package/src/features/users/components/UsersCard/UsersCard.tsx +4 -2
  205. package/src/features/users/layouts/UserDetailsLayout/UserDetailsLayout.tsx +26 -10
  206. package/src/features/users/layouts/UsersLayout/UsersLayout.tsx +12 -8
  207. package/src/pages/accounting-field.tsx +28 -15
  208. package/src/pages/address-details.tsx +37 -26
  209. package/src/pages/addresses.tsx +27 -12
  210. package/src/pages/budgets-details.tsx +25 -15
  211. package/src/pages/budgets.tsx +31 -17
  212. package/src/pages/buyer-organization-manager.tsx +23 -12
  213. package/src/pages/buying-policies.tsx +38 -20
  214. package/src/pages/buying-policy-details.tsx +20 -19
  215. package/src/pages/contracts.tsx +15 -1
  216. package/src/pages/credit-cards.tsx +30 -17
  217. package/src/pages/order-entry.tsx +26 -15
  218. package/src/pages/org-unit-details.tsx +23 -8
  219. package/src/pages/org-units.tsx +20 -6
  220. package/src/pages/payment-groups.tsx +15 -12
  221. package/src/pages/productAssortment.tsx +10 -1
  222. package/src/pages/profile.tsx +14 -1
  223. package/src/pages/role-details.tsx +19 -6
  224. package/src/pages/roles.tsx +27 -14
  225. package/src/pages/user-details.tsx +29 -17
  226. package/src/pages/users.tsx +11 -0
  227. package/vitest.config.ts +13 -0
  228. package/src/features/addresses/constants/messages.ts +0 -8
  229. package/src/features/credit-cards/constants/messages.ts +0 -6
package/CHANGELOG.md CHANGED
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.0.14] - 2026-07-21
11
+
12
+ ### Added
13
+
14
+ - Custom dependency-free localization system: `LocalizationClient`, `getLocalizationLabelsService` (multi-entry merge, permanent process-local cache, single-flight dedup, graceful error fallback), `createLocalize` pure factory, `LocalizationProvider`, `useLocalization()` hook, and `withLocalization` HOC
15
+ - `LocalizeFn` overloaded to support `ReactNode[]` interpolation, returning alternating text/node arrays for inline JSX embedding
16
+ - `LOCALIZATION_ENTRY_IDS` constant map with real content-platform UUIDs for 13 feature domains
17
+ - `withLocalization` wired into `withProviders` before `withBuyerPortal` so labels are available across all pages
18
+ - CMS content-type schema for localization entries (`cms/b2b/pages/cms_content_type__i18n.jsonc`)
19
+ - Unit tests for `createLocalize` and `getLocalizationLabelsService` covering all spec acceptance criteria
20
+ - Vitest config with `discovery.config` stub alias for host-store-provided modules
21
+
22
+ ### Fixed
23
+
24
+ - `Navbar`'s "Start shopping" link used a hardcoded `href="/"`, dropping the selected locale; it now resolves through `useLink_unstable`'s `resolveLink()`, consistent with every other internal link in the portal
25
+
26
+ ## [2.0.13] - 2026-07-21
27
+
28
+ ### Fixed
29
+
30
+ - Navigating between organizational units (hierarchy tree, breadcrumbs, and the "View" links after creating/editing/deleting a unit) now carries the currently viewed contract's id through, instead of dropping it — this previously caused the `suma`-flag redirect on `/org-unit/[orgUnitId]` to land on the target org unit's default contract instead of the one being viewed. The Contract Listing page now also honors a `contractId` carried in the URL when it belongs to the org unit's attached contracts, falling back to the org unit's default contract otherwise
31
+ - Accounting Fields data (sidebar list, Buying Policy criteria selectors, the Accounting Field details page, and Budget allocation drawers) was fetched using the session's active contract instead of the contract actually being viewed — noticeable when navigating straight to a page for a non-active contract (e.g. from Contract Listing) without first "switching" to it. All accounting-fields read hooks now take an explicit `contractId` instead of silently substituting the session's customerId
32
+
10
33
  ## [2.0.12] - 2026-07-17
11
34
 
12
35
  ### Fixed
@@ -763,7 +786,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
763
786
  - Add CHANGELOG file
764
787
  - Add README file
765
788
 
766
- [unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v2.0.12...HEAD
789
+ [unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v2.0.14...HEAD
767
790
  [1.3.55]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.54...v1.3.55
768
791
  [1.3.54]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.53...v1.3.54
769
792
  [1.3.53]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.52...v1.3.53
@@ -861,5 +884,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
861
884
  [2.0.10]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v2.0.9...v2.0.10
862
885
  [2.0.9]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/2.0.9
863
886
 
887
+ [2.0.14]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v2.0.13...v2.0.14
888
+ [2.0.13]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v2.0.12...v2.0.13
864
889
  [2.0.12]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v2.0.11...v2.0.12
865
890
  [2.0.11]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/2.0.11
File without changes
@@ -0,0 +1,36 @@
1
+ {
2
+ "b2bLocalization": {
3
+ "$extends": [
4
+ "#/components/base-page-template"
5
+ ],
6
+ "$singleton": false,
7
+ "type": "object",
8
+ "title": "B2B Localization",
9
+ "properties": {
10
+ "labels": {
11
+ "title": "Labels",
12
+ "description": "Labels for B2B Localization",
13
+ "type": "array",
14
+ "items": {
15
+ "type": "object",
16
+ "title": "Label",
17
+ "properties": {
18
+ "key": {
19
+ "type": "string",
20
+ "title": "Key"
21
+ },
22
+ "value": {
23
+ "type": "string",
24
+ "title": "Value"
25
+ }
26
+ }
27
+ }
28
+ },
29
+ },
30
+ "readOnly": false,
31
+ "writeOnly": false,
32
+ "deprecated": false,
33
+ "$abstract": false,
34
+ "identifierKeys": []
35
+ }
36
+ }
@@ -0,0 +1,482 @@
1
+ import { TEST_CONFIG, TEST_DATA } from "../constants";
2
+ import { useInternalSearch } from "../shared";
3
+
4
+ const LOCALE_UNDER_TEST = "pt-BR";
5
+
6
+ // Matches CMS-key-like tokens (e.g. "users.delete.messages.confirmDeleteUser",
7
+ // "layouts.labels.language") that should never leak into visible UI text — `t()`
8
+ // only falls back to the raw key when a translation is missing.
9
+ const RAW_CMS_KEY_PATTERN =
10
+ /\b[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z][a-zA-Z0-9]*){2,}\b/;
11
+
12
+ // Matches an unreplaced positional placeholder left over from a broken `t()` call.
13
+ const RAW_PLACEHOLDER_PATTERN = /\{\d+\}/;
14
+
15
+ function assertNoRawCmsKeysOrPlaceholders(selector: string) {
16
+ cy.get(selector)
17
+ .invoke("text")
18
+ .then((text) => {
19
+ const keyMatch = text.match(RAW_CMS_KEY_PATTERN);
20
+ assert.isNull(
21
+ keyMatch,
22
+ `found a raw CMS key in "${selector}": "${keyMatch?.[0]}"`
23
+ );
24
+
25
+ const placeholderMatch = text.match(RAW_PLACEHOLDER_PATTERN);
26
+ assert.isNull(
27
+ placeholderMatch,
28
+ `found an unreplaced interpolation placeholder in "${selector}": "${placeholderMatch?.[0]}"`
29
+ );
30
+ });
31
+ }
32
+
33
+ // Switching locale mid-session (outside the selector's own Save+redirect flow, which
34
+ // lands on a fresh origin/session for the target binding) can leave a stale cart/session
35
+ // cookie bound to the previous channel — this was found to cause both an intermittent
36
+ // client-side validation error AND, non-deterministically, `resolveLink` rendering hrefs
37
+ // for the wrong (previous) locale. Visiting with only the auth cookie reflects a clean
38
+ // session, same as a genuinely fresh visit under the target locale, and avoids both.
39
+ function visitFreshUnderLocale(path: string) {
40
+ return cy.getCookie(TEST_CONFIG.AUTH.COOKIE_NAME).then((authCookie) => {
41
+ cy.clearCookies();
42
+ if (authCookie) {
43
+ cy.setCookie(TEST_CONFIG.AUTH.COOKIE_NAME, authCookie.value);
44
+ }
45
+ cy.visit(`/${LOCALE_UNDER_TEST}${path}`, { failOnStatusCode: false });
46
+ });
47
+ }
48
+
49
+ // Multi-locale delivery (B2BTEAM-3709) is rolled out per host-store environment via
50
+ // discovery.config's `localization.enabled` + `localization.locales`. When the target
51
+ // environment hasn't enabled it yet, visiting a locale-prefixed path won't resolve to
52
+ // the Buyer Portal. We detect that up front and skip instead of failing, so this spec
53
+ // stays green until the feature is live, then starts enforcing automatically.
54
+ function isLocalizationEnabledInThisEnvironment() {
55
+ return visitFreshUnderLocale(TEST_CONFIG.ROUTES.BUYER_PORTAL)
56
+ .get("body")
57
+ .then(($body) => $body.find("[data-fs-vertical-nav-menu-item]").length > 0);
58
+ }
59
+
60
+ // Client-side (SPA) navigation between tabs can land on a stale locale — that's a
61
+ // separate, `@faststore/core`-level concern from what this plugin's Navbar controls.
62
+ // To assert exactly what `resolveLink("/")` renders for a given page, we re-request the
63
+ // landed-on page directly, under the locale prefix, and check the *server-rendered* href.
64
+ function reloadUnderLocale() {
65
+ return cy.location("pathname").then((pathname) => {
66
+ const pagePath = pathname.replace(new RegExp(`^/${LOCALE_UNDER_TEST}`), "");
67
+ visitFreshUnderLocale(pagePath);
68
+ });
69
+ }
70
+
71
+ describe("Multi-locale (i18n)", { retries: 2 }, () => {
72
+ beforeEach(() => {
73
+ cy.login();
74
+ });
75
+
76
+ it("keeps the locale prefix on the main Navbar 'Start shopping' link and breadcrumb links", () => {
77
+ isLocalizationEnabledInThisEnvironment().then((enabled) => {
78
+ if (!enabled) {
79
+ cy.log(
80
+ `Localization not enabled for locale "${LOCALE_UNDER_TEST}" in this environment — skipping.`
81
+ );
82
+ return;
83
+ }
84
+
85
+ // Navigate to a tabbed page (renders the main Navbar).
86
+ cy.contains("Profile").click();
87
+ reloadUnderLocale();
88
+
89
+ cy.get("[data-fs-bp-nav-menu-start-shopping-link]")
90
+ .should("have.attr", "href")
91
+ .and("match", new RegExp(`^/${LOCALE_UNDER_TEST}(/|$)`));
92
+
93
+ // Breadcrumb only renders a clickable (non-last) link when the current org unit
94
+ // has an ancestor — guard defensively since that depends on seed-data hierarchy.
95
+ cy.get("body").then(($body) => {
96
+ const breadcrumbLinks = $body.find(
97
+ "[data-fs-bp-breadcrumb-link] a[data-fs-bp-breadcrumb-text]"
98
+ );
99
+ if (breadcrumbLinks.length === 0) {
100
+ cy.log("No clickable breadcrumb link for this org unit — skipping.");
101
+ return;
102
+ }
103
+ cy.wrap(breadcrumbLinks.first())
104
+ .should("have.attr", "href")
105
+ .and("match", new RegExp(`^/${LOCALE_UNDER_TEST}(/|$)`));
106
+ });
107
+ });
108
+ });
109
+
110
+ it("keeps the locale prefix on the Org Unit Details navbar 'Start shopping' link and the hierarchy tree links, consistently with the main Navbar", () => {
111
+ isLocalizationEnabledInThisEnvironment().then((enabled) => {
112
+ if (!enabled) {
113
+ cy.log(
114
+ `Localization not enabled for locale "${LOCALE_UNDER_TEST}" in this environment — skipping.`
115
+ );
116
+ return;
117
+ }
118
+
119
+ cy.contains("Organizational Units").click();
120
+
121
+ cy.get("[data-fs-hierarchy-tree-wrapper]", {
122
+ timeout: TEST_CONFIG.TIMEOUTS.LONG_DELAY,
123
+ }).should("exist");
124
+
125
+ useInternalSearch(TEST_DATA.ORG_UNITS.CHILD_1);
126
+
127
+ // The hierarchy tree's search results are fetched and rendered client-side, which
128
+ // can hit the already-flagged SPA/client-render locale-sync finding (separate from
129
+ // this plugin's `resolveLink` usage, itself proven correct via SSR). Log instead of
130
+ // hard-failing on this specific link so it doesn't mask the click-through below.
131
+ cy.get("[data-fs-org-unit-item-name]")
132
+ .contains(TEST_DATA.ORG_UNITS.CHILD_1)
133
+ .parents("[data-fs-org-unit-item]")
134
+ .find("[data-fs-org-unit-item-click-area]")
135
+ .invoke("attr", "href")
136
+ .then((href) => {
137
+ if (!href?.startsWith(`/${LOCALE_UNDER_TEST}`)) {
138
+ cy.log(
139
+ `Hierarchy tree link "${href}" lost the locale prefix (known client-render finding) — continuing.`
140
+ );
141
+ }
142
+ });
143
+
144
+ cy.get("[data-fs-org-unit-item-name]")
145
+ .contains(TEST_DATA.ORG_UNITS.CHILD_1)
146
+ .parents("[data-fs-org-unit-item]")
147
+ .find("[data-fs-org-unit-item-click-area]")
148
+ .click();
149
+
150
+ cy.get("[data-fs-bp-org-unit-details-navbar]", {
151
+ timeout: TEST_CONFIG.TIMEOUTS.PAGE_LOAD,
152
+ }).should("be.visible");
153
+
154
+ reloadUnderLocale();
155
+
156
+ cy.get("[data-fs-bp-org-unit-details-navbar-start-shopping-link]")
157
+ .should("have.attr", "href")
158
+ .and("match", new RegExp(`^/${LOCALE_UNDER_TEST}(/|$)`));
159
+ });
160
+ });
161
+
162
+ it("shows the locale selector (globe) in both the main Navbar and the Org Unit Details navbar, with non-empty labels and no crash on missing CMS keys", () => {
163
+ isLocalizationEnabledInThisEnvironment().then((enabled) => {
164
+ if (!enabled) {
165
+ cy.log(
166
+ `Localization not enabled for locale "${LOCALE_UNDER_TEST}" in this environment — skipping.`
167
+ );
168
+ return;
169
+ }
170
+
171
+ cy.contains("Profile").click();
172
+
173
+ cy.get("[data-fs-localization-button]").should("be.visible").click();
174
+
175
+ // Whether or not the `shared` CMS entry already has these keys populated, `t()`
176
+ // must never crash and must never render blank text (fallback = key itself).
177
+ cy.get("[data-fs-localization-selector-content]")
178
+ .should("be.visible")
179
+ .invoke("text")
180
+ .should("not.be.empty");
181
+ assertNoRawCmsKeysOrPlaceholders(
182
+ "[data-fs-localization-selector-content]"
183
+ );
184
+
185
+ // No blank page / crash after opening the selector.
186
+ cy.get("[data-fs-bp-nav]").should("be.visible");
187
+
188
+ // Close the selector (its overlay can otherwise block later clicks) and start
189
+ // from a fresh home visit — top-level nav links are only reachable from the
190
+ // home page's sidebar, not from within an already-open tabbed page.
191
+ cy.get("body").type("{esc}");
192
+ cy.get("[data-fs-overlay]", {
193
+ timeout: TEST_CONFIG.TIMEOUTS.PAGE_LOAD,
194
+ }).should("not.exist");
195
+
196
+ // Same check on the Org Unit Details navbar.
197
+ visitFreshUnderLocale(TEST_CONFIG.ROUTES.BUYER_PORTAL);
198
+ cy.contains("Organizational Units").click();
199
+ cy.get("[data-fs-hierarchy-tree-wrapper]", {
200
+ timeout: TEST_CONFIG.TIMEOUTS.LONG_DELAY,
201
+ }).should("exist");
202
+ useInternalSearch(TEST_DATA.ORG_UNITS.CHILD_1);
203
+ cy.get("[data-fs-org-unit-item-name]")
204
+ .contains(TEST_DATA.ORG_UNITS.CHILD_1)
205
+ .parents("[data-fs-org-unit-item]")
206
+ .find("[data-fs-org-unit-item-click-area]")
207
+ .click();
208
+ cy.get("[data-fs-bp-org-unit-details-navbar]", {
209
+ timeout: TEST_CONFIG.TIMEOUTS.PAGE_LOAD,
210
+ }).should("be.visible");
211
+ cy.get(
212
+ "[data-fs-bp-org-unit-details-navbar] [data-fs-localization-button]"
213
+ ).should("be.visible");
214
+ });
215
+ });
216
+
217
+ it("persists the selected locale across a browser reload", () => {
218
+ // KNOWN BUG (found while writing this test, confirmed via a plain `curl`, no
219
+ // client JS involved): `GET /pt-BR/pvt/organization-account` returns a 302 whose
220
+ // `Location` header points to `/pvt/organization-account/org-unit/{id}` WITHOUT
221
+ // the `/pt-BR` prefix, so the redirect target renders `lang="en-US"`. This is a
222
+ // server-side redirect issue (unrelated to `resolveLink`/client navigation), and
223
+ // is expected to keep this assertion red until fixed.
224
+ isLocalizationEnabledInThisEnvironment().then((enabled) => {
225
+ if (!enabled) {
226
+ cy.log(
227
+ `Localization not enabled for locale "${LOCALE_UNDER_TEST}" in this environment — skipping.`
228
+ );
229
+ return;
230
+ }
231
+
232
+ cy.get("html").should("have.attr", "lang", LOCALE_UNDER_TEST);
233
+
234
+ // A plain reload of the same URL must keep reflecting the locale.
235
+ cy.reload();
236
+
237
+ cy.get("html").should("have.attr", "lang", LOCALE_UNDER_TEST);
238
+ });
239
+ });
240
+
241
+ it("does not break the Navbar layout on mobile/tablet viewports", () => {
242
+ isLocalizationEnabledInThisEnvironment().then((enabled) => {
243
+ if (!enabled) {
244
+ cy.log(
245
+ `Localization not enabled for locale "${LOCALE_UNDER_TEST}" in this environment — skipping.`
246
+ );
247
+ return;
248
+ }
249
+
250
+ cy.contains("Profile").click();
251
+
252
+ (
253
+ [
254
+ [375, 667],
255
+ [768, 1024],
256
+ ] as const
257
+ ).forEach(([width, height]) => {
258
+ cy.viewport(width, height);
259
+ cy.get("[data-fs-localization-button]").should("be.visible");
260
+ cy.get("[data-fs-bp-nav]").then(($nav) => {
261
+ expect($nav[0].scrollWidth).to.be.at.most(
262
+ $nav[0].parentElement?.clientWidth
263
+ ? $nav[0].parentElement.clientWidth + 1
264
+ : width + 1
265
+ );
266
+ });
267
+ });
268
+
269
+ // Reset to the default viewport so later tests in this spec aren't affected.
270
+ cy.viewport(
271
+ Cypress.config("viewportWidth"),
272
+ Cypress.config("viewportHeight")
273
+ );
274
+ });
275
+ });
276
+
277
+ it("does not fire localization requests from the client — SSR/loader only", () => {
278
+ cy.intercept("GET", "**/api/content-platform/data/vtex/b2b/**").as(
279
+ "localizationRequest"
280
+ );
281
+
282
+ cy.visit(TEST_CONFIG.ROUTES.BUYER_PORTAL);
283
+ cy.get("[data-fs-vertical-nav-menu-item]").should("exist");
284
+
285
+ cy.contains("Profile").click();
286
+ cy.get("[data-fs-bp-profile]").should("exist");
287
+
288
+ // Top-level nav links are only reachable from the home page's sidebar, not from
289
+ // within an already-open tabbed page — go back to home for the second page.
290
+ cy.visit(TEST_CONFIG.ROUTES.BUYER_PORTAL);
291
+ cy.contains("Organizational Units").click();
292
+ cy.get("[data-fs-bp-nav]").should("be.visible");
293
+
294
+ cy.get("@localizationRequest.all").should("have.length", 0);
295
+ });
296
+
297
+ it("shows translated error text, not a raw CMS key, when a client-side error occurs", () => {
298
+ // Deliberately reproduces the client-side error found while validating this ticket
299
+ // (visiting a locale-prefixed page with a stale, different-channel session cookie)
300
+ // to confirm the Error component's fallback text is translated, per the checklist's
301
+ // "loader/runtime error shows translated text" requirement.
302
+ isLocalizationEnabledInThisEnvironment().then((enabled) => {
303
+ if (!enabled) {
304
+ cy.log(
305
+ `Localization not enabled for locale "${LOCALE_UNDER_TEST}" in this environment — skipping.`
306
+ );
307
+ return;
308
+ }
309
+
310
+ cy.get("[data-fs-vertical-nav-menu-item]")
311
+ .contains("Assortments")
312
+ .click({ timeout: TEST_CONFIG.TIMEOUTS.PAGE_LOAD });
313
+
314
+ cy.get("body").then(($body) => {
315
+ if ($body.find("[data-fs-bp-header-inside-title]").length === 0) {
316
+ cy.log(
317
+ "Did not land on the Assortments page this run (known client-side SPA-navigation finding) — skipping."
318
+ );
319
+ return;
320
+ }
321
+
322
+ cy.location("pathname").then((path) => {
323
+ // Re-visit the SAME path (default locale, same channel) WITHOUT clearing
324
+ // cookies twice in a row from two different locale contexts, to reliably land
325
+ // the client-side session/channel conflict on the Assortments page.
326
+ cy.visit(`/${LOCALE_UNDER_TEST}${path}`, { failOnStatusCode: false });
327
+ cy.visit(path, { failOnStatusCode: false });
328
+ cy.visit(`/${LOCALE_UNDER_TEST}${path}`, { failOnStatusCode: false });
329
+
330
+ cy.get("body").then(($body2) => {
331
+ if ($body2.find("[data-fs-bp-error]").length === 0) {
332
+ cy.log(
333
+ "Client-side error did not reproduce this run (known intermittent finding) — skipping."
334
+ );
335
+ return;
336
+ }
337
+ cy.get("[data-fs-bp-error-title]")
338
+ .should("be.visible")
339
+ .invoke("text")
340
+ .should("not.be.empty");
341
+ assertNoRawCmsKeysOrPlaceholders("[data-fs-bp-error]");
342
+ });
343
+ });
344
+ });
345
+ });
346
+ });
347
+
348
+ it("renders pagination counters with real numbers, not raw interpolation placeholders", () => {
349
+ cy.visit(TEST_CONFIG.ROUTES.BUYER_PORTAL);
350
+ cy.contains("Organizational Units").click();
351
+ cy.get("[data-fs-hierarchy-tree-wrapper]", {
352
+ timeout: TEST_CONFIG.TIMEOUTS.LONG_DELAY,
353
+ }).should("exist");
354
+
355
+ cy.get("body").then(($body) => {
356
+ const counter = $body.find("[data-fs-bp-paginator-counter]");
357
+ if (counter.length === 0) {
358
+ cy.log("No paginator rendered on this page — skipping.");
359
+ return;
360
+ }
361
+ cy.wrap(counter.first())
362
+ .invoke("text")
363
+ .should("match", /\d/)
364
+ .and("not.match", RAW_PLACEHOLDER_PATTERN);
365
+ });
366
+ });
367
+
368
+ it("does not leak raw CMS keys in the sidebar, About drawer, or locale selector", () => {
369
+ isLocalizationEnabledInThisEnvironment().then((enabled) => {
370
+ if (!enabled) {
371
+ cy.log(
372
+ `Localization not enabled for locale "${LOCALE_UNDER_TEST}" in this environment — skipping.`
373
+ );
374
+ return;
375
+ }
376
+
377
+ cy.contains("Profile").click();
378
+
379
+ cy.get("[data-fs-bp-sidebar-drawer-btn]").click();
380
+ cy.get("body").then(($body) => {
381
+ const sidebar = $body.find(
382
+ "[data-fs-bp-org-unit-details-navbar-link], [data-fs-org-units-hierarchy], nav [data-fs-vertical-nav]"
383
+ );
384
+ if (sidebar.length > 0) {
385
+ assertNoRawCmsKeysOrPlaceholders(
386
+ "[data-fs-bp-org-unit-details-navbar-link], [data-fs-org-units-hierarchy], nav [data-fs-vertical-nav]"
387
+ );
388
+ }
389
+ });
390
+ // Close the sidebar drawer before continuing (best-effort; ignore if already closed).
391
+ cy.get("body").type("{esc}");
392
+
393
+ cy.get("[data-fs-bp-nav-about-trigger]").click();
394
+ cy.get("[data-fs-bp-about-information]", {
395
+ timeout: TEST_CONFIG.TIMEOUTS.PAGE_LOAD,
396
+ }).should("be.visible");
397
+ assertNoRawCmsKeysOrPlaceholders("[data-fs-bp-about-information]");
398
+ });
399
+ });
400
+
401
+ it("interpolates the actual user name into a real link (not a raw placeholder) in the Delete User confirmation message", () => {
402
+ cy.visit(TEST_CONFIG.ROUTES.BUYER_PORTAL);
403
+ cy.contains("Users").click();
404
+ cy.get("[data-fs-users-section]", {
405
+ timeout: TEST_CONFIG.TIMEOUTS.LONG_DELAY,
406
+ }).should("exist");
407
+
408
+ // Use whichever user happens to be first in the list — this test only exercises
409
+ // the confirmation drawer's interpolation and cancels out, it never deletes anyone,
410
+ // so it doesn't depend on any specific seeded user or table column order.
411
+ cy.get("[data-fs-bp-table-row]")
412
+ .first()
413
+ .within(() => {
414
+ cy.get("[data-fs-bp-basic-dropdown-menu-trigger]").click();
415
+ });
416
+ cy.contains("Delete").click();
417
+
418
+ cy.get("[data-fs-bp-delete-user-drawer]").should("be.visible");
419
+ cy.get("[data-fs-bp-delete-user-drawer-user-name]")
420
+ .should("be.visible")
421
+ .and("have.attr", "href")
422
+ .and("not.be.empty");
423
+ cy.get("[data-fs-bp-delete-user-drawer-user-name]")
424
+ .invoke("text")
425
+ .should("not.be.empty");
426
+ assertNoRawCmsKeysOrPlaceholders("[data-fs-bp-delete-user-drawer-body]");
427
+
428
+ // Cancel — this test only validates the interpolation, it must not delete the user.
429
+ cy.get("[data-fs-bp-delete-user-drawer]")
430
+ .contains("button", "Cancel")
431
+ .click();
432
+ cy.get("[data-fs-bp-delete-user-drawer]").should("not.exist");
433
+ });
434
+
435
+ it("renders actual CMS-translated content, not just a preserved URL prefix (Assortments page title)", () => {
436
+ // The Assortments page is the one page with real pt-BR/en-US CMS entries today
437
+ // (per B2BTEAM-3709), so it's the only reliable place to assert that *content*,
438
+ // not just routing, changes with the locale.
439
+ cy.visit(TEST_CONFIG.ROUTES.BUYER_PORTAL);
440
+ cy.get("[data-fs-vertical-nav-menu-item]")
441
+ .contains("Assortments")
442
+ .click({ timeout: TEST_CONFIG.TIMEOUTS.PAGE_LOAD });
443
+
444
+ cy.get("[data-fs-bp-header-inside-title]", {
445
+ timeout: TEST_CONFIG.TIMEOUTS.PAGE_LOAD,
446
+ }).should("be.visible");
447
+
448
+ cy.get("html").invoke("attr", "lang").as("defaultLocaleLangAttr");
449
+ cy.get("[data-fs-bp-header-inside-title]")
450
+ .invoke("text")
451
+ .as("defaultLocaleTitle");
452
+
453
+ cy.location("pathname").then((defaultLocalePath) => {
454
+ visitFreshUnderLocale(defaultLocalePath);
455
+
456
+ cy.get("body").then(($body) => {
457
+ if ($body.find("[data-fs-bp-header-inside-title]").length === 0) {
458
+ cy.log(
459
+ `Localization not enabled for locale "${LOCALE_UNDER_TEST}" in this environment — skipping.`
460
+ );
461
+ return;
462
+ }
463
+
464
+ cy.get("html")
465
+ .should("have.attr", "lang", LOCALE_UNDER_TEST)
466
+ .then(function () {
467
+ expect(LOCALE_UNDER_TEST).to.not.equal(this.defaultLocaleLangAttr);
468
+ });
469
+
470
+ cy.get("[data-fs-bp-header-inside-title]")
471
+ .invoke("text")
472
+ .should("not.be.empty")
473
+ .then(function (translatedTitle) {
474
+ expect(
475
+ translatedTitle,
476
+ "page title should be translated, not the same text as the default locale"
477
+ ).to.not.equal(this.defaultLocaleTitle);
478
+ });
479
+ });
480
+ });
481
+ });
482
+ });
@@ -2,6 +2,14 @@ import { TEST_CONFIG } from "../constants";
2
2
 
3
3
  Cypress.Commands.add("login", () => {
4
4
  cy.session("organization-account-login", () => {
5
+ const authToken = Cypress.env("VTEX_AUTH_TOKEN");
6
+
7
+ if (authToken) {
8
+ cy.visit(TEST_CONFIG.LOCAL_URL);
9
+ cy.setCookie(TEST_CONFIG.AUTH.COOKIE_NAME, authToken);
10
+ return;
11
+ }
12
+
5
13
  const loginOrigin = new URL(TEST_CONFIG.AUTH.LOGIN_URL).origin;
6
14
  const login = Cypress.env("VTEX_ACCOUNT_LOGIN");
7
15
  const password = Cypress.env("VTEX_ACCOUNT_PASSWORD");