@controleonline/ui-common 1.2.70 → 1.2.75

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 (188) hide show
  1. package/.github/actions/workers/manager/action.yml +147 -0
  2. package/.github/actions/workers/qa/action.yml +44 -0
  3. package/.github/actions/workers/security/action.yml +44 -0
  4. package/.github/actions/workers/technical-documenter/action.yml +51 -0
  5. package/.github/workflows/manager-worker.yml +168 -0
  6. package/.github/workflows/publish-npm.yml +45 -0
  7. package/.gitmodules +3 -0
  8. package/AGENTS.md +6 -0
  9. package/README.md +14 -0
  10. package/package.json +32 -28
  11. package/src/api/fetch.js +1 -1
  12. package/src/api/index.js +328 -58
  13. package/src/react/components/AddImportModal.js +95 -111
  14. package/src/react/components/AnimatedModal.js +171 -0
  15. package/src/react/components/AnimatedModal.styles.js +21 -0
  16. package/src/react/components/AppTypeSwitcher.js +164 -0
  17. package/src/react/components/AppTypeSwitcher.styles.js +109 -0
  18. package/src/react/components/BackgroundRuntimeBridge.js +5 -4
  19. package/src/react/components/BottomNavigationBar.config.js +211 -231
  20. package/src/react/components/BottomNavigationBar.js +86 -31
  21. package/src/react/components/BottomNavigationBar.styles.js +118 -110
  22. package/src/react/components/CategoryForm.js +52 -35
  23. package/src/react/components/DefaultProvider.native.js +194 -156
  24. package/src/react/components/DefaultProvider.web.js +188 -112
  25. package/src/react/components/DeliveryPushBridge.native.js +2 -2
  26. package/src/react/components/DeviceAlertSoundService.js +3 -3
  27. package/src/react/components/KioskModeBridge.js +2 -2
  28. package/src/react/components/LauncherModeBridge.js +2 -2
  29. package/src/react/components/ManagerPushBridge.native.js +2 -2
  30. package/src/react/components/NetworkPrinterPreviewModal.js +249 -0
  31. package/src/react/components/NetworkPrinterPreviewModal.styles.js +159 -0
  32. package/src/react/components/RemoteCheckoutService.js +28 -20
  33. package/src/react/components/RuntimeBottomNavigationBar.js +146 -119
  34. package/src/react/components/RuntimeInfoFooter.js +137 -45
  35. package/src/react/components/RuntimeInfoFooter.styles.js +3 -0
  36. package/src/react/components/StateStore.js +258 -0
  37. package/src/react/components/UnifiedPaymentBar.js +7 -4
  38. package/src/react/components/UserAvatar.js +160 -0
  39. package/src/react/components/WebsocketListener.native.js +11 -11
  40. package/src/react/components/integrations/IntegrationHero/index.js +48 -0
  41. package/src/react/components/integrations/IntegrationHero/styles.js +57 -0
  42. package/src/react/components/integrations/IntegrationTabs/index.js +89 -0
  43. package/src/react/components/integrations/IntegrationTabs/styles.js +44 -0
  44. package/src/react/config/deviceConfigBootstrap.js +71 -30
  45. package/src/react/css/orders.js +72 -0
  46. package/src/react/pages/Connections.js +229 -0
  47. package/src/react/pages/Connections.styles.js +222 -0
  48. package/src/react/pages/DeviceDetailPage.js +2814 -0
  49. package/src/react/pages/DeviceDetailPage.styles.js +308 -0
  50. package/src/react/pages/Devices/currentDevice.js +194 -0
  51. package/src/react/pages/Devices/deviceTypes/DeviceGroupCard.js +257 -0
  52. package/src/react/pages/Devices/deviceTypes/all.js +23 -0
  53. package/src/react/pages/Devices/deviceTypes/device.js +25 -0
  54. package/src/react/pages/Devices/deviceTypes/deviceListHelpers.js +178 -0
  55. package/src/react/pages/Devices/deviceTypes/display.js +26 -0
  56. package/src/react/pages/Devices/deviceTypes/index.js +145 -0
  57. package/src/react/pages/Devices/deviceTypes/ipCamera.js +35 -0
  58. package/src/react/pages/Devices/deviceTypes/pdv.js +88 -0
  59. package/src/react/pages/Devices/deviceTypes/printer.js +37 -0
  60. package/src/react/pages/Devices/deviceTypes/shared.js +453 -0
  61. package/src/react/pages/Devices/deviceTypes/useDeviceNetworkConnectivity.js +145 -0
  62. package/src/react/pages/Devices.js +183 -0
  63. package/src/react/pages/Devices.styles.js +353 -0
  64. package/src/react/pages/Food99IntegrationPage/components/Food99CatalogTab.js +219 -0
  65. package/src/react/pages/Food99IntegrationPage/components/Food99OverviewTab.js +134 -0
  66. package/src/react/pages/Food99IntegrationPage/components/Food99PreviewModal.js +173 -0
  67. package/src/react/pages/Food99IntegrationPage/components/Food99ProductCard.js +1 -0
  68. package/src/react/pages/Food99IntegrationPage/components/Food99QuickWalletModal.js +181 -0
  69. package/src/react/pages/Food99IntegrationPage/components/Food99SettingsTab.js +306 -0
  70. package/src/react/pages/Food99IntegrationPage/components/Food99StoreTab.js +417 -0
  71. package/src/react/pages/Food99IntegrationPage/index.js +1530 -0
  72. package/src/react/pages/Food99IntegrationPage/styles.js +792 -0
  73. package/src/react/pages/Food99IntegrationPage/utils.js +155 -0
  74. package/src/react/pages/Food99IntegrationPage.js +1 -0
  75. package/src/react/pages/Food99IntegrationPage.styles.js +1 -0
  76. package/src/react/pages/GenericLogPage.js +42 -167
  77. package/src/react/pages/IFoodIntegrationPage/components/IFoodCatalogTab.js +8 -0
  78. package/src/react/pages/IFoodIntegrationPage/components/IFoodOperationsTab.js +420 -0
  79. package/src/react/pages/IFoodIntegrationPage/components/IFoodOverviewTab.js +118 -0
  80. package/src/react/pages/IFoodIntegrationPage/components/IFoodPreviewModal.js +103 -0
  81. package/src/react/pages/IFoodIntegrationPage/components/IFoodProductCard.js +1 -0
  82. package/src/react/pages/IFoodIntegrationPage/components/IFoodStoreTab.js +150 -0
  83. package/src/react/pages/IFoodIntegrationPage/index.js +1298 -0
  84. package/src/react/pages/IFoodIntegrationPage/styles.js +764 -0
  85. package/src/react/pages/IFoodIntegrationPage/utils.js +91 -0
  86. package/src/react/pages/IFoodIntegrationPage.js +1 -0
  87. package/src/react/pages/IFoodIntegrationPage.styles.js +1 -0
  88. package/src/react/pages/Imports.js +214 -166
  89. package/src/react/pages/IntegrationConfigPage.js +744 -0
  90. package/src/react/pages/IntegrationConfigPage.styles.js +269 -0
  91. package/src/react/pages/Integrations.js +299 -0
  92. package/src/react/pages/Integrations.styles.js +130 -0
  93. package/src/react/pages/ManagerOrderNotificationsPage.js +642 -0
  94. package/src/react/pages/MarketplaceIntegrationPage/components/MarketplaceProductCard.js +205 -0
  95. package/src/react/pages/MarketplaceIntegrationPage/components/RemoteMarketplaceMenuTab.js +335 -0
  96. package/src/react/pages/MarketplaceIntegrationPage/index.js +60 -0
  97. package/src/react/pages/MarketplaceIntegrationPage/utils.js +137 -0
  98. package/src/react/pages/MercadoLivreIntegrationPage.js +538 -0
  99. package/src/react/pages/PrinterDeviceDetailPage.js +1916 -0
  100. package/src/react/pages/PrinterDeviceDetailPage.styles.js +223 -0
  101. package/src/react/pages/PrinterDeviceFormPage.js +521 -0
  102. package/src/react/pages/PrinterDeviceFormPage.styles.js +159 -0
  103. package/src/react/pages/SettingsPage/PaymentTypesByWalletTab.js +484 -454
  104. package/src/react/pages/SettingsPage/index.js +1266 -1167
  105. package/src/react/pages/WhatsAppConnectionPage.js +459 -0
  106. package/src/react/pages/WhatsAppConnectionPage.styles.js +284 -0
  107. package/src/react/pages/integrationsCatalog.js +399 -0
  108. package/src/react/print/providers/local.js +1 -1
  109. package/src/react/router/publicRoutes.js +25 -0
  110. package/src/react/services/Cielo/Checkout.js +39 -0
  111. package/src/react/services/Cielo/Cielo.js +193 -0
  112. package/src/react/services/Cielo/Print.js +7 -0
  113. package/src/react/services/InfinitePay/Checkout.js +33 -0
  114. package/src/react/services/InfinitePay/InfinitePay.js +24 -0
  115. package/src/react/{utils → services}/paymentGatewayExecution.js +91 -91
  116. package/src/react/styles/global.js +115 -0
  117. package/src/react/utils/categoryContexts.js +46 -0
  118. package/src/react/utils/entityDisplay.js +222 -217
  119. package/src/react/utils/fileUrl.js +201 -16
  120. package/src/react/utils/integrationPage.js +16 -0
  121. package/src/react/utils/menuNavigation.js +31 -0
  122. package/src/react/utils/orderIdentity.js +277 -0
  123. package/src/react/utils/remotePayment.js +115 -61
  124. package/src/react/utils/runtimeFooter.js +40 -22
  125. package/src/react/utils/runtimeLanguage.js +50 -9
  126. package/src/react/utils/runtimeMenu.js +190 -98
  127. package/src/react/utils/runtimeZoom.js +43 -0
  128. package/src/react/utils/shopConfig.js +72 -34
  129. package/src/react/utils/shopFranchises.js +182 -23
  130. package/src/react/utils/socketRuntimePipeline.js +14 -14
  131. package/src/react/utils/storeColumns.js +23 -4
  132. package/src/react/utils/userAvatar.js +104 -0
  133. package/src/store/categories/index.js +48 -20
  134. package/src/store/configs/index.js +2 -2
  135. package/src/store/device_config/index.js +62 -21
  136. package/src/store/entity_log/index.js +22 -1
  137. package/src/store/imports/customActions.js +51 -0
  138. package/src/store/imports/index.js +89 -88
  139. package/src/store/status/index.js +11 -11
  140. package/src/store/themes/index.js +22 -0
  141. package/src/store/timezones/index.js +3 -1
  142. package/src/tests/browser/manager/connections.spec.js +143 -0
  143. package/src/tests/browser/manager/devices-current.spec.js +343 -0
  144. package/src/tests/browser/manager/mercado-livre-integration.spec.js +268 -0
  145. package/src/tests/react/api/deviceHeaders.test.js +114 -0
  146. package/src/tests/react/api/loadSmokeIndex.test.js +75 -0
  147. package/src/tests/react/components/AddImportModal.test.js +166 -0
  148. package/src/tests/react/components/BottomNavigationBar.config.test.js +20 -0
  149. package/src/tests/react/components/UserAvatar.test.js +52 -0
  150. package/src/tests/react/config/deviceConfigBootstrap.test.js +47 -0
  151. package/src/tests/react/pages/DevicePickerStyles.test.js +47 -0
  152. package/src/tests/react/pages/Devices/deviceListHelpers.test.js +39 -0
  153. package/src/tests/react/pages/DevicesCurrentDevice.test.js +198 -0
  154. package/src/tests/react/pages/connectionsHelpers.test.js +34 -0
  155. package/src/tests/react/pages/mercadoLivreIntegrationPageContract.test.mjs +16 -0
  156. package/src/tests/react/print/localPrintProvider.test.js +1 -1
  157. package/src/tests/react/services/Cielo.test.js +190 -0
  158. package/src/tests/react/store/categoriesOrdering.test.js +26 -0
  159. package/src/tests/react/utils/appDomain.test.js +62 -0
  160. package/src/tests/react/utils/entityDisplay.test.js +2 -2
  161. package/src/tests/react/utils/fileUrl.test.js +62 -0
  162. package/src/tests/react/utils/formatter.test.mjs +33 -0
  163. package/src/tests/react/utils/importStatus.test.js +2 -2
  164. package/src/tests/react/utils/orderIdentity.test.js +139 -0
  165. package/src/tests/react/utils/remotePayment.test.js +62 -0
  166. package/src/tests/react/utils/runtimeFooter.test.js +32 -9
  167. package/src/tests/react/utils/runtimeLanguage.test.js +80 -10
  168. package/src/tests/react/utils/runtimeMenu.test.js +195 -111
  169. package/src/tests/react/utils/runtimeZoom.test.js +38 -0
  170. package/src/tests/react/utils/shopConfig.test.js +75 -0
  171. package/src/tests/react/utils/shopFranchises.test.js +122 -12
  172. package/src/tests/react/utils/translate.discovery.test.mjs +170 -0
  173. package/src/tests/react/utils/translate.test.helpers.mjs +152 -0
  174. package/src/tests/react/utils/translate.test.mjs +252 -396
  175. package/src/tests/react/utils/userAvatar.test.js +63 -0
  176. package/src/utils/appDomain.js +15 -4
  177. package/src/utils/formatter.js +122 -16
  178. package/src/utils/integrationConfigs.js +1 -0
  179. package/src/utils/translate.js +277 -398
  180. package/src/utils/translateDiscovery.js +96 -0
  181. package/src/utils/translateNormalize.js +33 -0
  182. package/src/utils/translatePending.js +92 -0
  183. package/src/utils/translateStorage.js +142 -0
  184. package/.github/agents/developer.agent.md +0 -30
  185. package/.github/agents/devops.agent.md +0 -30
  186. package/.github/agents/qa.agent.md +0 -30
  187. package/.github/agents/security.agent.md +0 -30
  188. package/.github/workflows/npmjs.yml +0 -35
