@controleonline/ui-default 1.0.277 → 1.0.278
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/docs/compact-order-history-1047.md +25 -0
- package/package.json +1 -1
- package/src/react/components/filters/CompactFilterSelector.js +194 -185
- package/src/react/components/filters/DefaultColumnFilter.js +8 -6
- package/src/react/components/filters/DefaultCompactFilterPicker.js +35 -0
- package/src/react/components/table/DefaultColumnMenu.js +36 -2
- package/src/react/components/table/DefaultCompactDialog.js +36 -0
- package/src/react/components/table/DefaultDebug.js +199 -197
- package/src/react/components/table/DefaultFiltersModal.js +44 -12
- package/src/react/components/table/DefaultTable.js +27 -27
- package/src/react/components/table/DefaultTableCards.js +18 -7
- package/src/react/components/table/DefaultTableCompact.helpers.js +26 -0
- package/src/react/components/table/DefaultTableCompact.styles.js +28 -0
- package/src/react/components/table/DefaultTableCompactToolbar.js +112 -0
- package/src/react/components/table/DefaultTableInput.js +30 -11
- package/src/react/components/table/DefaultTablePaginationBar.js +38 -0
- package/src/react/components/table/DefaultTableRows.js +17 -9
- package/src/react/components/table/DefaultTableToolbar.js +7 -0
- package/src/react/components/table/DefaultTableView.js +5 -2
- package/src/react/components/table/useDefaultTableCreateForm.js +29 -0
- package/src/react/components/table/useDefaultTablePagination.js +15 -1
- package/src/react/components/table/useDefaultTableRowInteraction.js +19 -0
- package/src/react/components/table/useDefaultTableStoreSync.js +30 -21
- package/src/react/components/table/useDefaultTableTheme.js +44 -41
- package/src/tests/react/components/table/DefaultTable.group1.test.js +441 -0
- package/src/tests/react/components/table/DefaultTable.group2.test.js +453 -0
- package/src/tests/react/components/table/DefaultTable.group3.test.js +289 -0
- package/src/tests/react/components/table/DefaultTable.test.js +1 -1381
- package/src/tests/react/components/table/DefaultTableToolbar.waiter.test.js +1 -0
- package/src/tests/react/components/table/compactDialogs.test.js +53 -0
- package/src/tests/react/components/table/compactFilters.test.js +47 -0
- package/src/tests/react/components/table/compactPagination.test.js +66 -0
- package/src/tests/react/components/table/compactTheme.test.js +34 -0
- package/src/tests/react/components/table/compactToolbar.test.js +41 -0
- package/src/tests/react/components/table/defaultTableStoreHarness.cjs +35 -0
- package/src/tests/react/components/table/defaultTableTestSetup.cjs +207 -0
- package/src/tests/react/components/table/useDefaultTableStoreSync.test.js +38 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const renderer = require('react-test-renderer');
|
|
3
|
+
global.IS_REACT_ACT_ENVIRONMENT = true;
|
|
4
|
+
let mockStore;
|
|
5
|
+
const mockGetItems = jest.fn();
|
|
6
|
+
jest.mock('@store', () => ({useStore: () => mockStore, getAllStores: () => ({people: {actions: {getItems: mockGetItems}}})}));
|
|
7
|
+
jest.mock('react-native', () => {
|
|
8
|
+
const React = require('react'); const component = name => p => React.createElement(name, p, p.children);
|
|
9
|
+
return {View: component('View'), Text: component('Text'), TouchableOpacity: component('Button'), ScrollView: component('Scroll'), Modal: component('Modal'),
|
|
10
|
+
TextInput: component('Input'), Image: component('Image'), StyleSheet: {create: v => v}, Platform: {select: v => v.web}, useWindowDimensions: () => ({width: 393, height: 852})};
|
|
11
|
+
});
|
|
12
|
+
jest.mock('react-native-vector-icons/Feather', () => () => null);
|
|
13
|
+
jest.mock('@controleonline/ui-common/src/react/utils/storeColumns', () => ({formatStoreColumnLabel: ({fallbackLabel}) => fallbackLabel}));
|
|
14
|
+
jest.mock('../../../../react/components/table/useDefaultTableTheme', () => () => ({themeColors: {compact: true, border: '#ddd', textPrimary: '#123'}, checkboxSelectedMarkColor: '#987', modalColors: {textColor: '#123'}}));
|
|
15
|
+
jest.mock('../../../../react/components/filters/CompactFilterSelector', () => p => require('react').createElement('Picker', p));
|
|
16
|
+
jest.mock('../../../../react/components/filters/DateShortcutFilter', () => () => null);
|
|
17
|
+
const Columns = require('../../../../react/components/table/DefaultColumnMenu').default;
|
|
18
|
+
const Filter = require('../../../../react/components/filters/DefaultColumnFilter').default;
|
|
19
|
+
let tree;
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
global.t = {t: (_store, _kind, key) => key};
|
|
22
|
+
mockGetItems.mockReset().mockResolvedValue([{id: 22, name: 'Ana'}]);
|
|
23
|
+
mockStore = {getters: {currentCompany: {id: 14}, columns: [{name: 'id', isIdentity: true, compactLabel: 'Pedido'}, {name: 'price', compactLabel: 'Valor'}],
|
|
24
|
+
configs: {appearance: 'compact', tablePreferenceScope: {storeName: 'test'}}, visibleColumns: {}}, actions: {setVisibleColumns: jest.fn()}};
|
|
25
|
+
});
|
|
26
|
+
afterEach(() => {if (tree) renderer.act(() => tree.unmount()); tree = null;});
|
|
27
|
+
it('shows selectable columns in a noncollapsed scroll body, preserving required identity', () => {
|
|
28
|
+
renderer.act(() => {tree = renderer.create(React.createElement(Columns, {storeName: 'orders', visible: true}));});
|
|
29
|
+
const buttons = tree.root.findAllByType('Button');
|
|
30
|
+
const id = buttons.find(b => b.props.accessibilityLabel === 'Pedido');
|
|
31
|
+
expect(id.props.disabled).toBe(true);
|
|
32
|
+
const amount = buttons.find(b => b.props.accessibilityLabel === 'Valor');
|
|
33
|
+
renderer.act(() => amount.props.onPress());
|
|
34
|
+
expect(mockStore.actions.setVisibleColumns).toHaveBeenCalledWith(expect.objectContaining({price: false, id: true}));
|
|
35
|
+
expect(tree.root.findByType('Scroll').props.style.height).toBeGreaterThan(0);
|
|
36
|
+
});
|
|
37
|
+
it('uses fixed channel options without remote loading and emits the selected channel', () => {
|
|
38
|
+
const onChange = jest.fn();
|
|
39
|
+
renderer.act(() => {tree = renderer.create(React.createElement(Filter, {storeName: 'orders', column: {name: 'app', compactLabel: 'Canal', list: [{value: 'POS', label: 'POS'}]}, onChange}));});
|
|
40
|
+
const picker = tree.root.findByType('Picker');
|
|
41
|
+
expect(picker.props.options).toEqual(expect.arrayContaining([expect.objectContaining({key: 'POS', label: 'POS'})]));
|
|
42
|
+
expect(picker.props.onSearchChange).toBeUndefined();
|
|
43
|
+
expect(mockGetItems).not.toHaveBeenCalled();
|
|
44
|
+
renderer.act(() => picker.props.onSelect('POS'));
|
|
45
|
+
expect(onChange).toHaveBeenCalledWith('app', 'POS');
|
|
46
|
+
});
|
|
47
|
+
it('loads remote options only on opening, using the existing list action', async () => {
|
|
48
|
+
renderer.act(() => {tree = renderer.create(React.createElement(Filter, {storeName: 'orders', column: {name: 'client', list: 'people/getItems'}}));});
|
|
49
|
+
expect(mockGetItems).not.toHaveBeenCalled();
|
|
50
|
+
await renderer.act(async () => {await tree.root.findByType('Picker').props.onBeforeOpen();});
|
|
51
|
+
expect(mockGetItems).toHaveBeenCalledTimes(1);
|
|
52
|
+
expect(tree.root.findByType('Picker').props.options).toEqual(expect.arrayContaining([expect.objectContaining({key: '22'})]));
|
|
53
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const renderer = require('react-test-renderer');
|
|
3
|
+
global.IS_REACT_ACT_ENVIRONMENT = true;
|
|
4
|
+
let mockStore;
|
|
5
|
+
jest.mock('@store', () => ({useStore: () => mockStore}));
|
|
6
|
+
jest.mock('react-native', () => {
|
|
7
|
+
const React = require('react');
|
|
8
|
+
const component = name => props => React.createElement(name, props, props.children);
|
|
9
|
+
return {View: component('View'), Text: component('Text'), TouchableOpacity: component('Button'), Modal: component('Modal'),
|
|
10
|
+
ScrollView: component('Scroll'), StyleSheet: {create: value => value}, Platform: {select: value => value.web}};
|
|
11
|
+
});
|
|
12
|
+
jest.mock('react-native-vector-icons/Feather', () => () => null);
|
|
13
|
+
jest.mock('@controleonline/ui-common/src/react/utils/storeColumns', () => ({formatStoreColumnLabel: ({fallbackLabel}) => fallbackLabel}));
|
|
14
|
+
jest.mock('../../../../react/components/filters/DefaultColumnFilter', () => props => require('react').createElement('Filter', props));
|
|
15
|
+
jest.mock('../../../../react/components/table/useDefaultTableTheme', () => () => ({modalColors: {}, resolvedAccentColor: 'theme-accent'}));
|
|
16
|
+
jest.mock('../../../../react/utils/tableVisibleColumnsPreferences', () => ({
|
|
17
|
+
persistTableFiltersPreference: jest.fn(), resolveDefaultTablePreferenceScope: () => ({}), sanitizeTableFiltersPreference: ({filters}) => filters,
|
|
18
|
+
}));
|
|
19
|
+
const Filters = require('../../../../react/components/table/DefaultFiltersModal').default;
|
|
20
|
+
let tree;let close;
|
|
21
|
+
const render = visible => React.createElement(Filters, {storeName: 'orders', visible, deferred: true, onClose: close});
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
global.t = {t: (_store, _kind, key) => key};
|
|
24
|
+
close = jest.fn();
|
|
25
|
+
mockStore = {getters: {columns: [{name: 'status'}], filters: {status: 'paid', search: 'Marina'}, configs: {onFilterChange: jest.fn()}}, actions: {setFilters: jest.fn()}};
|
|
26
|
+
renderer.act(() => {tree = renderer.create(render(true));});
|
|
27
|
+
});
|
|
28
|
+
afterEach(() => renderer.act(() => tree.unmount()));
|
|
29
|
+
const click = label => tree.root.findAllByType('Button').find(button => button.findAllByType('Text').some(text => text.props.children === label)).props.onPress();
|
|
30
|
+
it('applies edited filters once and retains search only after Apply', () => {
|
|
31
|
+
renderer.act(() => tree.root.findByType('Filter').props.onChange('status', 'closed'));
|
|
32
|
+
expect(mockStore.actions.setFilters).not.toHaveBeenCalled();
|
|
33
|
+
expect(mockStore.getters.configs.onFilterChange).not.toHaveBeenCalled();
|
|
34
|
+
renderer.act(() => click('Aplicar filtros'));
|
|
35
|
+
expect(mockStore.getters.configs.onFilterChange).toHaveBeenCalledTimes(1);
|
|
36
|
+
expect(mockStore.getters.configs.onFilterChange).toHaveBeenCalledWith({status: 'closed', search: 'Marina'});
|
|
37
|
+
expect(close).toHaveBeenCalledTimes(1);
|
|
38
|
+
});
|
|
39
|
+
it('closing discards draft changes and clearing stays deferred', () => {
|
|
40
|
+
renderer.act(() => click('Limpar'));
|
|
41
|
+
expect(tree.root.findByType('Filter').props.filters).toEqual({});
|
|
42
|
+
expect(mockStore.actions.setFilters).not.toHaveBeenCalled();
|
|
43
|
+
renderer.act(() => tree.root.findByType('Modal').props.onRequestClose());
|
|
44
|
+
renderer.act(() => tree.update(render(false)));
|
|
45
|
+
renderer.act(() => tree.update(render(true)));
|
|
46
|
+
expect(tree.root.findByType('Filter').props.filters).toEqual({status: 'paid', search: 'Marina'});
|
|
47
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const renderer = require('react-test-renderer');
|
|
3
|
+
global.IS_REACT_ACT_ENVIRONMENT = true;
|
|
4
|
+
jest.mock('react-native', () => ({StyleSheet: {create: value => value}, Platform: {select: value => value.web}}));
|
|
5
|
+
jest.mock('@store', () => ({getAllStores: () => ({})}));
|
|
6
|
+
jest.mock('@controleonline/ui-common/src/react/utils/storeColumns', () => ({formatStoreColumnValue: () => ''}));
|
|
7
|
+
const {useDefaultTablePagination} = require('../../../../react/components/table/useDefaultTablePagination');
|
|
8
|
+
const {getVisiblePageNumbers, isActiveTableFilter} = require('../../../../react/components/table/DefaultTableCompact.helpers');
|
|
9
|
+
let tree;
|
|
10
|
+
let result;
|
|
11
|
+
let options;
|
|
12
|
+
const Harness = () => {result = useDefaultTablePagination(options); return null;};
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
options = {autoMode: true, columnsForTable: [], filters: {}, isFocused: true, pageSize: 20,
|
|
15
|
+
paginationMode: 'pages', requestParams: {provider: '/people/1'}, resolvedTotalItems: 45,
|
|
16
|
+
resolvedSort: {field: 'id', direction: 'desc'}, storeName: 'orders', showError: jest.fn(),
|
|
17
|
+
store: {getters: {items: [], resourceEndpoint: 'orders'}},
|
|
18
|
+
resolvedActions: {getItems: jest.fn(async query => {
|
|
19
|
+
const member = [{id: query.page * 100}];
|
|
20
|
+
options.store.getters.items = query.append ? [...options.store.getters.items, ...member] : member;
|
|
21
|
+
return {member, totalItems: 45};
|
|
22
|
+
})}};
|
|
23
|
+
});
|
|
24
|
+
afterEach(() => renderer.act(() => tree?.unmount()));
|
|
25
|
+
const mount = async () => {await renderer.act(async () => {tree = renderer.create(React.createElement(Harness));});};
|
|
26
|
+
it('loads numbered pages with the same scope, filters and server sort, replacing records', async () => {
|
|
27
|
+
options.filters = {search: 'Marina'};
|
|
28
|
+
await mount();
|
|
29
|
+
await renderer.act(async () => {await result.goToPage(2);});
|
|
30
|
+
expect(options.resolvedActions.getItems).toHaveBeenLastCalledWith({provider: '/people/1', itemsPerPage: 20, page: 2, 'order[id]': 'desc', search: 'Marina'});
|
|
31
|
+
expect(result.currentPage).toBe(2);
|
|
32
|
+
expect(result.resolvedData).toEqual([{id: 200}]);
|
|
33
|
+
renderer.act(() => result.handleEndReached());
|
|
34
|
+
expect(options.resolvedActions.getItems).toHaveBeenCalledTimes(2);
|
|
35
|
+
});
|
|
36
|
+
it('resets to page one when filters change and rejects invalid pages', async () => {
|
|
37
|
+
await mount();
|
|
38
|
+
await renderer.act(async () => {await result.goToPage(3);});
|
|
39
|
+
options = {...options, filters: {search: 'Carlos'}};
|
|
40
|
+
await renderer.act(async () => {tree.update(React.createElement(Harness));});
|
|
41
|
+
expect(result.currentPage).toBe(1);
|
|
42
|
+
expect(options.resolvedActions.getItems.mock.calls.at(-1)[0].search).toBe('Carlos');
|
|
43
|
+
const count = options.resolvedActions.getItems.mock.calls.length;
|
|
44
|
+
await result.goToPage(0); await result.goToPage(4); await result.goToPage(1.5);
|
|
45
|
+
expect(options.resolvedActions.getItems).toHaveBeenCalledTimes(count);
|
|
46
|
+
});
|
|
47
|
+
it('preserves infinite append for consumers that did not opt into numbered pages', async () => {
|
|
48
|
+
options.paginationMode = 'infinite';
|
|
49
|
+
await mount();
|
|
50
|
+
await renderer.act(async () => {result.handleEndReached();});
|
|
51
|
+
expect(options.resolvedActions.getItems.mock.calls.at(-1)[0]).toMatchObject({page: 2, append: true});
|
|
52
|
+
expect(result.resolvedData).toHaveLength(2);
|
|
53
|
+
});
|
|
54
|
+
it('does not advance the page after an API error', async () => {
|
|
55
|
+
await mount();
|
|
56
|
+
options.resolvedActions.getItems.mockRejectedValueOnce(new Error('Falha demonstrativa'));
|
|
57
|
+
await renderer.act(async () => {await result.goToPage(2);});
|
|
58
|
+
expect(result.currentPage).toBe(1);
|
|
59
|
+
expect(options.showError).toHaveBeenCalledWith('Falha demonstrativa');
|
|
60
|
+
});
|
|
61
|
+
it('keeps compact page controls bounded and ignores empty date shortcuts', () => {
|
|
62
|
+
expect(getVisiblePageNumbers(1, 50)).toEqual([1, 2, 3]);
|
|
63
|
+
expect(getVisiblePageNumbers(50, 50)).toEqual([48, 49, 50]);
|
|
64
|
+
expect(isActiveTableFilter({shortcut: 'all'})).toBe(false);
|
|
65
|
+
expect(isActiveTableFilter({shortcut: 'today'})).toBe(true);
|
|
66
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const renderer = require('react-test-renderer');
|
|
3
|
+
global.IS_REACT_ACT_ENVIRONMENT = true;
|
|
4
|
+
const mockColors = {primary: '#123456', background: '#abcdef', text: '#654321', modalBackground: '#fedcba'};
|
|
5
|
+
jest.mock('@store', () => ({useStore: () => ({getters: {colors: mockColors}})}));
|
|
6
|
+
const {default: useTheme, DefaultTableThemeContext} = require('../../../../react/components/table/useDefaultTableTheme');
|
|
7
|
+
const {resolveDomainThemeColors} = require('@controleonline/ui-common/src/react/components/resolveDomainThemeColors');
|
|
8
|
+
let result;
|
|
9
|
+
function Probe({appearance}) {result = useTheme(null, appearance); return null;}
|
|
10
|
+
it('uses configured theme colors in compact mode and responds to changes without a fixed palette', () => {
|
|
11
|
+
let tree;
|
|
12
|
+
renderer.act(() => {tree = renderer.create(React.createElement(Probe, {appearance: 'compact'}));});
|
|
13
|
+
expect(result.themeColors).toEqual({...mockColors, compact: true});
|
|
14
|
+
mockColors.background = '#998877';
|
|
15
|
+
renderer.act(() => tree.update(React.createElement(Probe, {appearance: 'default'})));
|
|
16
|
+
expect(result.themeColors.background).toBe('#998877');
|
|
17
|
+
renderer.act(() => tree.unmount());
|
|
18
|
+
});
|
|
19
|
+
it('shares configured theme colors with nested controls', () => {
|
|
20
|
+
const colors = {primary: '#abcdef', modalBackground: '#123abc'};
|
|
21
|
+
let tree;
|
|
22
|
+
renderer.act(() => {tree = renderer.create(React.createElement(DefaultTableThemeContext.Provider, {value: colors}, React.createElement(Probe)));});
|
|
23
|
+
expect(result.themeColors).toBe(colors);
|
|
24
|
+
expect(result.modalColors.backgroundColor).toBe('#123abc');
|
|
25
|
+
renderer.act(() => tree.unmount());
|
|
26
|
+
});
|
|
27
|
+
it('preserves domain branding regardless of the selected franchise', () => {
|
|
28
|
+
const main = {theme: {colors: {primary: '#000001', text: '#000002'}}};
|
|
29
|
+
const domain = {primary: '#000003'};
|
|
30
|
+
const result = resolveDomainThemeColors(domain, main);
|
|
31
|
+
expect(result).toEqual({primary: '#000003', text: '#000002'});
|
|
32
|
+
expect(resolveDomainThemeColors({}, main).primary).toBe('#000001');
|
|
33
|
+
expect(domain).toEqual({primary: '#000003'});
|
|
34
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const renderer = require('react-test-renderer');
|
|
3
|
+
global.IS_REACT_ACT_ENVIRONMENT = true;
|
|
4
|
+
let mockStore;
|
|
5
|
+
jest.mock('@store', () => ({useStore: () => mockStore, getAllStores: () => ({})}));
|
|
6
|
+
jest.mock('react-native', () => {
|
|
7
|
+
const React = require('react');
|
|
8
|
+
const component = name => props => React.createElement(name, props, props.children);
|
|
9
|
+
return {View: component('View'), Text: component('Text'), TextInput: component('Input'), TouchableOpacity: component('Button'),
|
|
10
|
+
Modal: component('Modal'), ScrollView: component('Scroll'), useWindowDimensions: () => ({width: 393}),
|
|
11
|
+
StyleSheet: {create: value => value}, Platform: {select: value => value.web}};
|
|
12
|
+
});
|
|
13
|
+
jest.mock('react-native-vector-icons/Feather', () => () => null);
|
|
14
|
+
jest.mock('@controleonline/ui-common/src/react/utils/storeColumns', () => ({formatStoreColumnLabel: ({fallbackLabel}) => fallbackLabel}));
|
|
15
|
+
jest.mock('../../../../react/components/table/DefaultFiltersModal', () => () => null);
|
|
16
|
+
jest.mock('../../../../react/components/table/DefaultColumnMenu', () => () => null);
|
|
17
|
+
jest.mock('../../../../react/components/table/DefaultDebug', () => () => null);
|
|
18
|
+
jest.mock('../../../../react/components/table/DefaultToolbarAction', () => () => null);
|
|
19
|
+
jest.mock('../../../../react/components/table/useDefaultTableTheme', () => () => ({themeColors: {textPrimary: 'theme-text', inputBackground: 'theme-input'}}));
|
|
20
|
+
const Toolbar = require('../../../../react/components/table/DefaultTableCompactToolbar').default;
|
|
21
|
+
let tree;
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
mockStore = {getters: {add: true, columns: [{name: 'id', sortable: true}], filters: {status: 'paid'}, configs: {
|
|
24
|
+
add: false, onAdd: jest.fn(), effectiveViewMode: 'cards', tablePreferenceScope: {}, onFilterChange: jest.fn(), resolvedSort: {field: 'id', direction: 'desc'},
|
|
25
|
+
}}, actions: {setFilters: jest.fn(), setConfigs: jest.fn()}};
|
|
26
|
+
renderer.act(() => {tree = renderer.create(React.createElement(Toolbar, {storeName: 'orders'}));});
|
|
27
|
+
});
|
|
28
|
+
afterEach(() => renderer.act(() => tree.unmount()));
|
|
29
|
+
it('submits search through the history callback, retaining existing filters', () => {
|
|
30
|
+
renderer.act(() => tree.root.findByType('Input').props.onChangeText(' Marina '));
|
|
31
|
+
renderer.act(() => tree.root.findAllByType('Button').find(b => b.props.accessibilityLabel === 'Pesquisar').props.onPress());
|
|
32
|
+
expect(mockStore.getters.configs.onFilterChange).toHaveBeenCalledWith({status: 'paid', search: 'Marina'});
|
|
33
|
+
expect(mockStore.actions.setFilters).not.toHaveBeenCalled();
|
|
34
|
+
expect(tree.root.findByType('Input').props.style.flat().some(s => s?.fontSize === 16)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
it('lets explicit add=false override the store default and switches view without clearing filters', () => {
|
|
37
|
+
expect(tree.root.findAllByType('Button').some(b => b.props.accessibilityLabel === 'Adicionar')).toBe(false);
|
|
38
|
+
renderer.act(() => tree.root.findAllByType('Button').find(b => b.props.accessibilityLabel === 'Tabela').props.onPress());
|
|
39
|
+
expect(mockStore.actions.setConfigs).toHaveBeenCalledWith(expect.objectContaining({effectiveViewMode: 'table'}));
|
|
40
|
+
expect(mockStore.getters.filters).toEqual({status: 'paid'});
|
|
41
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
// Mirror store subscriptions: config publication happens in effects, never render.
|
|
3
|
+
module.exports = function createStoreHarness(getStores) {
|
|
4
|
+
let registry;
|
|
5
|
+
let fallback = {};
|
|
6
|
+
const states = new WeakMap();
|
|
7
|
+
return function useStore(name) {
|
|
8
|
+
const next = getStores();
|
|
9
|
+
if (next !== registry) {registry = next; fallback = {};}
|
|
10
|
+
const store = next[name] || (fallback[name] ||= {actions: {}, getters: {}});
|
|
11
|
+
if (!states.has(store)) {
|
|
12
|
+
const state = {version: 0, listeners: new Set()};
|
|
13
|
+
states.set(store, state);
|
|
14
|
+
store.actions ||= {};
|
|
15
|
+
store.getters ||= {};
|
|
16
|
+
for (const field of ['configs', 'columns', 'items', 'viewMode', 'filters', 'visibleColumns']) {
|
|
17
|
+
const action = `set${field[0].toUpperCase()}${field.slice(1)}`;
|
|
18
|
+
const original = store.actions[action];
|
|
19
|
+
store.actions[action] = value => {
|
|
20
|
+
original?.(value);
|
|
21
|
+
if (store.getters[field] === value) return;
|
|
22
|
+
store.getters[field] = value;
|
|
23
|
+
state.version += 1;
|
|
24
|
+
state.listeners.forEach(notify => notify());
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const state = states.get(store);
|
|
29
|
+
React.useSyncExternalStore(notify => {
|
|
30
|
+
state.listeners.add(notify);
|
|
31
|
+
return () => state.listeners.delete(notify);
|
|
32
|
+
}, () => state.version, () => state.version);
|
|
33
|
+
return store;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const renderer = require('react-test-renderer');
|
|
3
|
+
|
|
4
|
+
const {afterEach, beforeEach, describe, expect, it} = global;
|
|
5
|
+
|
|
6
|
+
global.IS_REACT_ACT_ENVIRONMENT = true;
|
|
7
|
+
|
|
8
|
+
let consoleErrorSpy = null;
|
|
9
|
+
const mockState = {stores: {}, windowDimensions: {width: 480, height: 800}, capturedColumnFilterProps: [], capturedDefaultFormProps: [], capturedDefaultInputProps: []};
|
|
10
|
+
|
|
11
|
+
const createLocalStorageMock = () => {
|
|
12
|
+
let storage = {};
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
clear: () => {
|
|
16
|
+
storage = {};
|
|
17
|
+
},
|
|
18
|
+
getItem: key => (key in storage ? storage[key] : null),
|
|
19
|
+
removeItem: key => {
|
|
20
|
+
delete storage[key];
|
|
21
|
+
},
|
|
22
|
+
setItem: (key, value) => {
|
|
23
|
+
storage[key] = String(value);
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
global.localStorage = createLocalStorageMock();
|
|
29
|
+
|
|
30
|
+
jest.mock('@store', () => ({
|
|
31
|
+
getAllStores: jest.fn(() => ({})),
|
|
32
|
+
useStore: require('./defaultTableStoreHarness.cjs')(() => mockState.stores),
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
jest.mock('@react-navigation/native', () => ({
|
|
36
|
+
NavigationRouteContext: require('react').createContext({name: 'OrderHistoryPage'}),
|
|
37
|
+
useIsFocused: jest.fn(() => false),
|
|
38
|
+
useRoute: jest.fn(() => ({
|
|
39
|
+
key: 'OrderHistoryPage-key',
|
|
40
|
+
name: 'OrderHistoryPage',
|
|
41
|
+
})),
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
jest.mock('@controleonline/ui-common/src/react/components/MessageService', () => ({
|
|
45
|
+
useMessage: jest.fn(() => ({
|
|
46
|
+
showError: jest.fn(),
|
|
47
|
+
})),
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
jest.mock('react-native-vector-icons/Feather', () => {
|
|
51
|
+
const React = require('react');
|
|
52
|
+
return props => React.createElement('icon', props, props.children);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
jest.mock('react-native', () => {
|
|
56
|
+
const React = require('react');
|
|
57
|
+
const createComponent = name => props =>
|
|
58
|
+
React.createElement(name, props, props.children);
|
|
59
|
+
|
|
60
|
+
const FlatList = props => {
|
|
61
|
+
const items = Array.isArray(props.data) ? props.data : [];
|
|
62
|
+
|
|
63
|
+
return React.createElement(
|
|
64
|
+
'FlatList',
|
|
65
|
+
props,
|
|
66
|
+
items.map((item, index) =>
|
|
67
|
+
props.renderItem ? props.renderItem({item, index}) : null,
|
|
68
|
+
),
|
|
69
|
+
items.length === 0 && props.ListEmptyComponent ? props.ListEmptyComponent : null,
|
|
70
|
+
props.ListFooterComponent || null,
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
FlatList,
|
|
76
|
+
Modal: createComponent('Modal'),
|
|
77
|
+
Platform: {
|
|
78
|
+
OS: 'web',
|
|
79
|
+
select: value => value.web || value.default || null,
|
|
80
|
+
},
|
|
81
|
+
ScrollView: createComponent('ScrollView'),
|
|
82
|
+
StyleSheet: {create: value => value},
|
|
83
|
+
Text: createComponent('Text'),
|
|
84
|
+
TouchableOpacity: createComponent('TouchableOpacity'),
|
|
85
|
+
View: createComponent('View'),
|
|
86
|
+
useWindowDimensions: jest.fn(() => mockState.windowDimensions),
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
jest.mock('../../../../react/components/filters/DefaultColumnFilter', () => props => {
|
|
91
|
+
const React = require('react');
|
|
92
|
+
mockState.capturedColumnFilterProps.push(props);
|
|
93
|
+
return React.createElement('DefaultColumnFilter', props);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
jest.mock('../../../../react/components/filters/DefaultSearch', () => props => {
|
|
97
|
+
const React = require('react');
|
|
98
|
+
return React.createElement('DefaultSearch', props);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
jest.mock('../../../../react/components/table/DefaultTableImportModal', () => props => {
|
|
102
|
+
const React = require('react');
|
|
103
|
+
return React.createElement('DefaultTableImportModal', props);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
jest.mock('../../../../react/components/form/DefaultForm', () => props => {
|
|
107
|
+
const React = require('react');
|
|
108
|
+
mockState.capturedDefaultFormProps.push(props);
|
|
109
|
+
return React.createElement('DefaultForm', props);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
jest.mock('../../../../react/components/inputs/DefaultInput', () => props => {
|
|
113
|
+
const React = require('react');
|
|
114
|
+
mockState.capturedDefaultInputProps.push(props);
|
|
115
|
+
return React.createElement('DefaultInput', props);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
jest.mock('@controleonline/ui-common/src/react/components/StateStore', () => props => {
|
|
119
|
+
const React = require('react');
|
|
120
|
+
return React.createElement('StateStore', props, props.children);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const {
|
|
124
|
+
default: DefaultTable,
|
|
125
|
+
mergeSortedDataWithLiveItems,
|
|
126
|
+
resolveColumnListLoadParams,
|
|
127
|
+
shouldTriggerEndReachedFromScroll,
|
|
128
|
+
} = require('../../../../react/components/table/DefaultTable');
|
|
129
|
+
const {
|
|
130
|
+
DEFAULT_TABLE_PREFERENCES_STORAGE_KEY,
|
|
131
|
+
} = require('../../../../react/utils/tableVisibleColumnsPreferences');
|
|
132
|
+
const reactNavigation = require('@react-navigation/native');
|
|
133
|
+
const {getAllStores} = require('@store');
|
|
134
|
+
const STORE_ACTION_META_KEY = '__storeMeta';
|
|
135
|
+
|
|
136
|
+
const flattenStyle = style => {
|
|
137
|
+
if (Array.isArray(style)) {
|
|
138
|
+
return style.reduce(
|
|
139
|
+
(acc, item) => ({
|
|
140
|
+
...acc,
|
|
141
|
+
...flattenStyle(item),
|
|
142
|
+
}),
|
|
143
|
+
{},
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return style && typeof style === 'object' ? style : {};
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
function registerDefaultTableTests(register) {
|
|
152
|
+
describe('DefaultTable', () => {
|
|
153
|
+
beforeEach(() => {
|
|
154
|
+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
155
|
+
mockState.windowDimensions = {width: 480, height: 800};
|
|
156
|
+
mockState.capturedColumnFilterProps = [];
|
|
157
|
+
mockState.capturedDefaultFormProps = [];
|
|
158
|
+
mockState.capturedDefaultInputProps = [];
|
|
159
|
+
getAllStores.mockImplementation(() => ({}));
|
|
160
|
+
global.localStorage = createLocalStorageMock();
|
|
161
|
+
global.t = {
|
|
162
|
+
t: jest.fn((store, type, key) => {
|
|
163
|
+
if (type === 'label' && key === 'search') return 'Buscar';
|
|
164
|
+
if (type === 'label' && key === 'items') return 'registros';
|
|
165
|
+
if (type === 'label' && key === 'loading') return 'Carregando...';
|
|
166
|
+
if (type === 'label' && key === 'empty') return 'Nenhum registro encontrado';
|
|
167
|
+
if (type === 'label' && key === 'filters') return 'Filtros';
|
|
168
|
+
if (type === 'label' && key === 'select') return 'Selecionar';
|
|
169
|
+
if (type === 'button' && key === 'add') return 'Adicionar';
|
|
170
|
+
if (type === 'button' && key === 'edit') return 'Editar';
|
|
171
|
+
if (type === 'button' && key === 'apply') return 'Aplicar';
|
|
172
|
+
if (type === 'button' && key === 'clear') return 'Limpar';
|
|
173
|
+
if (type === 'input' && key === 'search') return 'Buscar...';
|
|
174
|
+
return undefined;
|
|
175
|
+
}),
|
|
176
|
+
};
|
|
177
|
+
reactNavigation.useIsFocused.mockImplementation(() => false);
|
|
178
|
+
|
|
179
|
+
mockState.stores = {
|
|
180
|
+
people: {
|
|
181
|
+
getters: {
|
|
182
|
+
currentCompany: {
|
|
183
|
+
id: 1,
|
|
184
|
+
theme: {
|
|
185
|
+
colors: {},
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
theme: {
|
|
191
|
+
getters: {
|
|
192
|
+
colors: {},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
afterEach(() => {
|
|
198
|
+
delete global.t;
|
|
199
|
+
if (consoleErrorSpy) {
|
|
200
|
+
consoleErrorSpy.mockRestore();
|
|
201
|
+
consoleErrorSpy = null;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
register();
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
module.exports = {registerDefaultTableTests, React, renderer, mockState, DefaultTable, mergeSortedDataWithLiveItems, resolveColumnListLoadParams, shouldTriggerEndReachedFromScroll, DEFAULT_TABLE_PREFERENCES_STORAGE_KEY, reactNavigation, getAllStores, STORE_ACTION_META_KEY, flattenStyle};
|
|
@@ -12,6 +12,7 @@ const mockSanitizeTableFiltersPreference = jest.fn((args) => {
|
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
jest.mock('../../../../react/utils/tableVisibleColumnsPreferences', () => ({
|
|
15
|
+
persistTableFiltersPreference: jest.fn(),
|
|
15
16
|
resolveStoredTableFiltersPreference: (...args) =>
|
|
16
17
|
mockResolveStoredTableFiltersPreference(...args),
|
|
17
18
|
resolveStoredVisibleColumnsPreference: jest.fn(() => null),
|
|
@@ -25,8 +26,10 @@ const {
|
|
|
25
26
|
useDefaultTableStoreSync,
|
|
26
27
|
} = require('../../../../react/components/table/useDefaultTableStoreSync');
|
|
27
28
|
|
|
28
|
-
function Harness({data, store, storeFilters, columnsForTable, tablePreferenceScope, configsSignature = 'sig', configs = {}}) {
|
|
29
|
+
function Harness({data, store, storeFilters, columnsForTable, tablePreferenceScope, filters, onFilterChange, configsSignature = 'sig', configs = {}}) {
|
|
29
30
|
useDefaultTableStoreSync({
|
|
31
|
+
filters,
|
|
32
|
+
onFilterChange,
|
|
30
33
|
columns: [],
|
|
31
34
|
columnsForTable: columnsForTable || [],
|
|
32
35
|
data,
|
|
@@ -161,6 +164,7 @@ describe('useDefaultTableStoreSync', () => {
|
|
|
161
164
|
|
|
162
165
|
it('publishes changed same-count table configs but ignores equivalent snapshots', () => {
|
|
163
166
|
const setConfigs = jest.fn();
|
|
167
|
+
const setItems = jest.fn();
|
|
164
168
|
const store = {actions: {setConfigs, setItems}, getters: {}};
|
|
165
169
|
const first = [{id: 'device:396', configs: [{id: 487}]}];
|
|
166
170
|
const second = [{id: 'device:403', configs: [{id: 510}]}];
|
|
@@ -195,3 +199,36 @@ describe('useDefaultTableStoreSync', () => {
|
|
|
195
199
|
tree.unmount();
|
|
196
200
|
});
|
|
197
201
|
});
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
describe('controlled history filters and persisted preferences', () => {
|
|
205
|
+
it('keeps the initial date shown by the dialog aligned with the request owner', () => {
|
|
206
|
+
mockResolveStoredTableFiltersPreference.mockReturnValue(null);
|
|
207
|
+
const filters = {alterDate: {shortcut: 'today'}};
|
|
208
|
+
const store = {getters: {filters: {}}, actions: {setFilters: jest.fn()}};
|
|
209
|
+
const onFilterChange = jest.fn();
|
|
210
|
+
let tree;
|
|
211
|
+
renderer.act(() => {tree = renderer.create(React.createElement(Harness, {
|
|
212
|
+
store, filters, onFilterChange, columnsForTable: [{key: 'alterDate'}],
|
|
213
|
+
}));});
|
|
214
|
+
expect(store.actions.setFilters).toHaveBeenCalledWith(filters);
|
|
215
|
+
expect(onFilterChange).not.toHaveBeenCalled();
|
|
216
|
+
renderer.act(() => tree.unmount());
|
|
217
|
+
});
|
|
218
|
+
it.each([{}, {alterDate: {shortcut: 'all'}}])('notifies the query owner when restoring all periods: %j', saved => {
|
|
219
|
+
mockResolveStoredTableFiltersPreference.mockReturnValue(saved);
|
|
220
|
+
const store = {getters: {}, actions: {setFilters: jest.fn()}};
|
|
221
|
+
const onFilterChange = jest.fn();
|
|
222
|
+
const props = {store, storeFilters: {alterDate: {shortcut: 'today'}},
|
|
223
|
+
filters: {alterDate: {shortcut: 'today'}}, onFilterChange,
|
|
224
|
+
columnsForTable: [{key: 'alterDate'}]};
|
|
225
|
+
let tree;
|
|
226
|
+
renderer.act(() => {tree = renderer.create(React.createElement(Harness, props));});
|
|
227
|
+
expect(store.actions.setFilters).toHaveBeenCalledWith(saved);
|
|
228
|
+
expect(onFilterChange).toHaveBeenCalledTimes(1);
|
|
229
|
+
expect(onFilterChange).toHaveBeenCalledWith(saved);
|
|
230
|
+
renderer.act(() => {tree.update(React.createElement(Harness, {...props, filters: saved, storeFilters: saved}));});
|
|
231
|
+
expect(onFilterChange).toHaveBeenCalledTimes(1);
|
|
232
|
+
renderer.act(() => tree.unmount());
|
|
233
|
+
});
|
|
234
|
+
});
|