@controleonline/ui-common 1.2.80 → 1.2.82

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 (90) hide show
  1. package/AGENTS.md +8 -0
  2. package/package.json +9 -3
  3. package/src/api/fetch.js +7 -7
  4. package/src/react/components/AddImportModal.js +6 -2
  5. package/src/react/components/BackgroundRuntimeBridge.js +201 -215
  6. package/src/react/components/ConfirmModal.js +44 -9
  7. package/src/react/components/ConfirmModal.styles.js +55 -41
  8. package/src/react/components/CopyDeviceConfigModal.js +193 -0
  9. package/src/react/components/CopyDeviceConfigModal.styles.js +123 -0
  10. package/src/react/components/DefaultProvider.native.js +22 -0
  11. package/src/react/components/DefaultProvider.web.js +25 -1
  12. package/src/react/components/EntityLogBranch.js +482 -0
  13. package/src/react/components/EntityLogCards.js +369 -0
  14. package/src/react/components/EntityLogContent.js +113 -794
  15. package/src/react/components/LinkedOrderProductsTab.js +3 -3
  16. package/src/react/components/PrintService.js +130 -409
  17. package/src/react/components/RemoteCheckoutService.js +2 -2
  18. package/src/react/components/RuntimeBottomNavigationBar.js +141 -146
  19. package/src/react/components/RuntimeFooterMarqueeText.js +137 -0
  20. package/src/react/components/RuntimeInfoFooter.js +12 -15
  21. package/src/react/components/SystemErrorToast.js +49 -29
  22. package/src/react/components/SystemErrorToast.styles.js +43 -38
  23. package/src/react/components/UserAvatar.js +120 -28
  24. package/src/react/config/deviceConfigBootstrap.js +330 -304
  25. package/src/react/hooks/usePrintSpoolEffects.js +161 -0
  26. package/src/react/pages/DeviceDetailPage.js +6 -2882
  27. package/src/react/pages/Devices/detail/DeviceDetailAlertsCommandsSection.js +233 -0
  28. package/src/react/pages/Devices/detail/DeviceDetailHeader.js +138 -0
  29. package/src/react/pages/Devices/detail/DeviceDetailMovementSections.js +167 -0
  30. package/src/react/pages/Devices/detail/DeviceDetailOrdersPrintSection.js +216 -0
  31. package/src/react/pages/Devices/detail/DeviceDetailPaymentSection.js +44 -0
  32. package/src/react/pages/Devices/detail/DeviceDetailPdvConfigSection.js +390 -0
  33. package/src/react/pages/Devices/detail/DeviceDetailRenderers.js +135 -0
  34. package/src/react/pages/Devices/detail/DeviceDetailScreen.js +495 -0
  35. package/src/react/pages/Devices/detail/OptionButtonChip.js +65 -0
  36. package/src/react/pages/Devices/detail/deviceDetailConstants.js +36 -0
  37. package/src/react/pages/Devices/detail/deviceDetailHelpers.js +70 -0
  38. package/src/react/pages/Devices/detail/useDeviceDetailActions.js +483 -0
  39. package/src/react/pages/Devices/detail/useDeviceDetailCopyConfig.js +87 -0
  40. package/src/react/pages/Devices/detail/useDeviceDetailLoaders.js +327 -0
  41. package/src/react/pages/Devices/detail/useDeviceDetailSaves.js +429 -0
  42. package/src/react/pages/Devices/detail/useDeviceDetailStateA.js +489 -0
  43. package/src/react/pages/Devices/detail/useDeviceDetailStateB.js +98 -0
  44. package/src/react/pages/Devices/deviceTypes/index.js +2 -0
  45. package/src/react/pages/Devices/deviceTypes/pdv.js +11 -0
  46. package/src/react/pages/Imports.js +3 -2
  47. package/src/react/pages/IntegrationConfigFields.js +126 -0
  48. package/src/react/pages/IntegrationConfigPage.js +122 -591
  49. package/src/react/pages/IntegrationConfigPage.utils.js +95 -0
  50. package/src/react/print/usePrintButtonController.js +9 -1
  51. package/src/react/services/Getnet/Checkout.js +81 -0
  52. package/src/react/services/Getnet/Getnet.js +77 -0
  53. package/src/react/services/paymentGatewayExecution.js +20 -0
  54. package/src/react/utils/bottomNavigationToolbar.js +161 -0
  55. package/src/react/utils/commercialDocumentOrders.js +18 -6
  56. package/src/react/utils/copyDeviceConfigs.js +198 -0
  57. package/src/react/utils/fileUrl.js +43 -0
  58. package/src/react/utils/importStatus.js +19 -10
  59. package/src/react/utils/logSettings.js +7 -7
  60. package/src/react/utils/maintenanceSettings.js +16 -0
  61. package/src/react/utils/paymentDevices.js +6 -0
  62. package/src/react/utils/printManagedPrinter.js +45 -0
  63. package/src/react/utils/printRouting.js +63 -0
  64. package/src/react/utils/printSpoolUtils.js +119 -0
  65. package/src/react/utils/printerDevices.js +368 -368
  66. package/src/react/utils/runtimeFooter.js +10 -19
  67. package/src/react/utils/shopFranchises.js +186 -65
  68. package/src/react/utils/systemErrorMessage.js +71 -67
  69. package/src/store/device_config/customActions.js +119 -30
  70. package/src/tests/browser/manager/bottom-navigation-role-toolbar.spec.js +353 -0
  71. package/src/tests/browser/manager/device-detail-alias-save.spec.js +345 -0
  72. package/src/tests/browser/manager/device-detail-copy-config.spec.js +365 -0
  73. package/src/tests/browser/manager/runtime-footer-marquee.spec.js +262 -0
  74. package/src/tests/browser/manager/theme-confirm-toast.spec.js +236 -0
  75. package/src/tests/react/components/RuntimeFooterMarqueeText.test.js +171 -0
  76. package/src/tests/react/components/UserAvatar.test.js +6 -2
  77. package/src/tests/react/components/themeAwareModals.test.mjs +60 -0
  78. package/src/tests/react/config/deviceConfigBootstrap.test.js +104 -66
  79. package/src/tests/react/pages/deviceDetailDelete.test.js +78 -0
  80. package/src/tests/react/pages/receitaFederalIntegrationContract.test.mjs +48 -0
  81. package/src/tests/react/utils/bottomNavigationToolbar.test.js +222 -0
  82. package/src/tests/react/utils/copyDeviceConfigs.test.mjs +127 -0
  83. package/src/tests/react/utils/fileUrl.test.js +38 -0
  84. package/src/tests/react/utils/importStatus.test.js +26 -9
  85. package/src/tests/react/utils/passwordPolicy.test.js +27 -0
  86. package/src/tests/react/utils/printRouting.test.mjs +86 -0
  87. package/src/tests/react/utils/printSpoolUtils.test.mjs +52 -0
  88. package/src/tests/react/utils/runtimeFooter.test.js +7 -0
  89. package/src/tests/react/utils/shopFranchises.test.js +121 -115
  90. package/src/tests/react/utils/systemErrorMessage.test.js +32 -59
