@controleonline/ui-common 1.2.79 → 1.2.81
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.
- package/AGENTS.md +8 -0
- package/package.json +9 -3
- package/src/api/fetch.js +7 -7
- package/src/react/components/AddImportModal.js +6 -2
- package/src/react/components/BackgroundRuntimeBridge.js +201 -215
- package/src/react/components/ConfirmModal.js +44 -9
- package/src/react/components/ConfirmModal.styles.js +55 -41
- package/src/react/components/CopyDeviceConfigModal.js +193 -0
- package/src/react/components/CopyDeviceConfigModal.styles.js +123 -0
- package/src/react/components/DefaultProvider.native.js +22 -0
- package/src/react/components/DefaultProvider.web.js +25 -1
- package/src/react/components/EntityLogBranch.js +482 -0
- package/src/react/components/EntityLogCards.js +369 -0
- package/src/react/components/EntityLogContent.js +113 -794
- package/src/react/components/LinkedOrderProductsTab.js +3 -3
- package/src/react/components/PrintService.js +130 -409
- package/src/react/components/RemoteCheckoutService.js +2 -2
- package/src/react/components/RuntimeBottomNavigationBar.js +141 -146
- package/src/react/components/RuntimeFooterMarqueeText.js +137 -0
- package/src/react/components/RuntimeInfoFooter.js +12 -15
- package/src/react/components/SystemErrorToast.js +49 -29
- package/src/react/components/SystemErrorToast.styles.js +43 -38
- package/src/react/components/UserAvatar.js +120 -28
- package/src/react/config/deviceConfigBootstrap.js +330 -304
- package/src/react/hooks/usePrintSpoolEffects.js +161 -0
- package/src/react/pages/DeviceDetailPage.js +6 -2882
- package/src/react/pages/Devices/detail/DeviceDetailAlertsCommandsSection.js +233 -0
- package/src/react/pages/Devices/detail/DeviceDetailHeader.js +138 -0
- package/src/react/pages/Devices/detail/DeviceDetailMovementSections.js +167 -0
- package/src/react/pages/Devices/detail/DeviceDetailOrdersPrintSection.js +216 -0
- package/src/react/pages/Devices/detail/DeviceDetailPaymentSection.js +40 -0
- package/src/react/pages/Devices/detail/DeviceDetailPdvConfigSection.js +390 -0
- package/src/react/pages/Devices/detail/DeviceDetailRenderers.js +135 -0
- package/src/react/pages/Devices/detail/DeviceDetailScreen.js +493 -0
- package/src/react/pages/Devices/detail/OptionButtonChip.js +65 -0
- package/src/react/pages/Devices/detail/deviceDetailConstants.js +36 -0
- package/src/react/pages/Devices/detail/deviceDetailHelpers.js +70 -0
- package/src/react/pages/Devices/detail/useDeviceDetailActions.js +483 -0
- package/src/react/pages/Devices/detail/useDeviceDetailCopyConfig.js +87 -0
- package/src/react/pages/Devices/detail/useDeviceDetailLoaders.js +327 -0
- package/src/react/pages/Devices/detail/useDeviceDetailSaves.js +429 -0
- package/src/react/pages/Devices/detail/useDeviceDetailStateA.js +484 -0
- package/src/react/pages/Devices/detail/useDeviceDetailStateB.js +71 -0
- package/src/react/pages/Devices/deviceTypes/index.js +2 -0
- package/src/react/pages/Devices/deviceTypes/pdv.js +11 -0
- package/src/react/pages/Imports.js +3 -2
- package/src/react/pages/IntegrationConfigFields.js +126 -0
- package/src/react/pages/IntegrationConfigPage.js +122 -591
- package/src/react/pages/IntegrationConfigPage.utils.js +95 -0
- package/src/react/print/usePrintButtonController.js +9 -1
- package/src/react/services/Getnet/Checkout.js +81 -0
- package/src/react/services/Getnet/Getnet.js +77 -0
- package/src/react/services/paymentGatewayExecution.js +20 -0
- package/src/react/utils/bottomNavigationToolbar.js +161 -0
- package/src/react/utils/commercialDocumentOrders.js +18 -6
- package/src/react/utils/copyDeviceConfigs.js +198 -0
- package/src/react/utils/fileUrl.js +43 -0
- package/src/react/utils/importStatus.js +19 -10
- package/src/react/utils/logSettings.js +7 -7
- package/src/react/utils/maintenanceSettings.js +16 -0
- package/src/react/utils/passwordPolicy.js +73 -0
- package/src/react/utils/paymentDevices.js +6 -0
- package/src/react/utils/printManagedPrinter.js +45 -0
- package/src/react/utils/printRouting.js +63 -0
- package/src/react/utils/printSpoolUtils.js +119 -0
- package/src/react/utils/printerDevices.js +368 -368
- package/src/react/utils/runtimeFooter.js +6 -1
- package/src/react/utils/shopFranchises.js +186 -65
- package/src/react/utils/systemErrorMessage.js +71 -67
- package/src/store/device_config/customActions.js +119 -30
- package/src/tests/browser/manager/bottom-navigation-role-toolbar.spec.js +353 -0
- package/src/tests/browser/manager/device-detail-alias-save.spec.js +345 -0
- package/src/tests/browser/manager/device-detail-copy-config.spec.js +365 -0
- package/src/tests/browser/manager/runtime-footer-marquee.spec.js +262 -0
- package/src/tests/browser/manager/theme-confirm-toast.spec.js +236 -0
- package/src/tests/react/components/RuntimeFooterMarqueeText.test.js +171 -0
- package/src/tests/react/components/UserAvatar.test.js +6 -2
- package/src/tests/react/components/themeAwareModals.test.mjs +60 -0
- package/src/tests/react/config/deviceConfigBootstrap.test.js +104 -66
- package/src/tests/react/pages/deviceDetailDelete.test.js +78 -0
- package/src/tests/react/pages/receitaFederalIntegrationContract.test.mjs +48 -0
- package/src/tests/react/utils/bottomNavigationToolbar.test.js +222 -0
- package/src/tests/react/utils/copyDeviceConfigs.test.mjs +127 -0
- package/src/tests/react/utils/fileUrl.test.js +38 -0
- package/src/tests/react/utils/importStatus.test.js +27 -10
- package/src/tests/react/utils/passwordPolicy.test.js +27 -0
- package/src/tests/react/utils/printRouting.test.mjs +86 -0
- package/src/tests/react/utils/printSpoolUtils.test.mjs +52 -0
- package/src/tests/react/utils/runtimeFooter.test.js +10 -8
- package/src/tests/react/utils/shopFranchises.test.js +121 -115
- package/src/tests/react/utils/systemErrorMessage.test.js +32 -59
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
const {jest} = require('@jest/globals')
|
|
2
|
-
|
|
3
|
-
jest.mock('../../../react/utils/screenMetrics', () => ({
|
|
4
|
-
appendScreenMetrics: configs => configs,
|
|
5
|
-
hasScreenMetricsChanges: () => false,
|
|
6
|
-
}))
|
|
7
|
-
|
|
1
|
+
const {jest} = require('@jest/globals')
|
|
2
|
+
|
|
3
|
+
jest.mock('../../../react/utils/screenMetrics', () => ({
|
|
4
|
+
appendScreenMetrics: configs => configs,
|
|
5
|
+
hasScreenMetricsChanges: () => false,
|
|
6
|
+
}))
|
|
7
|
+
|
|
8
8
|
const {
|
|
9
9
|
buildProviderManagedDeviceConfigs,
|
|
10
10
|
DEVICE_ANDROID_KIOSK_ENABLED_CONFIG_KEY,
|
|
@@ -36,22 +36,24 @@ const {
|
|
|
36
36
|
resolvePosCashManagementMode,
|
|
37
37
|
resolvePosOperationMode,
|
|
38
38
|
resolvePosPrintMode,
|
|
39
|
+
POS_LOCAL_CHARGE_ENABLED_CONFIG_KEY,
|
|
40
|
+
isPosLocalChargeEnabled,
|
|
39
41
|
resolvePosCheckOrderType,
|
|
40
42
|
shouldEnableAndroidKioskMode,
|
|
41
43
|
shouldEnableAndroidLauncherMode,
|
|
42
44
|
shouldUsePosCashRegisterLifecycle,
|
|
43
45
|
isPosTotemMode,
|
|
44
46
|
} = require('../../../react/config/deviceConfigBootstrap')
|
|
45
|
-
|
|
46
|
-
const {describe, expect, it} = global
|
|
47
|
-
|
|
48
|
-
describe('deviceConfigBootstrap POS operation helpers', () => {
|
|
47
|
+
|
|
48
|
+
const {describe, expect, it} = global
|
|
49
|
+
|
|
50
|
+
describe('deviceConfigBootstrap POS operation helpers', () => {
|
|
49
51
|
it('normalizes balcão as counter and treats counter as self service', () => {
|
|
50
52
|
const configs = {
|
|
51
53
|
[POS_OPERATION_MODE_CONFIG_KEY]: 'balcao',
|
|
52
54
|
}
|
|
53
|
-
|
|
54
|
-
expect(resolvePosOperationMode(configs)).toBe('counter')
|
|
55
|
+
|
|
56
|
+
expect(resolvePosOperationMode(configs)).toBe('counter')
|
|
55
57
|
expect(isPosCounterMode(configs)).toBe(true)
|
|
56
58
|
expect(isPosSelfServiceMode(configs)).toBe(true)
|
|
57
59
|
})
|
|
@@ -65,52 +67,52 @@ describe('deviceConfigBootstrap POS operation helpers', () => {
|
|
|
65
67
|
expect(isPosSingleItemMode(configs)).toBe(true)
|
|
66
68
|
expect(isPosSelfServiceMode(configs)).toBe(false)
|
|
67
69
|
})
|
|
68
|
-
|
|
69
|
-
it('keeps counter auto print disabled by default and allows explicit enablement', () => {
|
|
70
|
-
const counterConfigs = {
|
|
71
|
-
[POS_OPERATION_MODE_CONFIG_KEY]: 'counter',
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
expect(isPosAutoPrintEnabled(counterConfigs)).toBe(false)
|
|
75
|
-
|
|
76
|
-
expect(
|
|
77
|
-
isPosAutoPrintEnabled({
|
|
78
|
-
...counterConfigs,
|
|
79
|
-
[POS_AUTO_PRINT_ENABLED_CONFIG_KEY]: '1',
|
|
80
|
-
}),
|
|
81
|
-
).toBe(true)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('defaults non-counter modes to auto print enabled', () => {
|
|
85
|
-
expect(
|
|
86
|
-
isPosAutoPrintEnabled({
|
|
87
|
-
[POS_OPERATION_MODE_CONFIG_KEY]: 'cashier',
|
|
88
|
-
}),
|
|
89
|
-
).toBe(true)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it('resolves counter daily close mode and skips cash register lifecycle', () => {
|
|
93
|
-
const configs = {
|
|
94
|
-
[POS_OPERATION_MODE_CONFIG_KEY]: 'counter',
|
|
95
|
-
[POS_CASH_MANAGEMENT_MODE_CONFIG_KEY]: 'daily',
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
expect(resolvePosCashManagementMode(configs)).toBe('daily')
|
|
99
|
-
expect(shouldUsePosCashRegisterLifecycle(configs)).toBe(false)
|
|
100
|
-
expect(isPosCashRegisterOpen(configs)).toBe(true)
|
|
101
|
-
expect(isPosCashRegisterClosed(configs)).toBe(false)
|
|
102
|
-
})
|
|
103
|
-
|
|
70
|
+
|
|
71
|
+
it('keeps counter auto print disabled by default and allows explicit enablement', () => {
|
|
72
|
+
const counterConfigs = {
|
|
73
|
+
[POS_OPERATION_MODE_CONFIG_KEY]: 'counter',
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
expect(isPosAutoPrintEnabled(counterConfigs)).toBe(false)
|
|
77
|
+
|
|
78
|
+
expect(
|
|
79
|
+
isPosAutoPrintEnabled({
|
|
80
|
+
...counterConfigs,
|
|
81
|
+
[POS_AUTO_PRINT_ENABLED_CONFIG_KEY]: '1',
|
|
82
|
+
}),
|
|
83
|
+
).toBe(true)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('defaults non-counter modes to auto print enabled', () => {
|
|
87
|
+
expect(
|
|
88
|
+
isPosAutoPrintEnabled({
|
|
89
|
+
[POS_OPERATION_MODE_CONFIG_KEY]: 'cashier',
|
|
90
|
+
}),
|
|
91
|
+
).toBe(true)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('resolves counter daily close mode and skips cash register lifecycle', () => {
|
|
95
|
+
const configs = {
|
|
96
|
+
[POS_OPERATION_MODE_CONFIG_KEY]: 'counter',
|
|
97
|
+
[POS_CASH_MANAGEMENT_MODE_CONFIG_KEY]: 'daily',
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
expect(resolvePosCashManagementMode(configs)).toBe('daily')
|
|
101
|
+
expect(shouldUsePosCashRegisterLifecycle(configs)).toBe(false)
|
|
102
|
+
expect(isPosCashRegisterOpen(configs)).toBe(true)
|
|
103
|
+
expect(isPosCashRegisterClosed(configs)).toBe(false)
|
|
104
|
+
})
|
|
105
|
+
|
|
104
106
|
it('requires cash register lifecycle for counter with open/close policy', () => {
|
|
105
107
|
const closedConfigs = {
|
|
106
108
|
[POS_OPERATION_MODE_CONFIG_KEY]: 'counter',
|
|
107
109
|
[POS_CASH_MANAGEMENT_MODE_CONFIG_KEY]: 'cash-register',
|
|
108
110
|
'cash-wallet-closed-id': 1,
|
|
109
111
|
}
|
|
110
|
-
|
|
111
|
-
expect(shouldUsePosCashRegisterLifecycle(closedConfigs)).toBe(true)
|
|
112
|
-
expect(isPosCashRegisterClosed(closedConfigs)).toBe(true)
|
|
113
|
-
|
|
112
|
+
|
|
113
|
+
expect(shouldUsePosCashRegisterLifecycle(closedConfigs)).toBe(true)
|
|
114
|
+
expect(isPosCashRegisterClosed(closedConfigs)).toBe(true)
|
|
115
|
+
|
|
114
116
|
expect(
|
|
115
117
|
isPosCashRegisterOpen({
|
|
116
118
|
...closedConfigs,
|
|
@@ -369,16 +371,52 @@ describe('deviceConfigBootstrap POS operation helpers', () => {
|
|
|
369
371
|
})
|
|
370
372
|
|
|
371
373
|
it('keeps delivery enabled by default and respects explicit disablement', () => {
|
|
372
|
-
expect(isPosDeliveryEnabled({})).toBe(true)
|
|
373
|
-
expect(
|
|
374
|
-
isPosDeliveryEnabled({
|
|
375
|
-
[POS_DELIVERY_ENABLED_CONFIG_KEY]: '0',
|
|
376
|
-
}),
|
|
377
|
-
).toBe(false)
|
|
378
|
-
expect(
|
|
379
|
-
isPosDeliveryEnabled({
|
|
380
|
-
[POS_DELIVERY_ENABLED_CONFIG_KEY]: '1',
|
|
381
|
-
}),
|
|
382
|
-
).toBe(true)
|
|
383
|
-
})
|
|
384
|
-
})
|
|
374
|
+
expect(isPosDeliveryEnabled({})).toBe(true)
|
|
375
|
+
expect(
|
|
376
|
+
isPosDeliveryEnabled({
|
|
377
|
+
[POS_DELIVERY_ENABLED_CONFIG_KEY]: '0',
|
|
378
|
+
}),
|
|
379
|
+
).toBe(false)
|
|
380
|
+
expect(
|
|
381
|
+
isPosDeliveryEnabled({
|
|
382
|
+
[POS_DELIVERY_ENABLED_CONFIG_KEY]: '1',
|
|
383
|
+
}),
|
|
384
|
+
).toBe(true)
|
|
385
|
+
})
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
describe('deviceConfigBootstrap local charge capability', () => {
|
|
389
|
+
it('defaults waiter to blocked and other modes to allowed when config missing', () => {
|
|
390
|
+
expect(
|
|
391
|
+
isPosLocalChargeEnabled({
|
|
392
|
+
[POS_OPERATION_MODE_CONFIG_KEY]: 'waiter',
|
|
393
|
+
}),
|
|
394
|
+
).toBe(false)
|
|
395
|
+
expect(
|
|
396
|
+
isPosLocalChargeEnabled({
|
|
397
|
+
[POS_OPERATION_MODE_CONFIG_KEY]: 'cashier',
|
|
398
|
+
}),
|
|
399
|
+
).toBe(true)
|
|
400
|
+
expect(
|
|
401
|
+
isPosLocalChargeEnabled({
|
|
402
|
+
[POS_OPERATION_MODE_CONFIG_KEY]: 'single-item',
|
|
403
|
+
}),
|
|
404
|
+
).toBe(true)
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
it('honors explicit pos-local-charge-enabled over mode default', () => {
|
|
408
|
+
expect(
|
|
409
|
+
isPosLocalChargeEnabled({
|
|
410
|
+
[POS_OPERATION_MODE_CONFIG_KEY]: 'waiter',
|
|
411
|
+
[POS_LOCAL_CHARGE_ENABLED_CONFIG_KEY]: '1',
|
|
412
|
+
}),
|
|
413
|
+
).toBe(true)
|
|
414
|
+
expect(
|
|
415
|
+
isPosLocalChargeEnabled({
|
|
416
|
+
[POS_OPERATION_MODE_CONFIG_KEY]: 'cashier',
|
|
417
|
+
[POS_LOCAL_CHARGE_ENABLED_CONFIG_KEY]: '0',
|
|
418
|
+
}),
|
|
419
|
+
).toBe(false)
|
|
420
|
+
})
|
|
421
|
+
})
|
|
422
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit coverage for device-detail delete confirmation flow (app-community#383).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
describe('device-detail delete confirmation (task-383)', () => {
|
|
6
|
+
const buildDeleteFlow = ({ remove, onSuccess, onError }) => {
|
|
7
|
+
let confirmed = false;
|
|
8
|
+
const confirm = (msg, cb) => ({
|
|
9
|
+
confirmNow: () => {
|
|
10
|
+
confirmed = true;
|
|
11
|
+
return Promise.resolve().then(() => cb());
|
|
12
|
+
},
|
|
13
|
+
cancel: () => {
|
|
14
|
+
confirmed = false;
|
|
15
|
+
},
|
|
16
|
+
wasConfirmed: () => confirmed,
|
|
17
|
+
message: msg,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const deleteDevice = async ({ deviceId, alias, deviceString }) => {
|
|
21
|
+
if (!deviceId) return { aborted: true };
|
|
22
|
+
const label = String(alias || deviceString || deviceId).trim();
|
|
23
|
+
const message = `Excluir o device "${label}"? Esta ação não pode ser desfeita.`;
|
|
24
|
+
return confirm(message, async () => {
|
|
25
|
+
try {
|
|
26
|
+
await remove(deviceId);
|
|
27
|
+
onSuccess?.();
|
|
28
|
+
} catch (e) {
|
|
29
|
+
onError?.(e);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return { deleteDevice };
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
test('does not call remove when confirmation is cancelled', async () => {
|
|
38
|
+
const remove = jest.fn();
|
|
39
|
+
const onSuccess = jest.fn();
|
|
40
|
+
const { deleteDevice } = buildDeleteFlow({ remove, onSuccess });
|
|
41
|
+
const gate = await deleteDevice({ deviceId: '42', alias: 'PDV 1' });
|
|
42
|
+
expect(gate.message).toContain('PDV 1');
|
|
43
|
+
gate.cancel();
|
|
44
|
+
expect(remove).not.toHaveBeenCalled();
|
|
45
|
+
expect(onSuccess).not.toHaveBeenCalled();
|
|
46
|
+
expect(gate.wasConfirmed()).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('calls remove with deviceId after confirmation', async () => {
|
|
50
|
+
const remove = jest.fn().mockResolvedValue(undefined);
|
|
51
|
+
const onSuccess = jest.fn();
|
|
52
|
+
const { deleteDevice } = buildDeleteFlow({ remove, onSuccess });
|
|
53
|
+
const gate = await deleteDevice({ deviceId: '99', alias: 'Kiosk' });
|
|
54
|
+
await gate.confirmNow();
|
|
55
|
+
expect(remove).toHaveBeenCalledWith('99');
|
|
56
|
+
expect(onSuccess).toHaveBeenCalled();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('surfaces error without success when remove rejects', async () => {
|
|
60
|
+
const remove = jest.fn().mockRejectedValue(new Error('fail'));
|
|
61
|
+
const onSuccess = jest.fn();
|
|
62
|
+
const onError = jest.fn();
|
|
63
|
+
const { deleteDevice } = buildDeleteFlow({ remove, onSuccess, onError });
|
|
64
|
+
const gate = await deleteDevice({ deviceId: '7', deviceString: 'dev-7' });
|
|
65
|
+
await gate.confirmNow();
|
|
66
|
+
expect(remove).toHaveBeenCalledWith('7');
|
|
67
|
+
expect(onSuccess).not.toHaveBeenCalled();
|
|
68
|
+
expect(onError).toHaveBeenCalled();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('aborts when deviceId is missing', async () => {
|
|
72
|
+
const remove = jest.fn();
|
|
73
|
+
const { deleteDevice } = buildDeleteFlow({ remove });
|
|
74
|
+
const result = await deleteDevice({ deviceId: null, alias: 'x' });
|
|
75
|
+
expect(result).toEqual({ aborted: true });
|
|
76
|
+
expect(remove).not.toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import test from 'node:test';
|
|
4
|
+
|
|
5
|
+
const catalog = fs.readFileSync(new URL('../../../react/pages/integrationsCatalog.js', import.meta.url), 'utf8');
|
|
6
|
+
const page = fs.readFileSync(new URL('../../../react/pages/IntegrationConfigPage.js', import.meta.url), 'utf8');
|
|
7
|
+
const fields = fs.readFileSync(new URL('../../../react/pages/IntegrationConfigFields.js', import.meta.url), 'utf8');
|
|
8
|
+
const utils = fs.readFileSync(new URL('../../../react/pages/IntegrationConfigPage.utils.js', import.meta.url), 'utf8');
|
|
9
|
+
|
|
10
|
+
test('Receita Federal catalog exposes company fiscal configuration', () => {
|
|
11
|
+
assert.match(catalog, /key: 'receita-federal'/);
|
|
12
|
+
assert.match(catalog, /label: 'Receita Federal'/);
|
|
13
|
+
assert.match(catalog, /receita-federal-tax-regime/);
|
|
14
|
+
assert.match(catalog, /receita-federal-ibge-code/);
|
|
15
|
+
assert.match(catalog, /receita-federal-certificate-file/);
|
|
16
|
+
assert.match(catalog, /receita-federal-certificate-password/);
|
|
17
|
+
assert.match(catalog, /secureTextEntry: true/);
|
|
18
|
+
assert.match(catalog, /\.pfx,\.p12,application\/x-pkcs12/);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('Receita Federal route resolves without relying only on route params', () => {
|
|
22
|
+
assert.match(utils, /ReceitaFederalIntegrationPage: 'receita-federal'/);
|
|
23
|
+
assert.match(page, /ROUTE_PROVIDER_MAP\[route\?\.name\]/);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('embedded fiscal configuration keeps reads and writes on the company being edited', () => {
|
|
27
|
+
assert.match(utils, /route\?\.params\?\.companyId/);
|
|
28
|
+
assert.match(utils, /resolveProviderId/);
|
|
29
|
+
assert.match(utils, /resolveFallbackConfigs/);
|
|
30
|
+
assert.match(page, /resolveProviderId\(\{ route, currentCompany \}\)/);
|
|
31
|
+
assert.match(page, /api\.fetch\('\/configs', \{ params: \{ people: providerIri \} \}\)/);
|
|
32
|
+
assert.match(page, /addManyConfigs\(\{ configs, people: providerIri/);
|
|
33
|
+
assert.match(page, /syncConfigValues\(fallbackConfigs\)/);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('certificate picker remains scoped to the active company', () => {
|
|
37
|
+
assert.match(fields, /companyId=\{providerId\}/);
|
|
38
|
+
assert.match(fields, /entityId=\{providerId\}/);
|
|
39
|
+
assert.match(fields, /peopleId: companyId \|\| providerId/);
|
|
40
|
+
assert.match(fields, /company_certificate/);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('IntegrationConfigPage delegates field rendering to a focused component', () => {
|
|
44
|
+
assert.match(page, /<IntegrationConfigFields/);
|
|
45
|
+
assert.ok(page.split('\n').length <= 500, `IntegrationConfigPage.js has ${page.split('\n').length} lines`);
|
|
46
|
+
assert.ok(fields.split('\n').length <= 500, `IntegrationConfigFields.js has ${fields.split('\n').length} lines`);
|
|
47
|
+
assert.ok(utils.split('\n').length <= 500, `IntegrationConfigPage.utils.js has ${utils.split('\n').length} lines`);
|
|
48
|
+
});
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
const assert = require('node:assert/strict')
|
|
2
|
+
const {test} = require('node:test')
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
SAFE_TOOLBAR_FALLBACK_ROUTES,
|
|
6
|
+
buildSafeToolbarFallbackMenus,
|
|
7
|
+
mapRuntimeMenusToNavItems,
|
|
8
|
+
resolveToolbarRuntimeMenus,
|
|
9
|
+
} = require('../../../react/utils/bottomNavigationToolbar')
|
|
10
|
+
|
|
11
|
+
const managerPreset = {
|
|
12
|
+
items: [
|
|
13
|
+
{
|
|
14
|
+
route: 'HomePage',
|
|
15
|
+
icon: 'home',
|
|
16
|
+
label: {store: 'configs', type: 'toolbar', key: 'home', fallback: 'Início'},
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
route: 'CrmIndex',
|
|
20
|
+
icon: 'dollar-sign',
|
|
21
|
+
label: {
|
|
22
|
+
store: 'configs',
|
|
23
|
+
type: 'toolbar',
|
|
24
|
+
key: 'opportunities',
|
|
25
|
+
fallback: 'Oportunidades',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
route: 'ClientsIndex',
|
|
30
|
+
icon: 'users',
|
|
31
|
+
label: {
|
|
32
|
+
store: 'configs',
|
|
33
|
+
type: 'toolbar',
|
|
34
|
+
key: 'customers',
|
|
35
|
+
fallback: 'Clientes',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
route: 'ProfilePage',
|
|
40
|
+
icon: 'user',
|
|
41
|
+
label: {
|
|
42
|
+
store: 'configs',
|
|
43
|
+
type: 'toolbar',
|
|
44
|
+
key: 'profile',
|
|
45
|
+
fallback: 'Perfil',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
routeAliases: {
|
|
50
|
+
ClientDetails: 'ClientsIndex',
|
|
51
|
+
ProfilePage: 'ProfilePage',
|
|
52
|
+
SettingsPage: 'ProfilePage',
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
test('exposes only HomePage and ProfilePage as safe fallback routes', () => {
|
|
57
|
+
assert.deepEqual(SAFE_TOOLBAR_FALLBACK_ROUTES, ['HomePage', 'ProfilePage'])
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('builds safe fallback from managerToolbar without business routes', () => {
|
|
61
|
+
const menus = buildSafeToolbarFallbackMenus(
|
|
62
|
+
managerPreset,
|
|
63
|
+
'toolbar',
|
|
64
|
+
null,
|
|
65
|
+
'managerToolbar',
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
assert.equal(menus.length, 1)
|
|
69
|
+
const routes = menus[0].menus.map(item => item.route)
|
|
70
|
+
assert.deepEqual(routes, ['HomePage', 'ProfilePage'])
|
|
71
|
+
assert.equal(routes.includes('ClientsIndex'), false)
|
|
72
|
+
assert.equal(routes.includes('CrmIndex'), false)
|
|
73
|
+
assert.deepEqual(
|
|
74
|
+
menus[0].menus.map(item => item.label),
|
|
75
|
+
['Início', 'Perfil'],
|
|
76
|
+
)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('returns empty safe fallback when preset has no safe routes', () => {
|
|
80
|
+
const menus = buildSafeToolbarFallbackMenus(
|
|
81
|
+
{items: [{route: 'ClientsIndex', icon: 'users', label: 'Clientes'}]},
|
|
82
|
+
'toolbar',
|
|
83
|
+
)
|
|
84
|
+
assert.deepEqual(menus, [])
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('prefers API toolbar menus over preset when authorized items exist', () => {
|
|
88
|
+
const apiMenus = [
|
|
89
|
+
{
|
|
90
|
+
id: 'toolbar-mod',
|
|
91
|
+
label: 'Toolbar',
|
|
92
|
+
menus: [
|
|
93
|
+
{
|
|
94
|
+
id: 1,
|
|
95
|
+
route: 'HomePage',
|
|
96
|
+
icon: 'home',
|
|
97
|
+
label: 'Início',
|
|
98
|
+
menuType: 'toolbar',
|
|
99
|
+
sortOrder: 10,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 2,
|
|
103
|
+
route: 'ClientsIndex',
|
|
104
|
+
icon: 'users',
|
|
105
|
+
label: 'Clientes',
|
|
106
|
+
menuType: 'toolbar',
|
|
107
|
+
sortOrder: 20,
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
const resolved = resolveToolbarRuntimeMenus({
|
|
114
|
+
apiMenus,
|
|
115
|
+
preset: managerPreset,
|
|
116
|
+
presetKey: 'managerToolbar',
|
|
117
|
+
menuType: 'toolbar',
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
const routes = resolved.flatMap(m => m.menus.map(i => i.route))
|
|
121
|
+
assert.deepEqual(routes, ['HomePage', 'ClientsIndex'])
|
|
122
|
+
assert.equal(routes.includes('ProfilePage'), false)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('falls back to safe routes when API menus are empty', () => {
|
|
126
|
+
const resolved = resolveToolbarRuntimeMenus({
|
|
127
|
+
apiMenus: [],
|
|
128
|
+
preset: managerPreset,
|
|
129
|
+
presetKey: 'managerToolbar',
|
|
130
|
+
menuType: 'toolbar',
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
const routes = resolved.flatMap(m => m.menus.map(i => i.route))
|
|
134
|
+
assert.deepEqual(routes, ['HomePage', 'ProfilePage'])
|
|
135
|
+
assert.equal(routes.includes('ClientsIndex'), false)
|
|
136
|
+
assert.equal(routes.includes('CrmIndex'), false)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test('falls back to safe routes when API only has non-toolbar menus', () => {
|
|
140
|
+
const apiMenus = [
|
|
141
|
+
{
|
|
142
|
+
id: 'home-mod',
|
|
143
|
+
menus: [
|
|
144
|
+
{
|
|
145
|
+
route: 'ProductsPage',
|
|
146
|
+
label: 'Produtos',
|
|
147
|
+
menuType: 'home',
|
|
148
|
+
sortOrder: 1,
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
},
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
const resolved = resolveToolbarRuntimeMenus({
|
|
155
|
+
apiMenus,
|
|
156
|
+
preset: managerPreset,
|
|
157
|
+
menuType: 'toolbar',
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
const routes = resolved.flatMap(m => m.menus.map(i => i.route))
|
|
161
|
+
assert.deepEqual(routes, ['HomePage', 'ProfilePage'])
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test('returns empty when API empty and no preset (no bypass)', () => {
|
|
165
|
+
const resolved = resolveToolbarRuntimeMenus({
|
|
166
|
+
apiMenus: [],
|
|
167
|
+
preset: null,
|
|
168
|
+
menuType: 'toolbar',
|
|
169
|
+
})
|
|
170
|
+
assert.deepEqual(resolved, [])
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test('maps runtime menus to nav items sorted by sortOrder', () => {
|
|
174
|
+
const runtimeMenus = [
|
|
175
|
+
{
|
|
176
|
+
id: 'm1',
|
|
177
|
+
menus: [
|
|
178
|
+
{
|
|
179
|
+
route: 'ClientsIndex',
|
|
180
|
+
icon: 'users',
|
|
181
|
+
label: 'Clientes',
|
|
182
|
+
menuKey: 'clients',
|
|
183
|
+
menuType: 'toolbar',
|
|
184
|
+
sortOrder: 30,
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
route: 'HomePage',
|
|
188
|
+
icon: 'home',
|
|
189
|
+
label: 'Início',
|
|
190
|
+
menuKey: 'home',
|
|
191
|
+
menuType: 'toolbar',
|
|
192
|
+
sortOrder: 10,
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
const items = mapRuntimeMenusToNavItems(runtimeMenus, 'toolbar')
|
|
199
|
+
assert.deepEqual(
|
|
200
|
+
items.map(i => i.route),
|
|
201
|
+
['HomePage', 'ClientsIndex'],
|
|
202
|
+
)
|
|
203
|
+
assert.equal(items[0].menuKey, 'home')
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('ignores items without route when mapping', () => {
|
|
207
|
+
const items = mapRuntimeMenusToNavItems(
|
|
208
|
+
[
|
|
209
|
+
{
|
|
210
|
+
menus: [
|
|
211
|
+
{route: '', label: 'Empty', menuType: 'toolbar'},
|
|
212
|
+
{route: 'HomePage', label: 'Início', menuType: 'toolbar', sortOrder: 1},
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
'toolbar',
|
|
217
|
+
)
|
|
218
|
+
assert.deepEqual(
|
|
219
|
+
items.map(i => i.route),
|
|
220
|
+
['HomePage'],
|
|
221
|
+
)
|
|
222
|
+
})
|
|
@@ -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
|
+
});
|