@controleonline/ui-common 1.2.71 → 1.2.76

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 (150) 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 +3 -2
  11. package/src/api/fetch.js +1 -1
  12. package/src/api/index.js +92 -22
  13. package/src/react/components/AddImportModal.js +94 -110
  14. package/src/react/components/BottomNavigationBar.config.js +211 -231
  15. package/src/react/components/CategoryForm.js +52 -35
  16. package/src/react/components/DefaultProvider.native.js +131 -95
  17. package/src/react/components/DefaultProvider.web.js +148 -74
  18. package/src/react/components/NetworkPrinterPreviewModal.js +249 -0
  19. package/src/react/components/NetworkPrinterPreviewModal.styles.js +159 -0
  20. package/src/react/components/RuntimeInfoFooter.js +164 -104
  21. package/src/react/components/RuntimeInfoFooter.styles.js +3 -0
  22. package/src/react/components/UnifiedPaymentBar.js +7 -4
  23. package/src/react/components/UserAvatar.js +160 -0
  24. package/src/react/components/integrations/IntegrationHero/index.js +48 -0
  25. package/src/react/components/integrations/IntegrationHero/styles.js +57 -0
  26. package/src/react/components/integrations/IntegrationTabs/index.js +89 -0
  27. package/src/react/components/integrations/IntegrationTabs/styles.js +44 -0
  28. package/src/react/config/deviceConfigBootstrap.js +5 -3
  29. package/src/react/pages/Connections.js +229 -0
  30. package/src/react/pages/Connections.styles.js +222 -0
  31. package/src/react/pages/DeviceDetailPage.js +2865 -0
  32. package/src/react/pages/DeviceDetailPage.styles.js +308 -0
  33. package/src/react/pages/Devices/currentDevice.js +194 -0
  34. package/src/react/pages/Devices/deviceTypes/DeviceGroupCard.js +257 -0
  35. package/src/react/pages/Devices/deviceTypes/all.js +23 -0
  36. package/src/react/pages/Devices/deviceTypes/device.js +25 -0
  37. package/src/react/pages/Devices/deviceTypes/deviceListHelpers.js +178 -0
  38. package/src/react/pages/Devices/deviceTypes/display.js +26 -0
  39. package/src/react/pages/Devices/deviceTypes/index.js +145 -0
  40. package/src/react/pages/Devices/deviceTypes/ipCamera.js +35 -0
  41. package/src/react/pages/Devices/deviceTypes/pdv.js +88 -0
  42. package/src/react/pages/Devices/deviceTypes/printer.js +37 -0
  43. package/src/react/pages/Devices/deviceTypes/shared.js +453 -0
  44. package/src/react/pages/Devices/deviceTypes/useDeviceNetworkConnectivity.js +145 -0
  45. package/src/react/pages/Devices.js +183 -0
  46. package/src/react/pages/Devices.styles.js +353 -0
  47. package/src/react/pages/Food99IntegrationPage/components/Food99CatalogTab.js +219 -0
  48. package/src/react/pages/Food99IntegrationPage/components/Food99OverviewTab.js +134 -0
  49. package/src/react/pages/Food99IntegrationPage/components/Food99PreviewModal.js +173 -0
  50. package/src/react/pages/Food99IntegrationPage/components/Food99ProductCard.js +1 -0
  51. package/src/react/pages/Food99IntegrationPage/components/Food99QuickWalletModal.js +181 -0
  52. package/src/react/pages/Food99IntegrationPage/components/Food99SettingsTab.js +306 -0
  53. package/src/react/pages/Food99IntegrationPage/components/Food99StoreTab.js +417 -0
  54. package/src/react/pages/Food99IntegrationPage/index.js +1530 -0
  55. package/src/react/pages/Food99IntegrationPage/styles.js +792 -0
  56. package/src/react/pages/Food99IntegrationPage/utils.js +155 -0
  57. package/src/react/pages/Food99IntegrationPage.js +1 -0
  58. package/src/react/pages/Food99IntegrationPage.styles.js +1 -0
  59. package/src/react/pages/GenericLogPage.js +42 -167
  60. package/src/react/pages/IFoodIntegrationPage/components/IFoodCatalogTab.js +8 -0
  61. package/src/react/pages/IFoodIntegrationPage/components/IFoodOperationsTab.js +420 -0
  62. package/src/react/pages/IFoodIntegrationPage/components/IFoodOverviewTab.js +118 -0
  63. package/src/react/pages/IFoodIntegrationPage/components/IFoodPreviewModal.js +103 -0
  64. package/src/react/pages/IFoodIntegrationPage/components/IFoodProductCard.js +1 -0
  65. package/src/react/pages/IFoodIntegrationPage/components/IFoodStoreTab.js +150 -0
  66. package/src/react/pages/IFoodIntegrationPage/index.js +1298 -0
  67. package/src/react/pages/IFoodIntegrationPage/styles.js +764 -0
  68. package/src/react/pages/IFoodIntegrationPage/utils.js +91 -0
  69. package/src/react/pages/IFoodIntegrationPage.js +1 -0
  70. package/src/react/pages/IFoodIntegrationPage.styles.js +1 -0
  71. package/src/react/pages/Imports.js +214 -166
  72. package/src/react/pages/IntegrationConfigPage.js +744 -0
  73. package/src/react/pages/IntegrationConfigPage.styles.js +269 -0
  74. package/src/react/pages/Integrations.js +299 -0
  75. package/src/react/pages/Integrations.styles.js +130 -0
  76. package/src/react/pages/ManagerOrderNotificationsPage.js +642 -0
  77. package/src/react/pages/MarketplaceIntegrationPage/components/MarketplaceProductCard.js +205 -0
  78. package/src/react/pages/MarketplaceIntegrationPage/components/RemoteMarketplaceMenuTab.js +335 -0
  79. package/src/react/pages/MarketplaceIntegrationPage/index.js +60 -0
  80. package/src/react/pages/MarketplaceIntegrationPage/utils.js +137 -0
  81. package/src/react/pages/MercadoLivreIntegrationPage.js +538 -0
  82. package/src/react/pages/PrinterDeviceDetailPage.js +1916 -0
  83. package/src/react/pages/PrinterDeviceDetailPage.styles.js +223 -0
  84. package/src/react/pages/PrinterDeviceFormPage.js +521 -0
  85. package/src/react/pages/PrinterDeviceFormPage.styles.js +159 -0
  86. package/src/react/pages/WhatsAppConnectionPage.js +459 -0
  87. package/src/react/pages/WhatsAppConnectionPage.styles.js +284 -0
  88. package/src/react/pages/integrationsCatalog.js +399 -0
  89. package/src/react/services/Cielo/Cielo.js +193 -193
  90. package/src/react/utils/categoryContexts.js +46 -0
  91. package/src/react/utils/entityDisplay.js +222 -217
  92. package/src/react/utils/fileUrl.js +39 -20
  93. package/src/react/utils/integrationPage.js +16 -0
  94. package/src/react/utils/runtimeFooter.js +40 -22
  95. package/src/react/utils/runtimeLanguage.js +50 -9
  96. package/src/react/utils/runtimeMenu.js +191 -131
  97. package/src/react/utils/runtimeZoom.js +43 -0
  98. package/src/react/utils/shopConfig.js +27 -15
  99. package/src/react/utils/shopFranchises.js +126 -1
  100. package/src/react/utils/storeColumns.js +23 -4
  101. package/src/react/utils/userAvatar.js +104 -0
  102. package/src/store/categories/index.js +48 -20
  103. package/src/store/device_config/index.js +62 -21
  104. package/src/store/entity_log/index.js +22 -1
  105. package/src/store/imports/customActions.js +51 -0
  106. package/src/store/imports/index.js +89 -88
  107. package/src/store/status/index.js +11 -11
  108. package/src/store/themes/index.js +22 -0
  109. package/src/store/timezones/index.js +3 -1
  110. package/src/tests/browser/manager/connections.spec.js +143 -0
  111. package/src/tests/browser/manager/devices-current.spec.js +343 -0
  112. package/src/tests/browser/manager/mercado-livre-integration.spec.js +268 -0
  113. package/src/tests/react/api/deviceHeaders.test.js +114 -0
  114. package/src/tests/react/components/AddImportModal.test.js +166 -0
  115. package/src/tests/react/components/BottomNavigationBar.config.test.js +20 -0
  116. package/src/tests/react/components/UserAvatar.test.js +52 -0
  117. package/src/tests/react/pages/DevicePickerStyles.test.js +47 -0
  118. package/src/tests/react/pages/Devices/deviceListHelpers.test.js +39 -0
  119. package/src/tests/react/pages/DevicesCurrentDevice.test.js +198 -0
  120. package/src/tests/react/pages/connectionsHelpers.test.js +34 -0
  121. package/src/tests/react/pages/mercadoLivreIntegrationPageContract.test.mjs +16 -0
  122. package/src/tests/react/services/Cielo.test.js +190 -190
  123. package/src/tests/react/store/categoriesOrdering.test.js +26 -0
  124. package/src/tests/react/utils/appDomain.test.js +62 -0
  125. package/src/tests/react/utils/entityDisplay.test.js +2 -2
  126. package/src/tests/react/utils/fileUrl.test.js +3 -3
  127. package/src/tests/react/utils/formatter.test.mjs +33 -0
  128. package/src/tests/react/utils/runtimeFooter.test.js +29 -4
  129. package/src/tests/react/utils/runtimeLanguage.test.js +79 -9
  130. package/src/tests/react/utils/runtimeMenu.test.js +198 -134
  131. package/src/tests/react/utils/runtimeZoom.test.js +38 -0
  132. package/src/tests/react/utils/shopConfig.test.js +10 -9
  133. package/src/tests/react/utils/shopFranchises.test.js +43 -0
  134. package/src/tests/react/utils/translate.discovery.test.mjs +170 -0
  135. package/src/tests/react/utils/translate.test.helpers.mjs +152 -0
  136. package/src/tests/react/utils/translate.test.mjs +141 -349
  137. package/src/tests/react/utils/userAvatar.test.js +63 -0
  138. package/src/utils/appDomain.js +15 -4
  139. package/src/utils/formatter.js +104 -16
  140. package/src/utils/integrationConfigs.js +80 -80
  141. package/src/utils/translate.js +222 -438
  142. package/src/utils/translateDiscovery.js +96 -0
  143. package/src/utils/translateNormalize.js +33 -0
  144. package/src/utils/translatePending.js +92 -0
  145. package/src/utils/translateStorage.js +142 -0
  146. package/.github/agents/developer.agent.md +0 -30
  147. package/.github/agents/devops.agent.md +0 -30
  148. package/.github/agents/qa.agent.md +0 -30
  149. package/.github/agents/security.agent.md +0 -30
  150. package/.github/workflows/npmjs.yml +0 -35