@@ -0,0 +1,127 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import {
4
+ buildCopyPayloads,
5
+ buildSourceDeviceOptions,
6
+ executeCopyDeviceConfigs,
7
+ listCopyableConfigKeys,
8
+ } from '../../../react/utils/copyDeviceConfigs.js';
9
+
10
+ const companyId = '42';
11
+ const peopleIri = '/people/42';
12
+
13
+ const sourceRows = [
14
+ {
15
+ id: 1,
16
+ type: 'PDV',
17
+ people: {id: 42},
18
+ device: {id: 10, device: 'src-hash-1', alias: 'Caixa 1'},
19
+ configs: {
20
+ 'pos-operation-mode': 'cashier',
21
+ 'pos-gateway': 'cielo',
22
+ 'printer-enabled': '1',
23
+ },
24
+ },
25
+ {
26
+ id: 2,
27
+ type: 'PRINT',
28
+ people: {id: 42},
29
+ device: {id: 10, device: 'src-hash-1', alias: 'Caixa 1'},
30
+ configs: {'print-network-port': '9100'},
31
+ },
32
+ {
33
+ id: 3,
34
+ type: 'PDV',
35
+ people: {id: 99},
36
+ device: {id: 20, device: 'other-co', alias: 'Outra empresa'},
37
+ configs: {'pos-gateway': 'infinite-pay'},
38
+ },
39
+ {
40
+ id: 4,
41
+ type: 'PDV',
42
+ people: {id: 42},
43
+ device: {id: 30, device: 'dest-hash', alias: 'Destino'},
44
+ configs: {'pos-operation-mode': 'waiter'},
45
+ },
46
+ ];
47
+
48
+ test('listCopyableConfigKeys sorts keys from object or JSON string', () => {
49
+ assert.deepEqual(listCopyableConfigKeys({'b-key': '1', 'a-key': '2'}), [
50
+ 'a-key',
51
+ 'b-key',
52
+ ]);
53
+ assert.deepEqual(
54
+ listCopyableConfigKeys(JSON.stringify({'pos-gateway': 'cielo'})),
55
+ ['pos-gateway'],
56
+ );
57
+ });
58
+
59
+ test('buildSourceDeviceOptions excludes destination and other tenants', () => {
60
+ const options = buildSourceDeviceOptions({
61
+ companyDeviceConfigs: sourceRows,
62
+ companyId,
63
+ destinationDeviceString: 'dest-hash',
64
+ });
65
+ assert.equal(options.length, 1);
66
+ assert.equal(options[0].deviceString, 'src-hash-1');
67
+ assert.equal(options[0].configs.length, 2);
68
+ });
69
+
70
+ test('buildCopyPayloads keeps destination identity and source config keys', () => {
71
+ const payloads = buildCopyPayloads({
72
+ sourceConfigs: sourceRows.filter(r => r.device.device === 'src-hash-1'),
73
+ destinationDeviceString: 'dest-hash',
74
+ peopleIri,
75
+ });
76
+ assert.equal(payloads.length, 2);
77
+ const pdv = payloads.find(p => p.type === 'PDV');
78
+ const print = payloads.find(p => p.type === 'PRINT');
79
+ assert.equal(pdv.device, 'dest-hash');
80
+ assert.equal(pdv.people, peopleIri);
81
+ assert.equal(JSON.parse(pdv.configs)['pos-gateway'], 'cielo');
82
+ assert.deepEqual(pdv.keys, [
83
+ 'pos-gateway',
84
+ 'pos-operation-mode',
85
+ 'printer-enabled',
86
+ ]);
87
+ assert.deepEqual(print.keys, ['print-network-port']);
88
+ });
89
+
90
+ test('executeCopyDeviceConfigs posts one payload per type', async () => {
91
+ const calls = [];
92
+ const addDeviceConfigs = async params => {
93
+ calls.push(params);
94
+ return {ok: true, type: params.type};
95
+ };
96
+ const summary = await executeCopyDeviceConfigs({
97
+ addDeviceConfigs,
98
+ sourceConfigs: sourceRows.filter(r => r.device.device === 'src-hash-1'),
99
+ destinationDeviceString: 'dest-hash',
100
+ peopleIri,
101
+ });
102
+ assert.equal(calls.length, 2);
103
+ assert.ok(calls.every(c => c.device === 'dest-hash'));
104
+ assert.ok(calls.every(c => c.people === peopleIri));
105
+ assert.deepEqual(summary.types.sort(), ['PDV', 'PRINT']);
106
+ assert.ok(summary.keys.includes('pos-gateway'));
107
+ assert.ok(summary.keys.includes('print-network-port'));
108
+ });
109
+
110
+ test('buildCopyPayloads returns empty without dest or people', () => {
111
+ assert.deepEqual(
112
+ buildCopyPayloads({
113
+ sourceConfigs: sourceRows,
114
+ destinationDeviceString: '',
115
+ peopleIri,
116
+ }),
117
+ [],
118
+ );
119
+ assert.deepEqual(
120
+ buildCopyPayloads({
121
+ sourceConfigs: sourceRows,
122
+ destinationDeviceString: 'dest-hash',
123
+ peopleIri: '',
124
+ }),
125
+ [],
126
+ );
127
+ });
@@ -59,4 +59,42 @@ describe('fileUrl helpers', () => {
59
59
  headers: {},
60
60
  });
