@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,82 @@
1
+ jest.mock('@controleonline/ui-common/src/api', () => ({
2
+ api: {
3
+ fetch: jest.fn(),
4
+ },
5
+ }));
6
+
7
+ const {api} = require('@controleonline/ui-common/src/api');
8
+ const {
9
+ fetchShopFranchiseDirectory,
10
+ } = require('../../../react/utils/shopFranchises');
11
+
12
+ const {beforeEach, describe, expect, it} = global;
13
+
14
+ describe('shopFranchises', () => {
15
+ beforeEach(() => {
16
+ api.fetch.mockReset();
17
+ });
18
+
19
+ it('uses the public shop franchises endpoint and preserves shop addresses', async () => {
20
+ api.fetch.mockResolvedValueOnce({
21
+ member: [
22
+ {
23
+ id: 21,
24
+ alias: 'Centro',
25
+ shopAddresses: [{id: 501, nickname: 'Loja Centro'}],
26
+ },
27
+ ],
28
+ 'hydra:member': [
29
+ {
30
+ id: 21,
31
+ alias: 'Centro',
32
+ shopAddresses: [{id: 501, nickname: 'Loja Centro'}],
33
+ },
34
+ ],
35
+ });
36
+
37
+ const directory = await fetchShopFranchiseDirectory({
38
+ companyId: 10,
39
+ publicDirectory: true,
40
+ });
41
+
42
+ expect(api.fetch).toHaveBeenCalledWith('/shop/franchises', {
43
+ params: {
44
+ itemsPerPage: 50,
45
+ page: 1,
46
+ },
47
+ });
48
+ expect(directory).toEqual([
49
+ {
50
+ id: 21,
51
+ alias: 'Centro',
52
+ shopAddresses: [{id: 501, nickname: 'Loja Centro'}],
53
+ },
54
+ ]);
55
+ });
56
+
57
+ it('keeps authenticated management lookup scoped by company', async () => {
58
+ api.fetch.mockResolvedValueOnce({
59
+ member: [
60
+ {
61
+ id: 22,
62
+ alias: 'Norte',
63
+ address: [{id: 601, nickname: 'Loja Norte'}],
64
+ },
65
+ ],
66
+ });
67
+
68
+ const directory = await fetchShopFranchiseDirectory({companyId: 10});
69
+
70
+ expect(api.fetch).toHaveBeenCalledWith('people', {
71
+ params: {
72
+ 'link.company': '/people/10',
73
+ 'link.linkType': 'franchisee',
74
+ itemsPerPage: 50,
75
+ page: 1,
76
+ },
77
+ });
78
+ expect(directory[0].shopAddresses).toEqual([
79
+ {id: 601, nickname: 'Loja Norte'},
80
+ ]);
81
+ });
82
+ });
@@ -0,0 +1,124 @@
1
+ const {jest} = require('@jest/globals');
2
+
3
+ const {
4
+ createSocketRuntimePipeline,
5
+ } = require('../../../react/utils/socketRuntimePipeline');
6
+
7
+ const {describe, expect, it, beforeEach, afterEach} = global;
8
+
9
+ describe('socketRuntimePipeline', () => {
10
+ let websocketActions;
11
+ let runtimeDebugActions;
12
+ let storeMessages;
13
+ let stores;
14
+
15
+ beforeEach(() => {
16
+ storeMessages = [];
17
+ stores = {
18
+ orders: {
19
+ getters: {
20
+ messages: storeMessages,
21
+ },
22
+ actions: {
23
+ setMessages: nextMessages => {
24
+ storeMessages = nextMessages;
25
+ stores.orders.getters.messages = nextMessages;
26
+ },
27
+ },
28
+ },
29
+ };
30
+
31
+ websocketActions = {
32
+ setSummary: jest.fn(),
33
+ };
34
+
35
+ runtimeDebugActions = {
36
+ setFooterEntry: jest.fn(),
37
+ };
38
+ });
39
+
40
+ afterEach(() => {
41
+ jest.clearAllMocks();
42
+ });
43
+
44
+ it('tags delivered messages and updates the socket summary', async () => {
45
+ const pipeline = createSocketRuntimePipeline({
46
+ getDeviceId: () => 'device-1',
47
+ getCurrentCompanyId: () => 'company-1',
48
+ websocketActions,
49
+ runtimeDebugActions,
50
+ getStoreByName: name => stores[name],
51
+ });
52
+
53
+ pipeline.processPayload([
54
+ {
55
+ store: 'orders',
56
+ company: {id: 'company-1'},
57
+ order: '/orders/11',
58
+ },
59
+ ]);
60
+
61
+ await Promise.resolve();
62
+
63
+ expect(storeMessages).toEqual([
64
+ {
65
+ store: 'orders',
66
+ company: {id: 'company-1'},
67
+ order: '/orders/11',
68
+ source: 'background-runtime',
69
+ },
70
+ ]);
71
+ expect(websocketActions.setSummary).toHaveBeenCalledWith(
72
+ expect.objectContaining({
73
+ connected: true,
74
+ identified: true,
75
+ status: 'connected',
76
+ device: 'device-1',
77
+ lastEventCount: 1,
78
+ lastStores: ['orders'],
79
+ lastCompanies: ['company-1'],
80
+ }),
81
+ );
82
+ expect(runtimeDebugActions.setFooterEntry).toHaveBeenCalledWith(
83
+ expect.objectContaining({
84
+ key: 'socket',
85
+ }),
86
+ );
87
+ });
88
+
89
+ it('applies socket-state snapshots without dropping owner metadata', () => {
90
+ const pipeline = createSocketRuntimePipeline({
91
+ getDeviceId: () => 'device-1',
92
+ getCurrentCompanyId: () => 'company-1',
93
+ websocketActions,
94
+ runtimeDebugActions,
95
+ getStoreByName: name => stores[name],
96
+ });
97
+
98
+ pipeline.processPayload({
99
+ type: 'socket-state',
100
+ connected: true,
101
+ identified: true,
102
+ status: 'connected',
103
+ attempts: 0,
104
+ device: 'device-1',
105
+ ownerRegistrationId: 'manager::device-1::company-1',
106
+ ownerPackageName: 'com.controleonline.manager',
107
+ ownerCompanyId: 'company-1',
108
+ lastEventCount: 3,
109
+ lastStores: ['orders', 'print'],
110
+ lastCompanies: ['company-1'],
111
+ });
112
+
113
+ expect(websocketActions.setSummary).toHaveBeenCalledWith(
114
+ expect.objectContaining({
115
+ connected: true,
116
+ identified: true,
117
+ status: 'connected',
118
+ ownerRegistrationId: 'manager::device-1::company-1',
119
+ ownerPackageName: 'com.controleonline.manager',
120
+ ownerCompanyId: 'company-1',
121
+ }),
122
+ );
123
+ });
124
+ });
@@ -0,0 +1,40 @@
1
+ /* global describe, it, expect */
2
+
3
+ const {
4
+ forgetTrackedSpool,
5
+ getTrackedSpoolKey,
6
+ hasTrackedSpool,
7
+ pruneTrackedSpools,
8
+ rememberTrackedSpool,
9
+ } = require('../../../react/utils/spoolAckTracker')
10
+
11
+ describe('spoolAckTracker', () => {
12
+ it('normalizes spool ids before tracking them', () => {
13
+ expect(getTrackedSpoolKey('/spools/123')).toBe('123')
14
+ expect(getTrackedSpoolKey(' spool-456 ')).toBe('456')
15
+ expect(getTrackedSpoolKey(null)).toBe('')
16
+ })
17
+
18
+ it('tracks and forgets printed spools by normalized id', () => {
19
+ const registry = new Map()
20
+
21
+ rememberTrackedSpool(registry, '/spools/77', {printedAt: 123})
22
+
23
+ expect(hasTrackedSpool(registry, 77)).toBe(true)
24
+ expect(registry.get('77')).toEqual({printedAt: 123})
25
+
26
+ forgetTrackedSpool(registry, '77')
27
+
28
+ expect(hasTrackedSpool(registry, '/spools/77')).toBe(false)
29
+ })
30
+
31
+ it('prunes spools that are no longer open', () => {
32
+ const registry = new Map()
33
+
34
+ rememberTrackedSpool(registry, 10, {printedAt: 1})
35
+ rememberTrackedSpool(registry, 20, {printedAt: 2})
36
+ pruneTrackedSpools(registry, ['/spools/20', '/spools/30'])
37
+
38
+ expect(Array.from(registry.keys())).toEqual(['20'])
39
+ })
40
+ })
@@ -0,0 +1,66 @@
1
+ /* global jest */
2
+
3
+ const {beforeEach, describe, expect, it} = global
4
+
5
+ jest.mock('@controleonline/ui-common/src/react/utils/systemErrorChannel', () => ({
6
+ publishSystemError: jest.fn(() => true),
7
+ }))
8
+
9
+ const {publishSystemError} = require('@controleonline/ui-common/src/react/utils/systemErrorChannel')
10
+ const {
11
+ bridgeStoreError,
12
+ isStoreErrorMutation,
13
+ shouldSkipStoreErrorMutation,
14
+ } = require('../../../react/stores/storeErrorBridge')
15
+
16
+ describe('storeErrorBridge', () => {
17
+ beforeEach(() => {
18
+ jest.clearAllMocks()
19
+ })
20
+
21
+ it('recognizes store error mutations by suffix and ignores empty payloads', () => {
22
+ expect(isStoreErrorMutation('SET_ERROR', 'Falha')).toBe(true)
23
+ expect(isStoreErrorMutation('LOGIN_SET_ERROR', 'Falha')).toBe(true)
24
+ expect(isStoreErrorMutation('SET_ERROR', '')).toBe(false)
25
+ expect(isStoreErrorMutation('SET_ITEMS', 'Falha')).toBe(false)
26
+ })
27
+
28
+ it('allows a caller to suppress the shared visual error for locally handled flows', () => {
29
+ expect(shouldSkipStoreErrorMutation({skipSystemError: true})).toBe(true)
30
+ expect(isStoreErrorMutation('SET_ERROR', 'Falha', {skipSystemError: true})).toBe(false)
31
+ })
32
+
33
+ it('publishes canonical visual errors for store mutations', () => {
34
+ expect(
35
+ bridgeStoreError({
36
+ storeName: 'invoice',
37
+ mutationType: 'SET_ERROR',
38
+ error: 'Nao foi possivel gerar a cobranca.',
39
+ }),
40
+ ).toBe(true)
41
+
42
+ expect(publishSystemError).toHaveBeenCalledWith(
43
+ 'Nao foi possivel gerar a cobranca.',
44
+ expect.objectContaining({
45
+ source: 'store',
46
+ storeName: 'invoice',
47
+ mutationType: 'SET_ERROR',
48
+ }),
49
+ )
50
+ })
51
+
52
+ it('does not publish when the store caller opted to handle the error locally', () => {
53
+ expect(
54
+ bridgeStoreError({
55
+ storeName: 'phones',
56
+ mutationType: 'SET_ERROR',
57
+ error: 'Item not found for "/people/30".',
58
+ options: {
59
+ skipSystemError: true,
60
+ },
61
+ }),
62
+ ).toBe(false)
63
+
64
+ expect(publishSystemError).not.toHaveBeenCalled()
65
+ })
66
+ })
@@ -0,0 +1,58 @@
1
+ /* global jest */
2
+
3
+ const {beforeEach, describe, expect, it} = global
4
+
5
+ const {
6
+ publishSystemError,
7
+ subscribeSystemErrors,
8
+ resetSystemErrorChannel,
9
+ DEFAULT_SYSTEM_ERROR_DEDUPE_WINDOW_MS,
10
+ } = require('../../../react/utils/systemErrorChannel')
11
+
12
+ describe('systemErrorChannel', () => {
13
+ beforeEach(() => {
14
+ resetSystemErrorChannel()
15
+ jest.restoreAllMocks()
16
+ })
17
+
18
+ it('queues store and manual errors until the presenter subscribes', () => {
19
+ expect(publishSystemError('Falha ao salvar')).toBe(true)
20
+
21
+ const received = []
22
+
23
+ const unsubscribe = subscribeSystemErrors(payload => {
24
+ received.push(payload)
25
+ })
26
+
27
+ expect(received).toEqual([
28
+ expect.objectContaining({
29
+ message: 'Falha ao salvar',
30
+ }),
31
+ ])
32
+
33
+ unsubscribe()
34
+ })
35
+
36
+ it('deduplicates the same transient error inside the shared time window', () => {
37
+ const nowSpy = jest.spyOn(Date, 'now')
38
+ nowSpy.mockReturnValue(1000)
39
+
40
+ const received = []
41
+ const unsubscribe = subscribeSystemErrors(payload => {
42
+ received.push(payload.message)
43
+ })
44
+
45
+ expect(publishSystemError('Erro de sincronizacao')).toBe(true)
46
+ expect(publishSystemError('Erro de sincronizacao')).toBe(false)
47
+
48
+ nowSpy.mockReturnValue(1000 + DEFAULT_SYSTEM_ERROR_DEDUPE_WINDOW_MS + 1)
49
+
50
+ expect(publishSystemError('Erro de sincronizacao')).toBe(true)
51
+ expect(received).toEqual([
52
+ 'Erro de sincronizacao',
53
+ 'Erro de sincronizacao',
54
+ ])
55
+
56
+ unsubscribe()
57
+ })
58
+ })
@@ -0,0 +1,59 @@
1
+ const {describe, expect, it} = global
2
+
3
+ const {
4
+ resolveSystemErrorMessage,
5
+ } = require('../../../react/utils/systemErrorMessage')
6
+
7
+ describe('systemErrorMessage', () => {
8
+ it('prefers problem-json detail messages when available', () => {
9
+ expect(
10
+ resolveSystemErrorMessage({
11
+ title: 'An error occurred',
12
+ detail: 'Telefone ja cadastrado para outra pessoa.',
13
+ }),
14
+ ).toBe('Telefone ja cadastrado para outra pessoa.')
15
+ })
16
+
17
+ it('reads canonical hydra error envelopes', () => {
18
+ expect(
19
+ resolveSystemErrorMessage({
20
+ '@type': 'Error',
21
+ 'hydra:title': 'An error occurred',
22
+ 'hydra:description': 'Pedido sem endereco de entrega valido.',
23
+ }),
24
+ ).toBe('Pedido sem endereco de entrega valido.')
25
+ })
26
+
27
+ it('reads hydra envelopes nested under response data', () => {
28
+ expect(
29
+ resolveSystemErrorMessage({
30
+ response: {
31
+ data: {
32
+ '@type': 'Error',
33
+ 'hydra:description': 'Pedido sem endereco de entrega valido.',
34
+ },
35
+ },
36
+ }),
37
+ ).toBe('Pedido sem endereco de entrega valido.')
38
+ })
39
+
40
+ it('formats constraint violations into a readable multiline message', () => {
41
+ expect(
42
+ resolveSystemErrorMessage({
43
+ violations: [
44
+ {propertyPath: 'ddd', message: 'DDD invalido.'},
45
+ {propertyPath: 'phone', message: 'Telefone obrigatorio.'},
46
+ ],
47
+ }),
48
+ ).toBe('DDD invalido.\nTelefone obrigatorio.')
49
+ })
50
+
51
+ it('accepts plain strings and legacy message arrays', () => {
52
+ expect(resolveSystemErrorMessage('Falha ao salvar.')).toBe('Falha ao salvar.')
53
+ expect(
54
+ resolveSystemErrorMessage({
55
+ message: [{message: 'Primeiro erro'}, {title: 'Segundo erro'}],
56
+ }),
57
+ ).toBe('Primeiro erro\nSegundo erro')
58
+ })
59
+ })
@@ -0,0 +1,75 @@
1
+ const {describe, expect, it} = global
2
+
3
+ const {
4
+ TOAST_ERROR_STYLES,
5
+ TOAST_WARNING_STYLES,
6
+ mergeToastStyles,
7
+ withErrorToastStyles,
8
+ withWarningToastStyles,
9
+ } = require('../../../react/utils/toastPresentation')
10
+
11
+ describe('toastPresentation', () => {
12
+ it('keeps the shared error toast on a light, high-contrast panel', () => {
13
+ expect(TOAST_ERROR_STYLES.pressable).toMatchObject({
14
+ backgroundColor: '#FFF7F7',
15
+ borderColor: '#FECACA',
16
+ borderWidth: 1,
17
+ })
18
+ expect(TOAST_ERROR_STYLES.indicator).toMatchObject({
19
+ backgroundColor: '#DC2626',
20
+ })
21
+ })
22
+
23
+ it('merges toast style groups without dropping prior sections', () => {
24
+ expect(
25
+ mergeToastStyles(
26
+ {
27
+ pressable: {backgroundColor: '#FFFFFF'},
28
+ indicator: {backgroundColor: '#000000'},
29
+ },
30
+ {
31
+ pressable: {borderWidth: 2},
32
+ text: {color: '#111827'},
33
+ },
34
+ ),
35
+ ).toEqual({
36
+ pressable: {
37
+ backgroundColor: '#FFFFFF',
38
+ borderWidth: 2,
39
+ },
40
+ indicator: {
41
+ backgroundColor: '#000000',
42
+ },
43
+ text: {
44
+ color: '#111827',
45
+ },
46
+ })
47
+ })
48
+
49
+ it('decorates error and warning toast options with the shared styles', () => {
50
+ expect(withErrorToastStyles({styles: {pressable: {paddingHorizontal: 8}}})).toEqual({
51
+ styles: {
52
+ pressable: {
53
+ backgroundColor: '#FFF7F7',
54
+ borderColor: '#FECACA',
55
+ borderWidth: 1,
56
+ paddingHorizontal: 8,
57
+ },
58
+ indicator: {
59
+ backgroundColor: '#DC2626',
60
+ },
61
+ },
62
+ })
63
+
64
+ expect(withWarningToastStyles({styles: {text: {fontWeight: '700'}}})).toEqual({
65
+ styles: {
66
+ indicator: {
67
+ backgroundColor: TOAST_WARNING_STYLES.indicator.backgroundColor,
68
+ },
69
+ text: {
70
+ fontWeight: '700',
71
+ },
72
+ },
73
+ })
74
+ })
75
+ })