@controleonline/ui-default 1.0.270 → 1.0.272
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/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 +203 -0
- package/docs/wiki.md +1 -0
- package/package.json +22 -21
- package/scripts/validate-wiki-pointer.cjs +13 -0
- package/src/react/components/address/DefaultAddress.js +435 -0
- package/src/react/components/address/DefaultAddress.styles.js +133 -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 +136 -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 +58 -41
- package/src/react/components/filters/DefaultExternalFilters.js +331 -265
- package/src/react/components/form/DefaultForm.js +99 -79
- package/src/react/components/help/DefaultTooltip.js +59 -33
- package/src/react/components/help/DefaultTooltip.styles.js +23 -3
- 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 +69 -5
- 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 +69 -43
- 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 +109 -108
- 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 +213 -107
- package/src/react/components/table/DefaultTable.styles.js +367 -314
- 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 +224 -363
- 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 +436 -0
- package/src/react/components/upload/DefaultUpload.styles.js +153 -0
- package/src/react/components/upload/DefaultUploadAttachmentsList.js +138 -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 +145 -0
- package/src/react/index.js +6 -0
- package/src/react/services/addressFormUtils.js +196 -0
- package/src/react/services/addressGeo.js +227 -0
- package/src/react/utils/tableVisibleColumnsPreferences.js +29 -12
- package/src/tests/react/components/address/defaultAddressHelpers.test.js +104 -0
- package/src/tests/react/components/files/DefaultFile.test.js +6 -6
- package/src/tests/react/components/form/DefaultForm.test.js +123 -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/DefaultTableControls.filters.test.mjs +21 -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 +82 -0
- package/src/tests/react/components/upload/peopleMediaLibraryPreview.test.mjs +57 -0
- package/src/tests/react/hooks/usePostalCodeLookup.test.js +40 -0
- package/src/tests/react/services/SMOKE-637-cep-hydrate.md +45 -0
- package/src/tests/react/services/addressFormUtils.test.js +176 -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 +84 -48
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const React = require('react');
|
|
2
|
-
const renderer = require('react-test-renderer');
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const renderer = require('react-test-renderer');
|
|
3
3
|
const {jest} = require('@jest/globals');
|
|
4
4
|
|
|
5
5
|
const {beforeEach, describe, expect, it} = global;
|
|
@@ -14,37 +14,37 @@ jest.mock('@store', () => ({
|
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
16
|
jest.mock('react-native', () => {
|
|
17
|
-
const React = require('react');
|
|
18
|
-
const createComponent = name => props =>
|
|
19
|
-
React.createElement(name, props, props.children);
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
Modal: createComponent('Modal'),
|
|
23
|
-
ScrollView: createComponent('ScrollView'),
|
|
24
|
-
StyleSheet: {create: value => value},
|
|
25
|
-
Text: createComponent('Text'),
|
|
26
|
-
TextInput: createComponent('TextInput'),
|
|
27
|
-
TouchableOpacity: createComponent('TouchableOpacity'),
|
|
28
|
-
View: createComponent('View'),
|
|
29
|
-
};
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
jest.mock('react-native-vector-icons/Feather', () => {
|
|
33
|
-
const React = require('react');
|
|
34
|
-
return props => React.createElement('Icon', props, props.children);
|
|
35
|
-
});
|
|
36
|
-
|
|
17
|
+
const React = require('react');
|
|
18
|
+
const createComponent = name => props =>
|
|
19
|
+
React.createElement(name, props, props.children);
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
Modal: createComponent('Modal'),
|
|
23
|
+
ScrollView: createComponent('ScrollView'),
|
|
24
|
+
StyleSheet: {create: value => value},
|
|
25
|
+
Text: createComponent('Text'),
|
|
26
|
+
TextInput: createComponent('TextInput'),
|
|
27
|
+
TouchableOpacity: createComponent('TouchableOpacity'),
|
|
28
|
+
View: createComponent('View'),
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
jest.mock('react-native-vector-icons/Feather', () => {
|
|
33
|
+
const React = require('react');
|
|
34
|
+
return props => React.createElement('Icon', props, props.children);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
37
|
const DefaultSelect = require('../../../../react/components/inputs/DefaultSelect').default;
|
|
38
|
-
|
|
39
|
-
const createDeferred = () => {
|
|
40
|
-
let resolve;
|
|
41
|
-
const promise = new Promise(nextResolve => {
|
|
42
|
-
resolve = nextResolve;
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
return {promise, resolve};
|
|
46
|
-
};
|
|
47
|
-
|
|
38
|
+
|
|
39
|
+
const createDeferred = () => {
|
|
40
|
+
let resolve;
|
|
41
|
+
const promise = new Promise(nextResolve => {
|
|
42
|
+
resolve = nextResolve;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return {promise, resolve};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
48
|
describe('DefaultSelect', () => {
|
|
49
49
|
beforeEach(() => {
|
|
50
50
|
mockStores = {
|
|
@@ -64,53 +64,53 @@ describe('DefaultSelect', () => {
|
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
it('waits for onBeforeOpen before showing the options modal', async () => {
|
|
67
|
-
const gate = createDeferred();
|
|
68
|
-
const onBeforeOpen = jest.fn(() => gate.promise);
|
|
69
|
-
const onStartEditing = jest.fn();
|
|
70
|
-
let tree;
|
|
71
|
-
let openPromise;
|
|
72
|
-
|
|
73
|
-
await renderer.act(async () => {
|
|
74
|
-
tree = renderer.create(
|
|
75
|
-
React.createElement(DefaultSelect, {
|
|
76
|
-
column: {
|
|
77
|
-
key: 'jobTitle',
|
|
78
|
-
label: 'Cargo',
|
|
79
|
-
list: 'categories/getItems',
|
|
80
|
-
},
|
|
81
|
-
editing: false,
|
|
82
|
-
getOptionsForColumn: () => [
|
|
83
|
-
{
|
|
84
|
-
key: '1',
|
|
85
|
-
label: 'Analista de RH',
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
onBeforeOpen,
|
|
89
|
-
onStartEditing,
|
|
90
|
-
row: {},
|
|
91
|
-
storeName: 'employee_profiles',
|
|
92
|
-
value: '1',
|
|
93
|
-
}),
|
|
94
|
-
);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const readButton = tree.root.findAllByType('TouchableOpacity')[0];
|
|
98
|
-
const modal = tree.root.findByType('Modal');
|
|
99
|
-
|
|
100
|
-
expect(modal.props.visible).toBe(false);
|
|
101
|
-
|
|
102
|
-
renderer.act(() => {
|
|
103
|
-
openPromise = readButton.props.onPress();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
expect(onStartEditing).toHaveBeenCalledTimes(1);
|
|
107
|
-
expect(onBeforeOpen).toHaveBeenCalledTimes(1);
|
|
108
|
-
expect(modal.props.visible).toBe(false);
|
|
109
|
-
|
|
110
|
-
await renderer.act(async () => {
|
|
111
|
-
gate.resolve();
|
|
112
|
-
await openPromise;
|
|
113
|
-
});
|
|
67
|
+
const gate = createDeferred();
|
|
68
|
+
const onBeforeOpen = jest.fn(() => gate.promise);
|
|
69
|
+
const onStartEditing = jest.fn();
|
|
70
|
+
let tree;
|
|
71
|
+
let openPromise;
|
|
72
|
+
|
|
73
|
+
await renderer.act(async () => {
|
|
74
|
+
tree = renderer.create(
|
|
75
|
+
React.createElement(DefaultSelect, {
|
|
76
|
+
column: {
|
|
77
|
+
key: 'jobTitle',
|
|
78
|
+
label: 'Cargo',
|
|
79
|
+
list: 'categories/getItems',
|
|
80
|
+
},
|
|
81
|
+
editing: false,
|
|
82
|
+
getOptionsForColumn: () => [
|
|
83
|
+
{
|
|
84
|
+
key: '1',
|
|
85
|
+
label: 'Analista de RH',
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
onBeforeOpen,
|
|
89
|
+
onStartEditing,
|
|
90
|
+
row: {},
|
|
91
|
+
storeName: 'employee_profiles',
|
|
92
|
+
value: '1',
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const readButton = tree.root.findAllByType('TouchableOpacity')[0];
|
|
98
|
+
const modal = tree.root.findByType('Modal');
|
|
99
|
+
|
|
100
|
+
expect(modal.props.visible).toBe(false);
|
|
101
|
+
|
|
102
|
+
renderer.act(() => {
|
|
103
|
+
openPromise = readButton.props.onPress();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(onStartEditing).toHaveBeenCalledTimes(1);
|
|
107
|
+
expect(onBeforeOpen).toHaveBeenCalledTimes(1);
|
|
108
|
+
expect(modal.props.visible).toBe(false);
|
|
109
|
+
|
|
110
|
+
await renderer.act(async () => {
|
|
111
|
+
gate.resolve();
|
|
112
|
+
await openPromise;
|
|
113
|
+
});
|
|
114
114
|
|
|
115
115
|
expect(modal.props.visible).toBe(true);
|
|
116
116
|
});
|
|
@@ -254,4 +254,33 @@ describe('DefaultSelect', () => {
|
|
|
254
254
|
]),
|
|
255
255
|
);
|
|
256
256
|
});
|
|
257
|
+
|
|
258
|
+
it('uses the resolved form label as the modal search placeholder', async () => {
|
|
259
|
+
let tree;
|
|
260
|
+
|
|
261
|
+
await renderer.act(async () => {
|
|
262
|
+
tree = renderer.create(
|
|
263
|
+
React.createElement(DefaultSelect, {
|
|
264
|
+
column: {
|
|
265
|
+
key: 'paymentType',
|
|
266
|
+
label: 'Payment Type.payment Type',
|
|
267
|
+
list: 'walletPaymentType/getItems',
|
|
268
|
+
searchParam: 'paymentType.paymentType',
|
|
269
|
+
},
|
|
270
|
+
editing: true,
|
|
271
|
+
label: 'Tipo de pagamento',
|
|
272
|
+
row: {},
|
|
273
|
+
storeName: 'invoice',
|
|
274
|
+
variant: 'form',
|
|
275
|
+
}),
|
|
276
|
+
);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
await renderer.act(async () => {
|
|
280
|
+
await tree.root.findAllByType('TouchableOpacity')[0].props.onPress();
|
|
281
|
+
await Promise.resolve();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
expect(tree.root.findByType('TextInput').props.placeholder).toBe('Tipo de pagamento');
|
|
285
|
+
});
|
|
257
286
|
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
flattenGroupedTableItems,
|
|
3
|
+
getGroupingColumns,
|
|
4
|
+
groupTableRows,
|
|
5
|
+
toggleGroupSelection,
|
|
6
|
+
toggleSelectedId,
|
|
7
|
+
} from '../../../../src/react/components/table/DefaultTable.grouping';
|
|
8
|
+
|
|
9
|
+
const columns = [
|
|
10
|
+
{ name: 'companyName', grouping: true },
|
|
11
|
+
{ name: 'addressLabel', grouping: true },
|
|
12
|
+
{ name: 'invoiceNumber' },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const rows = [
|
|
16
|
+
{ id: 1, companyName: 'A', addressLabel: 'Rua 1', invoiceNumber: 10 },
|
|
17
|
+
{ id: 2, companyName: 'A', addressLabel: 'Rua 1', invoiceNumber: 11 },
|
|
18
|
+
{ id: 3, companyName: 'B', addressLabel: 'Rua 2', invoiceNumber: 12 },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
describe('DefaultTable grouping', () => {
|
|
22
|
+
it('reads boolean grouping from store columns', () => {
|
|
23
|
+
expect(getGroupingColumns(columns).map(column => column.name)).toEqual([
|
|
24
|
+
'companyName',
|
|
25
|
+
'addressLabel',
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('groups rows by grouping columns', () => {
|
|
30
|
+
const groups = groupTableRows(rows, columns);
|
|
31
|
+
expect(groups).toHaveLength(2);
|
|
32
|
+
expect(groups[0].count).toBe(2);
|
|
33
|
+
expect(groups[1].count).toBe(1);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('flattens group headers and rows', () => {
|
|
37
|
+
const items = flattenGroupedTableItems(rows, columns);
|
|
38
|
+
expect(items[0].type).toBe('group');
|
|
39
|
+
expect(items.filter(item => item.type === 'row')).toHaveLength(3);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('toggles row and group selection', () => {
|
|
43
|
+
const one = toggleSelectedId([], rows[0]);
|
|
44
|
+
expect(one).toEqual(['1']);
|
|
45
|
+
const group = toggleGroupSelection(one, [rows[0], rows[1]]);
|
|
46
|
+
expect(group).toEqual(['1', '2']);
|
|
47
|
+
expect(toggleGroupSelection(group, [rows[0], rows[1]])).toEqual([]);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -9,6 +9,7 @@ let consoleErrorSpy = null;
|
|
|
9
9
|
let mockStores = {};
|
|
10
10
|
let mockWindowDimensions = {width: 480, height: 800};
|
|
11
11
|
let mockCapturedColumnFilterProps = [];
|
|
12
|
+
let mockCapturedDefaultFormProps = [];
|
|
12
13
|
let mockCapturedDefaultInputProps = [];
|
|
13
14
|
|
|
14
15
|
const createLocalStorageMock = () => {
|
|
@@ -28,6 +29,8 @@ const createLocalStorageMock = () => {
|
|
|
28
29
|
};
|
|
29
30
|
};
|
|
30
31
|
|
|
32
|
+
global.localStorage = createLocalStorageMock();
|
|
33
|
+
|
|
31
34
|
jest.mock('@store', () => ({
|
|
32
35
|
getAllStores: jest.fn(() => ({})),
|
|
33
36
|
useStore: jest.fn(name => mockStores[name] || {actions: {}, getters: {}}),
|
|
@@ -98,9 +101,15 @@ jest.mock('../../../../react/components/filters/DefaultSearch', () => props => {
|
|
|
98
101
|
return React.createElement('DefaultSearch', props);
|
|
99
102
|
});
|
|
100
103
|
|
|
101
|
-
jest.mock('../../../../react/components/
|
|
104
|
+
jest.mock('../../../../react/components/table/DefaultTableImportModal', () => props => {
|
|
102
105
|
const React = require('react');
|
|
103
|
-
return React.createElement('
|
|
106
|
+
return React.createElement('DefaultTableImportModal', props);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
jest.mock('../../../../react/components/form/DefaultForm', () => props => {
|
|
110
|
+
const React = require('react');
|
|
111
|
+
mockCapturedDefaultFormProps.push(props);
|
|
112
|
+
return React.createElement('DefaultForm', props);
|
|
104
113
|
});
|
|
105
114
|
|
|
106
115
|
jest.mock('../../../../react/components/inputs/DefaultInput', () => props => {
|
|
@@ -230,6 +239,7 @@ describe('DefaultTable', () => {
|
|
|
230
239
|
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
231
240
|
mockWindowDimensions = {width: 480, height: 800};
|
|
232
241
|
mockCapturedColumnFilterProps = [];
|
|
242
|
+
mockCapturedDefaultFormProps = [];
|
|
233
243
|
mockCapturedDefaultInputProps = [];
|
|
234
244
|
getAllStores.mockImplementation(() => ({}));
|
|
235
245
|
global.localStorage = createLocalStorageMock();
|
|
@@ -614,6 +624,47 @@ describe('DefaultTable', () => {
|
|
|
614
624
|
expect(onAdd).toHaveBeenCalledTimes(1);
|
|
615
625
|
});
|
|
616
626
|
|
|
627
|
+
it('opens the default create form when add has no custom handler', () => {
|
|
628
|
+
mockWindowDimensions = {width: 430, height: 932};
|
|
629
|
+
const save = jest.fn(() => Promise.resolve({id: 10}));
|
|
630
|
+
mockStores.invoice = {
|
|
631
|
+
actions: {save},
|
|
632
|
+
getters: {
|
|
633
|
+
add: true,
|
|
634
|
+
columns: [{key: 'payer', label: 'Pagador', editable: true}],
|
|
635
|
+
},
|
|
636
|
+
};
|
|
637
|
+
let tree;
|
|
638
|
+
|
|
639
|
+
renderer.act(() => {
|
|
640
|
+
tree = renderer.create(
|
|
641
|
+
React.createElement(DefaultTable, {
|
|
642
|
+
requestParams: {receiver: 21},
|
|
643
|
+
showToolbar: false,
|
|
644
|
+
storeName: 'invoice',
|
|
645
|
+
}),
|
|
646
|
+
);
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
const addButton = tree.root
|
|
650
|
+
.findAllByType('TouchableOpacity')
|
|
651
|
+
.find(node => node.findAllByType('icon').some(icon => icon.props.name === 'plus'));
|
|
652
|
+
|
|
653
|
+
expect(tree.root.findAllByType('Modal').some(node => node.props.visible)).toBe(false);
|
|
654
|
+
|
|
655
|
+
renderer.act(() => addButton.props.onPress());
|
|
656
|
+
|
|
657
|
+
expect(tree.root.findAllByType('Modal').some(node => node.props.visible)).toBe(true);
|
|
658
|
+
expect(mockCapturedDefaultFormProps[mockCapturedDefaultFormProps.length - 1]).toEqual(
|
|
659
|
+
expect.objectContaining({
|
|
660
|
+
actions: expect.objectContaining({save}),
|
|
661
|
+
mode: 'create',
|
|
662
|
+
row: {receiver: 21},
|
|
663
|
+
storeName: 'invoice',
|
|
664
|
+
}),
|
|
665
|
+
);
|
|
666
|
+
});
|
|
667
|
+
|
|
617
668
|
it('collapses search into an icon on narrow toolbars and opens the search modal', () => {
|
|
618
669
|
mockWindowDimensions = {width: 375, height: 667};
|
|
619
670
|
mockStores.orders = {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// fluxo: outros | etapa: order-history-page-filtro | https://github.com/ControleOnline/app-community/wiki/Smoke-Test-Flows
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), '../../../../../');
|
|
9
|
+
const source = readFileSync(
|
|
10
|
+
join(root, 'src/react/components/table/DefaultTableControls.js'),
|
|
11
|
+
'utf8',
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
test('filter button opens DefaultFiltersModal in table and cards modes (#811)', () => {
|
|
15
|
+
assert.match(source, /DefaultFiltersModal/);
|
|
16
|
+
assert.match(source, /hasFilterableColumns \? \(/);
|
|
17
|
+
assert.match(source, /testID="default-table-filter-button"/);
|
|
18
|
+
// must not gate the modal only on cards / must not toggle orphan flag
|
|
19
|
+
assert.doesNotMatch(source, /tableFiltersVisible:\s*!tableFiltersVisible/);
|
|
20
|
+
assert.doesNotMatch(source, /effectiveViewMode === 'cards' \? \([\s\S]*DefaultFiltersModal/);
|
|
21
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), '../../../../../');
|
|
8
|
+
const sourcePath = join(root, 'src/react/components/table/DefaultTableRows.js');
|
|
9
|
+
const source = readFileSync(sourcePath, 'utf8');
|
|
10
|
+
|
|
11
|
+
test('resolveRowBackgroundColor ignores status.color in source', () => {
|
|
12
|
+
assert.doesNotMatch(source, /row\?\.status\?\.color/);
|
|
13
|
+
assert.doesNotMatch(source, /withAlpha\(statusColor/);
|
|
14
|
+
assert.match(source, /listItemEvenRow/);
|
|
15
|
+
assert.match(source, /listItemOddRow/);
|
|
16
|
+
assert.match(source, /export const resolveRowBackgroundColor/);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const resolveRowBackgroundColor = ({
|
|
20
|
+
selected,
|
|
21
|
+
index,
|
|
22
|
+
tableOddColor,
|
|
23
|
+
tableEvenColor,
|
|
24
|
+
selectedBackground = '#ECFEFF',
|
|
25
|
+
}) => {
|
|
26
|
+
if (selected) return selectedBackground;
|
|
27
|
+
return index % 2 === 0 ? tableOddColor : tableEvenColor;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
test('even/odd stripes use listItem tokens, not status', () => {
|
|
31
|
+
const even = '#111111';
|
|
32
|
+
const odd = '#222222';
|
|
33
|
+
assert.equal(
|
|
34
|
+
resolveRowBackgroundColor({ selected: false, index: 0, tableOddColor: odd, tableEvenColor: even }),
|
|
35
|
+
odd,
|
|
36
|
+
);
|
|
37
|
+
assert.equal(
|
|
38
|
+
resolveRowBackgroundColor({ selected: false, index: 1, tableOddColor: odd, tableEvenColor: even }),
|
|
39
|
+
even,
|
|
40
|
+
);
|
|
41
|
+
assert.equal(
|
|
42
|
+
resolveRowBackgroundColor({ selected: true, index: 0, tableOddColor: odd, tableEvenColor: even }),
|
|
43
|
+
'#ECFEFF',
|
|
44
|
+
);
|
|
45
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const renderer = require('react-test-renderer');
|
|
3
|
+
|
|
4
|
+
const {beforeEach, describe, expect, it} = global;
|
|
5
|
+
|
|
6
|
+
global.IS_REACT_ACT_ENVIRONMENT = true;
|
|
7
|
+
|
|
8
|
+
const mockResolveStoredTableFiltersPreference = jest.fn(() => null);
|
|
9
|
+
const mockSanitizeTableFiltersPreference = jest.fn((args) => {
|
|
10
|
+
if (args && args.filters) return args.filters;
|
|
11
|
+
return args || {};
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
jest.mock('../../../../react/utils/tableVisibleColumnsPreferences', () => ({
|
|
15
|
+
resolveStoredTableFiltersPreference: (...args) =>
|
|
16
|
+
mockResolveStoredTableFiltersPreference(...args),
|
|
17
|
+
resolveStoredVisibleColumnsPreference: jest.fn(() => null),
|
|
18
|
+
sanitizeTableFiltersPreference: (...args) =>
|
|
19
|
+
mockSanitizeTableFiltersPreference(...args),
|
|
20
|
+
sanitizeVisibleColumnsPreference: jest.fn(v => v?.visibleColumns || []),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
areTableFiltersEqual,
|
|
25
|
+
useDefaultTableStoreSync,
|
|
26
|
+
} = require('../../../../react/components/table/useDefaultTableStoreSync');
|
|
27
|
+
|
|
28
|
+
function Harness({data, store, storeFilters, columnsForTable, tablePreferenceScope}) {
|
|
29
|
+
useDefaultTableStoreSync({
|
|
30
|
+
columns: [],
|
|
31
|
+
columnsForTable: columnsForTable || [],
|
|
32
|
+
data,
|
|
33
|
+
defaultTableConfigs: {},
|
|
34
|
+
defaultTableConfigsSignature: 'sig',
|
|
35
|
+
store,
|
|
36
|
+
storeColumnsLength: 0,
|
|
37
|
+
storeFilters: storeFilters || {},
|
|
38
|
+
storeName: 'device_config',
|
|
39
|
+
tablePreferenceScope: tablePreferenceScope || {
|
|
40
|
+
companyKey: 'c1',
|
|
41
|
+
storeKey: 'orders',
|
|
42
|
+
routeKey: 'order-history-page',
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe('useDefaultTableStoreSync', () => {
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
mockResolveStoredTableFiltersPreference.mockReset();
|
|
51
|
+
mockResolveStoredTableFiltersPreference.mockReturnValue(null);
|
|
52
|
+
mockSanitizeTableFiltersPreference.mockClear();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('publishes controlled data once per array reference (avoids #185 loop)', () => {
|
|
56
|
+
const setItems = jest.fn();
|
|
57
|
+
const store = {
|
|
58
|
+
actions: {setItems},
|
|
59
|
+
getters: {},
|
|
60
|
+
};
|
|
61
|
+
const data = [{id: 1}];
|
|
62
|
+
|
|
63
|
+
let tree;
|
|
64
|
+
renderer.act(() => {
|
|
65
|
+
tree = renderer.create(React.createElement(Harness, {data, store}));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(setItems).toHaveBeenCalledTimes(1);
|
|
69
|
+
expect(setItems).toHaveBeenCalledWith(data);
|
|
70
|
+
|
|
71
|
+
// New store identity (zustand commit) with same data reference must NOT re-publish
|
|
72
|
+
const storeAfterCommit = {
|
|
73
|
+
actions: {setItems},
|
|
74
|
+
getters: {items: data},
|
|
75
|
+
};
|
|
76
|
+
renderer.act(() => {
|
|
77
|
+
tree.update(React.createElement(Harness, {data, store: storeAfterCommit}));
|
|
78
|
+
});
|
|
79
|
+
expect(setItems).toHaveBeenCalledTimes(1);
|
|
80
|
+
|
|
81
|
+
// New data reference must publish again
|
|
82
|
+
const data2 = [{id: 2}];
|
|
83
|
+
renderer.act(() => {
|
|
84
|
+
tree.update(
|
|
85
|
+
React.createElement(Harness, {data: data2, store: storeAfterCommit}),
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
expect(setItems).toHaveBeenCalledTimes(2);
|
|
89
|
+
expect(setItems).toHaveBeenLastCalledWith(data2);
|
|
90
|
+
|
|
91
|
+
tree.unmount();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('hydrates stored filters once and does not re-setFilters when storeFilters churns (period toggle #185)', () => {
|
|
95
|
+
const setFilters = jest.fn();
|
|
96
|
+
const setItems = jest.fn();
|
|
97
|
+
const stored = {
|
|
98
|
+
alterDate: {shortcut: 'today', customRange: {from: '', to: ''}},
|
|
99
|
+
};
|
|
100
|
+
mockResolveStoredTableFiltersPreference.mockReturnValue(stored);
|
|
101
|
+
mockSanitizeTableFiltersPreference.mockImplementation(({filters}) => filters || {});
|
|
102
|
+
|
|
103
|
+
const columnsForTable = [{name: 'alterDate', externalFilter: true}];
|
|
104
|
+
const store = {actions: {setFilters, setItems}, getters: {}};
|
|
105
|
+
|
|
106
|
+
let tree;
|
|
107
|
+
renderer.act(() => {
|
|
108
|
+
tree = renderer.create(
|
|
109
|
+
React.createElement(Harness, {
|
|
110
|
+
data: [],
|
|
111
|
+
store,
|
|
112
|
+
storeFilters: {},
|
|
113
|
+
columnsForTable,
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(setFilters).toHaveBeenCalledTimes(1);
|
|
119
|
+
expect(setFilters).toHaveBeenCalledWith(stored);
|
|
120
|
+
|
|
121
|
+
// Simulate user toggle today → all → today: storeFilters identity changes each time
|
|
122
|
+
const afterAll = {};
|
|
123
|
+
const afterToday = {
|
|
124
|
+
alterDate: {shortcut: 'today', customRange: {from: '', to: ''}},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
renderer.act(() => {
|
|
128
|
+
tree.update(
|
|
129
|
+
React.createElement(Harness, {
|
|
130
|
+
data: [],
|
|
131
|
+
store: {actions: {setFilters, setItems}, getters: {filters: afterAll}},
|
|
132
|
+
storeFilters: afterAll,
|
|
133
|
+
columnsForTable,
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
renderer.act(() => {
|
|
138
|
+
tree.update(
|
|
139
|
+
React.createElement(Harness, {
|
|
140
|
+
data: [],
|
|
141
|
+
store: {actions: {setFilters, setItems}, getters: {filters: afterToday}},
|
|
142
|
+
storeFilters: afterToday,
|
|
143
|
+
columnsForTable,
|
|
144
|
+
}),
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Must remain a single hydrate — no chase of storeFilters
|
|
149
|
+
expect(setFilters).toHaveBeenCalledTimes(1);
|
|
150
|
+
|
|
151
|
+
tree.unmount();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('areTableFiltersEqual treats equivalent period payloads as equal', () => {
|
|
155
|
+
const a = {alterDate: {shortcut: 'today', customRange: {from: '', to: ''}}};
|
|
156
|
+
const b = {alterDate: {shortcut: 'today', customRange: {from: '', to: ''}}};
|
|
157
|
+
expect(areTableFiltersEqual(a, b)).toBe(true);
|
|
158
|
+
expect(areTableFiltersEqual(a, {alterDate: {shortcut: 'all'}})).toBe(false);
|
|
159
|
+
expect(areTableFiltersEqual({}, {})).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
});
|