61
61
  });
62
+
63
+ it('does not path-inject environment hosts like staging (app-community#432)', () => {
64
+ const source = resolveDefaultFileSource(
65
+ {
66
+ id: 8444,
67
+ url: '/files/8444/download',
68
+ },
69
+ {
70
+ company: {
71
+ domain: 'staging.controleonline.com',
72
+ },
73
+ headers: {
74
+ Authorization: 'Bearer token',
75
+ },
76
+ },
77
+ );
78
+
79
+ expect(source).toEqual({
80
+ uri: 'https://api.controleonline.com/files/8444/download',
81
+ headers: {
82
+ Authorization: 'Bearer token',
83
+ 'app-domain': 'staging.controleonline.com',
84
+ },
85
+ });
86
+ expect(source.uri).not.toContain('staging.controleonline.com/files');
87
+ });
88
+
89
+ it('does not path-inject when fallback DOMAIN is manager platform host', () => {
90
+ // DOMAIN mock is manager.controleonline.com; no company → environment-like
91
+ const source = resolveDefaultFileSource(
92
+ {id: 9, url: '/files/9/download'},
93
+ {headers: {Authorization: 'Bearer t'}},
94
+ );
95
+
96
+ expect(source.uri).toBe('https://api.controleonline.com/files/9/download');
97
+ expect(source.headers['app-domain']).toBe('manager.controleonline.com');
98
+ expect(source.uri).not.toMatch(/manager\.controleonline\.com\/files/);
99
+ });
62
100
  });
