@controleonline/ui-default 1.0.277 → 1.0.279

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 (37) hide show
  1. package/docs/compact-order-history-1047.md +25 -0
  2. package/package.json +1 -1
  3. package/src/react/components/filters/CompactFilterSelector.js +194 -185
  4. package/src/react/components/filters/DefaultColumnFilter.js +8 -6
  5. package/src/react/components/filters/DefaultCompactFilterPicker.js +35 -0
  6. package/src/react/components/table/DefaultColumnMenu.js +36 -2
  7. package/src/react/components/table/DefaultCompactDialog.js +36 -0
  8. package/src/react/components/table/DefaultDebug.js +199 -197
  9. package/src/react/components/table/DefaultFiltersModal.js +44 -12
  10. package/src/react/components/table/DefaultTable.js +27 -27
  11. package/src/react/components/table/DefaultTableCards.js +44 -10
  12. package/src/react/components/table/DefaultTableCompact.helpers.js +26 -0
  13. package/src/react/components/table/DefaultTableCompact.styles.js +28 -0
  14. package/src/react/components/table/DefaultTableCompactToolbar.js +112 -0
  15. package/src/react/components/table/DefaultTableInput.js +30 -11
  16. package/src/react/components/table/DefaultTablePaginationBar.js +38 -0
  17. package/src/react/components/table/DefaultTableRows.js +17 -9
  18. package/src/react/components/table/DefaultTableToolbar.js +7 -0
  19. package/src/react/components/table/DefaultTableView.js +5 -2
  20. package/src/react/components/table/useDefaultTableCreateForm.js +29 -0
  21. package/src/react/components/table/useDefaultTablePagination.js +15 -1
  22. package/src/react/components/table/useDefaultTableRowInteraction.js +19 -0
  23. package/src/react/components/table/useDefaultTableStoreSync.js +30 -21
  24. package/src/react/components/table/useDefaultTableTheme.js +44 -41
  25. package/src/tests/react/components/table/DefaultTable.group1.test.js +441 -0
  26. package/src/tests/react/components/table/DefaultTable.group2.test.js +453 -0
  27. package/src/tests/react/components/table/DefaultTable.group3.test.js +289 -0
  28. package/src/tests/react/components/table/DefaultTable.test.js +1 -1381
  29. package/src/tests/react/components/table/DefaultTableToolbar.waiter.test.js +1 -0
  30. package/src/tests/react/components/table/compactDialogs.test.js +53 -0
  31. package/src/tests/react/components/table/compactFilters.test.js +47 -0
  32. package/src/tests/react/components/table/compactPagination.test.js +66 -0
  33. package/src/tests/react/components/table/compactTheme.test.js +34 -0
  34. package/src/tests/react/components/table/compactToolbar.test.js +41 -0
  35. package/src/tests/react/components/table/defaultTableStoreHarness.cjs +35 -0
  36. package/src/tests/react/components/table/defaultTableTestSetup.cjs +207 -0
  37. package/src/tests/react/components/table/useDefaultTableStoreSync.test.js +38 -1
