@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
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smoke browser: Manager /device-detail copy configs from another device.
|
|
3
|
+
* fluxo: manager-devices
|
|
4
|
+
* Refs: app-community#629
|
|
5
|
+
*
|
|
6
|
+
* Criteria:
|
|
7
|
+
* - Destino abre detalhe → ícone copy → escolhe origem → confirma.
|
|
8
|
+
* - POST /device_configs/add-configs usa identidade do destino (device/people).
|
|
9
|
+
* - Origem não é alterada; chaves copiadas vêm do configs da origem.
|
|
10
|
+
*/
|
|
11
|
+
const { expect, test } = require('playwright/test');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const packageJson = require('../../../../../../../package.json');
|
|
15
|
+
const { API_ORIGIN } = require('../../../../../../../src/tests/browser/apiOrigin');
|
|
16
|
+
|
|
17
|
+
const APP_VERSION = packageJson?.version || '1.0.0';
|
|
18
|
+
const DEST_DEVICE_STRING = 'web-7';
|
|
19
|
+
const DEST_ENTITY_ID = 396;
|
|
20
|
+
const DEST_ALIAS = 'Caixa destino';
|
|
21
|
+
const SRC_DEVICE_STRING = 'src-88';
|
|
22
|
+
const SRC_ENTITY_ID = 410;
|
|
23
|
+
const SRC_ALIAS = 'Caixa origem';
|
|
24
|
+
const OTHER_TENANT_DEVICE = 'other-co';
|
|
25
|
+
|
|
26
|
+
const CORS_HEADERS = {
|
|
27
|
+
'access-control-allow-origin': '*',
|
|
28
|
+
'access-control-allow-headers':
|
|
29
|
+
'API-TOKEN, APP-DOMAIN, DEVICE, ACCEPT, CONTENT-TYPE, X-Requested-With',
|
|
30
|
+
'access-control-allow-methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const jsonHeaders = () => ({
|
|
34
|
+
...CORS_HEADERS,
|
|
35
|
+
'content-type': 'application/ld+json; charset=utf-8',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const collection = (member) => ({
|
|
39
|
+
member,
|
|
40
|
+
'hydra:member': member,
|
|
41
|
+
totalItems: member.length,
|
|
42
|
+
'hydra:totalItems': member.length,
|
|
43
|
+
summary: {},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const destDevice = {
|
|
47
|
+
'@id': `/devices/${DEST_ENTITY_ID}`,
|
|
48
|
+
'@type': 'Device',
|
|
49
|
+
id: DEST_ENTITY_ID,
|
|
50
|
+
device: DEST_DEVICE_STRING,
|
|
51
|
+
alias: DEST_ALIAS,
|
|
52
|
+
metadata: { runtime: 'web', network: { publicIp: '127.0.0.1' } },
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const srcDevice = {
|
|
56
|
+
'@id': `/devices/${SRC_ENTITY_ID}`,
|
|
57
|
+
'@type': 'Device',
|
|
58
|
+
id: SRC_ENTITY_ID,
|
|
59
|
+
device: SRC_DEVICE_STRING,
|
|
60
|
+
alias: SRC_ALIAS,
|
|
61
|
+
metadata: { runtime: 'web' },
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const createDeviceConfig = ({ id, type, device, people = '/people/3', configs }) => ({
|
|
65
|
+
'@id': `/device_configs/${id}`,
|
|
66
|
+
'@type': 'DeviceConfig',
|
|
67
|
+
id,
|
|
68
|
+
type,
|
|
69
|
+
people,
|
|
70
|
+
device,
|
|
71
|
+
configs: JSON.stringify(configs),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const destManager = createDeviceConfig({
|
|
75
|
+
id: 487,
|
|
76
|
+
type: 'MANAGER',
|
|
77
|
+
device: destDevice,
|
|
78
|
+
configs: { 'config-version': APP_VERSION, 'pos-gateway': 'infinite-pay' },
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const srcPdv = createDeviceConfig({
|
|
82
|
+
id: 501,
|
|
83
|
+
type: 'PDV',
|
|
84
|
+
device: srcDevice,
|
|
85
|
+
configs: {
|
|
86
|
+
'pos-operation-mode': 'cashier',
|
|
87
|
+
'pos-gateway': 'cielo',
|
|
88
|
+
'printer-enabled': '1',
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const srcPrint = createDeviceConfig({
|
|
93
|
+
id: 502,
|
|
94
|
+
type: 'PRINT',
|
|
95
|
+
device: srcDevice,
|
|
96
|
+
configs: { 'print-network-port': '9100' },
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const otherTenant = createDeviceConfig({
|
|
100
|
+
id: 777,
|
|
101
|
+
type: 'PDV',
|
|
102
|
+
people: '/people/99',
|
|
103
|
+
device: {
|
|
104
|
+
'@id': '/devices/999',
|
|
105
|
+
id: 999,
|
|
106
|
+
device: OTHER_TENANT_DEVICE,
|
|
107
|
+
alias: 'Outra empresa',
|
|
108
|
+
},
|
|
109
|
+
configs: { 'pos-gateway': 'getnet' },
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const mockCopyApi = async (page) => {
|
|
113
|
+
const company = {
|
|
114
|
+
id: 3,
|
|
115
|
+
name: 'Teste',
|
|
116
|
+
alias: 'TESTE',
|
|
117
|
+
panel_enabled: true,
|
|
118
|
+
enabled: true,
|
|
119
|
+
commercial_enabled: true,
|
|
120
|
+
theme: {
|
|
121
|
+
colors: {
|
|
122
|
+
primary: '#0EA5E9',
|
|
123
|
+
cardBackground: '#FFFFFF',
|
|
124
|
+
cardBorder: '#D8E0EA',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const companyConfigs = [destManager, srcPdv, srcPrint];
|
|
130
|
+
/** @type {Array<object>} */
|
|
131
|
+
const addConfigPosts = [];
|
|
132
|
+
|
|
133
|
+
await page.route(`${API_ORIGIN}/**`, async (route) => {
|
|
134
|
+
const request = route.request();
|
|
135
|
+
const url = new URL(request.url());
|
|
136
|
+
const pathname = url.pathname.replace(/^\/+/, '');
|
|
137
|
+
const method = request.method().toUpperCase();
|
|
138
|
+
|
|
139
|
+
if (method === 'OPTIONS') {
|
|
140
|
+
return route.fulfill({ status: 204, headers: CORS_HEADERS, body: '' });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (pathname === 'themes-colors.css') {
|
|
144
|
+
return route.fulfill({
|
|
145
|
+
status: 200,
|
|
146
|
+
headers: { ...CORS_HEADERS, 'content-type': 'text/css; charset=utf-8' },
|
|
147
|
+
body: ':root { --primary: #0ea5e9; }',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (pathname === 'runtime/ip') {
|
|
152
|
+
return route.fulfill({
|
|
153
|
+
status: 200,
|
|
154
|
+
headers: jsonHeaders(),
|
|
155
|
+
body: JSON.stringify({ ip: '127.0.0.1' }),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (pathname === 'menus-people') {
|
|
160
|
+
return route.fulfill({
|
|
161
|
+
status: 200,
|
|
162
|
+
headers: jsonHeaders(),
|
|
163
|
+
body: JSON.stringify({ modules: {} }),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (pathname === 'people/companies/my') {
|
|
168
|
+
return route.fulfill({
|
|
169
|
+
status: 200,
|
|
170
|
+
headers: jsonHeaders(),
|
|
171
|
+
body: JSON.stringify(collection([company])),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (pathname === 'people/company/default') {
|
|
176
|
+
return route.fulfill({
|
|
177
|
+
status: 200,
|
|
178
|
+
headers: jsonHeaders(),
|
|
179
|
+
body: JSON.stringify(company),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (pathname === 'configs/discovery-configs') {
|
|
184
|
+
return route.fulfill({
|
|
185
|
+
status: 200,
|
|
186
|
+
headers: jsonHeaders(),
|
|
187
|
+
body: JSON.stringify({ configs: {} }),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (pathname === 'devices' && method === 'GET') {
|
|
192
|
+
return route.fulfill({
|
|
193
|
+
status: 200,
|
|
194
|
+
headers: jsonHeaders(),
|
|
195
|
+
body: JSON.stringify(collection([destDevice, srcDevice])),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (
|
|
200
|
+
(pathname === `devices/${DEST_ENTITY_ID}` ||
|
|
201
|
+
pathname === `devices/${DEST_DEVICE_STRING}`) &&
|
|
202
|
+
method === 'GET'
|
|
203
|
+
) {
|
|
204
|
+
return route.fulfill({
|
|
205
|
+
status: 200,
|
|
206
|
+
headers: jsonHeaders(),
|
|
207
|
+
body: JSON.stringify(destDevice),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (pathname === 'device_configs' && method === 'GET') {
|
|
212
|
+
const people = String(url.searchParams.get('people') || '');
|
|
213
|
+
const rows =
|
|
214
|
+
people.includes('/people/3') || !people
|
|
215
|
+
? companyConfigs
|
|
216
|
+
: [];
|
|
217
|
+
return route.fulfill({
|
|
218
|
+
status: 200,
|
|
219
|
+
headers: jsonHeaders(),
|
|
220
|
+
body: JSON.stringify(collection([...rows, otherTenant])),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (pathname === 'device_configs/add-configs' && method === 'POST') {
|
|
225
|
+
let body = {};
|
|
226
|
+
try {
|
|
227
|
+
body = request.postDataJSON() || {};
|
|
228
|
+
} catch {
|
|
229
|
+
body = {};
|
|
230
|
+
}
|
|
231
|
+
addConfigPosts.push(body);
|
|
232
|
+
return route.fulfill({
|
|
233
|
+
status: 200,
|
|
234
|
+
headers: jsonHeaders(),
|
|
235
|
+
body: JSON.stringify({
|
|
236
|
+
...destManager,
|
|
237
|
+
type: body.type || 'PDV',
|
|
238
|
+
device: destDevice,
|
|
239
|
+
configs: body.configs || destManager.configs,
|
|
240
|
+
}),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (pathname.startsWith('device_configs/') && method === 'GET') {
|
|
245
|
+
const id = Number(pathname.split('/')[1]);
|
|
246
|
+
const found =
|
|
247
|
+
[...companyConfigs, otherTenant].find((c) => c.id === id) || destManager;
|
|
248
|
+
return route.fulfill({
|
|
249
|
+
status: 200,
|
|
250
|
+
headers: jsonHeaders(),
|
|
251
|
+
body: JSON.stringify(found),
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return route.fulfill({
|
|
256
|
+
status: 200,
|
|
257
|
+
headers: jsonHeaders(),
|
|
258
|
+
body: JSON.stringify(collection([])),
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
await page.addInitScript(
|
|
263
|
+
({ appVersion }) => {
|
|
264
|
+
localStorage.setItem('token', 'smoke-token-629');
|
|
265
|
+
localStorage.setItem('config', JSON.stringify({ language: 'pt-br' }));
|
|
266
|
+
localStorage.setItem(
|
|
267
|
+
'device',
|
|
268
|
+
JSON.stringify({
|
|
269
|
+
id: 'web-7',
|
|
270
|
+
device: 'web-7',
|
|
271
|
+
type: 'MANAGER',
|
|
272
|
+
appName: 'Browser Manager',
|
|
273
|
+
appVersion,
|
|
274
|
+
buildNumber: appVersion,
|
|
275
|
+
systemName: 'web',
|
|
276
|
+
systemVersion: 'web',
|
|
277
|
+
deviceType: 'web',
|
|
278
|
+
metadata: { runtime: 'web' },
|
|
279
|
+
}),
|
|
280
|
+
);
|
|
281
|
+
},
|
|
282
|
+
{ appVersion: APP_VERSION },
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
return { addConfigPosts };
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
test.describe('device-detail copy config (browser smoke #629)', () => {
|
|
289
|
+
test('copy modal modules stay under 500 lines', async () => {
|
|
290
|
+
const files = [
|
|
291
|
+
path.join(__dirname, '../../../react/components/CopyDeviceConfigModal.js'),
|
|
292
|
+
path.join(__dirname, '../../../react/utils/copyDeviceConfigs.js'),
|
|
293
|
+
path.join(
|
|
294
|
+
__dirname,
|
|
295
|
+
'../../../react/pages/Devices/detail/useDeviceDetailCopyConfig.js',
|
|
296
|
+
),
|
|
297
|
+
];
|
|
298
|
+
for (const f of files) {
|
|
299
|
+
expect(fs.existsSync(f), `missing ${f}`).toBe(true);
|
|
300
|
+
const lines = fs.readFileSync(f, 'utf8').split(/\r?\n/).length;
|
|
301
|
+
expect(lines, `${f} has ${lines} lines`).toBeLessThanOrEqual(500);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test('copy from source device posts destination identity only', async ({
|
|
306
|
+
page,
|
|
307
|
+
}) => {
|
|
308
|
+
const api = await mockCopyApi(page);
|
|
309
|
+
|
|
310
|
+
await page.goto('/devices-index?store=device_config');
|
|
311
|
+
await page.getByTestId('device-config-487').click();
|
|
312
|
+
await expect(page).toHaveURL(/device-detail/);
|
|
313
|
+
await expect(page.getByTestId('device-alias-text')).toHaveText(DEST_ALIAS, {
|
|
314
|
+
timeout: 15000,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
await page.getByTestId('device-detail-copy-config-btn').click();
|
|
318
|
+
await expect(page.getByTestId('copy-device-config-modal')).toBeVisible({
|
|
319
|
+
timeout: 15000,
|
|
320
|
+
});
|
|
321
|
+
await expect(page.getByTestId('copy-device-config-hint')).toContainText(
|
|
322
|
+
DEST_ALIAS,
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
await expect(
|
|
326
|
+
page.getByTestId(`copy-device-config-option-${SRC_DEVICE_STRING}`),
|
|
327
|
+
).toBeVisible({ timeout: 10000 });
|
|
328
|
+
await expect(
|
|
329
|
+
page.getByTestId(`copy-device-config-option-${DEST_DEVICE_STRING}`),
|
|
330
|
+
).toHaveCount(0);
|
|
331
|
+
await expect(
|
|
332
|
+
page.getByTestId(`copy-device-config-option-${OTHER_TENANT_DEVICE}`),
|
|
333
|
+
).toHaveCount(0);
|
|
334
|
+
|
|
335
|
+
await page
|
|
336
|
+
.getByTestId(`copy-device-config-option-${SRC_DEVICE_STRING}`)
|
|
337
|
+
.click();
|
|
338
|
+
await expect(page.getByTestId('copy-device-config-preview')).toBeVisible();
|
|
339
|
+
await expect(page.getByTestId('copy-device-config-preview')).toContainText(
|
|
340
|
+
'PDV',
|
|
341
|
+
);
|
|
342
|
+
await expect(page.getByTestId('copy-device-config-preview')).toContainText(
|
|
343
|
+
'pos-gateway',
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
await page.getByTestId('copy-device-config-confirm').click();
|
|
347
|
+
|
|
348
|
+
await expect
|
|
349
|
+
.poll(() => api.addConfigPosts.length, { timeout: 15000 })
|
|
350
|
+
.toBeGreaterThanOrEqual(1);
|
|
351
|
+
|
|
352
|
+
const devicesPosted = api.addConfigPosts.map((p) => String(p.device || ''));
|
|
353
|
+
expect(devicesPosted.every((d) => d === DEST_DEVICE_STRING)).toBe(true);
|
|
354
|
+
expect(devicesPosted).not.toContain(SRC_DEVICE_STRING);
|
|
355
|
+
|
|
356
|
+
const peoplePosted = api.addConfigPosts.map((p) => String(p.people || ''));
|
|
357
|
+
expect(peoplePosted.every((p) => p === '/people/3')).toBe(true);
|
|
358
|
+
|
|
359
|
+
const types = api.addConfigPosts.map((p) => String(p.type || '').toUpperCase());
|
|
360
|
+
expect(types).toEqual(expect.arrayContaining(['PDV', 'PRINT']));
|
|
361
|
+
|
|
362
|
+
await expect(page).toHaveURL(/device-detail/);
|
|
363
|
+
await expect(page.getByTestId('device-alias-text')).toHaveText(DEST_ALIAS);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fluxo: outros
|
|
3
|
+
* Justificativa: ajuste visual do footer runtime (marquee 1 linha), fora dos
|
|
4
|
+
* fluxos de negócio do catálogo canônico.
|
|
5
|
+
* Issue: app-community#630
|
|
6
|
+
*
|
|
7
|
+
* Passos:
|
|
8
|
+
* 1. Abrir shell Manager com mensagem curta no footer
|
|
9
|
+
* 2. Capturar footer (1 linha, estático)
|
|
10
|
+
* 3. Abrir shell Manager com mensagem longa
|
|
11
|
+
* 4. Capturar footer (1 linha + overflow/marquee)
|
|
12
|
+
*/
|
|
13
|
+
const {expect, test} = require('playwright/test');
|
|
14
|
+
const packageJson = require('../../../../../../../package.json');
|
|
15
|
+
const {API_ORIGIN} = require('../../../../../../../src/tests/browser/apiOrigin');
|
|
16
|
+
|
|
17
|
+
const APP_VERSION = packageJson?.version || '1.0.0';
|
|
18
|
+
|
|
19
|
+
const CORS_HEADERS = {
|
|
20
|
+
'access-control-allow-origin': '*',
|
|
21
|
+
'access-control-allow-headers':
|
|
22
|
+
'API-TOKEN, APP-DOMAIN, DEVICE, ACCEPT, CONTENT-TYPE, X-Requested-With',
|
|
23
|
+
'access-control-allow-methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const jsonHeaders = () => ({
|
|
27
|
+
...CORS_HEADERS,
|
|
28
|
+
'content-type': 'application/ld+json; charset=utf-8',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const textHeaders = () => ({
|
|
32
|
+
...CORS_HEADERS,
|
|
33
|
+
'content-type': 'text/css; charset=utf-8',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const collection = member => ({
|
|
37
|
+
member,
|
|
38
|
+
'hydra:member': member,
|
|
39
|
+
totalItems: member.length,
|
|
40
|
+
'hydra:totalItems': member.length,
|
|
41
|
+
summary: {},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const LONG_FOOTER =
|
|
45
|
+
'Aviso operacional: fila de produção atrasada — conferir pedidos abertos, estoque crítico e dispositivos KDS offline antes de liberar o próximo turno de atendimento no salão e delivery';
|
|
46
|
+
|
|
47
|
+
const createCompany = footerText => ({
|
|
48
|
+
id: 3,
|
|
49
|
+
name: 'Controle Online',
|
|
50
|
+
alias: 'CONTROLE ONLINE',
|
|
51
|
+
panel_enabled: true,
|
|
52
|
+
enabled: true,
|
|
53
|
+
commercial_enabled: true,
|
|
54
|
+
theme: {
|
|
55
|
+
colors: {
|
|
56
|
+
primary: '#0EA5E9',
|
|
57
|
+
footerBackground: '#0F172A',
|
|
58
|
+
footerBorder: '#1E293B',
|
|
59
|
+
footerText: '#E2E8F0',
|
|
60
|
+
footerLink: '#38BDF8',
|
|
61
|
+
success: '#22C55E',
|
|
62
|
+
warning: '#F59E0B',
|
|
63
|
+
error: '#EF4444',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
configs: {
|
|
67
|
+
'device-runtime-footer-text': footerText,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const mockApi = async (page, footerText) => {
|
|
72
|
+
const company = createCompany(footerText);
|
|
73
|
+
|
|
74
|
+
await page.route(`${API_ORIGIN}/**`, async route => {
|
|
75
|
+
const request = route.request();
|
|
76
|
+
const url = new URL(request.url());
|
|
77
|
+
const pathname = url.pathname.replace(/^\/+/, '');
|
|
78
|
+
const method = request.method().toUpperCase();
|
|
79
|
+
|
|
80
|
+
if (method === 'OPTIONS') {
|
|
81
|
+
return route.fulfill({status: 204, headers: CORS_HEADERS, body: ''});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (pathname === 'themes-colors.css') {
|
|
85
|
+
return route.fulfill({
|
|
86
|
+
status: 200,
|
|
87
|
+
headers: textHeaders(),
|
|
88
|
+
body: ':root { --primary: #0ea5e9; --footer-text: #e2e8f0; }',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (pathname === 'runtime/ip') {
|
|
93
|
+
return route.fulfill({
|
|
94
|
+
status: 200,
|
|
95
|
+
headers: jsonHeaders(),
|
|
96
|
+
body: JSON.stringify({ip: '127.0.0.1'}),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (pathname === 'menus-people') {
|
|
101
|
+
return route.fulfill({
|
|
102
|
+
status: 200,
|
|
103
|
+
headers: jsonHeaders(),
|
|
104
|
+
body: JSON.stringify({modules: {}}),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (
|
|
109
|
+
pathname === 'people/company/default' ||
|
|
110
|
+
pathname === 'people/companies/my'
|
|
111
|
+
) {
|
|
112
|
+
return route.fulfill({
|
|
113
|
+
status: 200,
|
|
114
|
+
headers: jsonHeaders(),
|
|
115
|
+
body: JSON.stringify(
|
|
116
|
+
pathname === 'people/company/default' ? company : collection([company]),
|
|
117
|
+
),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (
|
|
122
|
+
pathname === 'people' ||
|
|
123
|
+
pathname === 'devices' ||
|
|
124
|
+
pathname === 'device_configs'
|
|
125
|
+
) {
|
|
126
|
+
return route.fulfill({
|
|
127
|
+
status: 200,
|
|
128
|
+
headers: jsonHeaders(),
|
|
129
|
+
body: JSON.stringify(collection([])),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return route.fulfill({
|
|
134
|
+
status: 200,
|
|
135
|
+
headers: jsonHeaders(),
|
|
136
|
+
body: JSON.stringify(collection([])),
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
await page.addInitScript(
|
|
141
|
+
({appVersion}) => {
|
|
142
|
+
window.localStorage.setItem(
|
|
143
|
+
'session',
|
|
144
|
+
JSON.stringify({
|
|
145
|
+
id: 7,
|
|
146
|
+
people: '/people/7',
|
|
147
|
+
api_key: 'test-api-key',
|
|
148
|
+
active: 1,
|
|
149
|
+
mycompany: 3,
|
|
150
|
+
roles: ['ROLE_SUPER'],
|
|
151
|
+
phone: '(11) 97114-0832',
|
|
152
|
+
email: 'qa@controleonline.com',
|
|
153
|
+
}),
|
|
154
|
+
);
|
|
155
|
+
window.localStorage.setItem('config', JSON.stringify({language: 'pt-br'}));
|
|
156
|
+
window.localStorage.setItem('app-type', 'MANAGER');
|
|
157
|
+
window.localStorage.setItem(
|
|
158
|
+
'device',
|
|
159
|
+
JSON.stringify({
|
|
160
|
+
id: 'web-manager',
|
|
161
|
+
device: 'web-manager',
|
|
162
|
+
type: 'WEB',
|
|
163
|
+
appName: 'Browser Manager',
|
|
164
|
+
appVersion,
|
|
165
|
+
buildNumber: appVersion,
|
|
166
|
+
systemName: 'web',
|
|
167
|
+
systemVersion: 'web',
|
|
168
|
+
deviceType: 'web',
|
|
169
|
+
metadata: {},
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
172
|
+
},
|
|
173
|
+
{appVersion: APP_VERSION},
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
async function assertSingleLineFooter(page, label) {
|
|
178
|
+
const footer = page.getByTestId('runtime-info-footer');
|
|
179
|
+
await expect(footer, `footer visible (${label})`).toBeVisible({timeout: 15000});
|
|
180
|
+
|
|
181
|
+
const box = await footer.boundingBox();
|
|
182
|
+
expect(box, `footer box (${label})`).toBeTruthy();
|
|
183
|
+
// One visual line of content + chrome: keep height bounded (not stacked multi-line body)
|
|
184
|
+
expect(box.height, `footer height single-line-ish (${label})`).toBeLessThan(120);
|
|
185
|
+
|
|
186
|
+
const marquee = page.getByTestId('runtime-footer-marquee-text');
|
|
187
|
+
if (await marquee.count()) {
|
|
188
|
+
await expect(marquee.first()).toBeVisible();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return footer;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
test.describe('runtime footer marquee (#630) — fluxo: outros', () => {
|
|
195
|
+
test('short message: footer one line and static', async ({page}, testInfo) => {
|
|
196
|
+
const consoleErrors = [];
|
|
197
|
+
page.on('console', message => {
|
|
198
|
+
if (message.type() === 'error') consoleErrors.push(message.text());
|
|
199
|
+
});
|
|
200
|
+
page.on('pageerror', error => {
|
|
201
|
+
consoleErrors.push(String(error?.message || error));
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
await mockApi(page, 'OK');
|
|
205
|
+
await page.goto('/devices-index?store=device_config');
|
|
206
|
+
await page.waitForTimeout(2500);
|
|
207
|
+
|
|
208
|
+
const footer = await assertSingleLineFooter(page, 'short');
|
|
209
|
+
await footer.screenshot({
|
|
210
|
+
path: testInfo.outputPath('step-short-footer.png'),
|
|
211
|
+
});
|
|
212
|
+
await page.screenshot({
|
|
213
|
+
path: testInfo.outputPath('step-short-full.png'),
|
|
214
|
+
fullPage: true,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const relevant = consoleErrors.filter(text =>
|
|
218
|
+
/TypeError|Cannot read|undefined is not|RuntimeInfoFooter|Marquee/i.test(
|
|
219
|
+
text,
|
|
220
|
+
),
|
|
221
|
+
);
|
|
222
|
+
expect(relevant, JSON.stringify(relevant)).toEqual([]);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('long message: footer stays one line and remains readable', async ({
|
|
226
|
+
page,
|
|
227
|
+
}, testInfo) => {
|
|
228
|
+
const consoleErrors = [];
|
|
229
|
+
page.on('console', message => {
|
|
230
|
+
if (message.type() === 'error') consoleErrors.push(message.text());
|
|
231
|
+
});
|
|
232
|
+
page.on('pageerror', error => {
|
|
233
|
+
consoleErrors.push(String(error?.message || error));
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
await mockApi(page, LONG_FOOTER);
|
|
237
|
+
await page.goto('/devices-index?store=device_config');
|
|
238
|
+
await page.waitForTimeout(2500);
|
|
239
|
+
|
|
240
|
+
const footer = await assertSingleLineFooter(page, 'long');
|
|
241
|
+
await footer.screenshot({
|
|
242
|
+
path: testInfo.outputPath('step-long-footer.png'),
|
|
243
|
+
});
|
|
244
|
+
await page.screenshot({
|
|
245
|
+
path: testInfo.outputPath('step-long-full.png'),
|
|
246
|
+
fullPage: true,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Wait a bit so marquee animation can start if overflow applies
|
|
250
|
+
await page.waitForTimeout(2000);
|
|
251
|
+
await footer.screenshot({
|
|
252
|
+
path: testInfo.outputPath('step-long-footer-after-hold.png'),
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const relevant = consoleErrors.filter(text =>
|
|
256
|
+
/TypeError|Cannot read|undefined is not|RuntimeInfoFooter|Marquee/i.test(
|
|
257
|
+
text,
|
|
258
|
+
),
|
|
259
|
+
);
|
|
260
|
+
expect(relevant, JSON.stringify(relevant)).toEqual([]);
|
|
261
|
+
});
|
|
262
|
+
});
|