@@ -0,0 +1,139 @@
1
+ const {jest} = require('@jest/globals');
2
+
3
+ const {resolveOrderIdentity} = require('../../../react/utils/orderIdentity');
4
+
5
+ const {describe, expect, it} = global;
6
+
7
+ global.t = {
8
+ t: jest.fn((store, type, key) => {
9
+ if (store === 'orders' && type === 'title' && key === 'table') {
10
+ return 'Mesa';
11
+ }
12
+
13
+ if (store === 'orders' && type === 'title' && key === 'order') {
14
+ return 'Pedido';
15
+ }
16
+
17
+ return '';
18
+ }),
19
+ };
20
+
21
+ describe('orderIdentity', () => {
22
+ it('prioritizes the 99Food code field from extra data', () => {
23
+ const order = {
24
+ id: 70911,
25
+ app: '99Food',
26
+ extraData: [
27
+ {
28
+ extraFields: {
29
+ context: 'Food99',
30
+ name: 'id',
31
+ },
32
+ value: '71759',
33
+ },
34
+ {
35
+ extraFields: {
36
+ context: 'Food99',
37
+ name: 'code',
38
+ },
39
+ value: '70001',
40
+ },
41
+ ],
42
+ };
43
+
44
+ const identity = resolveOrderIdentity(order);
45
+
46
+ expect(identity.externalLabel).toBe('99');
47
+ expect(identity.externalId).toBe('70001');
48
+ expect(identity.primaryText).toBe('#70001');
49
+ expect(identity.secondaryText).toBe('#70911');
50
+ });
51
+
52
+ it('does not invent an iFood marketplace code from pickup or display ids', () => {
53
+ const order = {
54
+ id: 81234,
55
+ app: 'ifood',
56
+ otherInformations: JSON.stringify({
57
+ ifood: {
58
+ latest_event_type: 'PLACED',
59
+ PLACED: {
60
+ order: {
61
+ id: 'ifood-order-1',
62
+ displayId: '70002',
63
+ delivery: {
64
+ pickupCode: '0176',
65
+ },
66
+ },
67
+ },
68
+ },
69
+ }),
70
+ };
71
+
72
+ const identity = resolveOrderIdentity(order);
73
+
74
+ expect(identity.externalLabel).toBe('');
75
+ expect(identity.externalId).toBe('');
76
+ expect(identity.primaryText).toBe('#81234');
77
+ expect(identity.secondaryText).toBe('');
78
+ });
79
+
80
+ it('prioritizes iFood code from extra data over pickup code', () => {
81
+ const order = {
82
+ id: 71759,
83
+ app: 'iFood',
84
+ extra_data: [
85
+ {
86
+ extra_fields: {
87
+ context: 'iFood',
88
+ name: 'pickup_code',
89
+ },
90
+ value: '9103',
91
+ },
92
+ {
93
+ extra_fields: {
94
+ context: 'iFood',
95
+ name: 'code',
96
+ },
97
+ value: '3984',
98
+ },
99
+ ],
100
+ };
101
+
102
+ const identity = resolveOrderIdentity(order);
103
+
104
+ expect(identity.externalLabel).toBe('IFOOD');
105
+ expect(identity.externalId).toBe('3984');
106
+ expect(identity.primaryText).toBe('#3984');
107
+ expect(identity.secondaryText).toBe('#71759');
108
+ });
109
+
110
+ it('prioritizes POS externalCode as the main order identity', () => {
111
+ const order = {
112
+ id: 71604,
113
+ app: 'POS',
114
+ externalCode: '570002',
115
+ };
116
+
117
+ const identity = resolveOrderIdentity(order);
118
+
119
+ expect(identity.externalLabel).toBe('');
120
+ expect(identity.externalId).toBe('570002');
121
+ expect(identity.primaryText).toBe('Mesa #570002');
122
+ expect(identity.secondaryText).toBe('#71604');
123
+ expect(global.t.t).toHaveBeenCalledWith('orders', 'title', 'table');
124
+ });
125
+
126
+ it('uses only the hash-prefixed local id for non-marketplace orders', () => {
127
+ const order = {
128
+ id: 70700,
129
+ app: 'shop',
130
+ };
131
+
132
+ const identity = resolveOrderIdentity(order);
133
+
134
+ expect(identity.externalLabel).toBe('');
135
+ expect(identity.externalId).toBe('');
136
+ expect(identity.primaryText).toBe('#70700');
137
+ expect(identity.secondaryText).toBe('');
138
+ });
139
+ });
@@ -0,0 +1,62 @@
1
+ const {
2
+ buildRemotePaymentResultMessage,
3
+ isRemotePaymentCancellation,
4
+ normalizeRemotePaymentResultStatus,
5
+ } = require('../../../react/utils/remotePayment')
6
+
7
+ const {describe, expect, it} = global
8
+
9
+ describe('remotePayment result status', () => {
10
+ it.each(['canceled', 'cancelled', 'cancel'])(
11
+ 'normalizes the explicit %s status as a cancellation',
12
+ status => {
13
+ expect(normalizeRemotePaymentResultStatus({status})).toBe('canceled')
14
+ expect(isRemotePaymentCancellation({status})).toBe(true)
15
+ },
16
+ )
17
+
18
+ it.each([
19
+ 'Pagamento PIX cancelado pelo usuario.',
20
+ 'Pagamento cancelado pelo usuário',
21
+ 'Payment cancelled by user',
22
+ 'Payment canceled by customer',
23
+ ])('recognizes legacy cancellation errors: %s', error => {
24
+ const result = {status: 'error', error}
25
+
26
+ expect(normalizeRemotePaymentResultStatus(result)).toBe('canceled')
27
+ expect(isRemotePaymentCancellation(result)).toBe(true)
28
+ })
29
+
30
+ it.each([
31
+ 'Falha de comunicacao com o terminal.',
32
+ 'Nao foi possivel cancelar a transacao.',
33
+ 'Tempo limite excedido.',
34
+ ])('keeps a real gateway failure as an error: %s', error => {
35
+ const result = {status: 'error', error}
36
+
37
+ expect(normalizeRemotePaymentResultStatus(result)).toBe('error')
38
+ expect(isRemotePaymentCancellation(result)).toBe(false)
39
+ })
40
+
41
+ it('preserves cancellation when building the remote result message', () => {
42
+ expect(
43
+ buildRemotePaymentResultMessage({
44
+ destinationDeviceId: 'web-7',
45
+ orderId: 123,
46
+ requestKey: '123:158:pix:1',
47
+ status: 'cancelled',
48
+ }),
49
+ ).toMatchObject({
50
+ action: 'pay-result',
51
+ destination: 'web-7',
52
+ order: '123',
53
+ requestKey: '123:158:pix:1',
54
+ status: 'canceled',
55
+ })
56
+ })
57
+
58
+ it('keeps successful and unknown results compatible with the existing contract', () => {
59
+ expect(normalizeRemotePaymentResultStatus({status: 'success'})).toBe('success')
60
+ expect(normalizeRemotePaymentResultStatus({status: 'unexpected'})).toBe('error')
61
+ })
62
+ })
@@ -1,6 +1,6 @@
1
1
  const assert = require('node:assert/strict')