@@ -1,239 +1,92 @@
1
- import assert from 'node:assert/strict';
2
- import test from 'node:test';
3
-
4
- import Translate from '../../../utils/translate.js';
5
-
6
- const installLocalStorage = (config = {language: 'pt-br'}) => {
7
- const storage = {};
8
-
9
- global.localStorage = {
10
- getItem(key) {
11
- return Object.prototype.hasOwnProperty.call(storage, key)
12
- ? storage[key]
13
- : null;
14
- },
15
- setItem(key, value) {
16
- storage[key] = String(value);
17
- },
18
- removeItem(key) {
19
- delete storage[key];
20
- },
21
- };
22
-
23
- localStorage.setItem('config', JSON.stringify(config));
24
- localStorage.setItem('translates', JSON.stringify({}));
25
- delete global.refreshTranslationsUI;
26
- };
27
-
28
- const flushAsync = async () => {
29
- await Promise.resolve();
30
- await new Promise(resolve => setTimeout(resolve, 0));
31
- await new Promise(resolve => setTimeout(resolve, 0));
32
- };
33
-
34
- const normalizeLanguage = value =>
35
- String(value || '')
36
- .trim()
37
- .replace(/_/g, '-')
38
- .toLowerCase();
39
-
40
- const normalizeId = value =>
41
- String(value || '')
42
- .replace(/\D+/g, '')
43
- .trim();
44
-
45
- const createPendingTranslateStore = ({getItems, save, resolveQueuedMessages} = {}) => {
46
- const store = {
47
- getters: {
48
- messages: {},
49
- pendingMessages: {},
50
- },
51
- actions: {},
52
- };
53
-
54
- const translateActions = {
55
- getItems: getItems || (async () => []),
56
- save: save || (async payload => payload),
57
- ...(resolveQueuedMessages
58
- ? {
59
- resolveQueuedMessages,
60
- }
61
- : {}),
62
- setMessages: nextMessages => {
63
- store.getters.messages = nextMessages;
64
- return nextMessages;
65
- },
66
- setPendingMessages: nextMessages => {
67
- store.getters.pendingMessages = nextMessages;
68
- return nextMessages;
69
- },
70
- queueMissingTranslate: ({language, companyId, store: storeName, type, key, translate}) => {
71
- const normalizedLanguage = normalizeLanguage(language);
72
- const normalizedCompanyId = normalizeId(companyId);
73
- const pendingMessages = store.getters.pendingMessages;
74
-
75
- if (!pendingMessages[normalizedLanguage]) {
76
- pendingMessages[normalizedLanguage] = {};
77
- }
78
-
79
- if (!pendingMessages[normalizedLanguage].companies) {
80
- pendingMessages[normalizedLanguage].companies = {};
81
- }
82
-
83
- if (!pendingMessages[normalizedLanguage].companies[normalizedCompanyId]) {
84
- pendingMessages[normalizedLanguage].companies[normalizedCompanyId] = {};
85
- }
86
-
87
- if (!pendingMessages[normalizedLanguage].companies[normalizedCompanyId][storeName]) {
88
- pendingMessages[normalizedLanguage].companies[normalizedCompanyId][storeName] = {};
89
- }
90
-
91
- if (
92
- !pendingMessages[normalizedLanguage].companies[normalizedCompanyId][storeName][type]
93
- ) {
94
- pendingMessages[normalizedLanguage].companies[normalizedCompanyId][storeName][type] =
95
- {};
96
- }
97
-
98
- pendingMessages[normalizedLanguage].companies[normalizedCompanyId][storeName][type][
99
- key
100
- ] = translate;
101
-
102
- return pendingMessages;
103
- },
104
- removePendingTranslate: ({language, companyId, store: storeName, type, key}) => {
105
- const normalizedLanguage = normalizeLanguage(language);
106
- const normalizedCompanyId = normalizeId(companyId);
107
- const pendingMessages = store.getters.pendingMessages;
108
- const languageBucket = pendingMessages[normalizedLanguage];
109
- const companyBucket = languageBucket?.companies?.[normalizedCompanyId];
110
- const storeBucket = companyBucket?.[storeName];
111
- const typeBucket = storeBucket?.[type];
112
-
113
- if (!typeBucket || !Object.prototype.hasOwnProperty.call(typeBucket, key)) {
114
- return pendingMessages;
115
- }
116
-
117
- delete typeBucket[key];
118
-
119
- if (Object.keys(typeBucket).length === 0) {
120
- delete storeBucket[type];
121
- }
122
-
123
- if (Object.keys(storeBucket).length === 0) {
124
- delete companyBucket[storeName];
125
- }
126
-
127
- if (Object.keys(companyBucket).length === 0) {
128
- delete languageBucket.companies[normalizedCompanyId];
129
- }
130
-
131
- if (Object.keys(languageBucket.companies || {}).length === 0) {
132
- delete languageBucket.companies;
133
- }
134
-
135
- if (Object.keys(languageBucket).length === 0) {
136
- delete pendingMessages[normalizedLanguage];
137
- }
138
-
139
- return pendingMessages;
140
- },
141
- };
142
-
143
- store.actions = translateActions;
144
-
145
- return {
146
- getters: store.getters,
147
- actions: store.actions,
148
- get messages() {
149
- return store.getters.messages;
150
- },
151
- get pendingMessages() {
152
- return store.getters.pendingMessages;
153
- },
154
- translateActions,
155
- };
156
- };
157
-
158
- test('prefers the current company translation and falls back to the default company', () => {
159
- installLocalStorage();
160
-
161
- const translateStore = createPendingTranslateStore();
162
- const translate = new Translate(
163
- [{id: 1}, {id: 5}],
164
- {id: 1},
165
- {id: 5},
166
- ['orders'],
167
- translateStore,
168
- );
169
-
170
- translate.findMessage('orders', 'label', 'save', 'Salvar', 1);
171
- translate.findMessage('orders', 'label', 'save', 'Save for ASC', 5);
172
- translate.persist();
173
-
174
- assert.equal(translate.t('orders', 'label', 'save'), 'Save for ASC');
175
-
176
- const fallbackTranslate = new Translate(
177
- [{id: 1}, {id: 5}],
178
- {id: 1},
179
- {id: 9},
180
- ['orders'],
181
- createPendingTranslateStore(),
182
- );
183
-
184
- assert.equal(fallbackTranslate.t('orders', 'label', 'save'), 'Salvar');
185
- });
186
-
187
- test('keeps the translate method bound when passed as a standalone function', () => {
188
- installLocalStorage();
189
-
190
- const translate = new Translate(
191
- [{id: 1}],
192
- {id: 1},
193
- {id: 1},
194
- ['orders'],
195
- createPendingTranslateStore(),
196
- );
197
-
198
- translate.findMessage('orders', 'label', 'save', 'Salvar', 1);
199
-
200
- const detachedTranslate = translate.t;
201
-
202
- assert.equal(detachedTranslate('orders', 'label', 'save'), 'Salvar');
203
- });
204
-
205
- test('hydrates the cached translations into the translate store on startup', () => {
206
- installLocalStorage();
207
- localStorage.setItem('translates', JSON.stringify({
208
- 'pt-br': {
209
- companies: {
210
- 1: {
211
- invoice: {
212
- label: {
213
- accountsReceivable: 'Contas a receber',
214
- },
215
- },
216
- },
217
- },
218
- },
219
- }));
220
-
221
- const translateStore = createPendingTranslateStore();
222
-
223
- new Translate(
224
- [{id: 1}],
225
- {id: 1},
226
- {id: 1},
227
- ['invoice'],
228
- translateStore,
229
- );
230
-
231
- assert.equal(
232
- translateStore.messages['pt-br'].companies[1].invoice.label.accountsReceivable,
233
- 'Contas a receber',
234
- );
235
- });
236
-
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+
4
+ import Translate from '../../../utils/translate.js';
5
+ import {
6
+ installLocalStorage,
7
+ flushAsync,
8
+ createPendingTranslateStore,
9
+ } from './translate.test.helpers.mjs';
10
+
11
+ test('prefers the current company translation and falls back to the default company', () => {
12
+ installLocalStorage();
13
+
14
+ const translateStore = createPendingTranslateStore();
15
+ const translate = new Translate(
16
+ [{id: 1}, {id: 5}],
17
+ {id: 1},
18
+ {id: 5},
19
+ ['orders'],
20
+ translateStore,
21
+ );
22
+
23
+ translate.findMessage('orders', 'label', 'save', 'Salvar', 1);
24
+ translate.findMessage('orders', 'label', 'save', 'Save for ASC', 5);
25
+ translate.persist();
26
+
27
+ assert.equal(translate.t('orders', 'label', 'save'), 'Save for ASC');
28
+
29
+ const fallbackTranslate = new Translate(
30
+ [{id: 1}, {id: 5}],
31
+ {id: 1},
32
+ {id: 9},
33
+ ['orders'],
34
+ createPendingTranslateStore(),
35
+ );
36
+
37
+ assert.equal(fallbackTranslate.t('orders', 'label', 'save'), 'Salvar');
38
+ });
39
+
40
+ test('keeps the translate method bound when passed as a standalone function', () => {
41
+ installLocalStorage();
42
+
43
+ const translate = new Translate(
44
+ [{id: 1}],
45
+ {id: 1},
46
+ {id: 1},
47
+ ['orders'],
48
+ createPendingTranslateStore(),
49
+ );
50
+
51
+ translate.findMessage('orders', 'label', 'save', 'Salvar', 1);
52
+
53
+ const detachedTranslate = translate.t;
54
+
55
+ assert.equal(detachedTranslate('orders', 'label', 'save'), 'Salvar');
56
+ });
57
+
58
+ test('hydrates the cached translations into the translate store on startup', () => {
59
+ installLocalStorage();
60
+ localStorage.setItem('translates', JSON.stringify({
61
+ 'pt-br': {
62
+ companies: {
63
+ 1: {
64
+ invoice: {
65
+ label: {
66
+ accountsReceivable: 'Contas a receber',
67
+ },
68
+ },
69
+ },
70
+ },
71
+ },
72
+ }));
73
+
74
+ const translateStore = createPendingTranslateStore();
75
+
76
+ new Translate(
77
+ [{id: 1}],
78
+ {id: 1},
79
+ {id: 1},
80
+ ['invoice'],
81
+ translateStore,
82
+ );
83
+
84
+ assert.equal(
85
+ translateStore.messages['pt-br'].companies[1].invoice.label.accountsReceivable,
86
+ 'Contas a receber',
87
+ );
88
+ });
89
+
237
90
  test('posts missing translations once and keeps the fallback until review', async () => {
238
91
  installLocalStorage();
239
92
 
@@ -242,36 +95,36 @@ test('posts missing translations once and keeps the fallback until review', asyn
242
95
  save: async payload => {
243
96
  saveCalls.push(payload);
244
97
  return payload;
245
- },
246
- });
247
- const translate = new Translate(
248
- [{id: 1}],
249
- {id: 1},
250
- {id: 1},
251
- ['contract'],
252
- translateStore,
253
- );
254
-
255
- const firstValue = translate.t('contract', 'empty', 'none_registered_title');
256
- const secondValue = translate.t('contract', 'empty', 'none_registered_title');
257
-
258
- assert.equal(firstValue, 'None registered title');
259
- assert.equal(secondValue, 'None registered title');
98
+ },
99
+ });
100
+ const translate = new Translate(
101
+ [{id: 1}],
102
+ {id: 1},
103
+ {id: 1},
104
+ ['contract'],
105
+ translateStore,
106
+ );
107
+
108
+ const firstValue = translate.t('contract', 'empty', 'none_registered_title');
109
+ const secondValue = translate.t('contract', 'empty', 'none_registered_title');
110
+
111
+ assert.equal(firstValue, 'None registered title');
112
+ assert.equal(secondValue, 'None registered title');
260
113
 
