@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,198 @@
1
+ /* global describe, expect, it, jest */
2
+
3
+ jest.mock(
4
+ '@controleonline/ui-common/src/react/utils/printerDevices',
5
+ () => ({
6
+ PDV_DEVICE_TYPE: 'PDV',
7
+ normalizeDeviceType: value =>
8
+ String(value || '')
9
+ .trim()
10
+ .toUpperCase(),
11
+ }),
12
+ );
13
+
14
+ jest.mock(
15
+ '@controleonline/ui-common/src/react/utils/paymentDevices',
16
+ () => ({
17
+ normalizeDeviceId: value => String(value || '').trim(),
18
+ normalizeEntityId: value =>
19
+ String(value || '')
20
+ .replace(/\D+/g, '')
21
+ .trim(),
22
+ }),
23
+ );
24
+
25
+ const {
26
+ findDeviceConfigByType,
27
+ filterDeviceConfigsByTypes,
28
+ getRuntimeDeviceIdentifier,
29
+ groupDeviceConfigs,
30
+ hasCurrentPdvConfig,
31
+ isCurrentDeviceConfig,
32
+ isCurrentDeviceGroup,
33
+ prioritizeCurrentDeviceGroups,
34
+ prioritizeCurrentDeviceConfigs,
35
+ readStoredRuntimeDevice,
36
+ } = require('../../../react/pages/Devices/currentDevice');
37
+
38
+ const currentManager = {
39
+ id: 487,
40
+ type: 'MANAGER',
41
+ device: {
42
+ id: 396,
43
+ device: '0d1bea46f3d93f07',
44
+ alias: 'Caixa',
45
+ },
46
+ };
47
+
48
+ const currentPdv = {
49
+ id: 488,
50
+ type: 'PDV',
51
+ device: {
52
+ id: 396,
53
+ device: '0d1bea46f3d93f07',
54
+ alias: 'Caixa',
55
+ },
56
+ };
57
+
58
+ const remotePdv = {
59
+ id: 410,
60
+ type: 'PDV',
61
+ device: {
62
+ id: 380,
63
+ device: '61378bea41ff1712',
64
+ alias: 'Outro caixa',
65
+ },
66
+ };
67
+
68
+ describe('current device profiles', () => {
69
+ it('reads the runtime identity from persisted device storage', () => {
70
+ const storage = {
71
+ getItem: jest.fn(() =>
72
+ JSON.stringify({
73
+ id: '0d1bea46f3d93f07',
74
+ type: 'MANAGER',
75
+ }),
76
+ ),
77
+ };
78
+
79
+ const runtimeDevice = readStoredRuntimeDevice(storage);
80
+
81
+ expect(getRuntimeDeviceIdentifier(runtimeDevice)).toBe(
82
+ '0d1bea46f3d93f07',
83
+ );
84
+ });
85
+
86
+ it('does not fail when persisted device storage is invalid', () => {
87
+ const storage = {
88
+ getItem: jest.fn(() => '{invalid'),
89
+ };
90
+
91
+ expect(readStoredRuntimeDevice(storage)).toEqual({});
92
+ });
93
+
94
+ it('recognizes every operational profile of the same physical device', () => {
95
+ const runtimeDeviceIdentifier = '0d1bea46f3d93f07';
96
+
97
+ expect(
98
+ isCurrentDeviceConfig({
99
+ deviceConfig: currentManager,
100
+ runtimeDeviceIdentifier,
101
+ }),
102
+ ).toBe(true);
103
+ expect(
104
+ isCurrentDeviceConfig({
105
+ deviceConfig: currentPdv,
106
+ runtimeDeviceIdentifier,
107
+ }),
108
+ ).toBe(true);
109
+ expect(
110
+ isCurrentDeviceConfig({
111
+ deviceConfig: remotePdv,
112
+ runtimeDeviceIdentifier,
113
+ }),
114
+ ).toBe(false);
115
+ });
116
+
117
+ it('pins all current profiles before remote devices without changing their order', () => {
118
+ expect(
119
+ prioritizeCurrentDeviceConfigs(
120
+ [remotePdv, currentManager, currentPdv],
121
+ '0d1bea46f3d93f07',
122
+ ).map(item => item.id),
123
+ ).toEqual([487, 488, 410]);
124
+ });
125
+
126
+ it('keeps only current PDV profiles when the PDV filter is active', () => {
127
+ expect(
128
+ filterDeviceConfigsByTypes(
129
+ [currentManager, currentPdv],
130
+ ['PDV'],
131
+ ).map(item => item.id),
132
+ ).toEqual([488]);
133
+ });
134
+
135
+ it('offers PDV setup only until the current physical device has a PDV profile', () => {
136
+ expect(
137
+ hasCurrentPdvConfig(
138
+ [currentManager, remotePdv],
139
+ '0d1bea46f3d93f07',
140
+ ),
141
+ ).toBe(false);
142
+ expect(
143
+ hasCurrentPdvConfig(
144
+ [currentManager, currentPdv, remotePdv],
145
+ '0d1bea46f3d93f07',
146
+ ),
147
+ ).toBe(true);
148
+ });
149
+
150
+ it('groups every configuration of the same Device into one card', () => {
151
+ const groups = groupDeviceConfigs([
152
+ remotePdv,
153
+ currentManager,
154
+ currentPdv,
155
+ ]);
156
+
157
+ expect(groups).toHaveLength(2);
158
+ expect(groups.find(group => group.key === 'device:396').deviceConfigs)
159
+ .toEqual([currentManager, currentPdv]);
160
+ });
161
+
162
+ it('pins the current Device once and resolves the session configuration by type', () => {
163
+ const groups = prioritizeCurrentDeviceGroups(
164
+ groupDeviceConfigs([remotePdv, currentManager, currentPdv]),
165
+ '0d1bea46f3d93f07',
166
+ );
167
+
168
+ expect(groups[0].key).toBe('device:396');
169
+ expect(
170
+ isCurrentDeviceGroup({
171
+ deviceGroup: groups[0],
172
+ runtimeDeviceIdentifier: '0d1bea46f3d93f07',
173
+ }),
174
+ ).toBe(true);
175
+ expect(findDeviceConfigByType(groups[0], 'PDV')).toBe(currentPdv);
176
+ });
177
+
178
+ it('creates a synthetic current Device group when it has no configuration yet', () => {
179
+ const groups = groupDeviceConfigs([remotePdv], {
180
+ includeRuntimeDevice: true,
181
+ runtimeDevice: {
182
+ id: 'new-device',
183
+ alias: 'Nova maquininha',
184
+ type: 'MANAGER',
185
+ },
186
+ });
187
+
188
+ expect(groups).toHaveLength(2);
189
+ expect(groups[1]).toMatchObject({
190
+ key: 'identifier:new-device',
191
+ device: {
192
+ device: 'new-device',
193
+ alias: 'Nova maquininha',
194
+ },
195
+ deviceConfigs: [],
196
+ });
197
+ });
198
+ });
@@ -0,0 +1,34 @@
1
+ /* global describe, expect, it */
2
+
3
+ describe('connections phone/status helpers (migration #288)', () => {
4
+ const formatPhoneLabel = phone => {
5
+ if (!phone) return 'Nao informado';
6
+ if (typeof phone === 'string') return phone;
7
+ const ddd = String(phone?.ddd || '').trim();
8
+ const digits = String(phone?.phone || '').replace(/\D/g, '');
9
+ if (!ddd && !digits) return 'Nao informado';
10
+ if (!digits) return `(${ddd})`;
11
+ const lastFour = digits.slice(-4);
12
+ const firstPart = digits.slice(0, -4);
13
+ if (!firstPart) return ddd ? `(${ddd}) ${lastFour}` : lastFour;
14
+ return ddd ? `(${ddd}) ${firstPart}-${lastFour}` : `${firstPart}-${lastFour}`;
15
+ };
16
+
17
+ const formatStatusLabel = status => {
18
+ if (!status) return '—';
19
+ if (typeof status === 'string') return status;
20
+ return status?.status || status?.name || '—';
21
+ };
22
+
23
+ it('formats phone objects', () => {
24
+ expect(formatPhoneLabel({ddd: '11', phone: '988887777'})).toBe('(11) 98888-7777');
25
+ });
26
+
27
+ it('handles missing phone', () => {
28
+ expect(formatPhoneLabel(null)).toBe('Nao informado');
29
+ });
30
+
31
+ it('formats status objects', () => {
32
+ expect(formatStatusLabel({status: 'connected'})).toBe('connected');
33
+ });
34
+ });
@@ -0,0 +1,16 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readFileSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+ import test from 'node:test';
5
+
6
+ const pagePath = resolve('modules/controleonline/ui-common/src/react/pages/MercadoLivreIntegrationPage.js');
7
+ const pageSource = readFileSync(pagePath, 'utf8');
8
+
9
+ test('MercadoLivreIntegrationPage keeps the backend contract for connect and import actions', () => {
10
+ assert.match(pageSource, /\/marketplace\/integrations\/mercadolivre\/authorization-page/);
11
+ assert.match(pageSource, /\/marketplace\/integrations\/mercadolivre\/products\/import/);
12
+ assert.match(pageSource, /provider_id/);
13
+ assert.match(pageSource, /showcase_id/);
14
+ assert.doesNotMatch(pageSource, /callback_url/);
15
+ assert.doesNotMatch(pageSource, /mercadolivre\/oauth\/return/);
16
+ });
@@ -2,7 +2,7 @@ const {jest} = require('@jest/globals')
2
2
 