@@ -0,0 +1,453 @@
1
+ const {registerDefaultTableTests, React, renderer, mockState, DefaultTable, mergeSortedDataWithLiveItems, resolveColumnListLoadParams, shouldTriggerEndReachedFromScroll, DEFAULT_TABLE_PREFERENCES_STORAGE_KEY, reactNavigation, getAllStores, STORE_ACTION_META_KEY, flattenStyle} = require('./defaultTableTestSetup.cjs');
2
+ registerDefaultTableTests(() => {
3
+ it('gives collapsed search a stable accessible name without a placeholder', () => {
4
+ mockState.windowDimensions = {width: 375, height: 667};
5
+ mockState.stores.orders = {
6
+ actions: {},
7
+ getters: {
8
+ columns: [{key: 'name', label: 'Nome', searchable: true}],
9
+ },
10
+ };
11
+ let tree;
12
+
13
+ renderer.act(() => {
14
+ tree = renderer.create(
15
+ React.createElement(DefaultTable, {
16
+ columns: [{key: 'name', label: 'Nome'}],
17
+ data: [],
18
+ storeName: 'orders',
19
+ }),
20
+ );
21
+ });
22
+
23
+ const searchButton = tree.root
24
+ .findAllByType('TouchableOpacity')
25
+ .find(node => node.findAllByType('icon').some(icon => icon.props.name === 'search'));
26
+
27
+ expect(searchButton.props.accessibilityRole).toBe('button');
28
+ expect(searchButton.props.accessibilityLabel).toBe('Buscar');
29
+ });
30
+ it('keeps the financial compact toolbar aligned when the total is already in the footer', () => {
31
+ mockState.windowDimensions = {width: 375, height: 667};
32
+ mockState.stores.orders = {
33
+ actions: {},
34
+ getters: {
35
+ columns: [{key: 'name', label: 'Nome', searchable: true}],
36
+ totalItems: 27,
37
+ },
38
+ };
39
+ let tree;
40
+
41
+ renderer.act(() => {
42
+ tree = renderer.create(
43
+ React.createElement(DefaultTable, {
44
+ columns: [{key: 'name', label: 'Nome'}],
45
+ data: [],
46
+ showTotalItemsInCompactToolbar: true,
47
+ showTotalItemsInFooter: true,
48
+ storeName: 'orders',
49
+ }),
50
+ );
51
+ });
52
+
53
+ const iconNames = tree.root.findAllByType('icon').map(node => node.props.name);
54
+
55
+ expect(tree.root.findAllByType('DefaultSearch')).toHaveLength(0);
56
+ expect(iconNames).toEqual(expect.arrayContaining(['search', 'filter', 'list', 'columns']));
57
+ expect(
58
+ tree.root.findAllByType('Text').filter(node => node.props.children === '27 registros'),
59
+ ).toHaveLength(1);
60
+ });
61
+ it('opens the filters modal with filterable columns only', () => {
62
+ mockState.windowDimensions = {width: 375, height: 667};
63
+ mockState.stores.orders = {
64
+ actions: {},
65
+ getters: {
66
+ columns: [
67
+ {key: 'name', label: 'Nome'},
68
+ {key: 'ignored', label: 'Ignorado', filter: false},
69
+ {
70
+ key: 'status',
71
+ label: 'Status',
72
+ list: [{value: 'pos', label: 'POS'}],
73
+ },
74
+ ],
75
+ filters: {},
76
+ },
77
+ };
78
+ let tree;
79
+
80
+ renderer.act(() => {
81
+ tree = renderer.create(
82
+ React.createElement(DefaultTable, {
83
+ data: [],
84
+ storeName: 'orders',
85
+ }),
86
+ );
87
+ });
88
+
89
+ expect(tree.root.findAllByType('DefaultColumnFilter')).toHaveLength(0);
90
+ const filterButton = tree.root
91
+ .findAllByType('TouchableOpacity')
92
+ .find(node => node.findAllByType('icon').some(icon => icon.props.name === 'filter'));
93
+
94
+ renderer.act(() => filterButton.props.onPress());
95
+
96
+ expect(tree.root.findAllByType('DefaultColumnFilter')).toHaveLength(2);
97
+ expect([...new Set(mockState.capturedColumnFilterProps.map(props => props.column.key))]).toEqual(['name', 'status']);
98
+ });
99
+ it('persists filters and sort under default-table[store][route]', () => {
100
+ const setFilters = jest.fn();
101
+ mockState.windowDimensions = {width: 1024, height: 800};
102
+ mockState.stores.orders = {
103
+ actions: {
104
+ setFilters,
105
+ },
106
+ getters: {
107
+ columns: [
108
+ {key: 'status', label: 'Status', sortable: true},
109
+ {key: 'price', label: 'Preco'},
110
+ ],
111
+ filters: {},
112
+ },
113
+ };
114
+
115
+ let tree;
116
+
117
+ renderer.act(() => {
118
+ tree = renderer.create(
119
+ React.createElement(DefaultTable, {
120
+ storeName: 'orders',
121
+ }),
122
+ );
123
+ });
124
+
125
+ const filterButton = tree.root
126
+ .findAllByType('TouchableOpacity')
127
+ .find(node => node.findAllByType('icon').some(icon => icon.props.name === 'filter'));
128
+
129
+ renderer.act(() => filterButton.props.onPress());
130
+
131
+ const statusFilterProps = mockState.capturedColumnFilterProps
132
+ .find(props => props.column.key === 'status');
133
+
134
+ renderer.act(() => {
135
+ statusFilterProps.onChange('status', 'paid');
136
+ });
137
+
138
+ const statusHeader = tree.root
139
+ .findAllByType('TouchableOpacity')
140
+ .find(node => node.findAllByType('Text').some(text => text.props.children === 'Status'));
141
+
142
+ renderer.act(() => {
143
+ statusHeader.props.onPress();
144
+ });
145
+
146
+ expect(setFilters).toHaveBeenCalledWith({status: 'paid'});
147
+ expect(
148
+ JSON.parse(
149
+ global.localStorage.getItem(DEFAULT_TABLE_PREFERENCES_STORAGE_KEY),
150
+ ),
151
+ ).toEqual({'1': {
152
+ orders: {
153
+ OrderHistoryPage: {
154
+ filters: {
155
+ status: 'paid',
156
+ },
157
+ sort: {
158
+ direction: 'asc',
159
+ field: 'status',
160
+ },
161
+ },
162
+ },
163
+ }});
164
+ });
165
+ it('hydrates the saved sort using a stable preference key on auto tables', async () => {
166
+ const getItems = jest.fn(() => Promise.resolve([]));
167
+ reactNavigation.useIsFocused.mockImplementation(() => true);
168
+ global.localStorage.setItem(
169
+ DEFAULT_TABLE_PREFERENCES_STORAGE_KEY,
170
+ JSON.stringify({'1': {
171
+ orders: {
172
+ 'order-history-page': {
173
+ sort: {
174
+ direction: 'desc',
175
+ field: 'status',
176
+ },
177
+ },
178
+ },
179
+ }}),
180
+ );
181
+ mockState.stores.orders = {
182
+ actions: {
183
+ getItems,
184
+ },
185
+ getters: {
186
+ columns: [
187
+ {key: 'status', label: 'Status', sortable: true},
188
+ {key: 'price', label: 'Preco'},
189
+ ],
190
+ filters: {},
191
+ items: [],
192
+ },
193
+ };
194
+
195
+ await renderer.act(async () => {
196
+ renderer.create(
197
+ React.createElement(DefaultTable, {
198
+ storeName: 'orders',
199
+ visibleColumnsPreferenceKey: 'order-history-page',
200
+ }),
201
+ );
202
+ await Promise.resolve();
203
+ });
204
+
205
+ expect(getItems).toHaveBeenCalledWith(
206
+ expect.objectContaining({
207
+ 'order[status]': 'desc',
208
+ itemsPerPage: 50,
209
+ page: 1,
210
+ }),
211
+ );
212
+ });
213
+ it('hydrates and persists controlled filters under default-table[store][route]', () => {
214
+ const onFilterChange = jest.fn();
215
+ const setFilters = jest.fn();
216
+ global.location = {pathname: '/product-showcases-page'};
217
+ global.localStorage.setItem(
218
+ DEFAULT_TABLE_PREFERENCES_STORAGE_KEY,
219
+ JSON.stringify({'1': {
220
+ product_showcase_items: {
221
+ 'product-showcases-page': {
222
+ filters: {
223
+ integration: 'pos',
224
+ },
225
+ },
226
+ },
227
+ }}),
228
+ );
229
+ mockState.stores.product_showcase_items = {
230
+ actions: {
231
+ setFilters,
232
+ },
233
+ getters: {
234
+ columns: [
235
+ {key: 'integration', label: 'Integration', externalFilter: true},
236
+ {key: 'price', label: 'Price'},
237
+ ],
238
+ filters: {},
239
+ },
240
+ };
241
+
242
+ let tree;
243
+
244
+ renderer.act(() => {
245
+ tree = renderer.create(
246
+ React.createElement(DefaultTable, {
247
+ filters: {},
248
+ onFilterChange,
249
+ storeName: 'product_showcase_items',
250
+ visibleColumnsPreferenceKey: 'product-showcases-page',
251
+ }),
252
+ );
253
+ });
254
+
255
+ expect(onFilterChange).toHaveBeenCalledWith({integration: 'pos'});
256
+
257
+ renderer.act(() => {
258
+ tree.update(
259
+ React.createElement(DefaultTable, {
260
+ filters: {integration: 'ifood'},
261
+ onFilterChange,
262
+ storeName: 'product_showcase_items',
263
+ visibleColumnsPreferenceKey: 'product-showcases-page',
264
+ }),
265
+ );
266
+ });
267
+
268
+ expect(
269
+ JSON.parse(
270
+ global.localStorage.getItem(DEFAULT_TABLE_PREFERENCES_STORAGE_KEY),
271
+ ),
272
+ ).toEqual({'1': {
273
+ product_showcase_items: {
274
+ 'product-showcases-page': {
275
+ filters: {
276
+ integration: 'ifood',
277
+ },
278
+ },
279
+ },
280
+ }});
281
+
282
+ delete global.location;
283
+ });
284
+ it('preserves the compact toolbar total when the footer total is disabled', () => {
285
+ mockState.windowDimensions = {width: 375, height: 667};
286
+ mockState.stores.orders = {
287
+ actions: {},
288
+ getters: {
289
+ totalItems: 27,
290
+ },
291
+ };
292
+ let tree;
293
+
294
+ renderer.act(() => {
295
+ tree = renderer.create(
296
+ React.createElement(DefaultTable, {
297
+ columns: [{key: 'name', label: 'Nome'}],
298
+ data: [],
299
+ showTotalItemsInCompactToolbar: true,
300
+ showTotalItemsInFooter: false,
301
+ storeName: 'orders',
302
+ }),
303
+ );
304
+ });
305
+
306
+ expect(tree.root.findAllByType('Text').map(node => node.props.children)).toContain('27 registros');
307
+ });
308
+ it('forces cards when entering compact mode and still toggles between cards and list', () => {
309
+ const props = {
310
+ columns: [{key: 'name', label: 'Nome'}],
311
+ data: [{id: 1, name: 'Pedido 1'}],
312
+ showColumnFiltersButton: false,
313
+ };
314
+ let tree;
315
+
316
+ mockState.windowDimensions = {width: 1024, height: 800};
317
+
318
+ renderer.act(() => {
319
+ tree = renderer.create(
320
+ React.createElement(DefaultTable, props),
321
+ );
322
+ });
323
+
324
+ expect(tree.root.findAllByType('ScrollView')).toHaveLength(1);
325
+
326
+ mockState.windowDimensions = {width: 480, height: 800};
327
+
328
+ renderer.act(() => {
329
+ tree.update(React.createElement(DefaultTable, props));
330
+ });
331
+
332
+ expect(tree.root.findAllByType('ScrollView')).toHaveLength(0);
333
+ expect(tree.root.findByProps({name: 'list'})).toBeTruthy();
334
+
335
+ renderer.act(() => {
336
+ tree.root.findByProps({name: 'list'}).parent.props.onPress();
337
+ tree.update(React.createElement(DefaultTable, props));
338
+ });
339
+
340
+ expect(tree.root.findAllByType('ScrollView')).toHaveLength(1);
341
+ expect(tree.root.findByProps({name: 'grid'})).toBeTruthy();
342
+
343
+ renderer.act(() => {
344
+ tree.root.findByProps({name: 'grid'}).parent.props.onPress();
345
+ tree.update(React.createElement(DefaultTable, props));
346
+ });
347
+
348
+ expect(tree.root.findAllByType('ScrollView')).toHaveLength(0);
349
+ expect(tree.root.findByProps({name: 'list'})).toBeTruthy();
350
+ });
351
+ it('applies a custom rowStyle to rendered table rows', () => {
352
+ let tree;
353
+ const rowStyle = jest.fn(() => ({
354
+ borderLeftColor: '#DC2626',
355
+ borderLeftWidth: 4,
356
+ }));
357
+
358
+ mockState.windowDimensions = {width: 1024, height: 800};
359
+
360
+ renderer.act(() => {
361
+ tree = renderer.create(
362
+ React.createElement(DefaultTable, {
363
+ columns: [{key: 'name', label: 'Nome'}],
364
+ data: [{id: 1, name: 'Pedido 1'}],
365
+ onRowPress: () => {},
366
+ rowStyle,
367
+ showColumnFiltersButton: false,
368
+ }),
369
+ );
370
+ });
371
+
372
+ const row = tree.root
373
+ .findAllByType('TouchableOpacity')
374
+ .find(node => node.props?.activeOpacity === 0.84);
375
+
376
+ expect(rowStyle).toHaveBeenCalledWith(
377
+ expect.objectContaining({id: 1, name: 'Pedido 1'}),
378
+ 0,
379
+ );
380
+ expect(Array.isArray(row.props.style)).toBe(true);
381
+ expect(
382
+ row.props.style.some(
383
+ style => style && style.borderLeftWidth === 4 && style.borderLeftColor === '#DC2626',
384
+ ),
385
+ ).toBe(true);
386
+ });
387
+ it('blocks row navigation while an inline cell is editing or saving', async () => {
388
+ let resolveSave;
389
+ const onRowPress = jest.fn();
390
+ const save = jest.fn(() => new Promise(resolve => {
391
+ resolveSave = resolve;
392
+ }));
393
+ mockState.stores.orders = {actions: {save}, getters: {}};
394
+ let tree;
395
+
396
+ mockState.windowDimensions = {width: 1024, height: 800};
397
+
398
+ renderer.act(() => {
399
+ tree = renderer.create(
400
+ React.createElement(DefaultTable, {
401
+ storeName: 'orders',
402
+ actions: {save},
403
+ columns: [{key: 'name', label: 'Nome', editable: true}],
404
+ data: [{id: 1, name: 'Cliente'}],
405
+ onRowPress,
406
+ showColumnFiltersButton: false,
407
+ showRowActions: false,
408
+ }),
409
+ );
410
+ });
411
+
412
+ const findRow = () => tree.root
413
+ .findAllByType('TouchableOpacity')
414
+ .find(node => node.props?.activeOpacity === 0.84);
415
+ const latestInput = () => mockState.capturedDefaultInputProps[mockState.capturedDefaultInputProps.length - 1];
416
+
417
+ renderer.act(() => {
418
+ findRow().props.onPress();
419
+ });
420
+ expect(onRowPress).toHaveBeenCalledTimes(1);
421
+ onRowPress.mockClear();
422
+
423
+ renderer.act(() => {
424
+ latestInput().onStartEditing();
425
+ });
426
+ renderer.act(() => {
427
+ findRow().props.onPress();
428
+ });
429
+ expect(onRowPress).not.toHaveBeenCalled();
430
+
431
+ let savePromise;
432
+ renderer.act(() => {
433
+ savePromise = latestInput().onSave('Cliente alterado');
434
+ });
435
+ renderer.act(() => {
436
+ latestInput().onCancelEditing();
437
+ });
438
+ renderer.act(() => {
439
+ findRow().props.onPress();
440
+ });
441
+ expect(onRowPress).not.toHaveBeenCalled();
442
+
443
+ await renderer.act(async () => {
444
+ resolveSave({id: 1, name: 'Cliente alterado'});
445
+ await savePromise;
446
+ });
447
+
448
+ renderer.act(() => {
449
+ findRow().props.onPress();
450
+ });
451
+ expect(onRowPress).toHaveBeenCalledTimes(1);
452
+ });
453
+ });