261
114
  await flushAsync();
262
115
 
263
116
  assert.deepEqual(saveCalls, [{
264
117
  people: '/people/1',
265
118
  language: 'pt-br',
266
- store: 'contract',
267
- type: 'empty',
268
- key: 'none_registered_title',
269
- translate: 'None registered title',
270
- revised: false,
271
- }]);
272
- assert.equal(
273
- translateStore.messages['pt-br'].companies[1].contract.empty.none_registered_title,
274
- 'None registered title',
119
+ store: 'contract',
120
+ type: 'empty',
121
+ key: 'none_registered_title',
122
+ translate: 'None registered title',
123
+ revised: false,
124
+ }]);
125
+ assert.equal(
126
+ translateStore.messages['pt-br'].companies[1].contract.empty.none_registered_title,
127
+ 'None registered title',
275
128
  );
276
129
  assert.deepEqual(translateStore.pendingMessages, {});
277
130
  });
@@ -359,102 +212,41 @@ test('queues missing translations in a single batch resolve request', async () =
359
212
  assert.deepEqual(translateStore.pendingMessages, {});
360
213
  });
361
214
 
362
- test('persists missing translations with the normalized configured language', async () => {
363
- installLocalStorage({language: 'en_US'});
364
-
365
- const saveCalls = [];
366
- const translateStore = createPendingTranslateStore({
367
- getItems: async () => [],
368
- save: async payload => {
369
- saveCalls.push(payload);
370
- return payload;
371
- },
372
- });
373
- const translate = new Translate(
374
- [{id: 1}],
375
- {id: 1},
376
- {id: 1},
377
- ['contract'],
378
- translateStore,
379
- );
380
-
381
- translate.t('contract', 'empty', 'none_registered_title');
382
-
383
- await flushAsync();
384
-
385
- assert.equal(
386
- saveCalls[0].language,
387
- 'en-us',
388
- );
389
- assert.equal(
390
- translateStore.messages['en-us'].companies[1].contract.empty.none_registered_title,
391
- 'None registered title',
392
- );
393
- });
394
-
395
- test('loads each requested store only for the current and default companies', async () => {
215
+ test('falls back past empty current-company values to the main company', () => {
396
216
  installLocalStorage();
217
+ const translateStore = createPendingTranslateStore();
218
+ const currentCompany = {id: 2};
219
+ const defaultCompany = {id: 1};
220
+ const translate = new Translate(
221
+ [defaultCompany, currentCompany],
222
+ defaultCompany,
223
+ currentCompany,
224
+ ['orders'],
225
+ translateStore,
226
+ );
397
227
 
398
- const calls = [];
399
- const translate = new Translate(
400
- [{id: 1}, {id: 5}, {id: 9}],
401
- {id: 1},
402
- {id: 5},
403
- ['orders', 'crm'],
404
- createPendingTranslateStore({
405
- getItems: async params => {
406
- calls.push(params);
407
- return [];
408
- },
409
- }),
410
- );
411
-
412
- await translate.discoveryAll();
413
-
414
- assert.deepEqual(calls, [
415
- {store: 'orders', 'language.language': 'pt-br', people: '/people/1', page: 1},
416
- {store: 'crm', 'language.language': 'pt-br', people: '/people/1', page: 1},
417
- {store: 'orders', 'language.language': 'pt-br', people: '/people/5', page: 1},
418
- {store: 'crm', 'language.language': 'pt-br', people: '/people/5', page: 1},
419
- ]);
228
+ translate.findMessage('orders', 'menu', 'overview', ' ', 2, 'pt-br');
229
+ translate.findMessage('orders', 'menu', 'overview', 'Visão geral', 1, 'pt-br');
230
+
231
+ assert.equal(translate.t('orders', 'menu', 'overview'), 'Visão geral');
420
232
  });
421
233
 
422
- test('reuses cached store buckets without refetching after in-memory discovery is cleared', async () => {
234
+ test('getResolveCompaniesToCache returns current and main companies', () => {
423
235
  installLocalStorage();
424
- localStorage.setItem('translates', JSON.stringify({
425
- 'pt-br': {
426
- companies: {
427
- 1: {
428
- contract: {
429
- empty: {
430
- none_registered_title: 'None registered title',
431
- },
432
- },
433
- },
434
- },
435
- },
436
- }));
437
-
438
- const calls = [];
439
- const translateStore = createPendingTranslateStore({
440
- getItems: async params => {
441
- calls.push(params);
442
- return [];
443
- },
444
- });
236
+ const translateStore = createPendingTranslateStore();
237
+ const currentCompany = {id: 2};
238
+ const defaultCompany = {id: 1};
445
239
  const translate = new Translate(
446
- [{id: 1}],
447
- {id: 1},
448
- {id: 1},
449
- ['contract'],
240
+ [defaultCompany, currentCompany],
241
+ defaultCompany,
242
+ currentCompany,
243
+ ['orders'],
450
244
  translateStore,
451
245
  );
452
246
 
453
- translate.discoveredStores.clear();
454
-
455
- assert.equal(translate.t('contract', 'empty', 'none_registered_title'), 'None registered title');
456
-
457
- await flushAsync();
458
-
459
- assert.deepEqual(calls, []);
247
+ const ids = translate
248
+ .getResolveCompaniesToCache()
249
+ .map((company) => String(company.id))
250
+ .sort();
251
+ assert.deepEqual(ids, ['1', '2']);
460
252
  });
@@ -0,0 +1,63 @@
1
+ import {
2
+ getAvatarDisplayName,
3
+ getGravatarUrl,
4
+ getUserInitials,
5
+ resolveUserAvatarUrl,
6
+ resolveUserPeopleIri,
7
+ } from '../../../react/utils/userAvatar';
8
+
9
+ describe('userAvatar', () => {
10
+ it('uses the first and last names for two initials', () => {
11
+ expect(getUserInitials({name: 'Alexandre AleMac Cunha'})).toBe('AC');
12
+ });
13
+
14
+ it('uses one initial for a single name', () => {
15
+ expect(getUserInitials({name: 'Alexandre'})).toBe('A');
16
+ });
17
+
18
+ it('falls back to the email initial when the name is unavailable', () => {
19
+ expect(getUserInitials({email: 'claudio@example.com'})).toBe('C');
20
+ });
21
+
22
+ it('asks Gravatar for a real image without an identicon fallback', () => {
23
+ expect(getGravatarUrl(' MyEmailAddress@example.com ', 120)).toBe(
24
+ 'https://www.gravatar.com/avatar/0bc83cb571cd1c50ba6f3e8a78ef1346?s=120&d=404',
25
+ );
26
+ });
27
+
28
+ it('does not call Gravatar without an email', () => {
29
+ expect(getGravatarUrl('')).toBe('');
30
+ });
31
+
32
+ it('uses the complete person name and never the functional alias', () => {
33
+ const user = {name: 'Claudio Medeiros', realname: 'Claudio Owner', alias: 'owner'};
34
+
35
+ expect(getAvatarDisplayName(user)).toBe('Claudio Medeiros');
36
+ expect(getUserInitials({name: getAvatarDisplayName(user)})).toBe('CM');
37
+ });
38
+
39
+ it('resolves the person from the same stored session shape used by Profile', () => {
40
+ expect(
41
+ resolveUserPeopleIri({id: 131}, {people: '/people/14'}),
42
+ ).toBe('/people/14');
43
+ });
44
+
45
+ it('resolves only the canonical persisted avatar', () => {
46
+ const user = {avatar: {id: 42}};
47
+
48
+ expect(
49
+ resolveUserAvatarUrl(user, avatar =>
50
+ avatar?.id ? `https://api.example/files/${avatar.id}/download` : '',
51
+ ),
52
+ ).toBe('https://api.example/files/42/download');
53
+ });
54
+
55
+ it('does not treat avatarUrl as an avatar source', () => {
56
+ expect(
57
+ resolveUserAvatarUrl(
58
+ {avatarUrl: 'https://legacy.example/avatar.png'},
59
+ () => '',
60
+ ),
61
+ ).toBe('');
62
+ });
63
+ });
@@ -1,4 +1,9 @@
1
- const toRawString = value => String(value || '').trim();
1
+ const INVALID_DOMAIN_VALUES = new Set(['undefined', 'null', 'false']);
2
+
3
+ const toRawString = value => {
4
+ const raw = String(value || '').trim();
5
+ return INVALID_DOMAIN_VALUES.has(raw.toLowerCase()) ? '' : raw;
6
+ };
2
7
 
3
8
  const tryParseHost = value => {
4
9
  const raw = toRawString(value);
@@ -23,10 +28,16 @@ export const resolveRuntimeHost = () => {
23
28
  return toRawString(location?.host || '');
24
29
  };
25
30
 
31
+ /**
32
+ * App-Domain header value.
33
+ * Browser (web multi-tenant): always prefer window.location.host so custom
34
+ * domains (e.g. erpjaguncos.com.br) resolve the correct tenant.
35
+ * Native / no location: fall back to configured DOMAIN from env.
36
+ */
26
37
  export const resolveAppDomain = configuredDomain => {
27
- const configuredHost = tryParseHost(configuredDomain);
28
- if (configuredHost) return configuredHost;
29
- return tryParseHost(resolveRuntimeHost());
38
+ const runtimeHost = tryParseHost(resolveRuntimeHost());
39
+ if (runtimeHost) return runtimeHost;
40
+ return tryParseHost(configuredDomain);
30
41
  };
31
42
 
32
43
  export const resolveCompanyDomain = (company, fallbackDomain = '') => {