@controleonline/ui-common 1.2.70 → 1.2.71

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 (61) hide show
  1. package/package.json +31 -28
  2. package/src/api/index.js +237 -37
  3. package/src/react/components/AddImportModal.js +1 -1
  4. package/src/react/components/AnimatedModal.js +171 -0
  5. package/src/react/components/AnimatedModal.styles.js +21 -0
  6. package/src/react/components/AppTypeSwitcher.js +164 -0
  7. package/src/react/components/AppTypeSwitcher.styles.js +109 -0
  8. package/src/react/components/BackgroundRuntimeBridge.js +5 -4
  9. package/src/react/components/BottomNavigationBar.js +86 -31
  10. package/src/react/components/BottomNavigationBar.styles.js +118 -110
  11. package/src/react/components/DefaultProvider.native.js +68 -66
  12. package/src/react/components/DefaultProvider.web.js +44 -42
  13. package/src/react/components/DeliveryPushBridge.native.js +2 -2
  14. package/src/react/components/DeviceAlertSoundService.js +3 -3
  15. package/src/react/components/KioskModeBridge.js +2 -2
  16. package/src/react/components/LauncherModeBridge.js +2 -2
  17. package/src/react/components/ManagerPushBridge.native.js +2 -2
  18. package/src/react/components/RemoteCheckoutService.js +28 -20
  19. package/src/react/components/RuntimeBottomNavigationBar.js +146 -119
  20. package/src/react/components/RuntimeInfoFooter.js +37 -9
  21. package/src/react/components/StateStore.js +258 -0
  22. package/src/react/components/WebsocketListener.native.js +11 -11
  23. package/src/react/config/deviceConfigBootstrap.js +66 -27
  24. package/src/react/css/orders.js +72 -0
  25. package/src/react/pages/SettingsPage/PaymentTypesByWalletTab.js +484 -454
  26. package/src/react/pages/SettingsPage/index.js +1266 -1167
  27. package/src/react/print/providers/local.js +1 -1
  28. package/src/react/router/publicRoutes.js +25 -0
  29. package/src/react/services/Cielo/Checkout.js +39 -0
  30. package/src/react/services/Cielo/Cielo.js +193 -0
  31. package/src/react/services/Cielo/Print.js +7 -0
  32. package/src/react/services/InfinitePay/Checkout.js +33 -0
  33. package/src/react/services/InfinitePay/InfinitePay.js +24 -0
  34. package/src/react/{utils → services}/paymentGatewayExecution.js +91 -91
  35. package/src/react/styles/global.js +115 -0
  36. package/src/react/utils/fileUrl.js +182 -16
  37. package/src/react/utils/menuNavigation.js +31 -0
  38. package/src/react/utils/orderIdentity.js +277 -0
  39. package/src/react/utils/remotePayment.js +115 -61
  40. package/src/react/utils/runtimeMenu.js +35 -3
  41. package/src/react/utils/shopConfig.js +50 -24
  42. package/src/react/utils/shopFranchises.js +56 -22
  43. package/src/react/utils/socketRuntimePipeline.js +14 -14
  44. package/src/store/configs/index.js +2 -2
  45. package/src/tests/react/api/loadSmokeIndex.test.js +75 -0
  46. package/src/tests/react/config/deviceConfigBootstrap.test.js +47 -0
  47. package/src/tests/react/print/localPrintProvider.test.js +1 -1
  48. package/src/tests/react/services/Cielo.test.js +190 -0
  49. package/src/tests/react/utils/fileUrl.test.js +62 -0
  50. package/src/tests/react/utils/importStatus.test.js +2 -2
  51. package/src/tests/react/utils/orderIdentity.test.js +139 -0
  52. package/src/tests/react/utils/remotePayment.test.js +62 -0
  53. package/src/tests/react/utils/runtimeFooter.test.js +4 -6
  54. package/src/tests/react/utils/runtimeLanguage.test.js +2 -2
  55. package/src/tests/react/utils/runtimeMenu.test.js +23 -3
  56. package/src/tests/react/utils/shopConfig.test.js +74 -0
  57. package/src/tests/react/utils/shopFranchises.test.js +79 -12
  58. package/src/tests/react/utils/translate.test.mjs +166 -102
  59. package/src/utils/formatter.js +18 -0
  60. package/src/utils/integrationConfigs.js +80 -79
  61. package/src/utils/translate.js +125 -30
