Bladeren bron

fix: Table defaultFilterdValue and defaultSortOrder bug #1188 (#1197)

* fix: Table defaultFilterdValue and defaultSortOrder bug #1188

* test: update table unit test

Co-authored-by: shijia.me <[email protected]>
走鹃 3 jaren geleden
bovenliggende
commit
e34b1ad946

+ 42 - 1
packages/semi-foundation/table/foundation.ts

@@ -17,7 +17,8 @@ import {
     filter,
     isMap,
     slice,
-    isEqual
+    isEqual,
+    isUndefined
 } from 'lodash';
 import memoizeOne from 'memoize-one';
 
@@ -114,6 +115,46 @@ class TableFoundation<RecordType> extends BaseFoundation<TableAdapter<RecordType
     memoizedFlattenFnsColumns: (columns: BaseColumnProps<RecordType>[], childrenColumnName?: string) => BaseColumnProps<RecordType>[];
     memoizedPagination: (pagination: BasePagination) => BasePagination;
 
+    /**
+     * update columns in place, and use default values as initial values if the sorting and filtering columns have no values
+     */
+    static initColumnsFilteredValueAndSorterOrder(columns: BaseColumnProps<unknown>[]) {
+        columns.forEach(column => {
+            TableFoundation.initFilteredValue(column);
+            TableFoundation.initSorterOrder(column);
+        });
+        return columns;
+    }
+
+    /**
+     * init filteredValue of filtering column, use defaultFilteredValue or [] when it is undefined
+     */
+    static initFilteredValue(column: BaseColumnProps<unknown>) {
+        const { defaultFilteredValue, filteredValue, filters } = column;
+        const hasFilter = Array.isArray(filters) && filters.length;
+        if (hasFilter && isUndefined(filteredValue)) {
+            if (Array.isArray(defaultFilteredValue) && defaultFilteredValue.length) {
+                column.filteredValue = defaultFilteredValue;
+            } else {
+                column.filteredValue = [];
+            }
+        }
+    }
+
+    /**
+     * init sortOrder of sorting column, use defaultSortOrder or [] when it is undefined
+     */
+    static initSorterOrder(column: BaseColumnProps<unknown>) {
+        const { defaultSortOrder, sortOrder, sorter } = column;
+        if (sorter && isUndefined(sortOrder)) {
+            if (!isUndefined(defaultSortOrder)) {
+                column.sortOrder = defaultSortOrder;
+            } else {
+                column.sortOrder = false;
+            }
+        }
+    }
+
     constructor(adapter: TableAdapter<RecordType>) {
         super({ ...adapter });
 

+ 9 - 6
packages/semi-ui/table/Table.tsx

@@ -201,11 +201,13 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
                 }
             },
             setSelectedRowKeys: selectedRowKeys => {
-                this.setState({ rowSelection: {
-                    ...this.state.rowSelection as Record<string, any>,
-                    selectedRowKeys: [...selectedRowKeys],
-                    selectedRowKeysSet: new Set(selectedRowKeys),
-                } });
+                this.setState({
+                    rowSelection: {
+                        ...this.state.rowSelection as Record<string, any>,
+                        selectedRowKeys: [...selectedRowKeys],
+                        selectedRowKeysSet: new Set(selectedRowKeys),
+                    }
+                });
             },
             setDisabledRowKeys: disabledRowKeys => {
                 this.setState({ disabledRowKeys, disabledRowKeysSet: new Set(disabledRowKeys) });
@@ -364,6 +366,7 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
         // columns cannot be deepClone, otherwise the comparison will be false
         const columns = this.getColumns(props.columns, props.children);
         const cachedflattenColumns = flattenColumns(columns);
+        const queries = TableFoundation.initColumnsFilteredValueAndSorterOrder(cloneDeep(cachedflattenColumns));
         this.state = {
             /**
              * Cached props
@@ -376,7 +379,7 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
             /**
              * State calculated based on prop
              */
-            queries: cloneDeep(cachedflattenColumns), // flatten columns, update when sorting or filtering
+            queries, // flatten columns, update when sorting or filtering
             dataSource: [], // data after paging
             flattenData: [],
             expandedRowKeys: [...(props.expandedRowKeys || []), ...(props.defaultExpandedRowKeys || [])], // cached expandedRowKeys

+ 232 - 9
packages/semi-ui/table/__test__/table.test.js

@@ -1753,7 +1753,7 @@ describe(`Table`, () => {
         expect(demo.find(`.${BASE_CLASS_PREFIX}-table-tbody .${BASE_CLASS_PREFIX}-table-row-section.on`).length).toBe(0);
     });
 
-    it('test defaultSortOrder', async () => {
+    it('test defaultSortOrder descend', async () => {
         const sortColumns = [
             {
                 title: 'Name',
@@ -1806,7 +1806,10 @@ describe(`Table`, () => {
         const firstCell = sortTable.find('.semi-table-tbody .semi-table-row .semi-table-row-cell').at(0);
         expect(firstCell.text()).toBe(name);
 
-        // test default ascend
+        sortTable.unmount();
+    });
+
+    it('test defaultSortOrder descend', async () => {
         const ascendOrderColumns = [
             {
                 title: 'Name',
@@ -1823,15 +1826,46 @@ describe(`Table`, () => {
                 dataIndex: 'address',
             }
         ];
-        sortTable.setProps({columns: ascendOrderColumns})
-        sortTable.update()
+        const sortData = [
+            {
+                key: '1',
+                name: 'long name',
+                age: 32,
+                address: 'New York No. 1 Lake Park',
+            },
+            {
+                key: '2',
+                name: 'longest name',
+                age: 42,
+                address: 'London No. 1 Lake Park',
+            },
+            {
+                key: '3',
+                name: 'longer name',
+                age: 32,
+                address: 'Sidney No. 1 Lake Park',
+            },
+            {
+                key: '4',
+                name: 'short',
+                age: 32,
+                address: 'London No. 2 Lake Park',
+            },
+        ];
+        const onChange = sinon.spy(() => {
+        });
+
+        const sortTable = mount(<Table columns={ascendOrderColumns} dataSource={sortData} onChange={onChange}/>);
+
         const ascendTargetData = sortData.reduce((a, b) => a.name.length < b.name.length ? a : b);
         const {name: ascendTargetName} = ascendTargetData;
         const ascendFirstCell = sortTable.find('.semi-table-tbody .semi-table-row .semi-table-row-cell').at(0);
         expect(ascendFirstCell.text()).toBe(ascendTargetName);
+        sortTable.unmount();
+    });
 
-        // test default false
-        const defaultOrderColumns = [
+    it('test defaultSortOrder false', async () => {
+        const sortColumns = [
             {
                 title: 'Name',
                 dataIndex: 'name',
@@ -1846,9 +1880,39 @@ describe(`Table`, () => {
                 title: 'Address',
                 dataIndex: 'address',
             }
-        ]
-        sortTable.setProps({columns: defaultOrderColumns})
-        sortTable.update()
+        ];
+        const sortData = [
+            {
+                key: '1',
+                name: 'long name',
+                age: 32,
+                address: 'New York No. 1 Lake Park',
+            },
+            {
+                key: '2',
+                name: 'longest name',
+                age: 42,
+                address: 'London No. 1 Lake Park',
+            },
+            {
+                key: '3',
+                name: 'longer name',
+                age: 32,
+                address: 'Sidney No. 1 Lake Park',
+            },
+            {
+                key: '4',
+                name: 'short',
+                age: 32,
+                address: 'London No. 2 Lake Park',
+            },
+        ];
+        const onChange = sinon.spy(() => {
+        });
+
+        const sortTable = mount(<Table columns={sortColumns} dataSource={sortData} onChange={onChange}/>);
+
+        // test default false
         const defaultTargetData = sortData[0]
         const {name: defaultTargetName} = defaultTargetData;
         const defaultFirstCell = sortTable.find('.semi-table-tbody .semi-table-row .semi-table-row-cell').at(0);
@@ -1895,6 +1959,7 @@ describe(`Table`, () => {
         const {name: newDescendTargetName} = newDescendTargetData;
         const newDataFirstCell = sortTable.find('.semi-table-tbody .semi-table-row .semi-table-row-cell').at(0);
         expect(newDataFirstCell.text()).toBe(newDescendTargetName);
+        sortTable.unmount();
     });
 
     it(`test expandRowByClick`, async () => {
@@ -1949,4 +2014,162 @@ describe(`Table`, () => {
         expandIcons.at(1).simulate('click');
         expect(expandedRowRender.calledTwice).toBeTruthy();
     });
+
+    it('test defaultFilteredValue is in onChange when click sorter', () => {
+        const columns = [
+            {
+                title: '标题',
+                dataIndex: 'name',
+                width: 400,
+                render: (text, record, index) => {
+                    return (
+                        <div>
+                            {text}
+                        </div>
+                    );
+                },
+                filters: [
+                    {
+                        text: 'Semi Design 设计稿',
+                        value: 'Semi Design 设计稿',
+                    },
+                    {
+                        text: 'Semi Pro 设计稿',
+                        value: 'Semi Pro 设计稿',
+                    },
+                ],
+                onFilter: (value, record) => record.name.includes(value),
+                defaultFilteredValue: ['Semi Pro 设计稿'],
+            },
+            {
+                title: '大小',
+                dataIndex: 'size',
+                sorter: (a, b) => a.size - b.size > 0 ? 1 : -1,
+                defaultSortOrder: 'ascend',
+                render: (text) => `${text} KB`
+            },
+            {
+                title: '所有者',
+                dataIndex: 'owner',
+                render: (text, record, index) => {
+                    return (
+                        <div>
+                            {text}
+                        </div>
+                    );
+                }
+    
+            }
+        ];
+
+        const getData = (total) => {
+            const data = [];
+            for (let i = 0; i < total; i++) {
+                const isSemiDesign = i % 2 === 0;
+                const randomNumber = (i * 1000) % 199;
+                data.push({
+                    key: '' + i,
+                    name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+                    owner: isSemiDesign ? '姜鹏志' : '郝宣',
+                    size: randomNumber,
+                    avatarBg: isSemiDesign ? 'grey' : 'red'
+                });
+            }
+            return data;
+        };
+
+        const data = getData(25);
+
+        const onChange = sinon.spy(() => {
+        });
+
+        const tableNode = mount(<Table columns={columns} dataSource={data} onChange={onChange}/>);
+        tableNode.find('.semi-table-column-sorter').simulate('click');
+        expect(onChange.calledOnce).toBe(true);
+        const arg = onChange.getCall(0).args[0];
+        expect(arg.filters.length).toBe(1);
+        expect(arg.filters[0].defaultFilteredValue).toEqual(['Semi Pro 设计稿']);
+        expect(arg.filters[0].filteredValue).toEqual(['Semi Pro 设计稿']);
+        tableNode.unmount();
+    });
+
+    it('test defaultSortOrder is in onChange when click filter', () => {
+        const defaultSortOrder = 'ascend';
+        const columns = [
+            {
+                title: '标题',
+                dataIndex: 'name',
+                width: 400,
+                render: (text, record, index) => {
+                    return (
+                        <div>
+                            {text}
+                        </div>
+                    );
+                },
+                filters: [
+                    {
+                        text: 'Semi Design 设计稿',
+                        value: 'Semi Design 设计稿',
+                    },
+                    {
+                        text: 'Semi Pro 设计稿',
+                        value: 'Semi Pro 设计稿',
+                    },
+                ],
+                onFilter: (value, record) => record.name.includes(value),
+                defaultFilteredValue: ['Semi Pro 设计稿'],
+            },
+            {
+                title: '大小',
+                dataIndex: 'size',
+                sorter: (a, b) => a.size - b.size > 0 ? 1 : -1,
+                defaultSortOrder: 'ascend',
+                render: (text) => `${text} KB`
+            },
+            {
+                title: '所有者',
+                dataIndex: 'owner',
+                render: (text, record, index) => {
+                    return (
+                        <div>
+                            {text}
+                        </div>
+                    );
+                }
+    
+            }
+        ];
+
+        const getData = (total) => {
+            const data = [];
+            for (let i = 0; i < total; i++) {
+                const isSemiDesign = i % 2 === 0;
+                const randomNumber = (i * 1000) % 199;
+                data.push({
+                    key: '' + i,
+                    name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+                    owner: isSemiDesign ? '姜鹏志' : '郝宣',
+                    size: randomNumber,
+                    avatarBg: isSemiDesign ? 'grey' : 'red'
+                });
+            }
+            return data;
+        };
+
+        const data = getData(25);
+
+        const onChange = sinon.spy(() => {
+        });
+
+        const tableNode = mount(<Table columns={columns} dataSource={data} onChange={onChange}/>);
+        tableNode.find('.semi-table-column-filter').simulate('click');
+        const filterNode = Array.from(document.querySelectorAll('.semi-checkbox-addon')).filter(node => node.textContent === 'Semi Design 设计稿');
+        filterNode[0].click();
+        expect(onChange.calledOnce).toBe(true);
+        const arg = onChange.getCall(0).args[0];
+        expect(arg.sorter.defaultSortOrder).toBe(defaultSortOrder);
+        expect(arg.sorter.sortOrder).toBe(defaultSortOrder);
+        tableNode.unmount();
+    });
 });

+ 98 - 0
packages/semi-ui/table/_story/v2/Fixed1188/index.tsx

@@ -0,0 +1,98 @@
+import React, { useState, useEffect, useMemo } from 'react';
+
+// eslint-disable-next-line semi-design/no-import
+import { Table, Avatar } from '@douyinfe/semi-ui';
+// eslint-disable-next-line semi-design/no-import
+import { ColumnProps } from '@douyinfe/semi-ui/table';
+
+const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
+
+App.storyName = 'fixed github 1188';
+/**
+ * fix https://github.com/DouyinFE/semi-design/issues/1188
+ */
+function App() {
+    const [dataSource, setData] = useState([]);
+    const [filteredValue, setFilteredValue] = useState(['Semi Pro 设计稿']);
+
+    const columns: ColumnProps[] = [
+        {
+            title: '标题',
+            dataIndex: 'name',
+            width: 400,
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" shape="square" src={figmaIconUrl} style={{ marginRight: 12 }}></Avatar>
+                        {text}
+                    </div>
+                );
+            },
+            filters: [
+                {
+                    text: 'Semi Design 设计稿',
+                    value: 'Semi Design 设计稿',
+                },
+                {
+                    text: 'Semi Pro 设计稿',
+                    value: 'Semi Pro 设计稿',
+                },
+            ],
+            onFilter: (value, record) => record.name.includes(value),
+            defaultFilteredValue: filteredValue,
+        },
+        {
+            title: '大小',
+            dataIndex: 'size',
+            sorter: (a, b) => a.size - b.size > 0 ? 1 : -1,
+            defaultSortOrder: 'ascend',
+            render: (text) => `${text} KB`
+        },
+        {
+            title: '所有者',
+            dataIndex: 'owner',
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" color={record.avatarBg} style={{ marginRight: 4 }}>{typeof text === 'string' && text.slice(0, 1)}</Avatar>
+                        {text}
+                    </div>
+                );
+            }
+
+        }
+    ];
+
+    const getData = (total) => {
+        const data = [];
+        for (let i = 0; i < total; i++) {
+            const isSemiDesign = i % 2 === 0;
+            const randomNumber = (i * 1000) % 199;
+            data.push({
+                key: '' + i,
+                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+                owner: isSemiDesign ? '姜鹏志' : '郝宣',
+                size: randomNumber,
+                avatarBg: isSemiDesign ? 'grey' : 'red'
+            });
+        }
+        return data;
+    };
+
+    const handleChange = (options) => {
+        console.log('onChange', options);
+    };
+
+    useEffect(() => {
+        const data = getData(46);
+        setData(data);
+    }, []);
+
+    return (
+        <div>
+            <Table columns={columns} dataSource={dataSource} onChange={handleChange} />
+        </div>
+    );
+}
+
+export default App;

+ 2 - 1
packages/semi-ui/table/_story/v2/index.js

@@ -10,4 +10,5 @@ export { default as RadioRowSelection } from './radioRowSelection';
 export { default as FixedVirtualizedEmpty } from './FixedVirtualizedEmpty';
 export { default as FixedFilter } from './FixedFilter';
 export { default as FixedSorter } from './FixedSorter';
-export { default as stickyHeaderTable } from './stickyHeader';
+export { default as stickyHeaderTable } from './stickyHeader';
+export { default as Fixed1188 } from './Fixed1188';