@controleonline/ui-common 1.2.70

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 (203) hide show
  1. package/.github/agents/developer.agent.md +30 -0
  2. package/.github/agents/devops.agent.md +30 -0
  3. package/.github/agents/qa.agent.md +30 -0
  4. package/.github/agents/security.agent.md +30 -0
  5. package/.github/workflows/npmjs.yml +35 -0
  6. package/.github/workflows/pull-request-checks.yml +19 -0
  7. package/.scrutinizer.yml +24 -0
  8. package/FUNDING.yml +1 -0
  9. package/package.json +28 -0
  10. package/src/api/fetch.js +74 -0
  11. package/src/api/index.js +155 -0
  12. package/src/api/localDB.js +131 -0
  13. package/src/api/queue.js +66 -0
  14. package/src/react/assets/tap.mp3 +0 -0
  15. package/src/react/components/AddImportModal.js +157 -0
  16. package/src/react/components/AddImportModal.styles.js +81 -0
  17. package/src/react/components/BackgroundRuntimeBridge.js +219 -0
  18. package/src/react/components/BottomNavigationBar.config.js +238 -0
  19. package/src/react/components/BottomNavigationBar.js +120 -0
  20. package/src/react/components/BottomNavigationBar.styles.js +110 -0
  21. package/src/react/components/CategoryForm.js +572 -0
  22. package/src/react/components/CategoryForm.styles.js +334 -0
  23. package/src/react/components/ConfirmModal.js +24 -0
  24. package/src/react/components/ConfirmModal.styles.js +43 -0
  25. package/src/react/components/ContextHelpButton.js +69 -0
  26. package/src/react/components/ContextHelpButton.styles.js +62 -0
  27. package/src/react/components/DefaultProvider.native.js +848 -0
  28. package/src/react/components/DefaultProvider.styles.js +21 -0
  29. package/src/react/components/DefaultProvider.web.js +909 -0
  30. package/src/react/components/DeliveryPushBridge.native.js +314 -0
  31. package/src/react/components/DeliveryPushBridge.web.js +5 -0
  32. package/src/react/components/DeviceAlertSoundService.js +499 -0
  33. package/src/react/components/EntityLogContent.js +909 -0
  34. package/src/react/components/EntityLogContent.styles.js +400 -0
  35. package/src/react/components/EventBus.js +12 -0
  36. package/src/react/components/KioskModeBridge.js +65 -0
  37. package/src/react/components/LauncherModeBridge.js +47 -0
  38. package/src/react/components/LinkedOrderProductsTab.js +388 -0
  39. package/src/react/components/LinkedOrderProductsTab.styles.js +180 -0
  40. package/src/react/components/ManagerPushBridge.native.js +345 -0
  41. package/src/react/components/MessageService.js +314 -0
  42. package/src/react/components/Paywall/Google.js +89 -0
  43. package/src/react/components/PrintService.js +778 -0
  44. package/src/react/components/ProductCatalogCacheService.js +45 -0
  45. package/src/react/components/RemoteCheckoutService.js +292 -0
  46. package/src/react/components/RuntimeBottomNavigationBar.js +151 -0
  47. package/src/react/components/RuntimeInfoFooter.js +214 -0
  48. package/src/react/components/RuntimeInfoFooter.styles.js +56 -0
  49. package/src/react/components/SystemErrorToast.js +29 -0
  50. package/src/react/components/SystemErrorToast.styles.js +38 -0
  51. package/src/react/components/TouchFeedbackProvider.js +65 -0
  52. package/src/react/components/UnifiedPaymentBar.js +155 -0
  53. package/src/react/components/UnifiedPaymentBar.styles.js +174 -0
  54. package/src/react/components/WebsocketListener.native.js +487 -0
  55. package/src/react/components/WebsocketListener.web.js +102 -0
  56. package/src/react/components/inputs/IdInput.js +49 -0
  57. package/src/react/components/inputs/IdInput.styles.js +26 -0
  58. package/src/react/components/inputs/InvoiceDueDateFilterInput.js +74 -0
  59. package/src/react/components/inputs/InvoiceDueDateFilterInput.styles.js +34 -0
  60. package/src/react/components/inputs/InvoiceDueDateRangeFilterInput.js +115 -0
  61. package/src/react/components/inputs/InvoiceDueDateRangeFilterInput.styles.js +39 -0
  62. package/src/react/components/lists/CategoriesList.js +56 -0
  63. package/src/react/components/lists/PaymentTypeList.js +55 -0
  64. package/src/react/components/lists/ReceiverList.js +56 -0
  65. package/src/react/components/lists/SelectList.styles.js +18 -0
  66. package/src/react/components/lists/StatusList.js +55 -0
  67. package/src/react/components/lists/WalletList.js +54 -0
  68. package/src/react/components/radio/Spotify.js +144 -0
  69. package/src/react/components/radio/Spotify.styles.js +32 -0
  70. package/src/react/components/radio/YouTube.js +82 -0
  71. package/src/react/components/radio/YouTube.styles.js +16 -0
  72. package/src/react/components/toastConfig.js +16 -0
  73. package/src/react/config/deviceConfigBootstrap.js +571 -0
  74. package/src/react/index.js +4 -0
  75. package/src/react/localStorage.js +30 -0
  76. package/src/react/pages/EntityLogPage.js +124 -0
  77. package/src/react/pages/EntityLogPage.styles.js +17 -0
  78. package/src/react/pages/GenericLogPage.js +760 -0
  79. package/src/react/pages/GenericLogPage.styles.js +401 -0
  80. package/src/react/pages/Imports.js +361 -0
  81. package/src/react/pages/Imports.styles.js +223 -0
  82. package/src/react/pages/SettingsPage/PaymentTypesByWalletTab.js +462 -0
  83. package/src/react/pages/SettingsPage/index.js +1171 -0
  84. package/src/react/pages/SettingsPage/index.styles.js +122 -0
  85. package/src/react/print/jobs.js +147 -0
  86. package/src/react/print/providers/local.js +53 -0
  87. package/src/react/print/providers/remote.js +81 -0
  88. package/src/react/print/selection.js +296 -0
  89. package/src/react/print/usePrintButtonController.js +474 -0
  90. package/src/react/router/routes.js +42 -0
  91. package/src/react/services/NetworkPrinterService.native.js +391 -0
  92. package/src/react/services/NetworkPrinterService.web.js +15 -0
  93. package/src/react/stores/index.js +128 -0
  94. package/src/react/stores/storeErrorBridge.js +36 -0
  95. package/src/react/utils/backgroundRuntimeRegistration.js +69 -0
  96. package/src/react/utils/cashPayment.js +87 -0
  97. package/src/react/utils/commercialDocumentOrders.js +269 -0
  98. package/src/react/utils/dateRangeFilter.js +264 -0
  99. package/src/react/utils/deviceRuntime.js +193 -0
  100. package/src/react/utils/entityDisplay.js +231 -0
  101. package/src/react/utils/entityLog.js +649 -0
  102. package/src/react/utils/fileUrl.js +102 -0
  103. package/src/react/utils/frontendDebugLog.js +45 -0
  104. package/src/react/utils/googleMapsConfig.js +49 -0
  105. package/src/react/utils/importStatus.js +100 -0
  106. package/src/react/utils/invoiceDueDateMessages.js +14 -0
  107. package/src/react/utils/invoicePresentation.js +46 -0
  108. package/src/react/utils/logSettings.js +143 -0
  109. package/src/react/utils/maintenanceSettings.js +53 -0
  110. package/src/react/utils/managerOrderNotifications.js +523 -0
  111. package/src/react/utils/mapNavigation.js +76 -0
  112. package/src/react/utils/menuCatalogConfig.js +8 -0
  113. package/src/react/utils/menuCatalogDownload.js +157 -0
  114. package/src/react/utils/networkCameraDevices.js +175 -0
  115. package/src/react/utils/networkDeviceProfiles.js +135 -0
  116. package/src/react/utils/normalizedCatalogDownload.js +157 -0
  117. package/src/react/utils/notificationNavigation.js +94 -0
  118. package/src/react/utils/notificationSound.js +42 -0
  119. package/src/react/utils/paymentDevices.js +397 -0
  120. package/src/react/utils/paymentGatewayExecution.js +91 -0
  121. package/src/react/utils/paymentOptions.js +88 -0
  122. package/src/react/utils/peopleDisplay.js +39 -0
  123. package/src/react/utils/printerDevices.js +465 -0
  124. package/src/react/utils/remotePayment.js +61 -0
  125. package/src/react/utils/runtimeFooter.js +475 -0
  126. package/src/react/utils/runtimeLanguage.js +50 -0
  127. package/src/react/utils/runtimeMenu.js +224 -0
  128. package/src/react/utils/screenMetrics.js +30 -0
  129. package/src/react/utils/shopConfig.js +294 -0
  130. package/src/react/utils/shopFranchises.js +114 -0
  131. package/src/react/utils/socketRuntimePipeline.js +454 -0
  132. package/src/react/utils/spoolAckTracker.js +77 -0
  133. package/src/react/utils/storeColumns.js +427 -0
  134. package/src/react/utils/systemErrorChannel.js +109 -0
  135. package/src/react/utils/systemErrorMessage.js +67 -0
  136. package/src/react/utils/toastPresentation.js +53 -0
  137. package/src/react/utils/websocketSharedRuntime.js +826 -0
  138. package/src/store/acl/getters.js +4 -0
  139. package/src/store/acl/index.js +19 -0
  140. package/src/store/acl/mutation_types.js +4 -0
  141. package/src/store/acl/mutations.js +17 -0
  142. package/src/store/address/city.js +22 -0
  143. package/src/store/categories/category_file/index.js +25 -0
  144. package/src/store/categories/index.js +93 -0
  145. package/src/store/configs/customActions.js +131 -0
  146. package/src/store/configs/index.js +25 -0
  147. package/src/store/connections/customActions.js +26 -0
  148. package/src/store/connections/index.js +124 -0
  149. package/src/store/device/index.js +22 -0
  150. package/src/store/device_config/customActions.js +190 -0
  151. package/src/store/device_config/index.js +28 -0
  152. package/src/store/entity_log/customActions.js +63 -0
  153. package/src/store/entity_log/index.js +25 -0
  154. package/src/store/extra/data.js +85 -0
  155. package/src/store/extra/fields.js +88 -0
  156. package/src/store/file/actions.js +27 -0
  157. package/src/store/file/index.js +24 -0
  158. package/src/store/imports/index.js +88 -0
  159. package/src/store/print/customActions.js +251 -0
  160. package/src/store/print/index.js +28 -0
  161. package/src/store/printer/customActions.js +54 -0
  162. package/src/store/printer/index.js +26 -0
  163. package/src/store/runtime_debug/customActions.js +71 -0
  164. package/src/store/runtime_debug/index.js +30 -0
  165. package/src/store/status/index.js +65 -0
  166. package/src/store/timezones/index.js +43 -0
  167. package/src/store/websocket/customActions.js +24 -0
  168. package/src/store/websocket/index.js +28 -0
  169. package/src/tests/react/components/LauncherModeBridge.test.js +93 -0
  170. package/src/tests/react/config/deviceConfigBootstrap.test.js +337 -0
  171. package/src/tests/react/print/localPrintProvider.test.js +45 -0
  172. package/src/tests/react/utils/backgroundRuntimeRegistration.test.js +123 -0
  173. package/src/tests/react/utils/cashPayment.test.js +41 -0
  174. package/src/tests/react/utils/dateRangeFilter.test.mjs +29 -0
  175. package/src/tests/react/utils/entityDisplay.test.js +18 -0
  176. package/src/tests/react/utils/googleMapsConfig.test.js +32 -0
  177. package/src/tests/react/utils/importStatus.test.js +85 -0
  178. package/src/tests/react/utils/invoicePresentation.test.mjs +40 -0
  179. package/src/tests/react/utils/managerOrderNotifications.test.js +127 -0
  180. package/src/tests/react/utils/mapNavigation.test.js +58 -0
  181. package/src/tests/react/utils/notificationNavigation.test.js +39 -0
  182. package/src/tests/react/utils/notificationSound.test.js +27 -0
  183. package/src/tests/react/utils/paymentDevices.test.js +88 -0
  184. package/src/tests/react/utils/runtimeFooter.test.js +429 -0
  185. package/src/tests/react/utils/runtimeLanguage.test.js +80 -0
  186. package/src/tests/react/utils/runtimeMenu.test.js +174 -0
  187. package/src/tests/react/utils/shopFranchises.test.js +82 -0
  188. package/src/tests/react/utils/socketRuntimePipeline.test.js +124 -0
  189. package/src/tests/react/utils/spoolAckTracker.test.js +40 -0
  190. package/src/tests/react/utils/storeErrorBridge.test.js +66 -0
  191. package/src/tests/react/utils/systemErrorChannel.test.js +58 -0
  192. package/src/tests/react/utils/systemErrorMessage.test.js +59 -0
  193. package/src/tests/react/utils/toastPresentation.test.js +75 -0
  194. package/src/tests/react/utils/translate.test.mjs +396 -0
  195. package/src/tests/react/utils/websocketSharedRuntime.test.js +245 -0
  196. package/src/utils/apiEntryPoint.js +31 -0
  197. package/src/utils/appDomain.js +50 -0
  198. package/src/utils/config.js +14 -0
  199. package/src/utils/formatter.js +419 -0
  200. package/src/utils/integrationConfigs.js +79 -0
  201. package/src/utils/methods.js +23 -0
  202. package/src/utils/oauth.js +42 -0
  203. package/src/utils/translate.js +620 -0