@@ -0,0 +1,190 @@
1
+ const {jest} = require('@jest/globals')
2
+
3
+ const mockPayment = jest.fn()
4
+ const mockFetch = jest.fn()
5
+ const mockGetAllStores = jest.fn()
6
+
7
+ jest.mock('@controleonline-rn/react-native-cielo-payment', () => ({
8
+ payment: (...args) => mockPayment(...args),
9
+ }))
10
+
11
+ jest.mock('@controleonline/ui-common/src/api', () => ({
12
+ api: {
13
+ fetch: (...args) => mockFetch(...args),
14
+ },
15
+ }))
16
+
17
+ jest.mock('@store', () => ({
18
+ getAllStores: (...args) => mockGetAllStores(...args),
19
+ }))
20
+
21
+ jest.mock('@env', () => ({
22
+ env: {
23
+ CIELO: {
24
+ ACCESS_TOKEN: '',
25
+ CLIENT_ID: '',
26
+ EMAIL: '',
27
+ MERCHANT_CODE: '',
28
+ },
29
+ },
30
+ }))
31
+
32
+ const {describe, expect, it, beforeEach} = global
33
+
34
+ const loadService = () => {
35
+ const serviceModule = require('../../../react/services/Cielo/Cielo')
36
+ return serviceModule.default || serviceModule
37
+ }
38
+
39
+ const runtimeStores = merchantCode => ({
40
+ people: {
41
+ getters: {
42
+ currentCompany: {
43
+ id: 99,
44
+ configs: {
45
+ CIELO: JSON.stringify({
46
+ ACCESS_TOKEN: 'runtime-token',
47
+ CLIENT_ID: 'runtime-client',
48
+ EMAIL: 'runtime@empresa.com',
49
+ ...(merchantCode ? {MERCHANT_CODE: merchantCode} : {}),
50
+ }),
51
+ },
52
+ },
53
+ defaultCompany: {id: 99},
54
+ },
55
+ },
56
+ configs: {
57
+ getters: {
58
+ items: {
59
+ CIELO: JSON.stringify({
60
+ ACCESS_TOKEN: 'runtime-token',
61
+ CLIENT_ID: 'runtime-client',
62
+ EMAIL: 'runtime@empresa.com',
63
+ ...(merchantCode ? {MERCHANT_CODE: merchantCode} : {}),
64
+ }),
65
+ },
66
+ },
67
+ },
68
+ })
69
+
70
+ describe('CieloService', () => {
71
+ beforeEach(() => {
72
+ jest.resetModules()
73
+ mockPayment.mockReset()
74
+ mockFetch.mockReset()
75
+ mockGetAllStores.mockReset()
76
+
77
+ mockPayment.mockResolvedValue({
78
+ success: true,
79
+ code: '0',
80
+ result: JSON.stringify({
81
+ status: 'ok',
82
+ payments: [{merchantCode: '0000000000000001'}],
83
+ }),
84
+ })
85
+ })
86
+
87
+ it('falls back to the main company private CIELO config', async () => {
88
+ mockGetAllStores.mockReturnValue({
89
+ people: {
90
+ getters: {
91
+ currentCompany: {id: 21, configs: {}},
92
+ defaultCompany: {id: 99},
93
+ },
94
+ },
95
+ configs: {getters: {items: {}}},
96
+ })
97
+ mockFetch.mockResolvedValue({
98
+ member: [
99
+ {
100
+ configKey: 'CIELO',
101
+ configValue: JSON.stringify({
102
+ ACCESS_TOKEN: 'private-token',
103
+ CLIENT_ID: 'private-client',
104
+ EMAIL: 'cielo@principal.com',
105
+ MERCHANT_CODE: '0000000000000001',
106
+ }),
107
+ },
108
+ ],
109
+ })
110
+
111
+ const CieloService = loadService()
112
+ await new CieloService().payment('credit', [{name: 'Produto'}], 1250)
113
+
114
+ expect(mockFetch).toHaveBeenCalledWith('/configs', {
115
+ params: {
116
+ configKey: 'CIELO',
117
+ people: '/people/99',
118
+ visibility: 'private',
119
+ },
120
+ })
121
+ expect(JSON.parse(mockPayment.mock.calls[0][0])).toMatchObject({
122
+ accessToken: 'private-token',
123
+ clientID: 'private-client',
124
+ email: 'cielo@principal.com',
125
+ merchantCode: '0000000000000001',
126
+ paymentCode: 'credit',
127
+ value: 1250,
128
+ })
129
+ })
130
+
131
+ it('sends the configured merchantCode to Cielo', async () => {
132
+ mockGetAllStores.mockReturnValue(runtimeStores('0000000000000001'))
133
+
134
+ const CieloService = loadService()
135
+ await new CieloService().payment('debit', [], 500)
136
+
137
+ expect(mockFetch).not.toHaveBeenCalled()
138
+ expect(JSON.parse(mockPayment.mock.calls[0][0])).toMatchObject({
139
+ merchantCode: '0000000000000001',
140
+ paymentCode: 'debit',
141
+ value: 500,
142
+ })
143
+ })
144
+
145
+ it('keeps the legacy request when merchantCode is not configured', async () => {
146
+ mockGetAllStores.mockReturnValue(runtimeStores(''))
147
+
148
+ const CieloService = loadService()
149
+ await new CieloService().payment('debit', [], 500)
150
+
151
+ expect(JSON.parse(mockPayment.mock.calls[0][0])).not.toHaveProperty(
152
+ 'merchantCode',
153
+ )
154
+ })
155
+
156
+ it('rejects a successful response without merchantCode', async () => {
157
+ mockGetAllStores.mockReturnValue(runtimeStores('0000000000000001'))
158
+ mockPayment.mockResolvedValue({
159
+ success: true,
160
+ code: '0',
161
+ result: JSON.stringify({status: 'ok', payments: [{}]}),
162
+ })
163
+
164
+ const CieloService = loadService()
165
+ const response = await new CieloService().payment('debit', [], 500)
166
+
167
+ expect(response.success).toBe(false)
168
+ })
169
+
170
+ it('rejects a successful response from a different merchantCode', async () => {
171
+ mockGetAllStores.mockReturnValue(runtimeStores('0000000000000001'))
172
+ mockPayment.mockResolvedValue({
173
+ success: true,
174
+ code: '0',
175
+ result: JSON.stringify({
176
+ status: 'ok',
177
+ payments: [{merchantCode: '0000000000000002'}],
178
+ }),
179
+ })
180
+
181
+ const CieloService = loadService()
182
+ const response = await new CieloService().payment('debit', [], 500)
183
+
184
+ expect(response).toMatchObject({
185
+ success: false,
186
+ result:
187
+ 'Pagamento processado em estabelecimento Cielo diferente do configurado.',
188
+ })
189
+ })
190
+ })
@@ -0,0 +1,62 @@
1
+ const {describe, expect, it} = global;
2
+ const {jest} = require('@jest/globals');
3
+
4
+ jest.mock('@env', () => ({
5
+ env: {
6
+ API_ENTRYPOINT: 'https://api.controleonline.com',
7
+ DOMAIN: 'manager.controleonline.com',
8
+ },
9
+ }));
10
+
11
+ const {
12
+ resolveDefaultFileSource,
13
+ resolveFileImageUrl,
14
+ } = require('../../../react/utils/fileUrl');
15
+
16
+ describe('fileUrl helpers', () => {
17
+ it('adds app-domain to backend download urls and keeps headers', () => {
18
+ const company = {
19
+ domain: 'maincompany.controleonline.com',
20
+ };
21
+
22
+ const source = resolveDefaultFileSource(
23
+ {
24
+ id: 3,
25
+ url: '/files/3/download',
26
+ },
27
+ {
28
+ company,
29
+ headers: {
30
+ Authorization: 'Bearer token',
31
+ },
32
+ },
33
+ );
34
+
35
+ expect(source).toEqual({
36
+ uri: 'https://api.controleonline.com/files/3/download?app-domain=maincompany.controleonline.com',
37
+ headers: {
38
+ Authorization: 'Bearer token',
39
+ 'app-domain': 'maincompany.controleonline.com',
40
+ },
41
+ });
42
+ expect(resolveFileImageUrl(3, {company})).toBe(
43
+ 'https://api.controleonline.com/files/3/download?app-domain=maincompany.controleonline.com',
44
+ );
45
+ });
46
+
47
+ it('keeps external image urls untouched', () => {
48
+ const source = resolveDefaultFileSource(
49
+ 'https://cdn.controleonline.com/logo.png',
50
+ {
51
+ company: {
52
+ domain: 'maincompany.controleonline.com',
53
+ },
54
+ },
55
+ );
56
+
57
+ expect(source).toEqual({
58
+ uri: 'https://cdn.controleonline.com/logo.png',
59
+ headers: {},
60
+ });
61
+ });
62
+ });
@@ -1,5 +1,5 @@
1
- const assert = require('node:assert/strict')
2
- const test = require('node:test')
1
+ const assert = require('node:assert/strict')
2
+ const {test} = global
3
3
 