2
- const test = require('node:test')
3
- const {jest, test: jestCompatTest} = require('@jest/globals')
2
+ const {beforeAll, afterAll, test} = global
3
+ const {jest} = require('@jest/globals')
4
4
 
5
5
  jest.mock('@controleonline/ui-common/src/react/utils/printerDevices', () => ({
6
6
  getDeviceTypeLabel: value => String(value || ''),
@@ -10,9 +10,12 @@ const {
10
10
  getRuntimeFooterDebugInfo,
11
11
  getRuntimeFooterNativeIdentifierCandidates,
12
12
  getRuntimeFooterPrimaryText,
13
- getRuntimeFooterStoredVersion,
14
- getRuntimeFooterWebIdentifierCandidates,
15
- getRuntimeFooterWebHost,
13
+ getRuntimeFooterRotationEntries,
14
+ getRuntimeFooterStoredVersion,
15
+ getRuntimeFooterTextLines,
16
+ getRuntimeFooterWebIdentifierCandidates,
17
+ getRuntimeFooterWebHost,
18
+ normalizeRuntimeFooterText,
16
19
  } = require('../../../react/utils/runtimeFooter')
17
20
 
18
21
  const originalLocationDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'location')
@@ -43,7 +46,7 @@ const restoreLocation = () => {
43
46
  delete globalThis.location
44
47
  }
45
48
 
46
- test.before(() => {
49
+ beforeAll(() => {
47
50
  global.t = {
48
51
  getMessageFromBuckets: (store, type, key) =>
49
52
  store === 'common' && type === 'option'
@@ -52,7 +55,7 @@ test.before(() => {
52
55
  }
53
56
  })
54
57
 
55
- test.after(() => {
58
+ afterAll(() => {
56
59
  global.t = originalTranslator
57
60
  })
58
61
 
@@ -421,9 +424,29 @@ test('falls back to the local device id when the backend identifier is unavailab
421
424
  device: {},
422
425
  },
423
426
  })
424
-
427
+
425
428
  assert.equal(candidates[0]?.value, 'local-device-99')
426
429
  assert.equal(candidates[0]?.source, 'device.id')
427
430
  })
428
431
 
429
- jestCompatTest('jest compatibility placeholder', () => {})
432
+ test('preserves free-text line breaks while normalizing each footer line', () => {
433
+ assert.equal(
434
+ normalizeRuntimeFooterText(' www.site.com \n\n (11) 99999-9999 \n suporte '),
435
+ 'www.site.com\n(11) 99999-9999\nsuporte',
436
+ )
437
+
438
+ assert.deepEqual(
439
+ getRuntimeFooterTextLines('www.site.com\n(11) 99999-9999\nsuporte'),
440
+ ['www.site.com', '(11) 99999-9999', 'suporte'],
441
+ )
442
+ })
443
+
444
+ test('rotates each footer line before the runtime version entry', () => {
445
+ assert.deepEqual(
446
+ getRuntimeFooterRotationEntries({
447
+ companyFooterText: 'www.site.com\n(11) 99999-9999',
448
+ primaryText: 'web (203.0.113.42) / v1.3.6',
449
+ }),
450
+ ['www.site.com', '(11) 99999-9999', 'web (203.0.113.42) / v1.3.6'],
451
+ )
452
+ })
@@ -1,11 +1,13 @@
1
- const assert = require('node:assert/strict')
2
- const test = require('node:test')
1
+ const assert = require('node:assert/strict')
2
+ const {test} = require('node:test')
3
3
 
4
- const {
5
- normalizeLanguageCode,
6
- resolveCompanyLanguageCode,
7
- resolveConfiguredLanguage,
8
- } = require('../../../react/utils/runtimeLanguage')
4
+ const {
5
+ buildTranslationBootstrapKey,
6
+ isTranslationBootstrapReady,
7
+ normalizeLanguageCode,
8
+ resolveCompanyLanguageCode,
9
+ resolveConfiguredLanguage,
10
+ } = require('../../../react/utils/runtimeLanguage')
9
11
 
10
12
  test('normalizes language separators and casing', () => {
11
13
  assert.equal(normalizeLanguageCode(' PT_BR '), 'pt-br')
@@ -46,7 +48,7 @@ test('prefers the selected company language over cached config and session value
46
48
  )
47
49
  })
48
50
 
49
- test('falls back through default company, config, session and the hardcoded default', () => {
51
+ test('falls back through default company, config, session and the hardcoded default', () => {
50
52
  assert.equal(
51
53
  resolveConfiguredLanguage({
52
54
  defaultCompany: {
@@ -76,5 +78,73 @@ test('falls back through default company, config, session and the hardcoded defa
76
78
  'nl-nl',
77
79
  )
78
80
 
79
- assert.equal(resolveConfiguredLanguage({}), 'pt-br')
80
- })
81
+ assert.equal(resolveConfiguredLanguage({}), 'pt-br')
82
+ })
83
+
84
+ test('builds the translation bootstrap key only from language and company context', () => {
85
+ assert.equal(
86
+ buildTranslationBootstrapKey({
87
+ language: ' PT_BR ',
88
+ currentCompanyId: 21,
89
+ defaultCompanyId: 1,
90
+ }),
91
+ 'pt-br::21::1',
92
+ )
93
+ assert.equal(
94
+ buildTranslationBootstrapKey({
95
+ language: 'pt-br',
96
+ currentCompanyId: '',
97
+ defaultCompanyId: 1,
98
+ }),
99
+ '',
100
+ )
101
+ })
102
+
103
+ test('keeps authenticated content blocked until the matching translator exists', () => {
104
+ const context = {
105
+ activeKey: 'pt-br::21::1',
106
+ expectedKey: 'pt-br::21::1',
107
+ required: true,
108
+ ready: true,
109
+ }
110
+
111
+ assert.equal(
112
+ isTranslationBootstrapReady({
113
+ ...context,
114
+ translator: undefined,
115
+ }),
116
+ false,
117
+ )
118
+ assert.equal(
119
+ isTranslationBootstrapReady({
120
+ ...context,
121
+ activeKey: 'pt-br::3::1',
122
+ translator: {t() {}},
123
+ }),
124
+ false,
125
+ )
126
+ assert.equal(
127
+ isTranslationBootstrapReady({
128
+ ...context,
129
+ ready: false,
130
+ translator: {t() {}},
131
+ }),
132
+ false,
133
+ )
134
+ assert.equal(
135
+ isTranslationBootstrapReady({
136
+ ...context,
137
+ translator: {t() {}},
138
+ }),
139
+ true,
140
+ )
141
+ })
142
+
143
+ test('does not block contexts where translation bootstrap is not required', () => {
144
+ assert.equal(
145
+ isTranslationBootstrapReady({
146
+ required: false,
147
+ }),
148
+ true,
149
+ )
150
+ })