@@ -0,0 +1,429 @@
1
+ const assert = require('node:assert/strict')
2
+ const test = require('node:test')
3
+ const {jest, test: jestCompatTest} = require('@jest/globals')
4
+
5
+ jest.mock('@controleonline/ui-common/src/react/utils/printerDevices', () => ({
6
+ getDeviceTypeLabel: value => String(value || ''),
7
+ }))
8
+
9
+ const {
10
+ getRuntimeFooterDebugInfo,
11
+ getRuntimeFooterNativeIdentifierCandidates,
12
+ getRuntimeFooterPrimaryText,
13
+ getRuntimeFooterStoredVersion,
14
+ getRuntimeFooterWebIdentifierCandidates,
15
+ getRuntimeFooterWebHost,
16
+ } = require('../../../react/utils/runtimeFooter')
17
+
18
+ const originalLocationDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'location')
19
+ const originalTranslator = global.t
20
+ const commonOptionTranslations = {
21
+ counterService: 'Balcão',
22
+ waiterService: 'Garçom',
23
+ selfServiceKiosk: 'Totem',
24
+ singleItemSale: 'Venda única',
25
+ cashierPOS: 'PDV',
26
+ }
27
+
28
+ const setLocation = value => {
29
+ Object.defineProperty(globalThis, 'location', {
30
+ configurable: true,
31
+ enumerable: true,
32
+ writable: true,
33
+ value,
34
+ })
35
+ }
36
+
37
+ const restoreLocation = () => {
38
+ if (originalLocationDescriptor) {
39
+ Object.defineProperty(globalThis, 'location', originalLocationDescriptor)
40
+ return
41
+ }
42
+
43
+ delete globalThis.location
44
+ }
45
+
46
+ test.before(() => {
47
+ global.t = {
48
+ getMessageFromBuckets: (store, type, key) =>
49
+ store === 'common' && type === 'option'
50
+ ? commonOptionTranslations[key] || ''
51
+ : '',
52
+ }
53
+ })
54
+
55
+ test.after(() => {
56
+ global.t = originalTranslator
57
+ })
58
+
59
+ test('prefers the persisted public ip for web runtime devices', () => {
60
+ setLocation({hostname: '10.0.0.15'})
61
+
62
+ try {
63
+ assert.equal(
64
+ getRuntimeFooterPrimaryText({
65
+ device: {
66
+ alias: 'app',
67
+ appVersion: '1.3.6',
68
+ deviceType: 'web',
69
+ id: 'web-12',
70
+ externalIp: '198.51.100.21',
71
+ metadata: {
72
+ network: {
73
+ publicIp: '198.51.100.21',
74
+ },
75
+ },
76
+ },
77
+ appVersion: '1.3.6',
78
+ deviceConfig: {
79
+ device: {
80
+ metadata: {
81
+ network: {
82
+ publicIp: '203.0.113.42',
83
+ },
84
+ },
85
+ },
86
+ configs: {
87
+ 'config-version': '1.3.5',
88
+ },
89
+ },
90
+ }),
91
+ 'web (203.0.113.42) / v1.3.6',
92
+ )
93
+
94
+ const debugInfo = getRuntimeFooterDebugInfo({
95
+ device: {
96
+ alias: 'app',
97
+ appVersion: '1.3.6',
98
+ deviceType: 'web',
99
+ id: 'web-12',
100
+ externalIp: '198.51.100.21',
101
+ metadata: {
102
+ network: {
103
+ publicIp: '198.51.100.21',
104
+ },
105
+ },
106
+ },
107
+ appVersion: '1.3.6',
108
+ deviceConfig: {
109
+ device: {
110
+ metadata: {
111
+ network: {
112
+ publicIp: '203.0.113.42',
113
+ },
114
+ },
115
+ },
116
+ configs: {
117
+ 'config-version': '1.3.5',
118
+ },
119
+ },
120
+ })
121
+
122
+ assert.equal(debugInfo.runtimeDetailSource, 'device_config.device.metadata.network.publicIp')
123
+ assert.equal(debugInfo.rawValues.storedDeviceMetadataPublicIp, '203.0.113.42')
124
+ assert.equal(debugInfo.rawValues.deviceMetadataPublicIp, '198.51.100.21')
125
+ } finally {
126
+ restoreLocation()
127
+ }
128
+ })
129
+
130
+ test('normalizes localhost to the loopback address on web', () => {
131
+ setLocation({hostname: 'localhost'})
132
+
133
+ try {
134
+ assert.equal(getRuntimeFooterWebHost(), '127.0.0.1')
135
+ } finally {
136
+ restoreLocation()
137
+ }
138
+ })
139
+
140
+ test('falls back to the browser hostname for web when no persisted public ip exists', () => {
141
+ setLocation({hostname: 'localhost'})
142
+
143
+ try {
144
+ const candidates = getRuntimeFooterWebIdentifierCandidates({
145
+ device: {
146
+ deviceType: 'web',
147
+ id: 'web-12',
148
+ },
149
+ deviceConfig: {},
150
+ })
151
+
152
+ assert.equal(candidates[0]?.value, '127.0.0.1')
153
+ assert.equal(candidates[0]?.source, 'location.hostname')
154
+ } finally {
155
+ restoreLocation()
156
+ }
157
+ })
158
+
159
+ test('recognizes backend web devices by their persisted runtime metadata or device key', () => {
160
+ const debugInfo = getRuntimeFooterDebugInfo({
161
+ device: {
162
+ device: 'web-12',
163
+ metadata: {
164
+ runtime: 'web',
165
+ network: {
166
+ publicIp: '198.51.100.21',
167
+ },
168
+ },
169
+ },
170
+ deviceConfig: {
171
+ device: {
172
+ device: 'web-12',
173
+ metadata: {
174
+ runtime: 'web',
175
+ network: {
176
+ publicIp: '203.0.113.42',
177
+ },
178
+ },
179
+ },
180
+ },
181
+ })
182
+
183
+ assert.equal(debugInfo.isWebRuntime, true)
184
+ assert.equal(debugInfo.runtimeDetail, '203.0.113.42')
185
+ assert.equal(
186
+ debugInfo.runtimeDetailSource,
187
+ 'device_config.device.metadata.network.publicIp',
188
+ )
189
+ })
190
+
191
+ test('reads the persisted config-version for non-web runtimes', () => {
192
+ assert.equal(
193
+ getRuntimeFooterStoredVersion({
194
+ configs: JSON.stringify({
195
+ 'config-version': '1.3.5',
196
+ }),
197
+ }),
198
+ 'v1.3.5',
199
+ )
200
+
201
+ assert.equal(
202
+ getRuntimeFooterPrimaryText({
203
+ device: {
204
+ alias: 'PDV',
205
+ appVersion: '1.3.7',
206
+ deviceType: 'android',
207
+ id: 'native-12',
208
+ },
209
+ appVersion: '1.3.6',
210
+ deviceConfig: {
211
+ device: {
212
+ device: '229252771069b294',
213
+ },
214
+ configs: {
215
+ 'config-version': '1.3.5',
216
+ },
217
+ },
218
+ }),
219
+ 'PDV (229252771069b294) / v1.3.7',
220
+ )
221
+ })
222
+
223
+ test('prefers the backend device identifier used by the devices page', () => {
224
+ assert.equal(
225
+ getRuntimeFooterPrimaryText({
226
+ device: {
227
+ alias: 'PDV',
228
+ appVersion: '1.3.7',
229
+ deviceType: 'android',
230
+ id: 'd41ac8afb9f178eb',
231
+ },
232
+ appVersion: '1.3.6',
233
+ deviceConfig: {
234
+ device: {
235
+ device: '229252771069b294',
236
+ metadata: {
237
+ app: {
238
+ version: '1.3.7',
239
+ },
240
+ },
241
+ },
242
+ configs: {
243
+ 'config-version': '1.3.7',
244
+ },
245
+ },
246
+ }),
247
+ 'PDV (229252771069b294) / v1.3.7',
248
+ )
249
+
250
+ const debugInfo = getRuntimeFooterDebugInfo({
251
+ device: {
252
+ alias: 'PDV',
253
+ appVersion: '1.3.7',
254
+ deviceType: 'android',
255
+ id: 'd41ac8afb9f178eb',
256
+ },
257
+ appVersion: '1.3.6',
258
+ deviceConfig: {
259
+ device: {
260
+ device: '229252771069b294',
261
+ metadata: {
262
+ app: {
263
+ version: '1.3.7',
264
+ },
265
+ },
266
+ },
267
+ configs: {
268
+ 'config-version': '1.3.7',
269
+ },
270
+ },
271
+ })
272
+
273
+ assert.equal(debugInfo.versionLabel, 'v1.3.7')
274
+ assert.equal(debugInfo.displayName, 'PDV (229252771069b294)')
275
+ assert.equal(debugInfo.runtimeDetail, '229252771069b294')
276
+ assert.equal(debugInfo.runtimeDetailSource, 'device_config.device.device')
277
+ assert.equal(debugInfo.versionCandidates[0]?.source, 'device_config.device.metadata.app.version')
278
+ assert.equal(debugInfo.rawValues.deviceId, 'd41ac8afb9f178eb')
279
+ assert.equal(debugInfo.rawValues.storedDeviceIdentifier, '229252771069b294')
280
+ })
281
+
282
+ test('shows the PDV operation mode when the runtime device type is available', () => {
283
+ const params = {
284
+ device: {
285
+ alias: 'PDV',
286
+ appVersion: '1.3.7',
287
+ deviceType: 'android',
288
+ id: 'd41ac8afb9f178eb',
289
+ type: 'PDV',
290
+ },
291
+ appVersion: '1.3.6',
292
+ deviceConfig: {
293
+ device: {
294
+ device: '229252771069b294',
295
+ type: 'PDV',
296
+ metadata: {
297
+ app: {
298
+ version: '1.3.7',
299
+ },
300
+ },
301
+ },
302
+ configs: {
303
+ 'config-version': '1.3.7',
304
+ 'pos-operation-mode': 'waiter',
305
+ },
306
+ },
307
+ }
308
+
309
+ assert.equal(
310
+ getRuntimeFooterPrimaryText(params),
311
+ 'PDV • Garçom / v1.3.7',
312
+ )
313
+
314
+ const debugInfo = getRuntimeFooterDebugInfo(params)
315
+
316
+ assert.equal(debugInfo.displayName, 'PDV • Garçom')
317
+ assert.equal(debugInfo.runtimeDetail, 'Garçom')
318
+ assert.equal(
319
+ debugInfo.runtimeDetailSource,
320
+ 'device_config.configs.pos-operation-mode',
321
+ )
322
+ assert.equal(debugInfo.rawValues.operationalType, 'PDV')
323
+ assert.equal(debugInfo.rawValues.operationMode, 'waiter')
324
+ assert.equal(debugInfo.rawValues.operationModeLabel, 'Garçom')
325
+ })
326
+
327
+ test('keeps the device identifier while adding the operation mode to the footer', () => {
328
+ assert.equal(
329
+ getRuntimeFooterPrimaryText({
330
+ device: {
331
+ alias: 'terminal-7',
332
+ appVersion: '1.3.7',
333
+ deviceType: 'android',
334
+ id: 'd41ac8afb9f178eb',
335
+ type: 'PDV',
336
+ },
337
+ appVersion: '1.3.6',
338
+ deviceConfig: {
339
+ device: {
340
+ device: '229252771069b294',
341
+ type: 'PDV',
342
+ metadata: {
343
+ app: {
344
+ version: '1.3.7',
345
+ },
346
+ },
347
+ },
348
+ configs: {
349
+ 'config-version': '1.3.7',
350
+ 'pos-operation-mode': 'totem',
351
+ },
352
+ },
353
+ }),
354
+ 'terminal-7 • PDV • Totem / v1.3.7',
355
+ )
356
+ })
357
+
358
+ test('does not invent a fallback label when the operation mode translation is missing', () => {
359
+ const debugInfo = getRuntimeFooterDebugInfo({
360
+ device: {
361
+ alias: 'PDV',
362
+ appVersion: '1.3.7',
363
+ deviceType: 'android',
364
+ id: 'd41ac8afb9f178eb',
365
+ type: 'PDV',
366
+ },
367
+ appVersion: '1.3.6',
368
+ deviceConfig: {
369
+ device: {
370
+ device: '229252771069b294',
371
+ type: 'PDV',
372
+ metadata: {
373
+ app: {
374
+ version: '1.3.7',
375
+ },
376
+ },
377
+ },
378
+ configs: {
379
+ 'config-version': '1.3.7',
380
+ 'pos-operation-mode': 'unknown-mode',
381
+ },
382
+ },
383
+ })
384
+
385
+ assert.equal(debugInfo.operationModeLabel, '')
386
+ assert.equal(debugInfo.displayName, 'PDV (229252771069b294)')
387
+ assert.equal(debugInfo.primaryText, 'PDV (229252771069b294) / v1.3.7')
388
+ })
389
+
390
+ test('renders totem mode in the runtime footer', () => {
391
+ assert.equal(
392
+ getRuntimeFooterPrimaryText({
393
+ device: {
394
+ appVersion: '1.3.7',
395
+ deviceType: 'android',
396
+ id: 'native-13',
397
+ type: 'PDV',
398
+ },
399
+ appVersion: '1.3.6',
400
+ deviceConfig: {
401
+ device: {
402
+ device: '229252771069b294',
403
+ type: 'PDV',
404
+ },
405
+ configs: {
406
+ 'config-version': '1.3.7',
407
+ 'pos-operation-mode': 'totem',
408
+ },
409
+ },
410
+ }),
411
+ 'android • PDV • Totem / v1.3.7',
412
+ )
413
+ })
414
+
415
+ test('falls back to the local device id when the backend identifier is unavailable', () => {
416
+ const candidates = getRuntimeFooterNativeIdentifierCandidates({
417
+ device: {
418
+ id: 'local-device-99',
419
+ },
420
+ deviceConfig: {
421
+ device: {},
422
+ },
423
+ })
424
+
425
+ assert.equal(candidates[0]?.value, 'local-device-99')
426
+ assert.equal(candidates[0]?.source, 'device.id')
427
+ })
428
+
429
+ jestCompatTest('jest compatibility placeholder', () => {})
@@ -0,0 +1,80 @@
1
+ const assert = require('node:assert/strict')
2
+ const test = require('node:test')
3
+
4
+ const {
5
+ normalizeLanguageCode,
6
+ resolveCompanyLanguageCode,
7
+ resolveConfiguredLanguage,
8
+ } = require('../../../react/utils/runtimeLanguage')
9
+
10
+ test('normalizes language separators and casing', () => {
11
+ assert.equal(normalizeLanguageCode(' PT_BR '), 'pt-br')
12
+ })
13
+
14
+ test('reads the current company language from nested company settings first', () => {
15
+ assert.equal(
16
+ resolveCompanyLanguageCode({
17
+ language: {
18
+ locale: 'en_US',
19
+ },
20
+ }),
21
+ 'en-us',
22
+ )
23
+ })
24
+
25
+ test('prefers the selected company language over cached config and session values', () => {
26
+ assert.equal(
27
+ resolveConfiguredLanguage({
28
+ currentCompany: {
29
+ language: {
30
+ code: 'en_US',
31
+ },
32
+ },
33
+ defaultCompany: {
34
+ language: {
35
+ code: 'pt_BR',
36
+ },
37
+ },
38
+ currentConfig: {
39
+ language: 'es-es',
40
+ },
41
+ sessionData: {
42
+ language: 'fr-fr',
43
+ },
44
+ }),
45
+ 'en-us',
46
+ )
47
+ })
48
+
49
+ test('falls back through default company, config, session and the hardcoded default', () => {
50
+ assert.equal(
51
+ resolveConfiguredLanguage({
52
+ defaultCompany: {
53
+ configs: {
54
+ language: 'de_DE',
55
+ },
56
+ },
57
+ }),
58
+ 'de-de',
59
+ )
60
+
61
+ assert.equal(
62
+ resolveConfiguredLanguage({
63
+ currentConfig: {
64
+ language: 'it_IT',
65
+ },
66
+ }),
67
+ 'it-it',
68
+ )
69
+
70
+ assert.equal(
71
+ resolveConfiguredLanguage({
72
+ sessionData: {
73
+ language: 'nl_NL',
74
+ },
75
+ }),
76
+ 'nl-nl',
77
+ )
78
+
79
+ assert.equal(resolveConfiguredLanguage({}), 'pt-br')
80
+ })
@@ -0,0 +1,174 @@
1
+ /* global describe, expect, it */
2
+
3
+ import {
4
+ filterRuntimeMenuModulesByType,
5
+ flattenRuntimeMenuItemsByType,
6
+ getRuntimeMenuRoutes,
7
+ normalizeRuntimeMenuIcon,
8
+ normalizeRuntimeMenuResponse,
9
+ userHasRole,
10
+ } from '../../../react/utils/runtimeMenu';
11
+
12
+ describe('runtimeMenu', () => {
13
+ it('normalizes legacy menu response modules into sorted arrays', () => {
14
+ const result = normalizeRuntimeMenuResponse({
15
+ response: {
16
+ data: {
17
+ modules: {
18
+ 2: {
19
+ id: 2,
20
+ label: 'Config',
21
+ menus: [
22
+ {id: 20, label: 'B', route: 'BRoute', sortOrder: 20, menuType: 'toolbar'},
23
+ {id: 10, label: 'A', route: 'ARoute', sortOrder: 10},
24
+ ],
25
+ },
26
+ },
27
+ },
28
+ },
29
+ });
30
+
31
+ expect(result).toHaveLength(1);
32
+ expect(result[0].menus.map(item => item.route)).toEqual(['ARoute', 'BRoute']);
33
+ expect(result[0].menus[0].routeParams).toEqual({});
34
+ expect(result[0].menus.map(item => item.menuType)).toEqual(['home', 'toolbar']);
35
+ });
36
+
37
+ it('builds route sets and checks super admin role', () => {
38
+ const menus = normalizeRuntimeMenuResponse({
39
+ modules: {
40
+ 1: {
41
+ menus: [
42
+ {route: 'HomePage'},
43
+ {route: 'MenuAccessConfigPage'},
44
+ ],
45
+ },
46
+ },
47
+ });
48
+
49
+ expect(getRuntimeMenuRoutes(menus).has('MenuAccessConfigPage')).toBe(true);
50
+ expect(userHasRole({roles: ['ROLE_SUPER']}, 'ROLE_SUPER')).toBe(true);
51
+ expect(userHasRole({roles: ['ROLE_MANAGER']}, 'ROLE_SUPER')).toBe(false);
52
+ });
53
+
54
+ it('normalizes snake_case menu_key into menuKey for runtime usage', () => {
55
+ const menus = normalizeRuntimeMenuResponse({
56
+ modules: {
57
+ 1: {
58
+ menus: [
59
+ {menu_key: 'menu_access', route: 'MenuAccessConfigPage'},
60
+ ],
61
+ },
62
+ },
63
+ });
64
+
65
+ expect(menus[0].menus[0].menuKey).toBe('menu_access');
66
+ });
67
+
68
+ it('converts legacy material icon names into feather-compatible names', () => {
69
+ expect(normalizeRuntimeMenuIcon('shopping_cart')).toBe('shopping-cart');
70
+ expect(normalizeRuntimeMenuIcon('account_balance')).toBe('dollar-sign');
71
+
72
+ const menus = normalizeRuntimeMenuResponse({
73
+ modules: {
74
+ 1: {
75
+ icon: 'account_balance',
76
+ menus: [
77
+ {
78
+ icon: 'shopping_cart',
79
+ route: 'OrderHistoryPage',
80
+ },
81
+ ],
82
+ },
83
+ },
84
+ });
85
+
86
+ expect(menus[0].icon).toBe('dollar-sign');
87
+ expect(menus[0].menus[0].icon).toBe('shopping-cart');
88
+ });
89
+
90
+ it('filters runtime menu modules by menu type', () => {
91
+ const menus = normalizeRuntimeMenuResponse({
92
+ modules: {
93
+ 1: {
94
+ label: 'Mix',
95
+ menus: [
96
+ {route: 'HomePage', menuType: 'home', sortOrder: 10},
97
+ {route: 'ToolbarPage', menu_type: 'toolbar', sortOrder: 20},
98
+ ],
99
+ },
100
+ },
101
+ });
102
+
103
+ const toolbarMenus = filterRuntimeMenuModulesByType(menus, 'toolbar');
104
+
105
+ expect(toolbarMenus).toHaveLength(1);
106
+ expect(toolbarMenus[0].menus).toHaveLength(1);
107
+ expect(toolbarMenus[0].menus[0]).toMatchObject({
108
+ route: 'ToolbarPage',
109
+ menuType: 'toolbar',
110
+ });
111
+ expect(
112
+ flattenRuntimeMenuItemsByType(menus, 'toolbar').map(item => item.route),
113
+ ).toEqual(['ToolbarPage']);
114
+ });
115
+
116
+ it('falls back to the manager core menu when the runtime menu is empty', () => {
117
+ const menus = normalizeRuntimeMenuResponse({}, {appType: 'MANAGER'});
118
+
119
+ expect(menus).toHaveLength(4);
120
+ expect(menus.map(module => module.label)).toEqual([
121
+ 'Financeiro',
122
+ 'Operacoes',
123
+ 'Comercial',
124
+ 'Configuracoes',
125
+ ]);
126
+ expect(menus[0].menus[0]).toMatchObject({
127
+ label: 'Financeiro',
128
+ menuKey: 'financial_hub',
129
+ route: 'FinancialHubPage',
130
+ menuType: 'home',
131
+ });
132
+ expect(menus[0].menus[1]).toMatchObject({
133
+ label: 'Demonstrativo de resultados',
134
+ menuKey: 'income_statement',
135
+ route: 'IncomeStatement',
136
+ });
137
+ expect(menus[1].menus[0]).toMatchObject({
138
+ label: 'Pedidos',
139
+ menuKey: 'orders',
140
+ route: 'OrderHistoryPage',
141
+ });
142
+ expect(menus[2].menus[0]).toMatchObject({
143
+ label: 'Cadastro de clientes',
144
+ menuKey: 'clients',
145
+ route: 'ClientsIndex',
146
+ });
147
+ expect(menus[2].menus[1]).toMatchObject({
148
+ label: 'Fornecedores',
149
+ menuKey: 'providers',
150
+ route: 'ProvidersIndex',
151
+ });
152
+ expect(menus[2].menus[2]).toMatchObject({
153
+ label: 'Cadastro de franquias',
154
+ menuKey: 'franchisees',
155
+ route: 'FranchiseesIndex',
156
+ });
157
+ expect(menus[3].menus.map(item => item.route)).toEqual([
158
+ 'DevicesIndex',
159
+ 'ConfiguratorPage',
160
+ ]);
161
+ });
162
+
163
+ it('does not invent a fallback menu for non-manager apps', () => {
164
+ expect(
165
+ normalizeRuntimeMenuResponse({}, {appType: 'SHOP'}),
166
+ ).toEqual([]);
167
+ });
168
+
169
+ it('can skip the manager fallback when requested', () => {
170
+ expect(
171
+ normalizeRuntimeMenuResponse({}, {appType: 'MANAGER', allowFallback: false}),
172
+ ).toEqual([]);
173
+ });
174
+ });