@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.
Files changed (100) hide show
  1. package/.github/workflows/integration-source-gate.yml +68 -0
  2. package/.github/workflows/manager-worker.yml +19 -0
  3. package/.github/workflows/publish-npm.yml +91 -0
  4. package/AGENTS.md +46 -50
  5. package/docs/technical/Contrato-de-colunas.md +67 -0
  6. package/docs/technical/Default-Components.md +76 -0
  7. package/docs/technical/DefaultForm.md +80 -0
  8. package/docs/technical/DefaultInput-e-DefaultSelect.md +73 -0
  9. package/docs/technical/DefaultTable.md +272 -0
  10. package/docs/wiki.md +1 -0
  11. package/package.json +4 -3
  12. package/scripts/validate-wiki-pointer.cjs +13 -0
  13. package/src/react/components/address/DefaultAddress.js +446 -0
  14. package/src/react/components/address/DefaultAddress.styles.js +133 -0
  15. package/src/react/components/address/DefaultAddressCoordFields.js +59 -0
  16. package/src/react/components/address/DefaultAddressMapPane.js +55 -0
  17. package/src/react/components/address/LatLonReadonlyFields.js +51 -0
  18. package/src/react/components/address/defaultAddressHelpers.js +163 -0
  19. package/src/react/components/address/defaultAddressStyles.js +148 -0
  20. package/src/react/components/address/defaultAddressUtils.js +129 -0
  21. package/src/react/components/extra-data/DefaultExtraData.js +152 -0
  22. package/src/react/components/extra-data/DefaultExtraData.styles.js +61 -0
  23. package/src/react/components/files/DefaultFile.js +231 -46
  24. package/src/react/components/files/DefaultFileColumn.js +123 -0
  25. package/src/react/components/files/DefaultFileColumn.styles.js +52 -0
  26. package/src/react/components/files/DefaultFileColumn.utils.js +104 -0
  27. package/src/react/components/filters/CompactFilterSelector.js +51 -35
  28. package/src/react/components/filters/DefaultColumnFilter.js +60 -48
  29. package/src/react/components/filters/DefaultExternalFilters.js +324 -265
  30. package/src/react/components/filters/filterValue.js +65 -0
  31. package/src/react/components/form/DefaultForm.js +99 -79
  32. package/src/react/components/help/DefaultTooltip.js +48 -12
  33. package/src/react/components/help/DefaultTooltip.styles.js +20 -0
  34. package/src/react/components/inputs/DefaultColorInput.js +153 -0
  35. package/src/react/components/inputs/DefaultFeatherIconPicker.js +146 -0
  36. package/src/react/components/inputs/DefaultFeatherIconPicker.styles.js +113 -0
  37. package/src/react/components/inputs/DefaultInput.js +296 -141
  38. package/src/react/components/inputs/DefaultInput.styles.js +26 -0
  39. package/src/react/components/inputs/DefaultSelect.js +134 -123
  40. package/src/react/components/inputs/defaultInputUtils.js +262 -213
  41. package/src/react/components/map/DefaultMap.native.js +73 -73
  42. package/src/react/components/map/DefaultMap.shared.js +911 -762
  43. package/src/react/components/map/DefaultMap.web.js +124 -68
  44. package/src/react/components/table/DefaultColumnMenu.js +22 -3
  45. package/src/react/components/table/DefaultDebug.js +44 -37
  46. package/src/react/components/table/DefaultSearchModal.js +9 -1
  47. package/src/react/components/table/DefaultTable.grouping.js +98 -0
  48. package/src/react/components/table/DefaultTable.js +217 -111
  49. package/src/react/components/table/DefaultTable.styles.js +369 -310
  50. package/src/react/components/table/DefaultTable.utils.js +38 -4
  51. package/src/react/components/table/DefaultTableBody.js +13 -8
  52. package/src/react/components/table/DefaultTableCards.js +20 -1
  53. package/src/react/components/table/DefaultTableControls.js +180 -205
  54. package/src/react/components/table/DefaultTableImportModal.js +108 -0
  55. package/src/react/components/table/DefaultTableInput.js +1 -0
  56. package/src/react/components/table/DefaultTableRowActions.js +1 -1
  57. package/src/react/components/table/DefaultTableRows.js +357 -325
  58. package/src/react/components/table/DefaultTableSearch.js +28 -5
  59. package/src/react/components/table/DefaultTableToolbar.js +92 -3
  60. package/src/react/components/table/useDefaultTablePagination.js +56 -7
  61. package/src/react/components/table/useDefaultTableStoreSync.js +164 -0
  62. package/src/react/components/upload/DefaultUpload.js +406 -0
  63. package/src/react/components/upload/DefaultUpload.styles.js +153 -0
  64. package/src/react/components/upload/DefaultUploadAttachmentsList.js +146 -0
  65. package/src/react/components/upload/DefaultUploadManagerModal.js +247 -0
  66. package/src/react/components/upload/defaultUploadHelpers.js +108 -0
  67. package/src/react/components/upload/defaultUploadLibrary.js +248 -0
  68. package/src/react/components/upload/fileUpload.js +93 -0
  69. package/src/react/hooks/usePostalCodeLookup.js +159 -0
  70. package/src/react/index.js +6 -0
  71. package/src/react/services/addressFormUtils.js +262 -0
  72. package/src/react/services/addressGeo.js +230 -0
  73. package/src/react/utils/tableVisibleColumnsPreferences.js +113 -15
  74. package/src/tests/react/components/address/defaultAddressHelpers.test.js +104 -0
  75. package/src/tests/react/components/files/DefaultFile.test.js +22 -6
  76. package/src/tests/react/components/files/DefaultFileColumn.company.test.js +24 -0
  77. package/src/tests/react/components/form/DefaultForm.test.js +123 -0
  78. package/src/tests/react/components/help/DefaultTooltip.test.js +93 -0
  79. package/src/tests/react/components/inputs/DefaultSelect.test.js +108 -79
  80. package/src/tests/react/components/table/DefaultTable.grouping.test.js +49 -0
  81. package/src/tests/react/components/table/DefaultTable.test.js +53 -2
  82. package/src/tests/react/components/table/DefaultTable.utils.test.js +10 -0
  83. package/src/tests/react/components/table/DefaultTableControls.filters.test.mjs +21 -0
  84. package/src/tests/react/components/table/DefaultTableRowActions.test.js +23 -0
  85. package/src/tests/react/components/table/DefaultTableRows.rowActions.test.js +152 -0
  86. package/src/tests/react/components/table/resolveRowBackgroundColor.test.mjs +45 -0
  87. package/src/tests/react/components/table/useDefaultTableStoreSync.test.js +161 -0
  88. package/src/tests/react/components/upload/DefaultUpload.test.js +353 -0
  89. package/src/tests/react/components/upload/defaultUploadLibrary.test.js +104 -0
  90. package/src/tests/react/components/upload/peopleMediaLibraryPreview.test.mjs +57 -0
  91. package/src/tests/react/filters/filterValue.test.js +46 -0
  92. package/src/tests/react/hooks/usePostalCodeLookup.test.js +107 -0
  93. package/src/tests/react/inputs/defaultInputUtils.test.js +9 -1
  94. package/src/tests/react/services/SMOKE-637-cep-hydrate.md +45 -0
  95. package/src/tests/react/services/addressFormUtils.test.js +179 -0
  96. package/src/tests/react/services/addressGeoLookup.async.test.js +104 -0
  97. package/src/tests/react/services/addressGeoLookup.test.js +105 -0
  98. package/src/tests/react/services/smoke-637/form-cep-12941040.png +0 -0
  99. package/src/tests/react/services/smoke-637/index.html +132 -0
  100. package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +96 -49