4
4
  const {
5
5
  getImportErrorDetail,
@@ -0,0 +1,139 @@
1
+ const {jest} = require('@jest/globals');
2
+
3
+ const {resolveOrderIdentity} = require('../../../react/utils/orderIdentity');
4
+
5
+ const {describe, expect, it} = global;
6
+
7
+ global.t = {
8
+ t: jest.fn((store, type, key) => {
9
+ if (store === 'orders' && type === 'title' && key === 'table') {
10
+ return 'Mesa';
11
+ }
12
+
13
+ if (store === 'orders' && type === 'title' && key === 'order') {
14
+ return 'Pedido';
15
+ }
16
+
17
+ return '';
18
+ }),
19
+ };
20
+
21
+ describe('orderIdentity', () => {
22
+ it('prioritizes the 99Food code field from extra data', () => {
23
+ const order = {
24
+ id: 70911,
25
+ app: '99Food',
26
+ extraData: [
27
+ {
28
+ extraFields: {
29
+ context: 'Food99',
30
+ name: 'id',
31
+ },
32
+ value: '71759',
33
+ },
34
+ {
35
+ extraFields: {
36
+ context: 'Food99',
37
+ name: 'code',
38
+ },
39
+ value: '70001',
40
+ },
41
+ ],
42
+ };
43
+
44
+ const identity = resolveOrderIdentity(order);
45
+
46
+ expect(identity.externalLabel).toBe('99');
47
+ expect(identity.externalId).toBe('70001');
48
+ expect(identity.primaryText).toBe('#70001');
49
+ expect(identity.secondaryText).toBe('#70911');
50
+ });
51
+
52
+ it('does not invent an iFood marketplace code from pickup or display ids', () => {
53
+ const order = {
54
+ id: 81234,
55
+ app: 'ifood',
56
+ otherInformations: JSON.stringify({
57
+ ifood: {
58
+ latest_event_type: 'PLACED',
59
+ PLACED: {
60
+ order: {
61
+ id: 'ifood-order-1',
62
+ displayId: '70002',
63
+ delivery: {
64
+ pickupCode: '0176',
65
+ },
66
+ },
67
+ },
68
+ },
69
+ }),
70
+ };
71
+
72
+ const identity = resolveOrderIdentity(order);
73
+
74
+ expect(identity.externalLabel).toBe('');
75
+ expect(identity.externalId).toBe('');
76
+ expect(identity.primaryText).toBe('#81234');
77
+ expect(identity.secondaryText).toBe('');
78
+ });
79
+
80
+ it('prioritizes iFood code from extra data over pickup code', () => {
81
+ const order = {
82
+ id: 71759,
83
+ app: 'iFood',
84
+ extra_data: [
85
+ {
86
+ extra_fields: {
87
+ context: 'iFood',
88
+ name: 'pickup_code',
89
+ },
90
+ value: '9103',
91
+ },
92
+ {
93
+ extra_fields: {
94
+ context: 'iFood',
95
+ name: 'code',
96
+ },
97
+ value: '3984',
98
+ },
99
+ ],
100
+ };
101
+
102
+ const identity = resolveOrderIdentity(order);
103
+
104
+ expect(identity.externalLabel).toBe('IFOOD');
105
+ expect(identity.externalId).toBe('3984');
106
+ expect(identity.primaryText).toBe('#3984');
107
+ expect(identity.secondaryText).toBe('#71759');
108
+ });
109
+
110
+ it('prioritizes POS externalCode as the main order identity', () => {
111
+ const order = {
112
+ id: 71604,
113
+ app: 'POS',
114
+ externalCode: '570002',
115
+ };
116
+
117
+ const identity = resolveOrderIdentity(order);
118
+
119
+ expect(identity.externalLabel).toBe('');
120
+ expect(identity.externalId).toBe('570002');
121
+ expect(identity.primaryText).toBe('Mesa #570002');
122
+ expect(identity.secondaryText).toBe('#71604');
123
+ expect(global.t.t).toHaveBeenCalledWith('orders', 'title', 'table');
124
+ });
125
+
126
+ it('uses only the hash-prefixed local id for non-marketplace orders', () => {
127
+ const order = {
128
+ id: 70700,
129
+ app: 'shop',
130
+ };
131
+
132
+ const identity = resolveOrderIdentity(order);
133
+
134
+ expect(identity.externalLabel).toBe('');
135
+ expect(identity.externalId).toBe('');
136
+ expect(identity.primaryText).toBe('#70700');
137
+ expect(identity.secondaryText).toBe('');
138
+ });
139
+ });
@@ -0,0 +1,62 @@
1
+ const {
2
+ buildRemotePaymentResultMessage,
3
+ isRemotePaymentCancellation,
4
+ normalizeRemotePaymentResultStatus,
5
+ } = require('../../../react/utils/remotePayment')
6
+
7
+ const {describe, expect, it} = global
8
+
9
+ describe('remotePayment result status', () => {
10
+ it.each(['canceled', 'cancelled', 'cancel'])(
11
+ 'normalizes the explicit %s status as a cancellation',
12
+ status => {
13
+ expect(normalizeRemotePaymentResultStatus({status})).toBe('canceled')
14
+ expect(isRemotePaymentCancellation({status})).toBe(true)
15
+ },
16
+ )
17
+
18
+ it.each([
19
+ 'Pagamento PIX cancelado pelo usuario.',
20
+ 'Pagamento cancelado pelo usuário',
21
+ 'Payment cancelled by user',
22
+ 'Payment canceled by customer',
23
+ ])('recognizes legacy cancellation errors: %s', error => {
24
+ const result = {status: 'error', error}
25
+
26
+ expect(normalizeRemotePaymentResultStatus(result)).toBe('canceled')
27
+ expect(isRemotePaymentCancellation(result)).toBe(true)
28
+ })
29
+
30
+ it.each([
31
+ 'Falha de comunicacao com o terminal.',
32
+ 'Nao foi possivel cancelar a transacao.',
33
+ 'Tempo limite excedido.',
34
+ ])('keeps a real gateway failure as an error: %s', error => {
35
+ const result = {status: 'error', error}
36
+
37
+ expect(normalizeRemotePaymentResultStatus(result)).toBe('error')
38
+ expect(isRemotePaymentCancellation(result)).toBe(false)
39
+ })
40
+
41
+ it('preserves cancellation when building the remote result message', () => {
42
+ expect(
43
+ buildRemotePaymentResultMessage({
44
+ destinationDeviceId: 'web-7',
45
+ orderId: 123,
46
+ requestKey: '123:158:pix:1',
47
+ status: 'cancelled',
48
+ }),
49
+ ).toMatchObject({
50
+ action: 'pay-result',
51
+ destination: 'web-7',
52
+ order: '123',
53
+ requestKey: '123:158:pix:1',
54
+ status: 'canceled',
55
+ })
56
+ })
57
+
58
+ it('keeps successful and unknown results compatible with the existing contract', () => {
59
+ expect(normalizeRemotePaymentResultStatus({status: 'success'})).toBe('success')
60
+ expect(normalizeRemotePaymentResultStatus({status: 'unexpected'})).toBe('error')
61
+ })
62
+ })
@@ -1,6 +1,6 @@
1
1
  const assert = require('node:assert/strict')
2
- const test = require('node:test')
3
- const {jest, test: jestCompatTest} = require('@jest/globals')
2
+ const {beforeAll, afterAll, test} = global
3
+ const {jest} = require('@jest/globals')
4
4
 
5
5
  jest.mock('@controleonline/ui-common/src/react/utils/printerDevices', () => ({
6
6
  getDeviceTypeLabel: value => String(value || ''),
@@ -43,7 +43,7 @@ const restoreLocation = () => {
43
43
  delete globalThis.location
44
44
  }
45
45
 
46
- test.before(() => {
46
+ beforeAll(() => {
47
47
  global.t = {
48
48
  getMessageFromBuckets: (store, type, key) =>
49
49
  store === 'common' && type === 'option'
@@ -52,7 +52,7 @@ test.before(() => {
52
52
  }
53
53
  })
54
54
 
55
- test.after(() => {
55
+ afterAll(() => {
56
56
  global.t = originalTranslator
57
57
  })
58
58
 
@@ -425,5 +425,3 @@ test('falls back to the local device id when the backend identifier is unavailab
425
425
  assert.equal(candidates[0]?.value, 'local-device-99')
426
426
  assert.equal(candidates[0]?.source, 'device.id')
427
427
  })
428
-
429
- jestCompatTest('jest compatibility placeholder', () => {})
@@ -1,5 +1,5 @@
1
- const assert = require('node:assert/strict')
2
- const test = require('node:test')
1
+ const assert = require('node:assert/strict')
2
+ const {test} = global
3
3
 
4
4
  const {
5
5
  normalizeLanguageCode,
@@ -157,9 +157,29 @@ describe('runtimeMenu', () => {
157
157
  expect(menus[3].menus.map(item => item.route)).toEqual([
158
158
  'DevicesIndex',
159
159
  'ConfiguratorPage',
160
- ]);
161
- });
162
-
160
+ ]);
161
+ });
162
+
163
+ it('falls back to the admin starter menu when the runtime menu is empty', () => {
164
+ const menus = normalizeRuntimeMenuResponse({}, {appType: 'ADMIN'});
165
+
166
+ expect(menus).toHaveLength(1);
167
+ expect(menus[0].label).toBe('Configuracoes');
168
+ expect(menus[0].menus).toHaveLength(2);
169
+ expect(menus[0].menus[0]).toMatchObject({
170
+ label: 'Menus por perfil',
171
+ menuKey: 'menu_access',
172
+ route: 'MenuAccessConfigPage',
173
+ menuType: 'home',
174
+ });
175
+ expect(menus[0].menus[1]).toMatchObject({
176
+ label: 'Resultados de testes',
177
+ menuKey: 'test_results',
178
+ route: 'TestsPlaygroundPage',
179
+ menuType: 'home',
180
+ });
181
+ });
182
+
163
183
  it('does not invent a fallback menu for non-manager apps', () => {
164
184
  expect(
165
185
  normalizeRuntimeMenuResponse({}, {appType: 'SHOP'}),
@@ -0,0 +1,74 @@
1
+ const assert = require('node:assert/strict');
2
+ const {describe, it} = global;
3
+
4
+ const {
5
+ resolveShopSettings,
6
+ SHOP_LOYALTY_STAMP_ICON_URL_CONFIG_KEY,
7
+ toggleAndSaveBooleanConfig,
8
+ } = require('../../../react/utils/shopConfig');
9
+
10
+ describe('shopConfig', () => {
11
+ it('reads the configured loyalty stamp icon URL from general settings', () => {
12
+ const result = resolveShopSettings({
13
+ [SHOP_LOYALTY_STAMP_ICON_URL_CONFIG_KEY]:
14
+ '"https://cdn.example.com/loyalty-stamp.png"',
15
+ });
16
+
17
+ assert.equal(
18
+ result.loyaltyStampIconUrl,
19
+ 'https://cdn.example.com/loyalty-stamp.png',
20
+ );
21
+ });
22
+
23
+ it('keeps the loyalty stamp icon URL empty when it is not configured', () => {
24
+ const result = resolveShopSettings({});
25
+
26
+ assert.equal(result.loyaltyStampIconUrl, '');
27
+ });
28
+
29
+ it('persists the next boolean value when a toggle is clicked', async () => {
30
+ const calls = [];
31
+ const setValue = value => {
32
+ calls.push(['setValue', value]);
33
+ };
34
+ const saveConfig = async (configKey, value) => {
35
+ calls.push(['saveConfig', configKey, value]);
36
+ return true;
37
+ };
38
+
39
+ await toggleAndSaveBooleanConfig({
40
+ configKey: 'shop-sales-page-enabled',
41
+ currentValue: false,
42
+ saveConfig,
43
+ setValue,
44
+ });
45
+
46
+ assert.deepEqual(calls, [
47
+ ['setValue', true],
48
+ ['saveConfig', 'shop-sales-page-enabled', '1'],
49
+ ]);
50
+ });
51
+
52
+ it('persists the disabled value when toggling an enabled config', async () => {
53
+ const calls = [];
54
+ const setValue = value => {
55
+ calls.push(['setValue', value]);
56
+ };
57
+ const saveConfig = async (configKey, value) => {
58
+ calls.push(['saveConfig', configKey, value]);
59
+ return true;
60
+ };
61
+
62
+ await toggleAndSaveBooleanConfig({
63
+ configKey: 'shop-bottom-bar-enabled',
64
+ currentValue: true,
65
+ saveConfig,
66
+ setValue,
67
+ });
68
+
69
+ assert.deepEqual(calls, [
70
+ ['setValue', false],
71
+ ['saveConfig', 'shop-bottom-bar-enabled', '0'],
72
+ ]);
73
+ });
74
+ });