@controleonline/ui-common 1.2.86 → 1.2.88
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/.github/workflows/integration-source-gate.yml +68 -0
- package/.github/workflows/manager-worker.yml +7 -156
- package/.github/workflows/publish-npm.yml +73 -27
- package/.github/workflows/pull-request-checks.yml +7 -5
- package/docs/technical/Device-Detail-Alias-UI-Sync.md +141 -0
- package/package.json +13 -8
- package/src/react/components/AddImportModal.js +61 -34
- package/src/react/components/BottomNavigationBar.js +178 -174
- package/src/react/components/BottomNavigationBar.styles.js +139 -118
- package/src/react/components/DefaultProvider.native.js +427 -421
- package/src/react/components/DefaultProvider.web.js +131 -125
- package/src/react/components/RemoteCheckoutService.js +3 -3
- package/src/react/components/RuntimeFooterMarqueeText.js +88 -36
- package/src/react/components/RuntimeInfoFooter.js +76 -43
- package/src/react/components/RuntimeInfoFooter.styles.js +22 -14
- package/src/react/config/deviceConfigBootstrap.js +3 -3
- package/src/react/pages/Devices/detail/DeviceDetailAlertsCommandsSection.js +4 -22
- package/src/react/pages/Devices/detail/DeviceDetailMovementSections.js +6 -2
- package/src/react/pages/Devices/detail/DeviceDetailOrdersPrintSection.js +4 -0
- package/src/react/pages/Devices/detail/DeviceDetailScreen.js +23 -338
- package/src/react/pages/Devices/detail/createDeviceDetailContext.js +20 -0
- package/src/react/pages/Devices/detail/useDeviceDetailActions.js +11 -36
- package/src/react/pages/Devices/detail/useDeviceDetailLoaders.js +129 -37
- package/src/react/pages/Devices/detail/useDeviceDetailSaves.js +11 -38
- package/src/react/pages/Devices/detail/useDeviceDetailStateA.js +9 -28
- package/src/react/pages/Imports.js +13 -11
- package/src/react/pages/IntegrationConfigFields.js +1 -0
- package/src/react/pages/IntegrationConfigPage.js +101 -45
- package/src/react/pages/IntegrationConfigPage.utils.js +21 -13
- package/src/react/pages/fetchPeopleConfigs.js +21 -0
- package/src/react/pages/integrationsCatalog.js +15 -110
- package/src/react/services/Cielo/Cielo.js +9 -9
- package/src/react/services/Getnet/Getnet.web.js +25 -0
- package/src/react/utils/companyAuthority.js +36 -0
- package/src/react/utils/fileUrl.js +14 -5
- package/src/react/utils/paymentDevices.js +375 -270
- package/src/react/utils/printerDevices.js +2 -2
- package/src/react/utils/runtimeFooter.js +178 -224
- package/src/react/utils/runtimeFooterText.js +160 -0
- package/src/react/utils/runtimeLanguage.js +5 -5
- package/src/react/utils/runtimeZoom.js +29 -5
- package/src/react/utils/shopFranchises.js +167 -12
- package/src/react/utils/storeColumns.js +235 -217
- package/src/react/utils/systemErrorMessage.js +2 -1
- package/src/store/imports/customActions.js +42 -2
- package/src/store/integration/index.js +43 -0
- package/src/tests/browser/manager/device-detail-apply-config-defined.spec.js +31 -0
- package/src/tests/browser/manager/device-detail-composition.spec.js +295 -0
- package/src/tests/browser/manager/device-detail-copy-config.spec.js +11 -1
- package/src/tests/browser/manager/device-detail-pdv-current-company.spec.js +273 -0
- package/src/tests/browser/manager/runtime-footer-rotation.spec.js +397 -0
- package/src/tests/react/components/DefaultProviderAuthority.test.js +28 -0
- package/src/tests/react/components/RuntimeFooterMarqueeText.build.test.js +29 -0
- package/src/tests/react/config/deviceConfigBootstrap.test.js +24 -1
- package/src/tests/react/pages/applyCurrentDeviceConfig.test.js +85 -0
- package/src/tests/react/pages/deviceDetailComposition.test.js +65 -0
- package/src/tests/react/pages/deviceDetailHookDependencies.test.mjs +31 -0
- package/src/tests/react/pages/deviceDetailLoaderDependencies.test.mjs +28 -0
- package/src/tests/react/pages/receitaFederalIntegrationContract.test.mjs +27 -6
- package/src/tests/react/services/Cielo.test.js +2 -2
- package/src/tests/react/utils/companyAuthority.test.js +53 -0
- package/src/tests/react/utils/formatter.test.mjs +6 -0
- package/src/tests/react/utils/paymentDevices.test.js +72 -0
- package/src/tests/react/utils/runtimeFooter.test.js +35 -0
- package/src/tests/react/utils/runtimeFooterText.test.js +88 -0
- package/src/tests/react/utils/runtimeLanguage.test.js +4 -4
- package/src/tests/react/utils/runtimeZoom.test.js +20 -0
- package/src/tests/react/utils/shopFranchises.test.js +32 -13
- package/src/tests/react/utils/translate.test.mjs +6 -6
- package/src/utils/formatter.js +7 -3
- package/src/utils/translate.js +16 -16
- package/src/utils/translatePending.js +6 -6
- package/src/utils/translateStorage.js +2 -2
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import {readFileSync} from 'node:fs';
|
|
3
|
+
import test from 'node:test';
|
|
4
|
+
|
|
5
|
+
const loaderSource = readFileSync(
|
|
6
|
+
new URL(
|
|
7
|
+
'../../../react/pages/Devices/detail/useDeviceDetailLoaders.js',
|
|
8
|
+
import.meta.url,
|
|
9
|
+
),
|
|
10
|
+
'utf8',
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
test('device detail loaders receive currentCompany from deps exactly once', () => {
|
|
14
|
+
const dependencies = loaderSource.match(
|
|
15
|
+
/export default function useDeviceDetailLoaders\(deps\)[\s\S]*?const \{([\s\S]*?)\}\s*=\s*deps;/,
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
assert.ok(dependencies, 'useDeviceDetailLoaders dependency block not found');
|
|
19
|
+
const block = dependencies[1];
|
|
20
|
+
const hits = block.match(/(?:^|,)\s*currentCompany\s*(?:,|$)/gm) || [];
|
|
21
|
+
assert.equal(
|
|
22
|
+
hits.length,
|
|
23
|
+
1,
|
|
24
|
+
`currentCompany must appear exactly once in deps destructure, found ${hits.length}`,
|
|
25
|
+
);
|
|
26
|
+
assert.match(loaderSource, /currentCompany\?\.id/);
|
|
27
|
+
assert.match(loaderSource, /filterDeviceConfigsByCompany\(items, currentCompany\?\.id\)/);
|
|
28
|
+
});
|
|
@@ -6,16 +6,20 @@ const catalog = fs.readFileSync(new URL('../../../react/pages/integrationsCatalo
|
|
|
6
6
|
const page = fs.readFileSync(new URL('../../../react/pages/IntegrationConfigPage.js', import.meta.url), 'utf8');
|
|
7
7
|
const fields = fs.readFileSync(new URL('../../../react/pages/IntegrationConfigFields.js', import.meta.url), 'utf8');
|
|
8
8
|
const utils = fs.readFileSync(new URL('../../../react/pages/IntegrationConfigPage.utils.js', import.meta.url), 'utf8');
|
|
9
|
+
const fetchConfigs = fs.readFileSync(new URL('../../../react/pages/fetchPeopleConfigs.js', import.meta.url), 'utf8');
|
|
9
10
|
|
|
10
11
|
test('Receita Federal catalog exposes company fiscal configuration', () => {
|
|
11
12
|
assert.match(catalog, /key: 'receita-federal'/);
|
|
12
13
|
assert.match(catalog, /label: 'Receita Federal'/);
|
|
13
14
|
assert.match(catalog, /receita-federal-tax-regime/);
|
|
14
|
-
assert.match(catalog, /receita-federal-ibge-code/);
|
|
15
15
|
assert.match(catalog, /receita-federal-certificate-file/);
|
|
16
16
|
assert.match(catalog, /receita-federal-certificate-password/);
|
|
17
17
|
assert.match(catalog, /secureTextEntry: true/);
|
|
18
18
|
assert.match(catalog, /\.pfx,\.p12,application\/x-pkcs12/);
|
|
19
|
+
assert.match(catalog, /key: 'general'/);
|
|
20
|
+
assert.match(catalog, /key: 'cte'/);
|
|
21
|
+
assert.doesNotMatch(catalog, /key: 'receita-federal-ibge-code'/);
|
|
22
|
+
assert.doesNotMatch(catalog, /key: 'receita-federal-(nfe|nfce|nfse|cte)-last-number'/);
|
|
19
23
|
});
|
|
20
24
|
|
|
21
25
|
test('Receita Federal route resolves without relying only on route params', () => {
|
|
@@ -25,12 +29,29 @@ test('Receita Federal route resolves without relying only on route params', () =
|
|
|
25
29
|
|
|
26
30
|
test('embedded fiscal configuration keeps reads and writes on the company being edited', () => {
|
|
27
31
|
assert.match(utils, /route\?\.params\?\.companyId/);
|
|
32
|
+
assert.match(utils, /route\?\.params\?\.clientId/);
|
|
28
33
|
assert.match(utils, /resolveProviderId/);
|
|
29
|
-
assert.match(utils, /
|
|
30
|
-
assert.
|
|
31
|
-
assert.
|
|
32
|
-
assert.match(page, /
|
|
33
|
-
assert.match(page, /
|
|
34
|
+
assert.match(utils, /if \(embedded\) return ''/);
|
|
35
|
+
assert.doesNotMatch(utils, /resolveFallbackConfigs/);
|
|
36
|
+
assert.doesNotMatch(page, /fallbackConfigs/);
|
|
37
|
+
assert.match(page, /resolveProviderId\(\{ route, currentCompany, embedded \}\)/);
|
|
38
|
+
assert.match(page, /fetchPeopleConfigs/);
|
|
39
|
+
assert.match(page, /visibleFields/);
|
|
40
|
+
assert.match(page, /persistConfigs\(\{ configs, people: providerIri/);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('fiscal tabs load all keys once; save only the current tab keys', () => {
|
|
44
|
+
assert.match(fetchConfigs, /configKey: keys/);
|
|
45
|
+
assert.match(fetchConfigs, /itemsPerPage: keys\.length/);
|
|
46
|
+
// Load all fiscal keys on mount/focus so tab switches do not re-fetch or toast.
|
|
47
|
+
assert.match(page, /allFieldKeys/);
|
|
48
|
+
assert.match(page, /needsMarketplaceIntegration/);
|
|
49
|
+
assert.match(page, /configKeys: keys/);
|
|
50
|
+
assert.match(page, /visibleFields\.map\(field => \(\{/);
|
|
51
|
+
assert.match(page, /Salvar \$\{activeTabDef\?\.label/);
|
|
52
|
+
assert.doesNotMatch(page, /itemsPerPage: 100/);
|
|
53
|
+
// Pure config (receita-federal) must not spam /marketplace/integrations on every tab.
|
|
54
|
+
assert.match(page, /needsMarketplaceIntegration = Boolean\(providerConfig\?\.oauthConnect\)/);
|
|
34
55
|
});
|
|
35
56
|
|
|
36
57
|
test('certificate picker remains scoped to the active company', () => {
|
|
@@ -50,7 +50,7 @@ const runtimeStores = merchantCode => ({
|
|
|
50
50
|
}),
|
|
51
51
|
},
|
|
52
52
|
},
|
|
53
|
-
|
|
53
|
+
mainCompany: {id: 99},
|
|
54
54
|
},
|
|
55
55
|
},
|
|
56
56
|
configs: {
|
|
@@ -89,7 +89,7 @@ describe('CieloService', () => {
|
|
|
89
89
|
people: {
|
|
90
90
|
getters: {
|
|
91
91
|
currentCompany: {id: 21, configs: {}},
|
|
92
|
-
|
|
92
|
+
mainCompany: {id: 99},
|
|
93
93
|
},
|
|
94
94
|
},
|
|
95
95
|
configs: {getters: {items: {}}},
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {describe, expect, it} from '@jest/globals';
|
|
2
|
+
import {
|
|
3
|
+
canAdministerCompany,
|
|
4
|
+
isSuperAdmin,
|
|
5
|
+
} from '../../../react/utils/companyAuthority';
|
|
6
|
+
|
|
7
|
+
describe('companyAuthority', () => {
|
|
8
|
+
it.each(['owner', 'manager'])(
|
|
9
|
+
'treats %s of the main company as superadmin',
|
|
10
|
+
permission => {
|
|
11
|
+
const mainCompany = {permissions: [permission]};
|
|
12
|
+
|
|
13
|
+
expect(isSuperAdmin({mainCompany})).toBe(true);
|
|
14
|
+
expect(
|
|
15
|
+
canAdministerCompany({company: {permissions: []}, mainCompany}),
|
|
16
|
+
).toBe(true);
|
|
17
|
+
},
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
it.each(['owner', 'manager'])(
|
|
21
|
+
'treats %s of the current company as local admin',
|
|
22
|
+
permission => {
|
|
23
|
+
expect(
|
|
24
|
+
canAdministerCompany({
|
|
25
|
+
company: {permission: [permission]},
|
|
26
|
+
mainCompany: {permissions: []},
|
|
27
|
+
}),
|
|
28
|
+
).toBe(true);
|
|
29
|
+
},
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
it.each(['director', 'employee'])(
|
|
33
|
+
'does not treat %s as administrative authority',
|
|
34
|
+
permission => {
|
|
35
|
+
expect(
|
|
36
|
+
canAdministerCompany({
|
|
37
|
+
company: {permission: [permission]},
|
|
38
|
+
mainCompany: {permissions: []},
|
|
39
|
+
}),
|
|
40
|
+
).toBe(false);
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
it('honors the global ROLE_SUPER resolved by the API', () => {
|
|
45
|
+
expect(
|
|
46
|
+
canAdministerCompany({
|
|
47
|
+
company: {permissions: []},
|
|
48
|
+
mainCompany: {permissions: []},
|
|
49
|
+
user: {roles: ['ROLE_SUPER']},
|
|
50
|
+
}),
|
|
51
|
+
).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -3,6 +3,12 @@ import test from 'node:test';
|
|
|
3
3
|
|
|
4
4
|
import Formatter from '../../../utils/formatter.js';
|
|
5
5
|
|
|
6
|
+
test('normalizes optional text values', () => {
|
|
7
|
+
assert.equal(Formatter.normalizeText(' iFood '), 'iFood');
|
|
8
|
+
assert.equal(Formatter.normalizeText(null), '');
|
|
9
|
+
assert.equal(Formatter.normalizeText(undefined), '');
|
|
10
|
+
});
|
|
11
|
+
|
|
6
12
|
test('formats API datetimes using the numeric offset returned by the backend', () => {
|
|
7
13
|
assert.equal(
|
|
8
14
|
Formatter.formatDateYmdTodmY('2026-07-22T00:00:00-02:00'),
|
|
@@ -86,3 +86,75 @@ describe('paymentDevices local Cielo print support', () => {
|
|
|
86
86
|
).toBe(false)
|
|
87
87
|
})
|
|
88
88
|
})
|
|
89
|
+
|
|
90
|
+
const {
|
|
91
|
+
buildWalletIdsForGateway,
|
|
92
|
+
selectPosWalletPaymentTypes,
|
|
93
|
+
walletPaymentTypeMatchesGateway,
|
|
94
|
+
} = require('../../../react/utils/paymentDevices')
|
|
95
|
+
|
|
96
|
+
describe('selectPosWalletPaymentTypes by gateway', () => {
|
|
97
|
+
const cieloDebit = {
|
|
98
|
+
id: 11,
|
|
99
|
+
wallet: {id: 10, wallet: 'Cielo'},
|
|
100
|
+
paymentType: {id: 5, paymentType: 'Debito'},
|
|
101
|
+
}
|
|
102
|
+
const infiniteDebit = {
|
|
103
|
+
id: 12,
|
|
104
|
+
wallet: {id: 20, wallet: 'Infinite Pay'},
|
|
105
|
+
paymentType: {id: 5, paymentType: 'Debito'},
|
|
106
|
+
}
|
|
107
|
+
const cash = {
|
|
108
|
+
id: 13,
|
|
109
|
+
wallet: {id: 30, wallet: 'Dinheiro'},
|
|
110
|
+
paymentType: {id: 1, paymentType: 'Dinheiro'},
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
it('builds wallet ids from discovered company configs', () => {
|
|
114
|
+
expect(
|
|
115
|
+
buildWalletIdsForGateway({
|
|
116
|
+
gateway: 'cielo',
|
|
117
|
+
companyConfigs: {
|
|
118
|
+
'pos-cielo-wallet': '/wallets/10',
|
|
119
|
+
'pos-cash-wallet': '/wallets/30',
|
|
120
|
+
},
|
|
121
|
+
}),
|
|
122
|
+
).toEqual(['/wallets/10', '/wallets/30'])
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('keeps only Cielo and cash wallets for a Cielo POS', () => {
|
|
126
|
+
expect(
|
|
127
|
+
selectPosWalletPaymentTypes({
|
|
128
|
+
walletPaymentTypes: [cieloDebit, infiniteDebit, cash],
|
|
129
|
+
gateway: 'cielo',
|
|
130
|
+
companyConfigs: {
|
|
131
|
+
'pos-cielo-wallet': 10,
|
|
132
|
+
'pos-cash-wallet': 30,
|
|
133
|
+
},
|
|
134
|
+
}),
|
|
135
|
+
).toEqual([cieloDebit, cash])
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('falls back to wallet name when discovery configs are still empty', () => {
|
|
139
|
+
expect(
|
|
140
|
+
selectPosWalletPaymentTypes({
|
|
141
|
+
walletPaymentTypes: [cieloDebit, infiniteDebit, cash],
|
|
142
|
+
gateway: 'cielo',
|
|
143
|
+
companyConfigs: {},
|
|
144
|
+
}),
|
|
145
|
+
).toEqual([cieloDebit, cash])
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('does not mix Cielo types into another gateway', () => {
|
|
149
|
+
expect(
|
|
150
|
+
walletPaymentTypeMatchesGateway(cieloDebit, 'getnet'),
|
|
151
|
+
).toBe(false)
|
|
152
|
+
expect(
|
|
153
|
+
selectPosWalletPaymentTypes({
|
|
154
|
+
walletPaymentTypes: [cieloDebit, infiniteDebit, cash],
|
|
155
|
+
gateway: 'infinite-pay',
|
|
156
|
+
companyConfigs: {},
|
|
157
|
+
}),
|
|
158
|
+
).toEqual([infiniteDebit, cash])
|
|
159
|
+
})
|
|
160
|
+
})
|
|
@@ -7,6 +7,7 @@ const {
|
|
|
7
7
|
getRuntimeFooterPrimaryText,
|
|
8
8
|
getRuntimeFooterRotationEntries,
|
|
9
9
|
getRuntimeFooterStoredVersion,
|
|
10
|
+
getRuntimeFooterText,
|
|
10
11
|
getRuntimeFooterTextLines,
|
|
11
12
|
getRuntimeFooterWebIdentifierCandidates,
|
|
12
13
|
getRuntimeFooterWebHost,
|
|
@@ -447,6 +448,40 @@ test('rotates each footer line before the runtime version entry', () => {
|
|
|
447
448
|
)
|
|
448
449
|
})
|
|
449
450
|
|
|
451
|
+
test('isolates one footer line before the runtime version entry', () => {
|
|
452
|
+
const entries = getRuntimeFooterRotationEntries({
|
|
453
|
+
companyFooterText: 'Linha isolada do rodapé',
|
|
454
|
+
primaryText: 'Browser Manager 1.10.5',
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
assert.deepEqual(entries, ['Linha isolada do rodapé', 'Browser Manager 1.10.5'])
|
|
458
|
+
assert.equal(entries.some(entry => entry.includes(' • ')), false)
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
test('resolves configured footer text from company and device config shapes', () => {
|
|
462
|
+
assert.equal(
|
|
463
|
+
getRuntimeFooterText({
|
|
464
|
+
configs: {'device-runtime-footer-text': 'current company'},
|
|
465
|
+
}),
|
|
466
|
+
'current company',
|
|
467
|
+
)
|
|
468
|
+
assert.equal(
|
|
469
|
+
getRuntimeFooterText(
|
|
470
|
+
{configs: {}},
|
|
471
|
+
[{configKey: 'device-runtime-footer-text', value: 'device config'}],
|
|
472
|
+
),
|
|
473
|
+
'device config',
|
|
474
|
+
)
|
|
475
|
+
assert.equal(
|
|
476
|
+
getRuntimeFooterText(
|
|
477
|
+
{configs: {}},
|
|
478
|
+
{'device-runtime-footer-text': ' line one \n line two '},
|
|
479
|
+
),
|
|
480
|
+
'line one\nline two',
|
|
481
|
+
)
|
|
482
|
+
assert.equal(getRuntimeFooterText({configs: {}}), '')
|
|
483
|
+
})
|
|
484
|
+
|
|
450
485
|
test('hides runtime device information on the customer paylist only', () => {
|
|
451
486
|
assert.equal(shouldShowRuntimeFooter('PaylistPage'), false)
|
|
452
487
|
assert.equal(shouldShowRuntimeFooter('LoginPage'), true)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const assert = require('node:assert/strict')
|
|
2
|
+
const {test} = require('node:test')
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
getRuntimeFooterRotationEntries,
|
|
6
|
+
getRuntimeFooterText,
|
|
7
|
+
getRuntimeFooterTextLines,
|
|
8
|
+
} = require('../../../react/utils/runtimeFooterText')
|
|
9
|
+
|
|
10
|
+
test('rotates a single footer line then primary without concatenating', () => {
|
|
11
|
+
const line = 'www.controleonline.com (11) 5555-5159'
|
|
12
|
+
const primaryText = 'web (203.0.113.42) / v1.3.6'
|
|
13
|
+
const entries = getRuntimeFooterRotationEntries({
|
|
14
|
+
companyFooterText: line,
|
|
15
|
+
primaryText,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
assert.deepEqual(entries, [line, primaryText])
|
|
19
|
+
assert.equal(entries.some(entry => entry.includes(' • ')), false)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('does not invent rotation entries when footer text is empty', () => {
|
|
23
|
+
assert.deepEqual(
|
|
24
|
+
getRuntimeFooterRotationEntries({
|
|
25
|
+
companyFooterText: '',
|
|
26
|
+
primaryText: 'PDV (229252771069b294) / v1.3.7',
|
|
27
|
+
}),
|
|
28
|
+
['PDV (229252771069b294) / v1.3.7'],
|
|
29
|
+
)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('keeps multi-line rotation plus primary', () => {
|
|
33
|
+
assert.deepEqual(
|
|
34
|
+
getRuntimeFooterRotationEntries({
|
|
35
|
+
companyFooterText: 'www.site.com\n(11) 99999-9999',
|
|
36
|
+
primaryText: 'web (203.0.113.42) / v1.3.6',
|
|
37
|
+
}),
|
|
38
|
+
['www.site.com', '(11) 99999-9999', 'web (203.0.113.42) / v1.3.6'],
|
|
39
|
+
)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('reads footer text from currentCompany map configs', () => {
|
|
43
|
+
assert.equal(
|
|
44
|
+
getRuntimeFooterText({
|
|
45
|
+
configs: {
|
|
46
|
+
'device-runtime-footer-text': 'Linha currentCompany',
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
'Linha currentCompany',
|
|
50
|
+
)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('falls back to extra configs when company has no footer text', () => {
|
|
54
|
+
assert.equal(
|
|
55
|
+
getRuntimeFooterText(
|
|
56
|
+
{configs: {}},
|
|
57
|
+
{
|
|
58
|
+
'device-runtime-footer-text': 'Linha mainCompany',
|
|
59
|
+
},
|
|
60
|
+
),
|
|
61
|
+
'Linha mainCompany',
|
|
62
|
+
)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('reads footer text from device_config array configs', () => {
|
|
66
|
+
assert.equal(
|
|
67
|
+
getRuntimeFooterText(null, [
|
|
68
|
+
{configKey: 'other', value: 'ignore'},
|
|
69
|
+
{configKey: 'device-runtime-footer-text', value: 'Linha device_config array'},
|
|
70
|
+
]),
|
|
71
|
+
'Linha device_config array',
|
|
72
|
+
)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('reads footer text from device_config map configs', () => {
|
|
76
|
+
assert.equal(
|
|
77
|
+
getRuntimeFooterText(undefined, {
|
|
78
|
+
'device-runtime-footer-text': {configValue: 'Linha device_config map'},
|
|
79
|
+
}),
|
|
80
|
+
'Linha device_config map',
|
|
81
|
+
)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('returns empty string when footer text is absent', () => {
|
|
85
|
+
assert.equal(getRuntimeFooterText({configs: {foo: 'bar'}}), '')
|
|
86
|
+
assert.equal(getRuntimeFooterText(null, []), '')
|
|
87
|
+
assert.deepEqual(getRuntimeFooterTextLines(''), [])
|
|
88
|
+
})
|
|
@@ -32,7 +32,7 @@ test('prefers the selected company language over cached config and session value
|
|
|
32
32
|
code: 'en_US',
|
|
33
33
|
},
|
|
34
34
|
},
|
|
35
|
-
|
|
35
|
+
mainCompany: {
|
|
36
36
|
language: {
|
|
37
37
|
code: 'pt_BR',
|
|
38
38
|
},
|
|
@@ -51,7 +51,7 @@ test('prefers the selected company language over cached config and session value
|
|
|
51
51
|
test('falls back through default company, config, session and the hardcoded default', () => {
|
|
52
52
|
assert.equal(
|
|
53
53
|
resolveConfiguredLanguage({
|
|
54
|
-
|
|
54
|
+
mainCompany: {
|
|
55
55
|
configs: {
|
|
56
56
|
language: 'de_DE',
|
|
57
57
|
},
|
|
@@ -86,7 +86,7 @@ test('builds the translation bootstrap key only from language and company contex
|
|
|
86
86
|
buildTranslationBootstrapKey({
|
|
87
87
|
language: ' PT_BR ',
|
|
88
88
|
currentCompanyId: 21,
|
|
89
|
-
|
|
89
|
+
mainCompanyId: 1,
|
|
90
90
|
}),
|
|
91
91
|
'pt-br::21::1',
|
|
92
92
|
)
|
|
@@ -94,7 +94,7 @@ test('builds the translation bootstrap key only from language and company contex
|
|
|
94
94
|
buildTranslationBootstrapKey({
|
|
95
95
|
language: 'pt-br',
|
|
96
96
|
currentCompanyId: '',
|
|
97
|
-
|
|
97
|
+
mainCompanyId: 1,
|
|
98
98
|
}),
|
|
99
99
|
'',
|
|
100
100
|
)
|
|
@@ -30,9 +30,29 @@ test('builds compensated container dimensions for native scaled content', () =>
|
|
|
30
30
|
assert.deepEqual(
|
|
31
31
|
buildRuntimeZoomStyle(70),
|
|
32
32
|
{
|
|
33
|
+
alignSelf: 'flex-start',
|
|
33
34
|
height: '142.85714285714286%',
|
|
34
35
|
transform: [{scale: 0.7}],
|
|
36
|
+
transformOrigin: 'top left',
|
|
35
37
|
width: '142.85714285714286%',
|
|
36
38
|
},
|
|
37
39
|
)
|
|
38
40
|
})
|
|
41
|
+
|
|
42
|
+
test('uses viewport pixels on native so scale origin stays top-left without distortion', () => {
|
|
43
|
+
assert.deepEqual(
|
|
44
|
+
buildRuntimeZoomStyle(70, {viewport: {width: 360, height: 800}}),
|
|
45
|
+
{
|
|
46
|
+
alignSelf: 'flex-start',
|
|
47
|
+
height: 800 / 0.7,
|
|
48
|
+
transform: [{scale: 0.7}],
|
|
49
|
+
transformOrigin: 'top left',
|
|
50
|
+
width: 360 / 0.7,
|
|
51
|
+
},
|
|
52
|
+
)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('does not apply style when zoom is 100 percent', () => {
|
|
56
|
+
assert.equal(buildRuntimeZoomStyle(100), null)
|
|
57
|
+
assert.equal(buildRuntimeZoomStyle(100, {isWeb: true}), null)
|
|
58
|
+
})
|
|
@@ -52,12 +52,12 @@ describe('shopFranchises', () => {
|
|
|
52
52
|
{
|
|
53
53
|
id: 21,
|
|
54
54
|
alias: 'Centro',
|
|
55
|
-
shopAddresses: [{id: 501, nickname: 'Loja Centro'}],
|
|
55
|
+
shopAddresses: [{id: 501, nickname: 'Loja Centro', latitude: null, longitude: null}],
|
|
56
56
|
},
|
|
57
57
|
]);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
it('
|
|
60
|
+
it('uses an authorized company-side link without duplicate fallback requests', async () => {
|
|
61
61
|
api.fetch
|
|
62
62
|
.mockResolvedValueOnce({
|
|
63
63
|
member: [
|
|
@@ -79,18 +79,11 @@ describe('shopFranchises', () => {
|
|
|
79
79
|
|
|
80
80
|
const directory = await fetchShopFranchiseDirectory({companyId: 10});
|
|
81
81
|
|
|
82
|
+
expect(api.fetch).toHaveBeenCalledTimes(1);
|
|
82
83
|
expect(api.fetch).toHaveBeenCalledWith('people_links', {
|
|
83
84
|
params: {
|
|
84
|
-
company: '
|
|
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',
|
|
85
|
+
company: '10',
|
|
86
|
+
linkType: ['franchisee'],
|
|
94
87
|
itemsPerPage: 50,
|
|
95
88
|
page: 1,
|
|
96
89
|
},
|
|
@@ -98,10 +91,36 @@ describe('shopFranchises', () => {
|
|
|
98
91
|
expect(directory[0].id).toBe(22);
|
|
99
92
|
expect(directory[0].alias).toBe('Norte');
|
|
100
93
|
expect(directory[0].shopAddresses).toEqual([
|
|
101
|
-
{id: 601, nickname: 'Loja Norte'},
|
|
94
|
+
{id: 601, nickname: 'Loja Norte', latitude: null, longitude: null},
|
|
102
95
|
]);
|
|
103
96
|
});
|
|
104
97
|
|
|
98
|
+
it('rejects unrelated or non-franchise links before loading addresses', async () => {
|
|
99
|
+
api.fetch.mockResolvedValueOnce({
|
|
100
|
+
member: [
|
|
101
|
+
{
|
|
102
|
+
id: 1,
|
|
103
|
+
linkType: 'franchisee',
|
|
104
|
+
company: {id: 999},
|
|
105
|
+
people: {id: 22, alias: 'Other tenant'},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 2,
|
|
109
|
+
linkType: 'supplier',
|
|
110
|
+
company: {id: 10},
|
|
111
|
+
people: {id: 23, alias: 'Wrong relation'},
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
});
|
|
115
|
+
api.fetch.mockResolvedValueOnce({member: []});
|
|
116
|
+
|
|
117
|
+
const directory = await fetchAllShopFranchiseDirectory({companyId: 10});
|
|
118
|
+
|
|
119
|
+
expect(directory).toEqual([]);
|
|
120
|
+
expect(api.fetch).toHaveBeenCalledTimes(2);
|
|
121
|
+
expect(api.fetch).not.toHaveBeenCalledWith('addresses', expect.anything());
|
|
122
|
+
});
|
|
123
|
+
|
|
105
124
|
it('loads every page when building the full franchise directory', async () => {
|
|
106
125
|
// Dual-side people_links: company side page1 (full), page2 empty break;
|
|
107
126
|
// people side empty.
|
|
@@ -216,10 +216,10 @@ test('falls back past empty current-company values to the main company', () => {
|
|
|
216
216
|
installLocalStorage();
|
|
217
217
|
const translateStore = createPendingTranslateStore();
|
|
218
218
|
const currentCompany = {id: 2};
|
|
219
|
-
const
|
|
219
|
+
const mainCompany = {id: 1};
|
|
220
220
|
const translate = new Translate(
|
|
221
|
-
[
|
|
222
|
-
|
|
221
|
+
[mainCompany, currentCompany],
|
|
222
|
+
mainCompany,
|
|
223
223
|
currentCompany,
|
|
224
224
|
['orders'],
|
|
225
225
|
translateStore,
|
|
@@ -235,10 +235,10 @@ test('getResolveCompaniesToCache returns current and main companies', () => {
|
|
|
235
235
|
installLocalStorage();
|
|
236
236
|
const translateStore = createPendingTranslateStore();
|
|
237
237
|
const currentCompany = {id: 2};
|
|
238
|
-
const
|
|
238
|
+
const mainCompany = {id: 1};
|
|
239
239
|
const translate = new Translate(
|
|
240
|
-
[
|
|
241
|
-
|
|
240
|
+
[mainCompany, currentCompany],
|
|
241
|
+
mainCompany,
|
|
242
242
|
currentCompany,
|
|
243
243
|
['orders'],
|
|
244
244
|
translateStore,
|
package/src/utils/formatter.js
CHANGED
|
@@ -39,9 +39,13 @@ const MOJIBAKE_REPLACEMENTS = [
|
|
|
39
39
|
];
|
|
40
40
|
|
|
41
41
|
export default class Formatter {
|
|
42
|
-
|
|
43
|
-
/* ================= UTIL ================= */
|
|
44
|
-
|
|
42
|
+
|
|
43
|
+
/* ================= UTIL ================= */
|
|
44
|
+
|
|
45
|
+
static normalizeText(value) {
|
|
46
|
+
return String(value || '').trim();
|
|
47
|
+
}
|
|
48
|
+
|
|
45
49
|
static onlyNumbers(value) {
|
|
46
50
|
return String(value || '').replace(/\D/g, '');
|
|
47
51
|
}
|