@@ -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/form/DefaultForm', () => () => {
104
+ jest.mock('../../../../react/components/table/DefaultTableImportModal', () => props => {
102
105
  const React = require('react');
103
- return React.createElement('DefaultForm');
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 = {
@@ -21,6 +21,7 @@ jest.mock('@controleonline/ui-common/src/react/utils/dateRangeFilter', () => ({
21
21
  const {
22
22
  getColumnMinWidth,
23
23
  getColumnStyle,
24
+ parseSortNumber,
24
25
  } = require('../../../../react/components/table/DefaultTable.utils');
25
26
 
26
27
  describe('DefaultTable utils', () => {
@@ -34,4 +35,13 @@ describe('DefaultTable utils', () => {
34
35
  ]),
35
36
  );
36
37
  });
38
+
39
+ it('parses Brazilian thousand and decimal masks as numbers', () => {
40
+ expect(parseSortNumber('1.234,56')).toBeCloseTo(1234.56);
41
+ expect(parseSortNumber('10')).toBe(10);
42
+ expect(parseSortNumber('2')).toBe(2);
43
+ expect(parseSortNumber('1.234')).toBe(1234);
44
+ expect(parseSortNumber('12/03/2024')).toBeNaN();
45
+ expect(parseSortNumber('abc')).toBeNaN();
46
+ });
37
47
  });
@@ -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,23 @@
1
+ const React = require('react');
2
+ const {describe, expect, it} = require('@jest/globals');
3
+
4
+ const {
5
+ hasDefaultTableRowActionsComponent,
6
+ resolveDefaultTableRowActionsWidth,
7
+ } = require('../../../../react/components/table/DefaultTableRowActions');
8
+
9
+ describe('DefaultTableRowActions helpers', () => {
10
+ it('accepts function and element types as custom row actions', () => {
11
+ expect(hasDefaultTableRowActionsComponent(() => null)).toBe(true);
12
+ expect(hasDefaultTableRowActionsComponent(React.createElement('span'))).toBe(true);
13
+ expect(hasDefaultTableRowActionsComponent(null)).toBe(false);
14
+ expect(hasDefaultTableRowActionsComponent('pdf')).toBe(false);
15
+ });
16
+
17
+ it('uses configured width and falls back to 140', () => {
18
+ expect(resolveDefaultTableRowActionsWidth({rowActionsWidth: 168})).toBe(168);
19
+ expect(resolveDefaultTableRowActionsWidth({actionsColumnWidth: 96})).toBe(96);
20
+ expect(resolveDefaultTableRowActionsWidth({rowActionsWidth: 0})).toBe(140);
21
+ expect(resolveDefaultTableRowActionsWidth({})).toBe(140);
22
+ });
23
+ });
@@ -0,0 +1,152 @@
1
+ const React = require('react');
2
+ const renderer = require('react-test-renderer');
3
+ const {describe, expect, it, beforeEach} = require('@jest/globals');
4
+
5
+ global.IS_REACT_ACT_ENVIRONMENT = true;
6
+
7
+ let mockStores = {};
8
+
9
+ jest.mock('@store', () => ({
10
+ useStore: jest.fn(name => mockStores[name] || {actions: {}, getters: {}}),
11
+ }));
12
+
13
+ jest.mock('@controleonline/ui-common/src/react/utils/storeColumns', () => ({
14
+ formatStoreColumnLabel: ({fallbackLabel, fieldName}) => fallbackLabel || fieldName,
15
+ }));
16
+
17
+ jest.mock('react-native-vector-icons/Feather', () => {
18
+ const React = require('react');
19
+ return props => React.createElement('icon', props);
20
+ });
21
+
22
+ jest.mock('react-native', () => {
23
+ const React = require('react');
24
+ const createComponent = name => props =>
25
+ React.createElement(name, props, props.children);
26
+ const FlatList = props => {
27
+ const items = Array.isArray(props.data) ? props.data : [];
28
+ return React.createElement(
29
+ 'FlatList',
30
+ props,
31
+ items.map((item, index) =>
32
+ props.renderItem ? props.renderItem({item, index}) : null,
33
+ ),
34
+ );
35
+ };
36
+ return {
37
+ FlatList,
38
+ ScrollView: createComponent('ScrollView'),
39
+ Text: createComponent('Text'),
40
+ TouchableOpacity: createComponent('TouchableOpacity'),
41
+ View: createComponent('View'),
42
+ StyleSheet: {create: value => value},
43
+ Platform: {select: value => value.web || value.default},
44
+ };
45
+ });
46
+
47
+ jest.mock('../../../../react/components/filters/DefaultColumnFilter', () => () => null);
48
+ jest.mock('../../../../react/components/table/DefaultTable.styles', () => ({
49
+ cell: {},
50
+ actionsCell: {},
51
+ pinnedActionsCell: {},
52
+ stickyActionsCell: {},
53
+ stickyHeaderCell: {},
54
+ stickyIdentityCell: {},
55
+ rowActionsGroup: {},
56
+ headerRow: {},
57
+ headerText: {},
58
+ sortableHeader: {},
59
+ row: {},
60
+ scroll: {},
61
+ content: {},
62
+ tableList: {},
63
+ tableListContent: {},
64
+ filterRow: {},
65
+ filterCell: {},
66
+ iconButton: {},
67
+ horizontalScrollContent: {},
68
+ }));
69
+ jest.mock('../../../../react/components/table/DefaultTableEmptyState', () => () => null);
70
+ jest.mock('../../../../react/components/table/DefaultTableInput', () => {
71
+ const React = require('react');
72
+ return ({column, row}) =>
73
+ React.createElement('cell', {
74
+ field: column?.key || column?.name,
75
+ value: row?.[column?.key || column?.name],
76
+ });
77
+ });
78
+ jest.mock('../../../../react/components/table/useDefaultTableTheme', () => () => ({
79
+ palette: {background: '#fff', text: '#111', textSecondary: '#666'},
80
+ resolvedAccentColor: '#eee',
81
+ tableBorderColors: {rowBorderColor: '#ddd', headerBorderColor: '#ccc'},
82
+ themeTokens: {},
83
+ }));
84
+
85
+ const DefaultTableRows = require('../../../../react/components/table/DefaultTableRows').default;
86
+
87
+ const collectTestIds = tree =>
88
+ [
89
+ ...tree.root.findAllByType('View'),
90
+ ...tree.root.findAllByType('TouchableOpacity'),
91
+ ...tree.root.findAllByType('Text'),
92
+ ]
93
+ .map(node => node.props.testID)
94
+ .filter(Boolean);
95
+
96
+ describe('DefaultTableRows row actions column', () => {
97
+ beforeEach(() => {
98
+ mockStores = {};
99
+ global.t = {t: (_store, _kind, key) => ({actions: 'Ações', edit: 'Editar'}[key])};
100
+ });
101
+
102
+ it('renders header + cell when rowActionsComponent is set', () => {
103
+ mockStores.orders = {
104
+ getters: {
105
+ columns: [{key: 'name', label: 'Nome'}],
106
+ items: [{id: 7, name: 'CT-e'}],
107
+ visibleColumns: {},
108
+ configs: {
109
+ rowActionsComponent: () => React.createElement('row-actions'),
110
+ showRowActions: true,
111
+ },
112
+ },
113
+ };
114
+
115
+ let tree;
116
+ renderer.act(() => {
117
+ tree = renderer.create(React.createElement(DefaultTableRows, {storeName: 'orders'}));
118
+ });
119
+
120
+ expect(collectTestIds(tree)).toEqual(
121
+ expect.arrayContaining([
122
+ 'default-table-row-actions-header',
123
+ 'default-table-row-actions-7',
124
+ ]),
125
+ );
126
+ expect(tree.root.findAllByType('row-actions')).toHaveLength(1);
127
+ });
128
+
129
+ it('does not render an actions column when showRowActions is false', () => {
130
+ mockStores.orders = {
131
+ getters: {
132
+ columns: [{key: 'name', label: 'Nome'}],
133
+ items: [{id: 7, name: 'CT-e'}],
134
+ visibleColumns: {},
135
+ configs: {
136
+ rowActionsComponent: () => React.createElement('row-actions'),
137
+ showRowActions: false,
138
+ },
139
+ },
140
+ };
141
+
142
+ let tree;
143
+ renderer.act(() => {
144
+ tree = renderer.create(React.createElement(DefaultTableRows, {storeName: 'orders'}));
145
+ });
146
+
147
+ expect(collectTestIds(tree)).not.toEqual(
148
+ expect.arrayContaining(['default-table-row-actions-header']),
149
+ );
150
+ expect(tree.root.findAllByType('row-actions')).toHaveLength(0);
151
+ });
152
+ });
@@ -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
+ });