@controleonline/ui-default 1.0.270 → 1.0.273
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 +19 -0
- package/.github/workflows/publish-npm.yml +91 -0
- package/AGENTS.md +46 -50
- package/docs/technical/Contrato-de-colunas.md +67 -0
- package/docs/technical/Default-Components.md +76 -0
- package/docs/technical/DefaultForm.md +80 -0
- package/docs/technical/DefaultInput-e-DefaultSelect.md +73 -0
- package/docs/technical/DefaultTable.md +272 -0
- package/docs/wiki.md +1 -0
- package/package.json +4 -3
- package/scripts/validate-wiki-pointer.cjs +13 -0
- package/src/react/components/address/DefaultAddress.js +446 -0
- package/src/react/components/address/DefaultAddress.styles.js +133 -0
- package/src/react/components/address/DefaultAddressCoordFields.js +59 -0
- package/src/react/components/address/DefaultAddressMapPane.js +55 -0
- package/src/react/components/address/LatLonReadonlyFields.js +51 -0
- package/src/react/components/address/defaultAddressHelpers.js +163 -0
- package/src/react/components/address/defaultAddressStyles.js +148 -0
- package/src/react/components/address/defaultAddressUtils.js +129 -0
- package/src/react/components/extra-data/DefaultExtraData.js +152 -0
- package/src/react/components/extra-data/DefaultExtraData.styles.js +61 -0
- package/src/react/components/files/DefaultFile.js +231 -46
- package/src/react/components/files/DefaultFileColumn.js +123 -0
- package/src/react/components/files/DefaultFileColumn.styles.js +52 -0
- package/src/react/components/files/DefaultFileColumn.utils.js +104 -0
- package/src/react/components/filters/CompactFilterSelector.js +51 -35
- package/src/react/components/filters/DefaultColumnFilter.js +60 -48
- package/src/react/components/filters/DefaultExternalFilters.js +324 -265
- package/src/react/components/filters/filterValue.js +65 -0
- package/src/react/components/form/DefaultForm.js +99 -79
- package/src/react/components/help/DefaultTooltip.js +48 -12
- package/src/react/components/help/DefaultTooltip.styles.js +20 -0
- package/src/react/components/inputs/DefaultColorInput.js +153 -0
- package/src/react/components/inputs/DefaultFeatherIconPicker.js +146 -0
- package/src/react/components/inputs/DefaultFeatherIconPicker.styles.js +113 -0
- package/src/react/components/inputs/DefaultInput.js +296 -141
- package/src/react/components/inputs/DefaultInput.styles.js +26 -0
- package/src/react/components/inputs/DefaultSelect.js +134 -123
- package/src/react/components/inputs/defaultInputUtils.js +262 -213
- package/src/react/components/map/DefaultMap.native.js +73 -73
- package/src/react/components/map/DefaultMap.shared.js +911 -762
- package/src/react/components/map/DefaultMap.web.js +124 -68
- package/src/react/components/table/DefaultColumnMenu.js +22 -3
- package/src/react/components/table/DefaultDebug.js +44 -37
- package/src/react/components/table/DefaultSearchModal.js +9 -1
- package/src/react/components/table/DefaultTable.grouping.js +98 -0
- package/src/react/components/table/DefaultTable.js +217 -111
- package/src/react/components/table/DefaultTable.styles.js +369 -310
- package/src/react/components/table/DefaultTable.utils.js +38 -4
- package/src/react/components/table/DefaultTableBody.js +13 -8
- package/src/react/components/table/DefaultTableCards.js +20 -1
- package/src/react/components/table/DefaultTableControls.js +180 -205
- package/src/react/components/table/DefaultTableImportModal.js +108 -0
- package/src/react/components/table/DefaultTableInput.js +1 -0
- package/src/react/components/table/DefaultTableRowActions.js +1 -1
- package/src/react/components/table/DefaultTableRows.js +357 -325
- package/src/react/components/table/DefaultTableSearch.js +28 -5
- package/src/react/components/table/DefaultTableToolbar.js +92 -3
- package/src/react/components/table/useDefaultTablePagination.js +56 -7
- package/src/react/components/table/useDefaultTableStoreSync.js +164 -0
- package/src/react/components/upload/DefaultUpload.js +406 -0
- package/src/react/components/upload/DefaultUpload.styles.js +153 -0
- package/src/react/components/upload/DefaultUploadAttachmentsList.js +146 -0
- package/src/react/components/upload/DefaultUploadManagerModal.js +247 -0
- package/src/react/components/upload/defaultUploadHelpers.js +108 -0
- package/src/react/components/upload/defaultUploadLibrary.js +248 -0
- package/src/react/components/upload/fileUpload.js +93 -0
- package/src/react/hooks/usePostalCodeLookup.js +159 -0
- package/src/react/index.js +6 -0
- package/src/react/services/addressFormUtils.js +262 -0
- package/src/react/services/addressGeo.js +230 -0
- package/src/react/utils/tableVisibleColumnsPreferences.js +113 -15
- package/src/tests/react/components/address/defaultAddressHelpers.test.js +104 -0
- package/src/tests/react/components/files/DefaultFile.test.js +22 -6
- package/src/tests/react/components/files/DefaultFileColumn.company.test.js +24 -0
- package/src/tests/react/components/form/DefaultForm.test.js +123 -0
- package/src/tests/react/components/help/DefaultTooltip.test.js +93 -0
- package/src/tests/react/components/inputs/DefaultSelect.test.js +108 -79
- package/src/tests/react/components/table/DefaultTable.grouping.test.js +49 -0
- package/src/tests/react/components/table/DefaultTable.test.js +53 -2
- package/src/tests/react/components/table/DefaultTable.utils.test.js +10 -0
- package/src/tests/react/components/table/DefaultTableControls.filters.test.mjs +21 -0
- package/src/tests/react/components/table/DefaultTableRowActions.test.js +23 -0
- package/src/tests/react/components/table/DefaultTableRows.rowActions.test.js +152 -0
- package/src/tests/react/components/table/resolveRowBackgroundColor.test.mjs +45 -0
- package/src/tests/react/components/table/useDefaultTableStoreSync.test.js +161 -0
- package/src/tests/react/components/upload/DefaultUpload.test.js +353 -0
- package/src/tests/react/components/upload/defaultUploadLibrary.test.js +104 -0
- package/src/tests/react/components/upload/peopleMediaLibraryPreview.test.mjs +57 -0
- package/src/tests/react/filters/filterValue.test.js +46 -0
- package/src/tests/react/hooks/usePostalCodeLookup.test.js +107 -0
- package/src/tests/react/inputs/defaultInputUtils.test.js +9 -1
- package/src/tests/react/services/SMOKE-637-cep-hydrate.md +45 -0
- package/src/tests/react/services/addressFormUtils.test.js +179 -0
- package/src/tests/react/services/addressGeoLookup.async.test.js +104 -0
- package/src/tests/react/services/addressGeoLookup.test.js +105 -0
- package/src/tests/react/services/smoke-637/form-cep-12941040.png +0 -0
- package/src/tests/react/services/smoke-637/index.html +132 -0
- package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +96 -49
|
@@ -3,12 +3,45 @@ export const TABLE_VIEW_MODE_PREFERENCES_KEY = 'viewMode';
|
|
|
3
3
|
export const TABLE_SORT_PREFERENCES_KEY = 'sort';
|
|
4
4
|
export const TABLE_FILTER_PREFERENCES_KEY = 'filters';
|
|
5
5
|
export const DEFAULT_TABLE_PREFERENCES_STORAGE_KEY = 'default-table';
|
|
6
|
+
export const REQUIRED_VISIBLE_COLUMN_KEYS = ['id'];
|
|
6
7
|
|
|
7
8
|
const isPlainObject = value =>
|
|
8
9
|
!!value && typeof value === 'object' && !Array.isArray(value);
|
|
9
10
|
|
|
10
11
|
const getColumnKey = column => column?.key || column?.name || '';
|
|
11
12
|
|
|
13
|
+
const normalizeColumnKey = value => String(value || '').trim().toLowerCase();
|
|
14
|
+
|
|
15
|
+
export const isRequiredVisibleColumn = column => {
|
|
16
|
+
const key = normalizeColumnKey(getColumnKey(column));
|
|
17
|
+
if (!key) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (column?.required === true || column?.hideable === false) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return REQUIRED_VISIBLE_COLUMN_KEYS.includes(key);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const isRequiredVisibleColumnKey = (key, columns = []) => {
|
|
29
|
+
const normalizedKey = normalizeColumnKey(key);
|
|
30
|
+
if (!normalizedKey) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const column = (Array.isArray(columns) ? columns : []).find(
|
|
35
|
+
item => normalizeColumnKey(getColumnKey(item)) === normalizedKey,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (column) {
|
|
39
|
+
return isRequiredVisibleColumn(column);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return REQUIRED_VISIBLE_COLUMN_KEYS.includes(normalizedKey);
|
|
43
|
+
};
|
|
44
|
+
|
|
12
45
|
const normalizePreferenceSegment = value =>
|
|
13
46
|
String(value || '')
|
|
14
47
|
.trim()
|
|
@@ -18,6 +51,7 @@ const normalizePreferenceSegment = value =>
|
|
|
18
51
|
.replace(/-+/g, '-');
|
|
19
52
|
|
|
20
53
|
export const resolveDefaultTablePreferenceScope = ({
|
|
54
|
+
companyId = '',
|
|
21
55
|
preferenceKey = '',
|
|
22
56
|
route = null,
|
|
23
57
|
storeName = '',
|
|
@@ -30,11 +64,13 @@ export const resolveDefaultTablePreferenceScope = ({
|
|
|
30
64
|
'unknown-route';
|
|
31
65
|
|
|
32
66
|
/*
|
|
33
|
-
* @agents DefaultTable preferences are saved as [store][route].
|
|
34
|
-
* The same store can back different screens,
|
|
35
|
-
*
|
|
67
|
+
* @agents DefaultTable preferences are saved as [company][store][route].
|
|
68
|
+
* The same store can back different screens, and the same user can switch
|
|
69
|
+
* companies, so filters, sort, view mode, and column visibility must be
|
|
70
|
+
* keyed by company, store and route together.
|
|
36
71
|
*/
|
|
37
72
|
return {
|
|
73
|
+
companyKey: normalizePreferenceSegment(companyId),
|
|
38
74
|
routeKey: routeName,
|
|
39
75
|
storeKey: normalizePreferenceSegment(storeName) || 'unknown-store',
|
|
40
76
|
};
|
|
@@ -45,10 +81,13 @@ const normalizePreferenceScope = scope => {
|
|
|
45
81
|
return null;
|
|
46
82
|
}
|
|
47
83
|
|
|
84
|
+
const companyKey = normalizePreferenceSegment(scope.companyKey);
|
|
48
85
|
const storeKey = normalizePreferenceSegment(scope.storeKey);
|
|
49
86
|
const routeKey = normalizePreferenceSegment(scope.routeKey);
|
|
50
87
|
|
|
51
|
-
return
|
|
88
|
+
return companyKey && storeKey && routeKey
|
|
89
|
+
? {companyKey, storeKey, routeKey}
|
|
90
|
+
: null;
|
|
52
91
|
};
|
|
53
92
|
|
|
54
93
|
const readStoredPreferences = () => {
|
|
@@ -86,7 +125,10 @@ const readScopedPreference = scope => {
|
|
|
86
125
|
}
|
|
87
126
|
|
|
88
127
|
const storedPreferences = readStoredPreferences();
|
|
89
|
-
const
|
|
128
|
+
const companyPreferences = storedPreferences[resolvedScope.companyKey];
|
|
129
|
+
const storePreferences = isPlainObject(companyPreferences)
|
|
130
|
+
? companyPreferences[resolvedScope.storeKey]
|
|
131
|
+
: null;
|
|
90
132
|
const routePreferences = isPlainObject(storePreferences)
|
|
91
133
|
? storePreferences[resolvedScope.routeKey]
|
|
92
134
|
: null;
|
|
@@ -101,8 +143,13 @@ const writeScopedPreference = (scope, nextValues = {}) => {
|
|
|
101
143
|
}
|
|
102
144
|
|
|
103
145
|
const storedPreferences = readStoredPreferences();
|
|
104
|
-
const
|
|
105
|
-
|
|
146
|
+
const companyPreferences = isPlainObject(
|
|
147
|
+
storedPreferences[resolvedScope.companyKey],
|
|
148
|
+
)
|
|
149
|
+
? storedPreferences[resolvedScope.companyKey]
|
|
150
|
+
: {};
|
|
151
|
+
const storePreferences = isPlainObject(companyPreferences[resolvedScope.storeKey])
|
|
152
|
+
? companyPreferences[resolvedScope.storeKey]
|
|
106
153
|
: {};
|
|
107
154
|
const routePreferences = isPlainObject(storePreferences[resolvedScope.routeKey])
|
|
108
155
|
? storePreferences[resolvedScope.routeKey]
|
|
@@ -110,11 +157,14 @@ const writeScopedPreference = (scope, nextValues = {}) => {
|
|
|
110
157
|
|
|
111
158
|
writeStoredPreferences({
|
|
112
159
|
...storedPreferences,
|
|
113
|
-
[resolvedScope.
|
|
114
|
-
...
|
|
115
|
-
[resolvedScope.
|
|
116
|
-
...
|
|
117
|
-
|
|
160
|
+
[resolvedScope.companyKey]: {
|
|
161
|
+
...companyPreferences,
|
|
162
|
+
[resolvedScope.storeKey]: {
|
|
163
|
+
...storePreferences,
|
|
164
|
+
[resolvedScope.routeKey]: {
|
|
165
|
+
...routePreferences,
|
|
166
|
+
...nextValues,
|
|
167
|
+
},
|
|
118
168
|
},
|
|
119
169
|
},
|
|
120
170
|
});
|
|
@@ -124,11 +174,41 @@ export const buildDefaultVisibleColumns = columns =>
|
|
|
124
174
|
(Array.isArray(columns) ? columns : []).reduce((accumulator, column) => {
|
|
125
175
|
const key = getColumnKey(column);
|
|
126
176
|
if (key) {
|
|
127
|
-
accumulator[key] = column
|
|
177
|
+
accumulator[key] = isRequiredVisibleColumn(column)
|
|
178
|
+
? true
|
|
179
|
+
: column?.visible !== false;
|
|
128
180
|
}
|
|
129
181
|
return accumulator;
|
|
130
182
|
}, {});
|
|
131
183
|
|
|
184
|
+
const enforceRequiredVisibleColumns = (columns = [], visibleColumns = {}) => {
|
|
185
|
+
const nextVisibleColumns = {...visibleColumns};
|
|
186
|
+
const columnList = Array.isArray(columns) ? columns : [];
|
|
187
|
+
|
|
188
|
+
columnList.forEach(column => {
|
|
189
|
+
const key = getColumnKey(column);
|
|
190
|
+
if (key && isRequiredVisibleColumn(column)) {
|
|
191
|
+
nextVisibleColumns[key] = true;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const hasVisibleColumn = Object.keys(nextVisibleColumns).some(
|
|
196
|
+
key => nextVisibleColumns[key] !== false,
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
if (!hasVisibleColumn) {
|
|
200
|
+
const fallbackColumn =
|
|
201
|
+
columnList.find(column => isRequiredVisibleColumn(column)) ||
|
|
202
|
+
columnList.find(column => getColumnKey(column));
|
|
203
|
+
const fallbackKey = getColumnKey(fallbackColumn);
|
|
204
|
+
if (fallbackKey) {
|
|
205
|
+
nextVisibleColumns[fallbackKey] = true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return nextVisibleColumns;
|
|
210
|
+
};
|
|
211
|
+
|
|
132
212
|
export const sanitizeVisibleColumnsPreference = ({
|
|
133
213
|
columns = [],
|
|
134
214
|
visibleColumns = null,
|
|
@@ -136,16 +216,34 @@ export const sanitizeVisibleColumnsPreference = ({
|
|
|
136
216
|
const defaultVisibleColumns = buildDefaultVisibleColumns(columns);
|
|
137
217
|
|
|
138
218
|
if (!isPlainObject(visibleColumns)) {
|
|
139
|
-
return defaultVisibleColumns;
|
|
219
|
+
return enforceRequiredVisibleColumns(columns, defaultVisibleColumns);
|
|
140
220
|
}
|
|
141
221
|
|
|
142
|
-
|
|
222
|
+
const sanitized = Object.keys(defaultVisibleColumns).reduce((accumulator, key) => {
|
|
143
223
|
accumulator[key] =
|
|
144
224
|
typeof visibleColumns[key] === 'boolean'
|
|
145
225
|
? visibleColumns[key]
|
|
146
226
|
: defaultVisibleColumns[key];
|
|
147
227
|
return accumulator;
|
|
148
228
|
}, {});
|
|
229
|
+
|
|
230
|
+
return enforceRequiredVisibleColumns(columns, sanitized);
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export const canHideVisibleColumn = ({
|
|
234
|
+
columns = [],
|
|
235
|
+
fieldName = '',
|
|
236
|
+
visibleColumns = {},
|
|
237
|
+
} = {}) => {
|
|
238
|
+
if (isRequiredVisibleColumnKey(fieldName, columns)) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const visibleCount = Object.keys(visibleColumns || {}).filter(
|
|
243
|
+
key => visibleColumns[key] !== false,
|
|
244
|
+
).length;
|
|
245
|
+
|
|
246
|
+
return visibleCount > 1;
|
|
149
247
|
};
|
|
150
248
|
|
|
151
249
|
export const sanitizeTableFiltersPreference = ({
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for defaultAddressHelpers (app-community#283).
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
emptyForm,
|
|
6
|
+
hasCoordinates,
|
|
7
|
+
hydrateFromRow,
|
|
8
|
+
mergePostalCodeData,
|
|
9
|
+
onlyDigits,
|
|
10
|
+
parseOptionalCoordinate,
|
|
11
|
+
buildMapMarkerPayload,
|
|
12
|
+
} from '../../../../react/components/address/defaultAddressHelpers';
|
|
13
|
+
|
|
14
|
+
describe('defaultAddressHelpers', () => {
|
|
15
|
+
test('onlyDigits strips non-digits', () => {
|
|
16
|
+
expect(onlyDigits('12.345-678')).toBe('12345678');
|
|
17
|
+
expect(onlyDigits(null)).toBe('');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('parseOptionalCoordinate accepts number and comma decimal', () => {
|
|
21
|
+
expect(parseOptionalCoordinate('-23.5505')).toBe(-23.5505);
|
|
22
|
+
expect(parseOptionalCoordinate('-46,6333')).toBeCloseTo(-46.6333);
|
|
23
|
+
expect(parseOptionalCoordinate('')).toBeNull();
|
|
24
|
+
expect(parseOptionalCoordinate('abc')).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('hasCoordinates requires both finite values', () => {
|
|
28
|
+
expect(hasCoordinates({latitude: -23.5, longitude: -46.6})).toBe(true);
|
|
29
|
+
expect(hasCoordinates({latitude: null, longitude: -46.6})).toBe(false);
|
|
30
|
+
expect(hasCoordinates(emptyForm)).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('hydrateFromRow maps nested street/district/city/state', () => {
|
|
34
|
+
const row = {
|
|
35
|
+
nickname: 'Matriz',
|
|
36
|
+
number: 100,
|
|
37
|
+
latitude: -23.5,
|
|
38
|
+
longitude: -46.6,
|
|
39
|
+
street: {
|
|
40
|
+
street: 'Av Paulista',
|
|
41
|
+
cep: {cep: '01310100'},
|
|
42
|
+
district: {
|
|
43
|
+
district: 'Bela Vista',
|
|
44
|
+
city: {
|
|
45
|
+
city: 'São Paulo',
|
|
46
|
+
state: {
|
|
47
|
+
uf: 'SP',
|
|
48
|
+
state: 'São Paulo',
|
|
49
|
+
country: {countrycode: 'BR', countryname: 'Brazil'},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const form = hydrateFromRow(row);
|
|
56
|
+
expect(form.street).toBe('Av Paulista');
|
|
57
|
+
expect(form.district).toBe('Bela Vista');
|
|
58
|
+
expect(form.city).toBe('São Paulo');
|
|
59
|
+
expect(form.uf).toBe('SP');
|
|
60
|
+
expect(form.cep).toBe('01310100');
|
|
61
|
+
expect(form.latitude).toBe(-23.5);
|
|
62
|
+
expect(form.longitude).toBe(-46.6);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('mergePostalCodeData fills lat/long from provider payload', () => {
|
|
66
|
+
const prev = {...emptyForm, street: 'Rua A'};
|
|
67
|
+
const data = {
|
|
68
|
+
cep: '01310100',
|
|
69
|
+
street: 'Av Paulista',
|
|
70
|
+
city: 'São Paulo',
|
|
71
|
+
uf: 'SP',
|
|
72
|
+
country: 'Brasil',
|
|
73
|
+
latitude: -23.561,
|
|
74
|
+
longitude: -46.656,
|
|
75
|
+
map: {staticUrl: 'https://example.com/map'},
|
|
76
|
+
provider: 'googlemaps',
|
|
77
|
+
};
|
|
78
|
+
const next = mergePostalCodeData(prev, data);
|
|
79
|
+
expect(next.latitude).toBe(-23.561);
|
|
80
|
+
expect(next.longitude).toBe(-46.656);
|
|
81
|
+
expect(next.provider).toBe('googlemaps');
|
|
82
|
+
expect(next.mapStaticUrl).toBe('https://example.com/map');
|
|
83
|
+
expect(next.countryCode).toBe('BR');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('buildMapMarkerPayload returns null without address or coords', () => {
|
|
87
|
+
expect(buildMapMarkerPayload(emptyForm)).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('buildMapMarkerPayload includes coords when present', () => {
|
|
91
|
+
const payload = buildMapMarkerPayload({
|
|
92
|
+
...emptyForm,
|
|
93
|
+
street: 'Av Paulista',
|
|
94
|
+
number: '1000',
|
|
95
|
+
city: 'São Paulo',
|
|
96
|
+
uf: 'SP',
|
|
97
|
+
latitude: -23.5,
|
|
98
|
+
longitude: -46.6,
|
|
99
|
+
});
|
|
100
|
+
expect(payload).not.toBeNull();
|
|
101
|
+
expect(payload.latitude).toBe(-23.5);
|
|
102
|
+
expect(payload.longitude).toBe(-46.6);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -20,6 +20,7 @@ jest.mock('@store', () => ({
|
|
|
20
20
|
}));
|
|
21
21
|
|
|
22
22
|
jest.mock('react-native', () => ({
|
|
23
|
+
Platform: {OS: 'android'},
|
|
23
24
|
Image: props => React.createElement('image', props, props.children),
|
|
24
25
|
}));
|
|
25
26
|
|
|
@@ -35,7 +36,7 @@ describe('DefaultFile', () => {
|
|
|
35
36
|
jest.clearAllTimers();
|
|
36
37
|
});
|
|
37
38
|
|
|
38
|
-
it('renders backend download files with app-domain
|
|
39
|
+
it('renders backend download files with path app-domain and headers', () => {
|
|
39
40
|
let tree;
|
|
40
41
|
|
|
41
42
|
renderer.act(() => {
|
|
@@ -58,11 +59,11 @@ describe('DefaultFile', () => {
|
|
|
58
59
|
|
|
59
60
|
const image = tree.root.findByType('image');
|
|
60
61
|
|
|
61
|
-
expect(image.props.source).toEqual({
|
|
62
|
-
uri: 'https://api.controleonline.com/files/3/download
|
|
63
|
-
headers: {
|
|
64
|
-
Authorization: 'Bearer token',
|
|
65
|
-
'app-domain': 'maincompany.controleonline.com',
|
|
62
|
+
expect(image.props.source).toEqual({
|
|
63
|
+
uri: 'https://api.controleonline.com/maincompany.controleonline.com/files/3/download',
|
|
64
|
+
headers: {
|
|
65
|
+
Authorization: 'Bearer token',
|
|
66
|
+
'app-domain': 'maincompany.controleonline.com',
|
|
66
67
|
},
|
|
67
68
|
});
|
|
68
69
|
expect(image.props.resizeMode).toBe('contain');
|
|
@@ -78,3 +79,18 @@ describe('DefaultFile', () => {
|
|
|
78
79
|
expect(tree.toJSON()).toBeNull();
|
|
79
80
|
});
|
|
80
81
|
});
|
|
82
|
+
|
|
83
|
+
it('uses the domain company from the store when another company is selected', () => {
|
|
84
|
+
require('@store').useStore.mockReturnValue({getters: {
|
|
85
|
+
mainCompany: {id: 1, domain: 'domain.example'},
|
|
86
|
+
currentCompany: {id: 2, domain: 'selected.example'},
|
|
87
|
+
}});
|
|
88
|
+
let tree;
|
|
89
|
+
renderer.act(() => {
|
|
90
|
+
tree = renderer.create(React.createElement(DefaultFile, {file: {id: 7, url: '/files/7/download'}}));
|
|
91
|
+
});
|
|
92
|
+
const source = tree.root.findByType('image').props.source;
|
|
93
|
+
expect(source.uri).toBe('https://api.controleonline.com/domain.example/files/7/download');
|
|
94
|
+
expect(source.headers['app-domain']).toBe('domain.example');
|
|
95
|
+
renderer.act(() => tree.unmount());
|
|
96
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const renderer = require('react-test-renderer');
|
|
3
|
+
const {expect, it, jest} = require('@jest/globals');
|
|
4
|
+
global.IS_REACT_ACT_ENVIRONMENT = true;
|
|
5
|
+
jest.mock('react-native', () => ({View: 'View', Text: 'Text', TouchableOpacity: 'TouchableOpacity',
|
|
6
|
+
Linking: {openURL: jest.fn()}, StyleSheet: {create: x => x}}));
|
|
7
|
+
jest.mock('react-native-vector-icons/Feather', () => 'Icon');
|
|
8
|
+
jest.mock('@store', () => ({useStore: jest.fn()}));
|
|
9
|
+
jest.mock('../../../../react/components/files/DefaultFile', () => 'Preview');
|
|
10
|
+
jest.mock('../../../../react/components/files/DefaultFileColumn.utils', () => ({
|
|
11
|
+
normalizeDefaultFileColumnItems: jest.fn(() => []), normalizeFileColumnText: value => value,
|
|
12
|
+
}));
|
|
13
|
+
const {useStore} = require('@store');
|
|
14
|
+
const {normalizeDefaultFileColumnItems} = require('../../../../react/components/files/DefaultFileColumn.utils');
|
|
15
|
+
const Column = require('../../../../react/components/files/DefaultFileColumn').default;
|
|
16
|
+
it('passes the domain company to file URL resolution when B is selected', () => {
|
|
17
|
+
const mainCompany = {id: 1, domain: 'domain.example'};
|
|
18
|
+
useStore.mockImplementation(name => name === 'people'
|
|
19
|
+
? {getters: {mainCompany, currentCompany: {id: 2}}} : {getters: {colors: {}}});
|
|
20
|
+
let tree;
|
|
21
|
+
renderer.act(() => { tree = renderer.create(React.createElement(Column, {})); });
|
|
22
|
+
expect(normalizeDefaultFileColumnItems.mock.calls.at(-1)[0].company).toEqual(mainCompany);
|
|
23
|
+
renderer.act(() => tree.unmount());
|
|
24
|
+
});
|
|
@@ -78,4 +78,127 @@ describe('DefaultForm', () => {
|
|
|
78
78
|
const inputAfterTyping = capturedInputs[capturedInputs.length - 1];
|
|
79
79
|
expect(inputAfterTyping.value).toBe('Cliente desistiu');
|
|
80
80
|
});
|
|
81
|
+
|
|
82
|
+
it('saves hidden create payload columns with row and default values', async () => {
|
|
83
|
+
const save = jest.fn(() => Promise.resolve({id: 10}));
|
|
84
|
+
const columns = [
|
|
85
|
+
{
|
|
86
|
+
name: 'price',
|
|
87
|
+
label: 'value',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'receiver',
|
|
91
|
+
createPayload: true,
|
|
92
|
+
visibleForm: false,
|
|
93
|
+
saveFormat: value => `/people/${value}`,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'invoiceType',
|
|
97
|
+
createPayload: true,
|
|
98
|
+
defaultValue: 'invoice',
|
|
99
|
+
visibleForm: false,
|
|
100
|
+
},
|
|
101
|
+
];
|
|
102
|
+
let tree;
|
|
103
|
+
|
|
104
|
+
await renderer.act(async () => {
|
|
105
|
+
tree = renderer.create(
|
|
106
|
+
React.createElement(DefaultForm, {
|
|
107
|
+
actions: {save},
|
|
108
|
+
columns,
|
|
109
|
+
mode: 'create',
|
|
110
|
+
row: {receiver: 21},
|
|
111
|
+
storeName: 'invoice',
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await renderer.act(async () => {
|
|
117
|
+
capturedInputs[capturedInputs.length - 1].onChangeValue('123.45');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const saveButton = tree.root.findAllByType('TouchableOpacity')[1];
|
|
121
|
+
|
|
122
|
+
await renderer.act(async () => {
|
|
123
|
+
await saveButton.props.onPress();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(save).toHaveBeenCalledWith({
|
|
127
|
+
price: 123.45,
|
|
128
|
+
receiver: '/people/21',
|
|
129
|
+
invoiceType: 'invoice',
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('preserves primitive ids for hidden create payload list columns', async () => {
|
|
134
|
+
const save = jest.fn(() => Promise.resolve({id: 11}));
|
|
135
|
+
const columns = [
|
|
136
|
+
{
|
|
137
|
+
name: 'price',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'receiver',
|
|
141
|
+
createPayload: true,
|
|
142
|
+
list: 'people/getItems',
|
|
143
|
+
visibleForm: false,
|
|
144
|
+
formatList: value => {
|
|
145
|
+
if (!value || typeof value !== 'object') {
|
|
146
|
+
return {value, label: String(value || '')};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {value: value.id, label: value.name};
|
|
150
|
+
},
|
|
151
|
+
saveFormat: value => `/people/${value?.value || value}`,
|
|
152
|
+
},
|
|
153
|
+
];
|
|
154
|
+
let tree;
|
|
155
|
+
|
|
156
|
+
await renderer.act(async () => {
|
|
157
|
+
tree = renderer.create(
|
|
158
|
+
React.createElement(DefaultForm, {
|
|
159
|
+
actions: {save},
|
|
160
|
+
columns,
|
|
161
|
+
mode: 'create',
|
|
162
|
+
row: {receiver: 21},
|
|
163
|
+
storeName: 'invoice',
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
await renderer.act(async () => {
|
|
169
|
+
capturedInputs[capturedInputs.length - 1].onChangeValue('1.23');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await renderer.act(async () => {
|
|
173
|
+
await tree.root.findAllByType('TouchableOpacity')[1].props.onPress();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
expect(save).toHaveBeenCalledWith({
|
|
177
|
+
price: 1.23,
|
|
178
|
+
receiver: '/people/21',
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('uses explicit form labels before translated column fallbacks', async () => {
|
|
183
|
+
const columns = [
|
|
184
|
+
{
|
|
185
|
+
name: 'paymentType',
|
|
186
|
+
label: 'paymentType.paymentType',
|
|
187
|
+
formLabel: 'Tipo de pagamento',
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
await renderer.act(async () => {
|
|
192
|
+
renderer.create(
|
|
193
|
+
React.createElement(DefaultForm, {
|
|
194
|
+
actions: {save: jest.fn()},
|
|
195
|
+
columns,
|
|
196
|
+
mode: 'create',
|
|
197
|
+
storeName: 'invoice',
|
|
198
|
+
}),
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
expect(capturedInputs[capturedInputs.length - 1].label).toBe('Tipo de pagamento');
|
|
203
|
+
});
|
|
81
204
|
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/* global jest */
|
|
2
|
+
|
|
3
|
+
const React = require('react');
|
|
4
|
+
const TestRenderer = require('react-test-renderer');
|
|
5
|
+
const {act} = TestRenderer;
|
|
6
|
+
const {describe, expect, it} = global;
|
|
7
|
+
|
|
8
|
+
jest.mock('react-native', () => {
|
|
9
|
+
const React = require('react');
|
|
10
|
+
const component = name => props => React.createElement(name, props, props.children);
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
Modal: props =>
|
|
14
|
+
React.createElement('modal', props, props.visible ? props.children : null),
|
|
15
|
+
Pressable: component('pressable'),
|
|
16
|
+
StyleSheet: {create: styles => styles},
|
|
17
|
+
Text: component('text'),
|
|
18
|
+
TouchableOpacity: component('touchable-opacity'),
|
|
19
|
+
View: component('view'),
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const DefaultTooltip = require('../../../../react/components/help/DefaultTooltip').default;
|
|
24
|
+
|
|
25
|
+
describe('DefaultTooltip', () => {
|
|
26
|
+
it('keeps the existing text message contract', () => {
|
|
27
|
+
let renderer;
|
|
28
|
+
act(() => {
|
|
29
|
+
renderer = TestRenderer.create(
|
|
30
|
+
React.createElement(DefaultTooltip, {
|
|
31
|
+
message: 'Ajuda já existente',
|
|
32
|
+
testID: 'message-trigger',
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const trigger = renderer.root
|
|
38
|
+
.findAllByType('touchable-opacity')
|
|
39
|
+
.find(node => node.props.testID === 'message-trigger');
|
|
40
|
+
|
|
41
|
+
act(() => trigger.props.onPress());
|
|
42
|
+
|
|
43
|
+
const renderedText = renderer.root
|
|
44
|
+
.findAllByType('text')
|
|
45
|
+
.flatMap(node => node.children)
|
|
46
|
+
.join(' ');
|
|
47
|
+
expect(renderedText).toContain('Ajuda já existente');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('renders structured rows and closes from the action or backdrop', () => {
|
|
51
|
+
let renderer;
|
|
52
|
+
act(() => {
|
|
53
|
+
renderer = TestRenderer.create(
|
|
54
|
+
React.createElement(DefaultTooltip, {
|
|
55
|
+
backdropTestID: 'tooltip-backdrop',
|
|
56
|
+
closeAccessibilityLabel: 'Fechar detalhes',
|
|
57
|
+
dialogTestID: 'tooltip-dialog',
|
|
58
|
+
label: 'i',
|
|
59
|
+
rows: [
|
|
60
|
+
{key: 'company', label: 'Empresa', value: 'GYROS'},
|
|
61
|
+
{key: 'device', label: 'Device', value: null},
|
|
62
|
+
],
|
|
63
|
+
testID: 'tooltip-trigger',
|
|
64
|
+
title: 'Detalhes',
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(renderer.root.findAllByProps({testID: 'tooltip-dialog'})).toHaveLength(0);
|
|
70
|
+
|
|
71
|
+
const trigger = renderer.root
|
|
72
|
+
.findAllByType('touchable-opacity')
|
|
73
|
+
.find(node => node.props.testID === 'tooltip-trigger');
|
|
74
|
+
|
|
75
|
+
act(() => trigger.props.onPress());
|
|
76
|
+
|
|
77
|
+
const renderedText = renderer.root
|
|
78
|
+
.findAllByType('text')
|
|
79
|
+
.flatMap(node => node.children)
|
|
80
|
+
.join(' ');
|
|
81
|
+
expect(renderedText).toContain('GYROS');
|
|
82
|
+
expect(renderedText).toContain('Não configurado');
|
|
83
|
+
|
|
84
|
+
act(() =>
|
|
85
|
+
renderer.root.findByProps({accessibilityLabel: 'Fechar detalhes'}).props.onPress(),
|
|
86
|
+
);
|
|
87
|
+
expect(renderer.root.findAllByProps({testID: 'tooltip-dialog'})).toHaveLength(0);
|
|
88
|
+
|
|
89
|
+
act(() => trigger.props.onPress());
|
|
90
|
+
act(() => renderer.root.findByProps({testID: 'tooltip-backdrop'}).props.onPress());
|
|
91
|
+
expect(renderer.root.findAllByProps({testID: 'tooltip-dialog'})).toHaveLength(0);
|
|
92
|
+
});
|
|
93
|
+
});
|