diff --git a/assets/index.less b/assets/index.less index f75d255dd..327b65477 100644 --- a/assets/index.less +++ b/assets/index.less @@ -19,38 +19,37 @@ } .@{tablePrefixCls} { - font-size: @font-size-base; + position: relative; + box-sizing: border-box; color: @text-color; + font-size: @font-size-base; line-height: @line-height; - box-sizing: border-box; - position: relative; &-rtl { direction: rtl; } // ================= Global ================= table { - border-spacing: 0px; width: 100%; + border-spacing: 0px; } th, td { + position: relative; + box-sizing: border-box; padding: 0; - position: relative; + padding: @cell-padding; + white-space: normal; + word-break: break-word; border: @border; border-top: 0; border-left: 0; transition: box-shadow 0.3s; - - padding: @cell-padding; - box-sizing: border-box; - white-space: normal; - word-break: break-word; .@{tablePrefixCls}-rtl& { - border-left: @border; border-right: 0; + border-left: @border; } } @@ -82,15 +81,15 @@ &-fix-left-first::after, &-fix-left-last::after { - pointer-events: none; - content: ''; - transition: box-shadow 0.3s; position: absolute; top: 0; + right: -1px; bottom: -1px; width: 20px; - right: -1px; transform: translateX(100%); + transition: box-shadow 0.3s; + content: ''; + pointer-events: none; } &-fix-right-first, @@ -102,21 +101,21 @@ } &::after { - pointer-events: none; - content: ''; - transition: box-shadow 0.3s; position: absolute; top: 0; bottom: -1px; - width: 20px; left: -1px; + width: 20px; transform: translateX(-100%); + transition: box-shadow 0.3s; + content: ''; + pointer-events: none; } } &&-ellipsis { - white-space: nowrap; overflow: hidden; + white-space: nowrap; text-overflow: ellipsis; // Fixed first or last should special process @@ -126,9 +125,9 @@ overflow: visible; .@{tablePrefixCls}-cell-content { + display: block; overflow: hidden; text-overflow: ellipsis; - display: block; } } } @@ -161,18 +160,18 @@ thead { td, th { - background: @table-head-background-color; text-align: center; + background: @table-head-background-color; } .@{tablePrefixCls}-cell-scrollbar::after { position: absolute; - content: ''; top: 0; bottom: 0; left: -1px; width: 1px; background: @table-head-background-color; + content: ''; .@{tablePrefixCls}-rtl& { right: -1px; @@ -211,13 +210,13 @@ } &-fixed-column &-body::after { - content: ''; position: absolute; - right: 0; top: 0; + right: 0; bottom: 0; - border-right: @border; z-index: 1; + border-right: @border; + content: ''; } // ================= Expand ================= @@ -229,17 +228,17 @@ &-fixed { box-sizing: border-box; margin: @cell-margin; - padding: @cell-padding; margin-right: -@horizontal-padding - 2 * @border-width; + padding: @cell-padding; &::after { - content: ''; position: absolute; - width: 0; top: 0; - bottom: 0; right: 1px; + bottom: 0; + width: 0; border-right: @border; + content: ''; } } } @@ -248,12 +247,12 @@ display: inline-block; width: 16px; height: 16px; - border: 1px solid currentColor; color: #aaa; - vertical-align: middle; + line-height: 16px; text-align: center; + vertical-align: middle; + border: 1px solid currentColor; cursor: pointer; - line-height: 16px; &.@{tablePrefixCls}-row-expanded::after { content: '-'; @@ -270,16 +269,16 @@ // ================= Title ================== &-title { + padding: @cell-padding; border: @border; border-bottom: 0; - padding: @cell-padding; } // ================= Footer ================= &-footer { + padding: @cell-padding; border: @border; border-top: 0; - padding: @cell-padding; } tfoot { @@ -287,6 +286,12 @@ background: #fff; } } + + &-summary { + border-top: @border; + border-left: @border; + } + &-sticky { &-header { position: sticky; @@ -295,20 +300,20 @@ &-scroll { position: sticky; bottom: 0; + z-index: 2; display: flex; align-items: center; border-top: 1px solid #f3f3f3; opacity: 0.6; transition: transform 0.1s ease-in 0s; - z-index: 2; &:hover { transform: scaleY(1.2); transform-origin: center bottom; } &-bar { height: 8px; - border-radius: 4px; background-color: #bbb; + border-radius: 4px; &:hover { background-color: #999; } diff --git a/docs/demo/stickyHeaderAndSummary.md b/docs/demo/stickyHeaderAndSummary.md new file mode 100644 index 000000000..8558ebf5a --- /dev/null +++ b/docs/demo/stickyHeaderAndSummary.md @@ -0,0 +1,3 @@ +## Sticky Header and Summary + + diff --git a/docs/examples/fixedColumns.tsx b/docs/examples/fixedColumns.tsx index d65281c28..273b980c8 100644 --- a/docs/examples/fixedColumns.tsx +++ b/docs/examples/fixedColumns.tsx @@ -34,7 +34,7 @@ const columns: ColumnType[] = [ { title: 'title8', dataIndex: 'b', key: 'h' }, { title: 'title9', dataIndex: 'b', key: 'i' }, { title: 'title10', dataIndex: 'b', key: 'j' }, - { title: 'title11', dataIndex: 'b', key: 'k' }, + { title: 'title11', dataIndex: 'b', key: 'k', width: 50, fixed: 'right' }, { title: 'title12', dataIndex: 'b', key: 'l', width: 100, fixed: 'right' }, ]; @@ -55,30 +55,39 @@ const data: RecordType[] = [ { a: '133', c: 'edd12221', d: 2, key: '9' }, ]; -const Demo = () => ( -
-

Fixed columns

- b || c} - scroll={{ x: 1200 }} - data={data} - summary={() => ( - <> - - - - Summary - - - Content - - Right - - - )} - /> - -); +const Demo = () => { + const [scrollY, setScrollY] = React.useState(true); + + return ( +
+ +
b || c} + scroll={{ x: 1200, y: scrollY ? 200 : null }} + data={data} + summary={() => ( + + + + + Summary + + + Content + + + Right + + + + )} + /> + + ); +}; export default Demo; diff --git a/docs/examples/stickyHeaderAndSummary.tsx b/docs/examples/stickyHeaderAndSummary.tsx new file mode 100644 index 000000000..fd3419a95 --- /dev/null +++ b/docs/examples/stickyHeaderAndSummary.tsx @@ -0,0 +1,106 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import Table from 'rc-table'; +import '../../assets/index.less'; +import type { ColumnType } from '@/interface'; + +interface RecordType { + a: string; + b?: string; + c?: string; + d: number; + key: string; +} + +const columns: ColumnType[] = [ + { title: 'title1', dataIndex: 'a', key: 'a', width: 100, fixed: 'left' }, + { title: 'title2', dataIndex: 'b', key: 'b', width: 100, fixed: 'left', ellipsis: true }, + { title: 'title3', dataIndex: 'c', key: 'c' }, + { title: 'title4', dataIndex: 'b', key: 'd' }, + { title: 'title5', dataIndex: 'b', key: 'e' }, + { title: 'title6', dataIndex: 'b', key: 'f' }, + { + title: ( +
+ title7 +
+
+
+ Hello world! +
+ ), + dataIndex: 'b', + key: 'g', + }, + { title: 'title8', dataIndex: 'b', key: 'h' }, + { title: 'title9', dataIndex: 'b', key: 'i' }, + { title: 'title10', dataIndex: 'b', key: 'j' }, + { title: 'title11', dataIndex: 'b', key: 'k', width: 50, fixed: 'right' }, + { title: 'title12', dataIndex: 'b', key: 'l', width: 100, fixed: 'right' }, +]; + +const data: RecordType[] = [ + { + a: '123', + b: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', + d: 3, + key: '1', + }, + { a: 'cdd', b: 'edd12221', d: 3, key: '2' }, + { a: '133', c: 'edd12221', d: 2, key: '3' }, + { a: '133', c: 'edd12221', d: 2, key: '4' }, + { a: '133', c: 'edd12221', d: 2, key: '5' }, + { a: '133', c: 'edd12221', d: 2, key: '6' }, + { a: '133', c: 'edd12221', d: 2, key: '7' }, + { a: '133', c: 'edd12221', d: 2, key: '8' }, + { a: '133', c: 'edd12221', d: 2, key: '9' }, +]; + +for (let i = 0; i < 20; i += 1) { + const str = `str_${i}`; + + data.push({ + a: str, + b: str, + d: i, + key: str, + }); +} + +const Demo = () => { + const [stickySummary, setStickySummary] = React.useState(true); + + return ( +
+ +
b || c} + scroll={{ x: 1200 }} + data={data} + summary={() => ( + + + + + Summary + + + Content + + + Right + + + + )} + /> + + ); +}; + +export default Demo; diff --git a/src/Header/FixedHeader.tsx b/src/FixedHolder/index.tsx similarity index 81% rename from src/Header/FixedHeader.tsx rename to src/FixedHolder/index.tsx index 40494e1c7..9b588c548 100644 --- a/src/Header/FixedHeader.tsx +++ b/src/FixedHolder/index.tsx @@ -2,8 +2,7 @@ import * as React from 'react'; import { useMemo } from 'react'; import classNames from 'classnames'; import { fillRef } from 'rc-util/lib/ref'; -import type { HeaderProps } from './Header'; -import Header from './Header'; +import type { HeaderProps } from '../Header/Header'; import ColGroup from '../ColGroup'; import type { ColumnsType, ColumnType } from '../interface'; import TableContext from '../context/TableContext'; @@ -24,20 +23,24 @@ function useColumnWidth(colWidths: readonly number[], columCount: number) { } export interface FixedHeaderProps extends HeaderProps { + className: string; noData: boolean; maxContentScroll: boolean; colWidths: readonly number[]; columCount: number; direction: 'ltr' | 'rtl'; fixHeader: boolean; - offsetHeader: number; + stickyTopOffset?: number; + stickyBottomOffset?: number; stickyClassName?: string; onScroll: (info: { currentTarget: HTMLDivElement; scrollLeft?: number }) => void; + children: (info: HeaderProps) => React.ReactNode; } -const FixedHeader = React.forwardRef>( +const FixedHolder = React.forwardRef>( ( { + className, noData, columns, flattenColumns, @@ -46,10 +49,12 @@ const FixedHeader = React.forwardRef>( stickyOffsets, direction, fixHeader, - offsetHeader, + stickyTopOffset, + stickyBottomOffset, stickyClassName, onScroll, maxContentScroll, + children, ...props }, ref, @@ -68,7 +73,7 @@ const FixedHeader = React.forwardRef>( React.useEffect(() => { function onWheel(e: WheelEvent) { - const { currentTarget, deltaX } = (e as unknown) as React.WheelEvent; + const { currentTarget, deltaX } = e as unknown as React.WheelEvent; if (deltaX) { onScroll({ currentTarget, scrollLeft: currentTarget.scrollLeft + deltaX }); e.preventDefault(); @@ -89,8 +94,9 @@ const FixedHeader = React.forwardRef>( // Add scrollbar column const lastColumn = flattenColumns[flattenColumns.length - 1]; - const ScrollBarColumn: ColumnType = { + const ScrollBarColumn: ColumnType & { scrollbar: true } = { fixed: lastColumn ? lastColumn.fixed : null, + scrollbar: true, onHeaderCell: () => ({ className: `${prefixCls}-cell-scrollbar`, }), @@ -127,10 +133,10 @@ const FixedHeader = React.forwardRef>(
@@ -147,18 +153,18 @@ const FixedHeader = React.forwardRef>( columns={flattenColumnsWithScrollbar} /> )} -
+ {children({ + ...props, + stickyOffsets: headerStickyOffsets, + columns: columnsWithScrollbar, + flattenColumns: flattenColumnsWithScrollbar, + })}
); }, ); -FixedHeader.displayName = 'FixedHeader'; +FixedHolder.displayName = 'FixedHolder'; -export default FixedHeader; +export default FixedHolder; diff --git a/src/Footer/Cell.tsx b/src/Footer/Cell.tsx index a8d562599..097124d7f 100644 --- a/src/Footer/Cell.tsx +++ b/src/Footer/Cell.tsx @@ -1,7 +1,9 @@ import * as React from 'react'; +import { SummaryContext } from '.'; import Cell from '../Cell'; import TableContext from '../context/TableContext'; -import { AlignType } from '../interface'; +import type { AlignType } from '../interface'; +import { getCellFixedInfo } from '../utils/fixUtil'; export interface SummaryCellProps { className?: string; @@ -16,13 +18,22 @@ export default function SummaryCell({ className, index, children, - colSpan, + colSpan = 1, rowSpan, align, }: SummaryCellProps) { - const { prefixCls, fixedInfoList } = React.useContext(TableContext); + const { prefixCls, direction } = React.useContext(TableContext); + const { scrollColumnIndex, stickyOffsets, flattenColumns } = React.useContext(SummaryContext); + const lastIndex = index + colSpan - 1; + const mergedColSpan = lastIndex + 1 === scrollColumnIndex ? colSpan + 1 : colSpan; - const fixedInfo = fixedInfoList[index]; + const fixedInfo = getCellFixedInfo( + index, + index + mergedColSpan - 1, + flattenColumns, + stickyOffsets, + direction, + ); return ( ({ children, props: { - colSpan, + colSpan: mergedColSpan, rowSpan, }, })} diff --git a/src/Footer/Row.tsx b/src/Footer/Row.tsx index e86a8b46e..0ca5457ea 100644 --- a/src/Footer/Row.tsx +++ b/src/Footer/Row.tsx @@ -6,6 +6,6 @@ export interface FooterRowProps { style?: React.CSSProperties; } -export default function FooterRow(props: FooterRowProps) { - return ; +export default function FooterRow({ children, ...props }: FooterRowProps) { + return {children}; } diff --git a/src/Footer/Summary.tsx b/src/Footer/Summary.tsx new file mode 100644 index 000000000..329835b92 --- /dev/null +++ b/src/Footer/Summary.tsx @@ -0,0 +1,20 @@ +import type * as React from 'react'; +import Cell from './Cell'; +import Row from './Row'; + +export interface SummaryProps { + fixed?: boolean; + children?: React.ReactNode; +} + +/** + * Syntactic sugar. Do not support HOC. + */ +function Summary({ children }: SummaryProps) { + return children as React.ReactElement; +} + +Summary.Row = Row; +Summary.Cell = Cell; + +export default Summary; diff --git a/src/Footer/index.tsx b/src/Footer/index.tsx index d6dd703ec..6fae4da98 100644 --- a/src/Footer/index.tsx +++ b/src/Footer/index.tsx @@ -1,20 +1,45 @@ import * as React from 'react'; import TableContext from '../context/TableContext'; -import Cell from './Cell'; -import Row from './Row'; +import Summary from './Summary'; +import type { ColumnType, StickyOffsets } from '../interface'; -export interface FooterProps { +type FlattenColumns = readonly (ColumnType & { scrollbar?: boolean })[]; + +export const SummaryContext = React.createContext<{ + stickyOffsets?: StickyOffsets; + scrollColumnIndex?: number; + flattenColumns?: FlattenColumns; +}>({}); + +export interface FooterProps { children: React.ReactNode; + stickyOffsets: StickyOffsets; + flattenColumns: FlattenColumns; } -function Footer({ children }: FooterProps) { - const { prefixCls } = React.useContext(TableContext); - return {children}; +function Footer({ children, stickyOffsets, flattenColumns }: FooterProps) { + const tableContext = React.useContext(TableContext); + const { prefixCls } = tableContext; + + const lastColumnIndex = flattenColumns.length - 1; + const scrollColumn = flattenColumns[lastColumnIndex]; + + const summaryContext = React.useMemo( + () => ({ + stickyOffsets, + flattenColumns, + scrollColumnIndex: scrollColumn?.scrollbar ? lastColumnIndex : null, + }), + [scrollColumn, flattenColumns, lastColumnIndex, stickyOffsets], + ); + + return ( + + {children} + + ); } export default Footer; -export const FooterComponents = { - Cell, - Row, -}; +export const FooterComponents = Summary; diff --git a/src/Header/Header.tsx b/src/Header/Header.tsx index b9bf773e7..8fac6480e 100644 --- a/src/Header/Header.tsx +++ b/src/Header/Header.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { +import type { ColumnsType, CellType, StickyOffsets, diff --git a/src/Table.tsx b/src/Table.tsx index feb1924df..d52b9d948 100644 --- a/src/Table.tsx +++ b/src/Table.tsx @@ -33,7 +33,6 @@ import ResizeObserver from 'rc-resize-observer'; import { getTargetScrollBarSize } from 'rc-util/lib/getScrollBarSize'; import ColumnGroup from './sugar/ColumnGroup'; import Column from './sugar/Column'; -import FixedHeader from './Header/FixedHeader'; import Header from './Header/Header'; import type { GetRowKey, @@ -71,6 +70,8 @@ import { findAllChildrenKeys, renderExpandIcon } from './utils/expandUtil'; import { getCellFixedInfo } from './utils/fixUtil'; import StickyScrollBar from './stickyScrollBar'; import useSticky from './hooks/useSticky'; +import FixedHolder from './FixedHolder'; +import Summary from './Footer/Summary'; // Used for conditions cache const EMPTY_DATA = []; @@ -365,6 +366,7 @@ function Table(props: TableProps(); const scrollHeaderRef = React.useRef(); const scrollBodyRef = React.useRef(); + const scrollSummaryRef = React.useRef(); const [pingedLeft, setPingedLeft] = React.useState(false); const [pingedRight, setPingedRight] = React.useState(false); const [colsWidths, updateColsWidths] = useLayoutState(new Map()); @@ -380,11 +382,18 @@ function Table(props: TableProps void }>(); - const { isSticky, offsetHeader, offsetScroll, stickyClassName, container } = useSticky( - sticky, - prefixCls, - ); - + const { isSticky, offsetHeader, offsetSummary, offsetScroll, stickyClassName, container } = + useSticky(sticky, prefixCls); + + // Footer (Fix footer must fixed header) + const summaryNode = summary?.(mergedData); + const fixFooter = + (fixHeader || isSticky) && + React.isValidElement(summaryNode) && + summaryNode.type === Summary && + summaryNode.props.fixed; + + // Scroll let scrollXStyle: React.CSSProperties; let scrollYStyle: React.CSSProperties; let scrollTableStyle: React.CSSProperties; @@ -453,6 +462,7 @@ function Table(props: TableProps(props: TableProps width)} columns={flattenColumns} /> ); - const footerTable = summary &&
{summary(mergedData)}
; const customizeScrollBody = getComponent(['body']) as CustomizeScrollBody; if ( @@ -578,6 +587,7 @@ function Table(props: TableProps>>>>> Fixed Header let bodyContent: React.ReactNode; if (typeof customizeScrollBody === 'function') { @@ -618,33 +628,56 @@ function Table(props: TableProps {bodyColGroup} {bodyTable} - {footerTable} + {!fixFooter && summaryNode && ( +
+ {summaryNode} +
+ )} ); } + // Fixed holder share the props + const fixedHolderProps = { + noData: !mergedData.length, + maxContentScroll: horizonScroll && scroll.x === 'max-content', + ...headerProps, + ...columnContext, + direction, + stickyClassName, + onScroll, + }; + groupTableNode = ( <> {/* Header Table */} {showHeader !== false && ( - + > + {fixedHolderPassProps =>
} + )} {/* Body Table */} {bodyContent} + {/* Summary Table */} + {fixFooter && ( + + {fixedHolderPassProps =>
{summaryNode}
} +
+ )} + {isSticky && ( (props: TableProps ); } else { + // >>>>>> Unique table groupTableNode = (
(props: TableProps} {bodyTable} - {footerTable} + {summaryNode && ( +
+ {summaryNode} +
+ )}
); diff --git a/src/hooks/useSticky.ts b/src/hooks/useSticky.ts index 92a4ef039..cda3fda52 100644 --- a/src/hooks/useSticky.ts +++ b/src/hooks/useSticky.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import canUseDom from 'rc-util/lib/Dom/canUseDom'; -import { TableSticky } from '../interface'; +import type { TableSticky } from '../interface'; // fix ssr render const defaultContainer = canUseDom() ? window : null; @@ -12,12 +12,17 @@ export default function useSticky( ): { isSticky: boolean; offsetHeader: number; + offsetSummary: number; offsetScroll: number; stickyClassName: string; container: Window | HTMLElement; } { - const { offsetHeader = 0, offsetScroll = 0, getContainer = () => defaultContainer } = - typeof sticky === 'object' ? sticky : {}; + const { + offsetHeader = 0, + offsetSummary = 0, + offsetScroll = 0, + getContainer = () => defaultContainer, + } = typeof sticky === 'object' ? sticky : {}; const container = getContainer() || defaultContainer; @@ -27,8 +32,9 @@ export default function useSticky( isSticky, stickyClassName: isSticky ? `${prefixCls}-sticky-header` : '', offsetHeader, + offsetSummary, offsetScroll, container, }; - }, [offsetScroll, offsetHeader, prefixCls, container]); + }, [offsetScroll, offsetHeader, offsetSummary, prefixCls, container]); } diff --git a/src/interface.ts b/src/interface.ts index 3c8f8ecab..25575ed7e 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -226,6 +226,7 @@ export type TriggerEventHandler = ( // =================== Sticky =================== export interface TableSticky { offsetHeader?: number; + offsetSummary?: number; offsetScroll?: number; getContainer?: () => Window | HTMLElement; } diff --git a/tests/FixedHeader.spec.js b/tests/FixedHeader.spec.js index 06d47a13e..5fa158837 100644 --- a/tests/FixedHeader.spec.js +++ b/tests/FixedHeader.spec.js @@ -35,21 +35,9 @@ describe('Table.FixedHeader', () => { />, ); - wrapper - .find('ResizeObserver') - .at(0) - .props() - .onResize({ width: 100, offsetWidth: 100 }); - wrapper - .find('ResizeObserver') - .at(1) - .props() - .onResize({ width: 200, offsetWidth: 200 }); - wrapper - .find('ResizeObserver') - .at(2) - .props() - .onResize({ width: 0, offsetWidth: 0 }); + wrapper.find('ResizeObserver').at(0).props().onResize({ width: 100, offsetWidth: 100 }); + wrapper.find('ResizeObserver').at(1).props().onResize({ width: 200, offsetWidth: 200 }); + wrapper.find('ResizeObserver').at(2).props().onResize({ width: 0, offsetWidth: 0 }); await act(async () => { jest.runAllTimers(); @@ -60,41 +48,16 @@ describe('Table.FixedHeader', () => { expect(wrapper.find('.rc-table-header table').props().style.visibility).toBeFalsy(); expect(); - expect( - wrapper - .find('colgroup col') - .at(0) - .props().style.width, - ).toEqual(100); - expect( - wrapper - .find('colgroup col') - .at(1) - .props().style.width, - ).toEqual(200); - expect( - wrapper - .find('colgroup col') - .at(2) - .props().style.width, - ).toEqual(0); + expect(wrapper.find('colgroup col').at(0).props().style.width).toEqual(100); + expect(wrapper.find('colgroup col').at(1).props().style.width).toEqual(200); + expect(wrapper.find('colgroup col').at(2).props().style.width).toEqual(0); // Update columns wrapper.setProps({ columns: [col2, col1] }); wrapper.update(); - expect( - wrapper - .find('colgroup col') - .at(0) - .props().style.width, - ).toEqual(200); - expect( - wrapper - .find('colgroup col') - .at(1) - .props().style.width, - ).toEqual(100); + expect(wrapper.find('colgroup col').at(0).props().style.width).toEqual(200); + expect(wrapper.find('colgroup col').at(1).props().style.width).toEqual(100); jest.useRealTimers(); }); @@ -114,22 +77,12 @@ describe('Table.FixedHeader', () => { />, ); - expect( - wrapper - .find('table') - .last() - .find('colgroup col') - .first() - .props().className, - ).toEqual('test-internal'); - expect( - wrapper - .find('table') - .first() - .find('colgroup col') - .first() - .props().className, - ).toEqual('test-internal'); + expect(wrapper.find('table').last().find('colgroup col').first().props().className).toEqual( + 'test-internal', + ); + expect(wrapper.find('table').first().find('colgroup col').first().props().className).toEqual( + 'test-internal', + ); }); it('show header when data is null', () => { @@ -194,43 +147,29 @@ describe('Table.FixedHeader', () => { />, ); - wrapper - .find('ResizeObserver') - .at(0) - .props() - .onResize({ width: 93, offsetWidth: 93 }); + wrapper.find('ResizeObserver').at(0).props().onResize({ width: 93, offsetWidth: 93 }); await act(async () => { jest.runAllTimers(); await Promise.resolve(); wrapper.update(); }); - expect( - wrapper - .find('FixedHeader col') - .first() - .props().style, - ).toEqual(expect.objectContaining({ width: 93 })); + expect(wrapper.find('FixedHolder col').first().props().style).toEqual( + expect.objectContaining({ width: 93 }), + ); // Hide Table should not modify column width visible = false; - wrapper - .find('ResizeObserver') - .at(0) - .props() - .onResize({ width: 0, offsetWidth: 0 }); + wrapper.find('ResizeObserver').at(0).props().onResize({ width: 0, offsetWidth: 0 }); act(() => { jest.runAllTimers(); wrapper.update(); }); - expect( - wrapper - .find('FixedHeader col') - .first() - .props().style, - ).toEqual(expect.objectContaining({ width: 93 })); + expect(wrapper.find('FixedHolder col').first().props().style).toEqual( + expect.objectContaining({ width: 93 }), + ); jest.useRealTimers(); }); diff --git a/tests/Sticky.spec.js b/tests/Sticky.spec.js index dbe3f1f58..178bef4f8 100644 --- a/tests/Sticky.spec.js +++ b/tests/Sticky.spec.js @@ -28,12 +28,12 @@ describe('Table.Sticky', () => { }; const wrapper = mount(); - expect(wrapper.find('.rc-table-header').prop('style')).toEqual({ + expect(wrapper.find('.rc-table-header').last().prop('style')).toEqual({ overflow: 'hidden', top: 0, }); - expect(wrapper.find('.rc-table-header').prop('className')).toBe( + expect(wrapper.find('.rc-table-header').last().prop('className')).toBe( 'rc-table-header rc-table-sticky-header', ); @@ -43,7 +43,7 @@ describe('Table.Sticky', () => { }, }); - expect(wrapper.find('.rc-table-header').prop('style')).toEqual({ + expect(wrapper.find('.rc-table-header').last().prop('style')).toEqual({ overflow: 'hidden', top: 10, }); diff --git a/tests/Summary.spec.tsx b/tests/Summary.spec.tsx new file mode 100644 index 000000000..01d195dbf --- /dev/null +++ b/tests/Summary.spec.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import Table from '../src'; + +describe('Table.Summary', () => { + const data = [ + { key: 'key0', name: 'Lucy' }, + { key: 'key1', name: 'Jack' }, + ]; + const createTable = props => { + const columns = [{ title: 'Name', dataIndex: 'name', key: 'name' }]; + + return ; + }; + + it('render correctly', () => { + const wrapper = mount( + createTable({ + summary: () => ( + + + + ), + }), + ); + + expect(wrapper.find('tfoot').text()).toEqual('Good'); + }); + + it('support data type', () => { + const wrapper = mount( +
Good
( + + + Light + + Bamboo + + 112.5 + + + )} + />, + ); + + expect(wrapper.find('tfoot').render()).toMatchSnapshot(); + }); + + describe('fixed summary', () => { + const getSummaryTable = (fixed: boolean) => + mount( +
( + + + Light + Bamboo + Little + + + )} + />, + ); + + it('fixed', () => { + const wrapper = getSummaryTable(false); + + expect(wrapper.exists('tfoot.rc-table-summary')).toBeTruthy(); + }); + + it('sticky', () => { + const wrapper = getSummaryTable(true); + + expect(wrapper.exists('div.rc-table-summary')).toBeTruthy(); + }); + }); +}); diff --git a/tests/Table.spec.js b/tests/Table.spec.js index 2cc5ad977..9355d1587 100644 --- a/tests/Table.spec.js +++ b/tests/Table.spec.js @@ -5,7 +5,10 @@ import Table from '../src'; import { INTERNAL_HOOKS } from '../src/Table'; describe('Table.Basic', () => { - const data = [{ key: 'key0', name: 'Lucy' }, { key: 'key1', name: 'Jack' }]; + const data = [ + { key: 'key0', name: 'Lucy' }, + { key: 'key1', name: 'Jack' }, + ]; const createTable = props => { const columns = [{ title: 'Name', dataIndex: 'name', key: 'name' }]; @@ -59,18 +62,8 @@ describe('Table.Basic', () => { }), ); expect(wrapper.render()).toMatchSnapshot(); - expect( - wrapper - .find('th') - .at(0) - .text(), - ).toEqual('姓名'); - expect( - wrapper - .find('th') - .at(1) - .text(), - ).toEqual('年龄'); + expect(wrapper.find('th').at(0).text()).toEqual('姓名'); + expect(wrapper.find('th').at(1).text()).toEqual('年龄'); }); it('falsy columns', () => { @@ -94,22 +87,12 @@ describe('Table.Basic', () => { describe('renders empty text correctly', () => { it('ReactNode', () => { const wrapper = mount(createTable({ data: [], emptyText: 'No data' })); - expect( - wrapper - .find('.rc-table-placeholder') - .hostNodes() - .text(), - ).toEqual('No data'); + expect(wrapper.find('.rc-table-placeholder').hostNodes().text()).toEqual('No data'); }); it('renderProps', () => { const wrapper = mount(createTable({ data: [], emptyText: () => 'No data' })); - expect( - wrapper - .find('.rc-table-placeholder') - .hostNodes() - .text(), - ).toEqual('No data'); + expect(wrapper.find('.rc-table-placeholder').hostNodes().text()).toEqual('No data'); }); it('effect update', () => { @@ -122,12 +105,7 @@ describe('Table.Basic', () => { }; const wrapper = mount(); wrapper.update(); - expect( - wrapper - .find('.rc-table-placeholder') - .hostNodes() - .text(), - ).toEqual('bamboo'); + expect(wrapper.find('.rc-table-placeholder').hostNodes().text()).toEqual('bamboo'); }); }); @@ -143,64 +121,12 @@ describe('Table.Basic', () => { it('renders title correctly', () => { const wrapper = mount(createTable({ title: () =>

title

})); - expect( - wrapper - .find('.rc-table-title') - .hostNodes() - .text(), - ).toEqual('title'); + expect(wrapper.find('.rc-table-title').hostNodes().text()).toEqual('title'); }); it('renders footer correctly', () => { const wrapper = mount(createTable({ footer: () =>

footer

})); - expect( - wrapper - .find('.rc-table-footer') - .hostNodes() - .text(), - ).toEqual('footer'); - }); - - describe('summary', () => { - it('render correctly', () => { - const wrapper = mount( - createTable({ - summary: () => ( -
- - - ), - }), - ); - - expect(wrapper.find('tfoot').text()).toEqual('Good'); - }); - - it('support data type', () => { - const wrapper = mount( -
Good
( - - - Light - - Bamboo - - 112.5 - - - )} - />, - ); - - expect(wrapper.find('tfoot').render()).toMatchSnapshot(); - }); + expect(wrapper.find('.rc-table-footer').hostNodes().text()).toEqual('footer'); }); it('renders with id correctly', () => { @@ -219,50 +145,20 @@ describe('Table.Basic', () => { describe('rowKey', () => { it('uses record.key', () => { const wrapper = mount(createTable()); - expect( - wrapper - .find('BodyRow') - .at(0) - .key(), - ).toBe('key0'); - expect( - wrapper - .find('BodyRow') - .at(1) - .key(), - ).toBe('key1'); + expect(wrapper.find('BodyRow').at(0).key()).toBe('key0'); + expect(wrapper.find('BodyRow').at(1).key()).toBe('key1'); }); it('sets by rowKey', () => { const wrapper = mount(createTable({ rowKey: 'name' })); - expect( - wrapper - .find('BodyRow') - .at(0) - .key(), - ).toBe('Lucy'); - expect( - wrapper - .find('BodyRow') - .at(1) - .key(), - ).toBe('Jack'); + expect(wrapper.find('BodyRow').at(0).key()).toBe('Lucy'); + expect(wrapper.find('BodyRow').at(1).key()).toBe('Jack'); }); it('sets by rowKey function', () => { const wrapper = mount(createTable({ rowKey: record => `${record.key}1` })); - expect( - wrapper - .find('BodyRow') - .at(0) - .key(), - ).toBe('key01'); - expect( - wrapper - .find('BodyRow') - .at(1) - .key(), - ).toBe('key11'); + expect(wrapper.find('BodyRow').at(0).key()).toBe('key01'); + expect(wrapper.find('BodyRow').at(1).key()).toBe('key11'); }); }); @@ -383,7 +279,10 @@ describe('Table.Basic', () => { ]; const wrapper = mount(createTable({ columns, data: localData })); - const targetData = [['John', 'Doe'], ['Terry', 'Garner']]; + const targetData = [ + ['John', 'Doe'], + ['Terry', 'Garner'], + ]; wrapper.find('tbody tr').forEach((tr, ri) => { tr.find('td').forEach((td, di) => { @@ -394,14 +293,12 @@ describe('Table.Basic', () => { }); it('render empty cell if text is empty object', () => { - const localData = [{ key: 'key0', name: {} }, { key: 'key1', name: 'Jack' }]; + const localData = [ + { key: 'key0', name: {} }, + { key: 'key1', name: 'Jack' }, + ]; const wrapper = mount(createTable({ data: localData })); - expect( - wrapper - .find('table td') - .first() - .text(), - ).toBe(''); + expect(wrapper.find('table td').first().text()).toBe(''); }); it('renders colSpan correctly', () => { @@ -573,10 +470,7 @@ describe('Table.Basic', () => { }; const wrapper = mount(); for (let i = 0; i < 10; i += 1) { - wrapper - .find('tbody tr td') - .last() - .simulate('click'); + wrapper.find('tbody tr td').last().simulate('click'); expect(wrapper.find('#count').text()).toEqual(String(i + 1)); } }); @@ -709,34 +603,12 @@ describe('Table.Basic', () => { { title: 'Age', dataIndex: 'age', key: 'age', align: 'center' }, ]; const wrapper = mount(createTable({ columns })); - expect( - wrapper - .find('th') - .at(0) - .props().style.textAlign, - ).toBeFalsy(); - expect( - wrapper - .find('th') - .at(1) - .props().style.textAlign, - ).toEqual('center'); - expect( - wrapper - .find('tbody tr') - .first() - .find('td') - .at(0) - .props().style.textAlign, - ).toBeFalsy(); - expect( - wrapper - .find('tbody tr') - .first() - .find('td') - .at(1) - .props().style.textAlign, - ).toEqual('center'); + expect(wrapper.find('th').at(0).props().style.textAlign).toBeFalsy(); + expect(wrapper.find('th').at(1).props().style.textAlign).toEqual('center'); + expect(wrapper.find('tbody tr').first().find('td').at(0).props().style.textAlign).toBeFalsy(); + expect(wrapper.find('tbody tr').first().find('td').at(1).props().style.textAlign).toEqual( + 'center', + ); }); it('align column should not override cell style', () => { @@ -751,21 +623,11 @@ describe('Table.Basic', () => { }, ]; const wrapper = mount(createTable({ columns })); - expect( - wrapper - .find('th') - .first() - .props().style, - ).toEqual({ + expect(wrapper.find('th').first().props().style).toEqual({ color: 'green', textAlign: 'center', }); - expect( - wrapper - .find('td') - .first() - .props().style, - ).toEqual({ + expect(wrapper.find('td').first().props().style).toEqual({ color: 'red', textAlign: 'center', }); @@ -869,12 +731,7 @@ describe('Table.Basic', () => { }), ); - expect( - wrapper - .find('tbody Cell') - .first() - .key(), - ).toBeTruthy(); + expect(wrapper.find('tbody Cell').first().key()).toBeTruthy(); }); it('syntactic sugar', () => { @@ -940,12 +797,7 @@ describe('Table.Basic', () => { />, ); - expect( - wrapper - .find('tr') - .last() - .props()['data-row-key'], - ).toEqual('light'); + expect(wrapper.find('tr').last().props()['data-row-key']).toEqual('light'); }); it('render with state change', () => { diff --git a/tests/__snapshots__/Summary.spec.tsx.snap b/tests/__snapshots__/Summary.spec.tsx.snap new file mode 100644 index 000000000..e72a20741 --- /dev/null +++ b/tests/__snapshots__/Summary.spec.tsx.snap @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Table.Summary support data type 1`] = ` + + + + + + + +`; diff --git a/tests/__snapshots__/Table.spec.js.snap b/tests/__snapshots__/Table.spec.js.snap index c19869220..5200ea495 100644 --- a/tests/__snapshots__/Table.spec.js.snap +++ b/tests/__snapshots__/Table.spec.js.snap @@ -735,33 +735,6 @@ exports[`Table.Basic renders rowSpan correctly 1`] = ` `; -exports[`Table.Basic summary support data type 1`] = ` - - - - - - - -`; - exports[`Table.Basic syntactic sugar 1`] = `
+ Light + + Bamboo + + 112.5 +
- Light - - Bamboo - - 112.5 -