@@ -4,10 +4,11 @@ const {test} = require('node:test')
4
4
  const {
5
5
  getImportErrorDetail,
6
6
  getImportStatusMeta,
7
- normalizeImportStatus,
8
- resolveImportLabels,
9
- resolveImportStatusLabel,
10
- } = require('@controleonline/ui-common/src/react/utils/importStatus')
7
+ normalizeImportStatus,
8
+ resolveImportLabels,
9
+ resolveImportTranslator,
10
+ resolveImportStatusLabel,
11
+ } = require('@controleonline/ui-common/src/react/utils/importStatus')
11
12
 
12
13
  test('normalizeImportStatus trims and lowercases the raw value', () => {
13
14
  assert.equal(normalizeImportStatus(' Processing '), 'processing')
@@ -50,7 +51,7 @@ test('getImportErrorDetail returns the first non-empty message candidate', () =>
50
51
  )
51
52
  })
52
53
 
53
- test('resolveImportLabels only reads translation keys without hardcoded fallbacks', () => {
54
+ test('resolveImportLabels only reads translation keys without hardcoded fallbacks', () => {
54
55
  const calls = []
55
56
  const labels = resolveImportLabels((...args) => {
56
57
  calls.push(args)
@@ -66,10 +67,26 @@ test('resolveImportLabels only reads translation keys without hardcoded fallback
66
67
  ['imports', 'message', 'processing_after_upload'],
67
68
  ])
68
69
  assert.equal(labels.refreshLabel, 'imports.button.refresh')
69
- assert.equal(labels.processingHelpLabel, 'imports.message.processing_after_upload')
70
- })
71
-
72
- test('resolveImportStatusLabel falls back to the raw backend status before the generic empty label', () => {
70
+ assert.equal(labels.processingHelpLabel, 'imports.message.processing_after_upload')
71
+ })
72
+
73
+ test('resolveImportTranslator preserves translator object context', () => {
74
+ const translator = {
75
+ prefix: 'translated',
76
+ t(type, scope, key) {
77
+ return `${this.prefix}.${type}.${scope}.${key}`
78
+ },
79
+ }
80
+
81
+ const translate = resolveImportTranslator(translator)
82
+
83
+ assert.equal(
84
+ translate('imports', 'button', 'refresh'),
85
+ 'translated.imports.button.refresh',
86
+ )
87
+ })
88
+
89
+ test('resolveImportStatusLabel falls back to the raw backend status before the generic empty label', () => {
73
90
  assert.equal(
74
91
  resolveImportStatusLabel(
75
92
  {
@@ -0,0 +1,27 @@
1
+ import {
2
+ PASSWORD_MIN_LENGTH,
3
+ PASSWORD_MSG_COMPROMISED,
4
+ PASSWORD_MSG_MIN_LENGTH,
5
+ mapPasswordErrorMessage,
6
+ validatePasswordClient,
7
+ } from '../../../react/utils/passwordPolicy';
8
+
9
+ describe('passwordPolicy', () => {
10
+ it('rejects short password with explicit min length', () => {
11
+ expect(validatePasswordClient('123')).toBe(PASSWORD_MSG_MIN_LENGTH);
12
+ });
13
+
14
+ it('accepts password meeting min length', () => {
15
+ expect(validatePasswordClient('abcdef')).toBeNull();
16
+ });
17
+
18
+ it('maps english breach message to portuguese', () => {
19
+ const raw =
20
+ 'This password has been leaked in a data breach, it must not be used. Please use another password.';
21
+ expect(mapPasswordErrorMessage(raw)).toBe(PASSWORD_MSG_COMPROMISED);
22
+ });
23
+
24
+ it('exposes policy min length constant', () => {
25
+ expect(PASSWORD_MIN_LENGTH).toBe(6);
26
+ });
27
+ });
@@ -0,0 +1,86 @@
1
+ import {describe, it} from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {
4
+ PRINT_ROUTE_MISSING_DESTINATION,
5
+ resolvePrintRoutingError,
6
+ resolveSpoolDeviceIdsForRuntime,
7
+ } from '../../../react/utils/printRouting.js';
8
+
9
+ const PRINT_DEVICE_TYPE = 'PRINT';
10
+ const PDV_DEVICE_TYPE = 'PDV';
11
+ const DISPLAY_DEVICE_TYPE = 'DISPLAY';
12
+
13
+ describe('resolveSpoolDeviceIdsForRuntime (print routing #628)', () => {
14
+ const managedPrinters = [
15
+ {device: 'printer-a', type: PRINT_DEVICE_TYPE},
16
+ {device: 'printer-b', type: PRINT_DEVICE_TYPE},
17
+ ];
18
+
19
+ it('PDV includes local + managed .device ids', () => {
20
+ const ids = resolveSpoolDeviceIdsForRuntime({
21
+ runtimeDeviceId: 'pdv-1',
22
+ runtimeDeviceType: PDV_DEVICE_TYPE,
23
+ managedPrinters,
24
+ includeLocalDevice: true,
25
+ });
26
+ assert.deepEqual(ids.sort(), ['pdv-1', 'printer-a', 'printer-b'].sort());
27
+ });
28
+
29
+ it('accepts deviceId fallback and prefers device', () => {
30
+ const idsWithOnlyDeviceId = resolveSpoolDeviceIdsForRuntime({
31
+ runtimeDeviceId: 'pdv-1',
32
+ runtimeDeviceType: PDV_DEVICE_TYPE,
33
+ managedPrinters: [{deviceId: 'should-not-be-used', type: PRINT_DEVICE_TYPE}],
34
+ includeLocalDevice: false,
35
+ });
36
+ const preferred = resolveSpoolDeviceIdsForRuntime({
37
+ runtimeDeviceId: 'pdv-1',
38
+ runtimeDeviceType: PDV_DEVICE_TYPE,
39
+ managedPrinters: [{device: 'printer-a'}],
40
+ includeLocalDevice: false,
41
+ });
42
+ assert.deepEqual(preferred, ['printer-a']);
43
+ assert.deepEqual(idsWithOnlyDeviceId, ['should-not-be-used']);
44
+ });
45
+
46
+ it('DISPLAY only polls managed printers', () => {
47
+ const ids = resolveSpoolDeviceIdsForRuntime({
48
+ runtimeDeviceId: 'display-1',
49
+ runtimeDeviceType: DISPLAY_DEVICE_TYPE,
50
+ managedPrinters,
51
+ });
52
+ assert.deepEqual(ids.sort(), ['printer-a', 'printer-b'].sort());
53
+ });
54
+
55
+ it('PRINT runtime does not receive jobs (no spool poll)', () => {
56
+ const ids = resolveSpoolDeviceIdsForRuntime({
57
+ runtimeDeviceId: 'printer-a',
58
+ runtimeDeviceType: PRINT_DEVICE_TYPE,
59
+ managedPrinters,
60
+ });
61
+ assert.deepEqual(ids, []);
62
+ });
63
+
64
+ it('empty managed list + no local => no destinations', () => {
65
+ const ids = resolveSpoolDeviceIdsForRuntime({
66
+ runtimeDeviceId: 'pdv-1',
67
+ runtimeDeviceType: PDV_DEVICE_TYPE,
68
+ managedPrinters: [],
69
+ includeLocalDevice: false,
70
+ });
71
+ assert.deepEqual(ids, []);
72
+ });
73
+ });
74
+
75
+ describe('resolvePrintRoutingError', () => {
76
+ it('returns a visible message when routing has no destination', () => {
77
+ assert.equal(resolvePrintRoutingError({}), PRINT_ROUTE_MISSING_DESTINATION);
78
+ });
79
+
80
+ it('is silent when a printer device is selected', () => {
81
+ assert.equal(
82
+ resolvePrintRoutingError({selectedPrinter: {device: 'printer-a'}}),
83
+ '',
84
+ );
85
+ });
86
+ });
@@ -0,0 +1,52 @@
1
+ import assert from 'node:assert/strict';
2
+ import {describe, it} from 'node:test';
3
+ import {
4
+ resolveSpoolId,
5
+ extractCollectionMembers,
6
+ mergeOpenSpoolCollections,
7
+ resolveRequestedTargetDeviceId,
8
+ resolveRequestedTargetDeviceTypeValue,
9
+ } from '../../../react/utils/printSpoolUtils.js';
10
+
11
+ describe('printSpoolUtils (#628 modularization)', () => {
12
+ it('resolveSpoolId handles number, string, object and @id', () => {
13
+ assert.equal(resolveSpoolId(12), 12);
14
+ assert.equal(resolveSpoolId('spool/34'), 34);
15
+ assert.equal(resolveSpoolId({id: 7}), 7);
16
+ assert.equal(resolveSpoolId({'@id': '/spools/9'}), 9);
17
+ assert.equal(resolveSpoolId(null), null);
18
+ });
19
+
20
+ it('extractCollectionMembers supports array, member, hydra:member', () => {
21
+ assert.deepEqual(extractCollectionMembers([1]), [1]);
22
+ assert.deepEqual(extractCollectionMembers({member: [2]}), [2]);
23
+ assert.deepEqual(extractCollectionMembers({'hydra:member': [3]}), [3]);
24
+ assert.deepEqual(extractCollectionMembers({}), []);
25
+ });
26
+
27
+ it('mergeOpenSpoolCollections dedupes and sorts by id', () => {
28
+ const merged = mergeOpenSpoolCollections([
29
+ [{id: 3}, {id: 1}],
30
+ [{id: 1}, {id: 2}],
31
+ ]);
32
+ assert.deepEqual(
33
+ merged.map(s => s.id),
34
+ [1, 2, 3],
35
+ );
36
+ });
37
+
38
+ it('resolveRequestedTargetDeviceId prefers explicit fields', () => {
39
+ assert.equal(
40
+ resolveRequestedTargetDeviceId({targetDeviceId: 5}, 9),
41
+ '5',
42
+ );
43
+ assert.equal(resolveRequestedTargetDeviceId({}, 9), '9');
44
+ });
45
+
46
+ it('resolveRequestedTargetDeviceTypeValue normalizes type', () => {
47
+ assert.equal(
48
+ resolveRequestedTargetDeviceTypeValue({targetDeviceType: 'pdv'}),
49
+ 'PDV',
50
+ );
51
+ });
52
+ });
@@ -11,6 +11,7 @@ const {
11
11
  getRuntimeFooterWebIdentifierCandidates,
12
12
  getRuntimeFooterWebHost,
13
13
  normalizeRuntimeFooterText,
14
+ shouldShowRuntimeFooter,
14
15
  } = require('../../../react/utils/runtimeFooter')
15
16
 
16
17
  const originalLocationDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'location')
@@ -445,3 +446,9 @@ test('rotates each footer line before the runtime version entry', () => {
445
446
  ['www.site.com', '(11) 99999-9999', 'web (203.0.113.42) / v1.3.6'],
446
447
  )
447
448
  })
449
+
450
+ test('hides runtime device information on the customer paylist only', () => {
451
+ assert.equal(shouldShowRuntimeFooter('PaylistPage'), false)
452
+ assert.equal(shouldShowRuntimeFooter('LoginPage'), true)
453
+ assert.equal(shouldShowRuntimeFooter('WalletsPage'), true)
454
+ })
@@ -1,152 +1,158 @@
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');
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
8
  const {
9
9
  extractAddressCategoryIds,
10
10
  filterShopFranchiseDirectory,
11
11
  fetchAllShopFranchiseDirectory,
12
12
  fetchShopFranchiseDirectory,
13
13
  } = require('../../../react/utils/shopFranchises');
14
-
15
- const {beforeEach, describe, expect, it} = global;
16
-
17
- describe('shopFranchises', () => {
18
- beforeEach(() => {
19
- api.fetch.mockReset();
20
- });
21
-
22
- it('uses the public shop franchises endpoint and preserves shop addresses', async () => {
23
- api.fetch.mockResolvedValueOnce({
24
- member: [
25
- {
26
- id: 21,
27
- alias: 'Centro',
28
- shopAddresses: [{id: 501, nickname: 'Loja Centro'}],
29
- },
30
- ],
31
- 'hydra:member': [
32
- {
33
- id: 21,
34
- alias: 'Centro',
35
- shopAddresses: [{id: 501, nickname: 'Loja Centro'}],
36
- },
37
- ],
38
- });
39
-
40
- const directory = await fetchShopFranchiseDirectory({
41
- companyId: 10,
42
- publicDirectory: true,
43
- });
44
-
45
- expect(api.fetch).toHaveBeenCalledWith('/shop/franchises', {
46
- params: {
47
- itemsPerPage: 50,
48
- page: 1,
49
- },
50
- });
51
- expect(directory).toEqual([
52
- {
53
- id: 21,
54
- alias: 'Centro',
55
- shopAddresses: [{id: 501, nickname: 'Loja Centro'}],
56
- },
57
- ]);
58
- });
59
-
60
- it('keeps authenticated management lookup scoped by company', async () => {
14
+
15
+ const {beforeEach, describe, expect, it} = global;
16
+
17
+ describe('shopFranchises', () => {
18
+ beforeEach(() => {
19
+ api.fetch.mockReset();
20
+ });
21
+
22
+ it('uses the public shop franchises endpoint and preserves shop addresses', async () => {
61
23
  api.fetch.mockResolvedValueOnce({
62
24
  member: [
63
25
  {
64
- id: 22,
65
- alias: 'Norte',
66
- address: [{id: 601, nickname: 'Loja Norte'}],
67
- },
68
- ],
69
- });
70
-
71
- const directory = await fetchShopFranchiseDirectory({companyId: 10});
72
-
73
- expect(api.fetch).toHaveBeenCalledWith('people', {
74
- params: {
75
- 'link.company': '/people/10',
76
- 'link.linkType': 'franchisee',
77
- itemsPerPage: 50,
78
- page: 1,
79
- },
80
- });
26
+ id: 21,
27
+ alias: 'Centro',
28
+ shopAddresses: [{id: 501, nickname: 'Loja Centro'}],
29
+ },
30
+ ],
31
+ 'hydra:member': [
32
+ {
33
+ id: 21,
34
+ alias: 'Centro',
35
+ shopAddresses: [{id: 501, nickname: 'Loja Centro'}],
36
+ },
37
+ ],
38
+ });
39
+
40
+ const directory = await fetchShopFranchiseDirectory({
41
+ companyId: 10,
42
+ publicDirectory: true,
43
+ });
44
+
45
+ expect(api.fetch).toHaveBeenCalledWith('/shop/franchises', {
46
+ params: {
47
+ itemsPerPage: 50,
48
+ page: 1,
49
+ },
50
+ });
51
+ expect(directory).toEqual([
52
+ {
53
+ id: 21,
54
+ alias: 'Centro',
55
+ shopAddresses: [{id: 501, nickname: 'Loja Centro'}],
56
+ },
57
+ ]);
58
+ });
59
+
60
+ it('loads franchises from people_links on both company and people sides', async () => {
61
+ api.fetch
62
+ .mockResolvedValueOnce({
63
+ member: [
64
+ {
65
+ id: 51,
66
+ linkType: 'franchisee',
67
+ company: {id: 10, alias: 'Franchisor'},
68
+ people: {
69
+ id: 22,
70
+ alias: 'Norte',
71
+ address: [{id: 601, nickname: 'Loja Norte'}],
72
+ },
73
+ },
74
+ ],
75
+ })
76
+ .mockResolvedValueOnce({
77
+ member: [],
78
+ });
79
+
80
+ const directory = await fetchShopFranchiseDirectory({companyId: 10});
81
+
82
+ expect(api.fetch).toHaveBeenCalledWith('people_links', {
83
+ params: {
84
+ company: '/people/10',
85
+ linkType: 'franchisee',
86
+ itemsPerPage: 50,
87
+ page: 1,
88
+ },
89
+ });
90
+ expect(api.fetch).toHaveBeenCalledWith('people_links', {
91
+ params: {
92
+ people: '/people/10',
93
+ linkType: 'franchisee',
94
+ itemsPerPage: 50,
95
+ page: 1,
96
+ },
97
+ });
98
+ expect(directory[0].id).toBe(22);
99
+ expect(directory[0].alias).toBe('Norte');
81
100
  expect(directory[0].shopAddresses).toEqual([
82
101
  {id: 601, nickname: 'Loja Norte'},
83
102
  ]);
84
103
  });
85
104
 
86
- it('loads every page when building the full franchise directory', async () => {
105
+ it('loads every page when building the full franchise directory', async () => {
106
+ // Dual-side people_links: company side page1 (full), page2 empty break;
107
+ // people side empty.
87
108
  api.fetch
88
109
  .mockResolvedValueOnce({
89
110
  member: [
90
111
  {
91
- id: 21,
92
- alias: 'Centro',
93
- shopAddresses: [{id: 501, nickname: 'Loja Centro'}],
112
+ id: 1,
113
+ linkType: 'franchisee',
114
+ company: {id: 10},
115
+ people: {
116
+ id: 21,
117
+ alias: 'Centro',
118
+ shopAddresses: [{id: 501, nickname: 'Loja Centro'}],
119
+ },
94
120
  },
95
121
  {
96
- id: 22,
97
- alias: 'Norte',
98
- shopAddresses: [{id: 601, nickname: 'Loja Norte'}],
122
+ id: 2,
123
+ linkType: 'franchisee',
124
+ company: {id: 10},
125
+ people: {
126
+ id: 22,
127
+ alias: 'Norte',
128
+ shopAddresses: [{id: 601, nickname: 'Loja Norte'}],
129
+ },
99
130
  },
100
131
  ],
101
132
  })
102
133
  .mockResolvedValueOnce({
103
134
  member: [
104
135
  {
105
- id: 23,
106
- alias: 'Sul',
107
- shopAddresses: [{id: 701, nickname: 'Loja Sul'}],
136
+ id: 3,
137
+ linkType: 'franchisee',
138
+ company: {id: 10},
139
+ people: {
140
+ id: 23,
141
+ alias: 'Sul',
142
+ shopAddresses: [{id: 701, nickname: 'Loja Sul'}],
143
+ },
108
144
  },
109
145
  ],
110
- });
146
+ })
147
+ .mockResolvedValueOnce({member: []});
111
148
 
112
149
  const directory = await fetchAllShopFranchiseDirectory({
113
150
  companyId: 10,
114
151
  itemsPerPage: 2,
115
152
  });
116
153
 
117
- expect(api.fetch).toHaveBeenNthCalledWith(1, 'people', {
118
- params: {
119
- 'link.company': '/people/10',
120
- 'link.linkType': 'franchisee',
121
- itemsPerPage: 2,
122
- page: 1,
123
- },
124
- });
125
- expect(api.fetch).toHaveBeenNthCalledWith(2, 'people', {
126
- params: {
127
- 'link.company': '/people/10',
128
- 'link.linkType': 'franchisee',
129
- itemsPerPage: 2,
130
- page: 2,
131
- },
132
- });
133
- expect(directory).toEqual([
134
- {
135
- id: 21,
136
- alias: 'Centro',
137
- shopAddresses: [{id: 501, nickname: 'Loja Centro'}],
138
- },
139
- {
140
- id: 22,
141
- alias: 'Norte',
142
- shopAddresses: [{id: 601, nickname: 'Loja Norte'}],
143
- },
144
- {
145
- id: 23,
146
- alias: 'Sul',
147
- shopAddresses: [{id: 701, nickname: 'Loja Sul'}],
148
- },
149
- ]);
154
+ expect(api.fetch.mock.calls[0][0]).toBe('people_links');
155
+ expect(directory.map(item => item.id).sort()).toEqual([21, 22, 23]);
150
156
  });
151
157
 
152
158
  it('extracts category ids from address category payload variants', () => {