3
3
  const mockPrint = jest.fn()
4
4
 
5
- jest.mock('@controleonline/ui-orders/src/react/services/Cielo/Print', () => ({
5
+ jest.mock('../../../react/services/Cielo/Print', () => ({
6
6
  CieloPrint: jest.fn().mockImplementation(() => ({
7
7
  print: mockPrint,
8
8
  })),
@@ -0,0 +1,190 @@
1
+ const {jest} = require('@jest/globals')
2
+
3
+ const mockPayment = jest.fn()
4
+ const mockFetch = jest.fn()
5
+ const mockGetAllStores = jest.fn()
6
+
7
+ jest.mock('@controleonline-rn/react-native-cielo-payment', () => ({
8
+ payment: (...args) => mockPayment(...args),
9
+ }))
10
+
11
+ jest.mock('@controleonline/ui-common/src/api', () => ({
12
+ api: {
13
+ fetch: (...args) => mockFetch(...args),
14
+ },
15
+ }))
16
+
17
+ jest.mock('@store', () => ({
18
+ getAllStores: (...args) => mockGetAllStores(...args),
19
+ }))
20
+
21
+ jest.mock('@env', () => ({
22
+ env: {
23
+ CIELO: {
24
+ ACCESS_TOKEN: '',
25
+ CLIENT_ID: '',
26
+ EMAIL: '',
27
+ MERCHANT_CODE: '',
28
+ },
29
+ },
30
+ }))
31
+
32
+ const {describe, expect, it, beforeEach} = global
33
+
34
+ const loadService = () => {
35
+ const serviceModule = require('../../../react/services/Cielo/Cielo')
36
+ return serviceModule.default || serviceModule
37
+ }
38
+
39
+ const runtimeStores = merchantCode => ({
40
+ people: {
41
+ getters: {
42
+ currentCompany: {
43
+ id: 99,
44
+ configs: {
45
+ CIELO: JSON.stringify({
46
+ ACCESS_TOKEN: 'runtime-token',
47
+ CLIENT_ID: 'runtime-client',
48
+ EMAIL: 'runtime@empresa.com',
49
+ ...(merchantCode ? {MERCHANT_CODE: merchantCode} : {}),
50
+ }),
51
+ },
52
+ },
53
+ defaultCompany: {id: 99},
54
+ },
55
+ },
56
+ configs: {
57
+ getters: {
58
+ items: {
59
+ CIELO: JSON.stringify({
60
+ ACCESS_TOKEN: 'runtime-token',
61
+ CLIENT_ID: 'runtime-client',
62
+ EMAIL: 'runtime@empresa.com',
63
+ ...(merchantCode ? {MERCHANT_CODE: merchantCode} : {}),
64
+ }),
65
+ },
66
+ },
67
+ },
68
+ })
69
+
70
+ describe('CieloService', () => {
71
+ beforeEach(() => {
72
+ jest.resetModules()
73
+ mockPayment.mockReset()
74
+ mockFetch.mockReset()
75
+ mockGetAllStores.mockReset()
76
+
77
+ mockPayment.mockResolvedValue({
78
+ success: true,
79
+ code: '0',
80
+ result: JSON.stringify({
81
+ status: 'ok',
82
+ payments: [{merchantCode: '0000000000000001'}],
83
+ }),
84
+ })
85
+ })
86
+
87
+ it('falls back to the main company private CIELO config', async () => {
88
+ mockGetAllStores.mockReturnValue({
89
+ people: {
90
+ getters: {
91
+ currentCompany: {id: 21, configs: {}},
92
+ defaultCompany: {id: 99},
93
+ },
94
+ },
95
+ configs: {getters: {items: {}}},
96
+ })
97
+ mockFetch.mockResolvedValue({
98
+ member: [
99
+ {
100
+ configKey: 'CIELO',
101
+ configValue: JSON.stringify({
102
+ ACCESS_TOKEN: 'private-token',
103
+ CLIENT_ID: 'private-client',
104
+ EMAIL: 'cielo@principal.com',
105
+ MERCHANT_CODE: '0000000000000001',
106
+ }),
107
+ },
108
+ ],
109
+ })
110
+
111
+ const CieloService = loadService()
112
+ await new CieloService().payment('credit', [{name: 'Produto'}], 1250)
113
+
114
+ expect(mockFetch).toHaveBeenCalledWith('/configs', {
115
+ params: {
116
+ configKey: 'CIELO',
117
+ people: '/people/99',
118
+ visibility: 'private',
119
+ },
120
+ })
121
+ expect(JSON.parse(mockPayment.mock.calls[0][0])).toMatchObject({
122
+ accessToken: 'private-token',
123
+ clientID: 'private-client',
124
+ email: 'cielo@principal.com',
125
+ merchantCode: '0000000000000001',
126
+ paymentCode: 'credit',
127
+ value: 1250,
128
+ })
129
+ })
130
+
131
+ it('sends the configured merchantCode to Cielo', async () => {
132
+ mockGetAllStores.mockReturnValue(runtimeStores('0000000000000001'))
133
+
134
+ const CieloService = loadService()
135
+ await new CieloService().payment('debit', [], 500)
136
+
137
+ expect(mockFetch).not.toHaveBeenCalled()
138
+ expect(JSON.parse(mockPayment.mock.calls[0][0])).toMatchObject({
139
+ merchantCode: '0000000000000001',
140
+ paymentCode: 'debit',
141
+ value: 500,
142
+ })
143
+ })
144
+
145
+ it('keeps the legacy request when merchantCode is not configured', async () => {
146
+ mockGetAllStores.mockReturnValue(runtimeStores(''))
147
+
148
+ const CieloService = loadService()
149
+ await new CieloService().payment('debit', [], 500)
150
+
151
+ expect(JSON.parse(mockPayment.mock.calls[0][0])).not.toHaveProperty(
152
+ 'merchantCode',
153
+ )
154
+ })
155
+
156
+ it('rejects a successful response without merchantCode', async () => {
157
+ mockGetAllStores.mockReturnValue(runtimeStores('0000000000000001'))
158
+ mockPayment.mockResolvedValue({
159
+ success: true,
160
+ code: '0',
161
+ result: JSON.stringify({status: 'ok', payments: [{}]}),
162
+ })
163
+
164
+ const CieloService = loadService()
165
+ const response = await new CieloService().payment('debit', [], 500)
166
+
167
+ expect(response.success).toBe(false)
168
+ })
169
+
170
+ it('rejects a successful response from a different merchantCode', async () => {
171
+ mockGetAllStores.mockReturnValue(runtimeStores('0000000000000001'))
172
+ mockPayment.mockResolvedValue({
173
+ success: true,
174
+ code: '0',
175
+ result: JSON.stringify({
176
+ status: 'ok',
177
+ payments: [{merchantCode: '0000000000000002'}],
178
+ }),
179
+ })
180
+
181
+ const CieloService = loadService()
182
+ const response = await new CieloService().payment('debit', [], 500)
183
+
184
+ expect(response).toMatchObject({
185
+ success: false,
186
+ result:
187
+ 'Pagamento processado em estabelecimento Cielo diferente do configurado.',
188
+ })
189
+ })
190
+ })
@@ -0,0 +1,26 @@
1
+ /* global jest */
2
+ jest.mock('@controleonline/ui-default/src/store/default/actions', () => ({}))
3
+ jest.mock('@controleonline/ui-default/src/store/default/getters', () => ({}))
4
+ jest.mock('@controleonline/ui-default/src/store/default/mutations', () => ({
5
+ __esModule: true,
6
+ default: {},
7
+ }))
8
+
9
+ const categoriesStore = require('../../../store/categories').default
10
+
11
+ const {describe, expect, it} = global
12
+
13
+ describe('categories store ordering', () => {
14
+ it('exposes the canonical order as the editable default table sort', () => {
15
+ const column = categoriesStore.state.columns.find(item => item.name === 'sortOrder')
16
+
17
+ expect(column).toMatchObject({
18
+ defaultSort: 'ASC',
19
+ editable: true,
20
+ inputType: 'number',
21
+ sortable: true,
22
+ })
23
+ expect(column.saveFormat(0)).toBe(0)
24
+ expect(column.saveFormat('')).toBeNull()
25
+ })
26
+ })
@@ -0,0 +1,62 @@
1
+ const {describe, expect, it} = global
2
+
3
+ const {resolveAppDomain} = require('../../../utils/appDomain')
4
+
5
+ describe('resolveAppDomain', () => {
6
+ const withLocation = (host, fn) => {
7
+ const previousLocation = Object.getOwnPropertyDescriptor(global, 'location')
8
+ try {
9
+ Object.defineProperty(global, 'location', {
10
+ configurable: true,
11
+ value: {host},
12
+ })
13
+ fn()
14
+ } finally {
15
+ if (previousLocation) {
16
+ Object.defineProperty(global, 'location', previousLocation)
17
+ } else {
18
+ delete global.location
19
+ }
20
+ }
21
+ }
22
+
23
+ it('ignores invalid configured domain values and uses the runtime host', () => {
24
+ withLocation('erp.exemplo.com.br', () => {
25
+ expect(resolveAppDomain('undefined')).toBe('erp.exemplo.com.br')
26
+ expect(resolveAppDomain('null')).toBe('erp.exemplo.com.br')
27
+ expect(resolveAppDomain('false')).toBe('erp.exemplo.com.br')
28
+ })
29
+ })
30
+
31
+ it('prefers window.location.host over configured DOMAIN (multi-tenant web)', () => {
32
+ withLocation('erpjaguncos.com.br', () => {
33
+ expect(resolveAppDomain('https://app.controleonline.com')).toBe(
34
+ 'erpjaguncos.com.br',
35
+ )
36
+ expect(resolveAppDomain('app.controleonline.com')).toBe(
37
+ 'erpjaguncos.com.br',
38
+ )
39
+ })
40
+ })
41
+
42
+ it('uses configured domain when there is no runtime location (native)', () => {
43
+ const previousLocation = Object.getOwnPropertyDescriptor(global, 'location')
44
+ try {
45
+ Object.defineProperty(global, 'location', {
46
+ configurable: true,
47
+ value: undefined,
48
+ })
49
+ // resolveRuntimeHost returns '' when location is undefined
50
+ expect(resolveAppDomain('https://app.controleonline.com')).toBe(
51
+ 'app.controleonline.com',
52
+ )
53
+ expect(resolveAppDomain('app.controleonline.com')).toBe(
54
+ 'app.controleonline.com',
55
+ )
56
+ } finally {
57
+ if (previousLocation) {
58
+ Object.defineProperty(global, 'location', previousLocation)
59
+ }
60
+ }
61
+ })
62
+ })
@@ -12,7 +12,7 @@ describe('entityDisplay helpers', () => {
12
12
  });
13
13
 
14
14
  it('uppercases pt-BR strings with cedilla correctly', () => {
15
- expect(formatDisplayUppercase('Jagunços')).toBe('JAGUNÇOS');
16
- expect(formatDisplayUppercase('Empório Jagunços Ltda')).toBe('EMPÓRIO JAGUNÇOS LTDA');
15
+ expect(formatDisplayUppercase('Empresa Exemplo')).toBe('EMPRESA EXEMPLO');
16
+ expect(formatDisplayUppercase('Empresa Exemplo Ltda')).toBe('EMPRESA EXEMPLO LTDA');
17
17
  });
18
18
  });
@@ -0,0 +1,62 @@
1
+ const {describe, expect, it} = global;
2
+ const {jest} = require('@jest/globals');
3
+
4
+ jest.mock('@env', () => ({
5
+ env: {
6
+ API_ENTRYPOINT: 'https://api.controleonline.com',
7
+ DOMAIN: 'manager.controleonline.com',
8
+ },
9
+ }));
10
+
11
+ const {
12
+ resolveDefaultFileSource,
13
+ resolveFileImageUrl,
14
+ } = require('../../../react/utils/fileUrl');
15
+
16
+ describe('fileUrl helpers', () => {
17
+ it('uses path app-domain for backend download urls and keeps headers', () => {
18
+ const company = {
19
+ domain: 'maincompany.controleonline.com',
20
+ };
21
+
22
+ const source = resolveDefaultFileSource(
23
+ {
24
+ id: 3,
25
+ url: '/files/3/download',
26
+ },
27
+ {
28
+ company,
29
+ headers: {
30
+ Authorization: 'Bearer token',
31
+ },
32
+ },
33
+ );
34
+
35
+ expect(source).toEqual({
36
+ uri: 'https://api.controleonline.com/maincompany.controleonline.com/files/3/download',
37
+ headers: {
38
+ Authorization: 'Bearer token',
39
+ 'app-domain': 'maincompany.controleonline.com',
40
+ },
41
+ });
42
+ expect(resolveFileImageUrl(3, {company})).toBe(
43
+ 'https://api.controleonline.com/maincompany.controleonline.com/files/3/download',
44
+ );
45
+ });
46
+
47
+ it('keeps external image urls untouched', () => {
48
+ const source = resolveDefaultFileSource(
49
+ 'https://cdn.controleonline.com/logo.png',
50
+ {
51
+ company: {
52
+ domain: 'maincompany.controleonline.com',
53
+ },
54
+ },
55
+ );
56
+
57
+ expect(source).toEqual({
58
+ uri: 'https://cdn.controleonline.com/logo.png',
59
+ headers: {},
60
+ });
61
+ });
62
+ });
@@ -0,0 +1,33 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+
4
+ import Formatter from '../../../utils/formatter.js';
5
+
6
+ test('formats API datetimes using the numeric offset returned by the backend', () => {
7
+ assert.equal(
8
+ Formatter.formatDateYmdTodmY('2026-07-22T00:00:00-02:00'),
9
+ '22/07/2026',
10
+ );
11
+
12
+ assert.equal(
13
+ Formatter.formatDateYmdTodmY('2026-07-22T22:34:22-02:00', true),
14
+ '22/07/2026, 22:34:22',
15
+ );
16
+
17
+ assert.equal(
18
+ Formatter.formatDateYmdTodmY('2026-07-22T21:34:22-03:00', true),
19
+ '22/07/2026, 21:34:22',
20
+ );
21
+ });
22
+
23
+ test('repairs mojibake from API text payloads', () => {
24
+ assert.equal(
25
+ Formatter.repairMojibake('Não, saída, cobrança, garçom, conferência'),
26
+ 'Não, saída, cobrança, garçom, conferência',
27
+ );
28
+
29
+ assert.equal(
30
+ Formatter.repairMojibake('Produção entrada → trabalhando → pronto'),
31
+ 'Produção entrada → trabalhando → pronto',
32
+ );
33
+ });
@@ -1,5 +1,5 @@
1
- const assert = require('node:assert/strict')
2
- const test = require('node:test')
1
+ const assert = require('node:assert/strict')
2
+ const {test} = global
3
3
 
4
4
  const {
5
5
  getImportErrorDetail,