@controleonline/ui-default 1.0.263 → 1.0.264

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 (29) hide show
  1. package/AGENTS.md +48 -47
  2. package/package.json +3 -6
  3. package/src/react/components/errors/DefaultErrors.js +360 -360
  4. package/src/react/components/files/DefaultFile.js +46 -46
  5. package/src/react/components/filters/CompactFilterSelector.js +22 -3
  6. package/src/react/components/filters/DefaultColumnFilter.js +15 -13
  7. package/src/react/components/filters/DefaultExternalFilters.js +44 -39
  8. package/src/react/components/filters/DefaultSearch.js +140 -103
  9. package/src/react/components/filters/DefaultSearch.styles.js +65 -70
  10. package/src/react/components/form/DefaultForm.js +200 -198
  11. package/src/react/components/help/DefaultTooltip.js +87 -87
  12. package/src/react/components/help/DefaultTooltip.styles.js +61 -61
  13. package/src/react/components/inputs/DefaultInput.js +162 -160
  14. package/src/react/components/inputs/DefaultSelect.js +203 -192
  15. package/src/react/components/map/DefaultGoogleMap.styles.js +115 -9
  16. package/src/react/components/map/DefaultGoogleMap.web.js +560 -446
  17. package/src/react/components/map/DefaultMap.native.js +73 -71
  18. package/src/react/components/map/DefaultMap.styles.js +16 -16
  19. package/src/react/components/map/DefaultMap.web.js +68 -66
  20. package/src/react/components/map/DefaultNativeMap.native.js +307 -183
  21. package/src/react/components/map/DefaultNativeMap.shared.js +439 -340
  22. package/src/react/components/map/DefaultNativeMap.styles.js +156 -122
  23. package/src/react/components/table/DefaultTable.js +529 -355
  24. package/src/react/components/table/DefaultTable.styles.js +30 -39
  25. package/src/react/index.js +10 -10
  26. package/src/tests/react/components/errors/DefaultErrors.test.js +162 -162
  27. package/src/tests/react/components/files/DefaultFile.test.js +80 -80
  28. package/src/tests/react/components/inputs/DefaultSelect.test.js +93 -0
  29. package/src/tests/react/components/table/DefaultTable.test.js +334 -0
@@ -0,0 +1,93 @@
1
+ const React = require('react');
2
+ const renderer = require('react-test-renderer');
3
+ const {jest} = require('@jest/globals');
4
+
5
+ const {describe, expect, it} = global;
6
+
7
+ global.IS_REACT_ACT_ENVIRONMENT = true;
8
+
9
+ jest.mock('react-native', () => {
10
+ const React = require('react');
11
+ const createComponent = name => props =>
12
+ React.createElement(name, props, props.children);
13
+
14
+ return {
15
+ Modal: createComponent('Modal'),
16
+ ScrollView: createComponent('ScrollView'),
17
+ StyleSheet: {create: value => value},
18
+ Text: createComponent('Text'),
19
+ TextInput: createComponent('TextInput'),
20
+ TouchableOpacity: createComponent('TouchableOpacity'),
21
+ View: createComponent('View'),
22
+ };
23
+ });
24
+
25
+ jest.mock('react-native-vector-icons/Feather', () => {
26
+ const React = require('react');
27
+ return props => React.createElement('Icon', props, props.children);
28
+ });
29
+
30
+ const DefaultSelect = require('../../../../react/components/inputs/DefaultSelect').default;
31
+
32
+ const createDeferred = () => {
33
+ let resolve;
34
+ const promise = new Promise(nextResolve => {
35
+ resolve = nextResolve;
36
+ });
37
+
38
+ return {promise, resolve};
39
+ };
40
+
41
+ describe('DefaultSelect', () => {
42
+ it('waits for onBeforeOpen before showing the options modal', async () => {
43
+ const gate = createDeferred();
44
+ const onBeforeOpen = jest.fn(() => gate.promise);
45
+ const onStartEditing = jest.fn();
46
+ let tree;
47
+ let openPromise;
48
+
49
+ await renderer.act(async () => {
50
+ tree = renderer.create(
51
+ React.createElement(DefaultSelect, {
52
+ column: {
53
+ key: 'jobTitle',
54
+ label: 'Cargo',
55
+ list: 'categories/getItems',
56
+ },
57
+ editing: false,
58
+ getOptionsForColumn: () => [
59
+ {
60
+ key: '1',
61
+ label: 'Analista de RH',
62
+ },
63
+ ],
64
+ onBeforeOpen,
65
+ onStartEditing,
66
+ row: {},
67
+ storeName: 'employee_profiles',
68
+ value: '1',
69
+ }),
70
+ );
71
+ });
72
+
73
+ const readButton = tree.root.findAllByType('TouchableOpacity')[0];
74
+ const modal = tree.root.findByType('Modal');
75
+
76
+ expect(modal.props.visible).toBe(false);
77
+
78
+ renderer.act(() => {
79
+ openPromise = readButton.props.onPress();
80
+ });
81
+
82
+ expect(onStartEditing).toHaveBeenCalledTimes(1);
83
+ expect(onBeforeOpen).toHaveBeenCalledTimes(1);
84
+ expect(modal.props.visible).toBe(false);
85
+
86
+ await renderer.act(async () => {
87
+ gate.resolve();
88
+ await openPromise;
89
+ });
90
+
91
+ expect(modal.props.visible).toBe(true);
92
+ });
93
+ });
@@ -0,0 +1,334 @@
1
+ const React = require('react');
2
+ const renderer = require('react-test-renderer');
3
+ const {jest} = require('@jest/globals');
4
+
5
+ const {afterEach, beforeEach, describe, expect, it} = global;
6
+
7
+ global.IS_REACT_ACT_ENVIRONMENT = true;
8
+
9
+ let consoleErrorSpy = null;
10
+ let mockStores = {};
11
+ let mockWindowDimensions = {width: 480, height: 800};
12
+ let capturedDefaultInputProps = [];
13
+
14
+ jest.mock('@store', () => ({
15
+ getAllStores: jest.fn(() => ({})),
16
+ useStore: jest.fn(name => mockStores[name] || {actions: {}, getters: {}}),
17
+ }));
18
+
19
+ jest.mock('@react-navigation/native', () => ({
20
+ useIsFocused: jest.fn(() => false),
21
+ }));
22
+
23
+ jest.mock('@controleonline/ui-common/src/react/components/MessageService', () => ({
24
+ useMessage: jest.fn(() => ({
25
+ showError: jest.fn(),
26
+ })),
27
+ }));
28
+
29
+ jest.mock('react-native-vector-icons/Feather', () => {
30
+ const React = require('react');
31
+ return props => React.createElement('icon', props, props.children);
32
+ });
33
+
34
+ jest.mock('react-native', () => {
35
+ const React = require('react');
36
+ const createComponent = name => props =>
37
+ React.createElement(name, props, props.children);
38
+
39
+ const FlatList = props => {
40
+ const items = Array.isArray(props.data) ? props.data : [];
41
+
42
+ return React.createElement(
43
+ 'FlatList',
44
+ props,
45
+ items.map((item, index) =>
46
+ props.renderItem ? props.renderItem({item, index}) : null,
47
+ ),
48
+ items.length === 0 && props.ListEmptyComponent ? props.ListEmptyComponent : null,
49
+ props.ListFooterComponent || null,
50
+ );
51
+ };
52
+
53
+ return {
54
+ FlatList,
55
+ Modal: createComponent('Modal'),
56
+ Platform: {
57
+ select: value => value.web || value.default || null,
58
+ },
59
+ ScrollView: createComponent('ScrollView'),
60
+ StyleSheet: {create: value => value},
61
+ Text: createComponent('Text'),
62
+ TouchableOpacity: createComponent('TouchableOpacity'),
63
+ View: createComponent('View'),
64
+ useWindowDimensions: jest.fn(() => mockWindowDimensions),
65
+ };
66
+ });
67
+
68
+ jest.mock('../../../../react/components/filters/DefaultColumnFilter', () => () =>
69
+ React.createElement('DefaultColumnFilter'),
70
+ );
71
+
72
+ jest.mock('../../../../react/components/filters/DefaultSearch', () => () =>
73
+ React.createElement('DefaultSearch'),
74
+ );
75
+
76
+ jest.mock('../../../../react/components/form/DefaultForm', () => () =>
77
+ React.createElement('DefaultForm'),
78
+ );
79
+
80
+ jest.mock('../../../../react/components/inputs/DefaultInput', () => props => {
81
+ capturedDefaultInputProps.push(props);
82
+ return React.createElement('DefaultInput', props);
83
+ });
84
+
85
+ jest.mock('@controleonline/ui-layout/src/react/components/StateStore', () => props =>
86
+ React.createElement('StateStore', props, props.children),
87
+ );
88
+
89
+ const {
90
+ default: DefaultTable,
91
+ resolveColumnListLoadParams,
92
+ } = require('../../../../react/components/table/DefaultTable');
93
+
94
+ describe('resolveColumnListLoadParams', () => {
95
+ it('uses company for category stores and people for financial owner stores', () => {
96
+ expect(resolveColumnListLoadParams({
97
+ column: {list: 'categories/getItems'},
98
+ currentCompanyId: 21,
99
+ })).toEqual({company: 21});
100
+ expect(resolveColumnListLoadParams({
101
+ column: {list: 'wallet/getItems'},
102
+ currentCompanyId: 21,
103
+ })).toEqual({people: 21});
104
+ expect(resolveColumnListLoadParams({
105
+ column: {list: 'paymentType/getItems'},
106
+ currentCompanyId: 21,
107
+ })).toEqual({people: 21});
108
+ });
109
+
110
+ it('resolves contextual list params without losing the company scope', () => {
111
+ expect(resolveColumnListLoadParams({
112
+ column: {
113
+ list: 'categories/getItems',
114
+ listRequestParams: ({requestParams}) => ({context: requestParams.context}),
115
+ },
116
+ currentCompanyId: 21,
117
+ requestParams: {context: 'receive'},
118
+ })).toEqual({company: 21, context: 'receive'});
119
+ });
120
+ });
121
+
122
+ describe('DefaultTable', () => {
123
+ beforeEach(() => {
124
+ consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
125
+ mockWindowDimensions = {width: 480, height: 800};
126
+ capturedDefaultInputProps = [];
127
+
128
+ mockStores = {
129
+ people: {
130
+ getters: {
131
+ currentCompany: {
132
+ theme: {
133
+ colors: {},
134
+ },
135
+ },
136
+ },
137
+ },
138
+ theme: {
139
+ getters: {
140
+ colors: {},
141
+ },
142
+ },
143
+ };
144
+ });
145
+
146
+ afterEach(() => {
147
+ if (consoleErrorSpy) {
148
+ consoleErrorSpy.mockRestore();
149
+ consoleErrorSpy = null;
150
+ }
151
+ });
152
+
153
+ it('keeps the list/card toggle visible on compact layouts even when cards are forced', () => {
154
+ let tree;
155
+
156
+ renderer.act(() => {
157
+ tree = renderer.create(
158
+ React.createElement(DefaultTable, {
159
+ columns: [{key: 'name', label: 'Nome'}],
160
+ data: [],
161
+ showColumnFiltersButton: false,
162
+ }),
163
+ );
164
+ });
165
+
166
+ const iconNames = tree.root.findAllByType('icon').map(node => node.props.name);
167
+
168
+ expect(iconNames).toEqual(expect.arrayContaining(['grid', 'columns']));
169
+ });
170
+
171
+ it('forces cards when entering compact mode and still toggles between cards and list', () => {
172
+ const props = {
173
+ columns: [{key: 'name', label: 'Nome'}],
174
+ data: [{id: 1, name: 'Pedido 1'}],
175
+ showColumnFiltersButton: false,
176
+ };
177
+ let tree;
178
+
179
+ mockWindowDimensions = {width: 1024, height: 800};
180
+
181
+ renderer.act(() => {
182
+ tree = renderer.create(
183
+ React.createElement(DefaultTable, props),
184
+ );
185
+ });
186
+
187
+ expect(tree.root.findAllByType('ScrollView')).toHaveLength(1);
188
+
189
+ mockWindowDimensions = {width: 480, height: 800};
190
+
191
+ renderer.act(() => {
192
+ tree.update(React.createElement(DefaultTable, props));
193
+ });
194
+
195
+ expect(tree.root.findAllByType('ScrollView')).toHaveLength(0);
196
+ expect(tree.root.findByProps({name: 'grid'})).toBeTruthy();
197
+
198
+ renderer.act(() => {
199
+ tree.root.findByProps({name: 'grid'}).parent.props.onPress();
200
+ });
201
+
202
+ expect(tree.root.findAllByType('ScrollView')).toHaveLength(1);
203
+ expect(tree.root.findByProps({name: 'list'})).toBeTruthy();
204
+
205
+ renderer.act(() => {
206
+ tree.root.findByProps({name: 'list'}).parent.props.onPress();
207
+ });
208
+
209
+ expect(tree.root.findAllByType('ScrollView')).toHaveLength(0);
210
+ expect(tree.root.findByProps({name: 'grid'})).toBeTruthy();
211
+ });
212
+
213
+ it('applies a custom rowStyle to rendered table rows', () => {
214
+ let tree;
215
+ const rowStyle = jest.fn(() => ({
216
+ borderLeftColor: '#DC2626',
217
+ borderLeftWidth: 4,
218
+ }));
219
+
220
+ mockWindowDimensions = {width: 1024, height: 800};
221
+
222
+ renderer.act(() => {
223
+ tree = renderer.create(
224
+ React.createElement(DefaultTable, {
225
+ columns: [{key: 'name', label: 'Nome'}],
226
+ data: [{id: 1, name: 'Pedido 1'}],
227
+ onRowPress: () => {},
228
+ rowStyle,
229
+ showColumnFiltersButton: false,
230
+ }),
231
+ );
232
+ });
233
+
234
+ const row = tree.root
235
+ .findAllByType('TouchableOpacity')
236
+ .find(node => node.props?.activeOpacity === 0.84);
237
+
238
+ expect(rowStyle).toHaveBeenCalledWith(
239
+ expect.objectContaining({id: 1, name: 'Pedido 1'}),
240
+ 0,
241
+ );
242
+ expect(Array.isArray(row.props.style)).toBe(true);
243
+ expect(
244
+ row.props.style.some(
245
+ style => style && style.borderLeftWidth === 4 && style.borderLeftColor === '#DC2626',
246
+ ),
247
+ ).toBe(true);
248
+ });
249
+
250
+ it('renders a custom footer component when provided', () => {
251
+ let tree;
252
+
253
+ const FooterComponent = props =>
254
+ React.createElement('CustomFooter', {
255
+ resolvedTotalItemsText: props.resolvedTotalItemsText,
256
+ });
257
+
258
+ mockWindowDimensions = {width: 1024, height: 800};
259
+
260
+ renderer.act(() => {
261
+ tree = renderer.create(
262
+ React.createElement(DefaultTable, {
263
+ columns: [{key: 'name', label: 'Nome'}],
264
+ data: [{id: 1, name: 'Pedido 1'}],
265
+ footerComponent: FooterComponent,
266
+ showColumnFiltersButton: false,
267
+ totalItemsText: '7 pedidos',
268
+ }),
269
+ );
270
+ });
271
+
272
+ const footer = tree.root.findByType('CustomFooter');
273
+
274
+ expect(footer.props.resolvedTotalItemsText).toBe('7 pedidos');
275
+ });
276
+
277
+ it('uses store actions when no explicit table actions are passed', async () => {
278
+ const save = jest.fn(() => Promise.resolve({
279
+ id: 1,
280
+ jobTitle: '/categories/2',
281
+ }));
282
+
283
+ mockStores.employee_profiles = {
284
+ actions: {
285
+ save,
286
+ },
287
+ getters: {
288
+ columns: [],
289
+ },
290
+ };
291
+
292
+ await renderer.act(async () => {
293
+ renderer.create(
294
+ React.createElement(DefaultTable, {
295
+ columns: [
296
+ {
297
+ key: 'jobTitle',
298
+ label: 'Cargo',
299
+ editable: true,
300
+ list: 'categories/getItems',
301
+ formatList: item => ({
302
+ value: item.id,
303
+ label: item.name,
304
+ }),
305
+ saveFormat: value => `/categories/${parseInt(value.value || value, 10)}`,
306
+ },
307
+ ],
308
+ data: [{
309
+ id: 1,
310
+ jobTitle: '/categories/1',
311
+ }],
312
+ showColumnFiltersButton: false,
313
+ showRowActions: false,
314
+ storeName: 'employee_profiles',
315
+ }),
316
+ );
317
+ });
318
+
319
+ expect(capturedDefaultInputProps.length).toBeGreaterThan(0);
320
+
321
+ await renderer.act(async () => {
322
+ await capturedDefaultInputProps[0].onSave({
323
+ value: '2',
324
+ label: 'Departamento',
325
+ object: {id: 2, name: 'Departamento'},
326
+ });
327
+ });
328
+
329
+ expect(save).toHaveBeenCalledWith({
330
+ id: '1',
331
+ jobTitle: '/categories/2',
332
+ });
333
+ });
334
+ });