diff --git a/example/src/components/TableDemo.tsx b/example/src/components/TableDemo.tsx index e4296d50..c4ef6177 100644 --- a/example/src/components/TableDemo.tsx +++ b/example/src/components/TableDemo.tsx @@ -392,6 +392,66 @@ export const TableDemo = () => { columnsWidth={['100px']} trProps={trProps} /> + +

custom header with order

+ + } + sortDescIcon={ + + } + columnsWidth={['100px']} + /> + +

custom 'shortened' header with order

+
+ } + sortDescIcon={ + + } + columnsWidth={['100px']} + /> ); }; diff --git a/jest.config.ts b/jest.config.ts index 1fa0b29e..72fcbb41 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,38 +1,46 @@ module.exports = { - preset: "ts-jest", - testEnvironment: "jsdom", - collectCoverage: true, - "moduleNameMapper": { - "^.+\\.(css|less|scss)$": "identity-obj-proxy" - }, - "modulePathIgnorePatterns": [ - "/example", - "/dist", - "/coverage", - "/static", - "/stories" - ], - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/index.{ts,tsx}", - "!src/typings.d.ts", - "!/node_modules/", - "!/path/to/dir/", - "!src/generatePresentationalComponent/createPresentationalComponent.ts" - ], - globals: { - "ts-jest": { - tsconfig: { - outDir: "./dist/", - sourceMap: true, - noImplicitAny: true, - module: "commonjs", - target: "es6", - jsx: "react", - allowSyntheticDefaultImports: true, - esModuleInterop: true, - }, + preset: 'ts-jest', + testEnvironment: 'jsdom', + collectCoverage: true, + 'coverageReporters': ['json', 'lcov', 'text', 'html'], + 'coverageThreshold': { + 'global': { + 'branches': 100, + 'functions': 100, + 'lines': 100, + 'statements': 100 + } + }, + 'moduleNameMapper': { + '^.+\\.(css|less|scss)$': 'identity-obj-proxy' + }, + 'modulePathIgnorePatterns': [ + '/example', + '/dist', + '/coverage', + '/static', + '/stories' + ], + 'collectCoverageFrom': [ + 'src/**/*.{ts,tsx}', + '!src/**/index.{ts,tsx}', + '!src/typings.d.ts', + '!/node_modules/', + '!/path/to/dir/', + '!src/generatePresentationalComponent/createPresentationalComponent.ts' + ], + globals: { + 'ts-jest': { + tsconfig: { + outDir: './dist/', + sourceMap: true, + noImplicitAny: true, + module: 'commonjs', + target: 'es6', + jsx: 'react', + allowSyntheticDefaultImports: true, + esModuleInterop: true, }, }, - }; - \ No newline at end of file + }, +}; diff --git a/src/table/Table.tsx b/src/table/Table.tsx index da19a5dd..eb9411bd 100644 --- a/src/table/Table.tsx +++ b/src/table/Table.tsx @@ -4,6 +4,11 @@ import { Body } from './Body'; import { Header } from './Header'; import { useSortableData } from './useSortable'; import { useTableSearch } from './useTableSearch'; +type CustomHeaderLabel = { + label: string; + data: string; + omit?: boolean; +}; type TableProps = { /** * the data source used to populate the table @@ -76,7 +81,7 @@ type TableProps = { /** * allow to specify a custom header labels */ - customHeaderLabels?: { label: string; data: string }[]; + customHeaderLabels?: CustomHeaderLabel[] | string[]; /** * allow to pass extra properties to each row */ @@ -117,13 +122,28 @@ export const Table = ({ data: withOrderBy ? items : dataSource, }); + const isListOfStrings = ( + headers: CustomHeaderLabel[] | string[] + ): headers is string[] => + headers.some( + (header: CustomHeaderLabel | string) => typeof header === 'string' + ); + const handleClick = (value: string) => { if (customHeaderLabels) { - const customHeaderEl = customHeaderLabels.find(c => c.label === value); + const customHeaderEl = isListOfStrings(customHeaderLabels) + ? customHeaderLabels.findIndex((c: string) => c === value) + : customHeaderLabels.find((c: CustomHeaderLabel) => c.label === value); + + const key = + typeof customHeaderEl === 'number' + ? Object.keys(dataSource[0])[customHeaderEl] + : (customHeaderEl as CustomHeaderLabel).data; + //@ts-ignore - requestSort(customHeaderEl.data); + requestSort(key); //@ts-ignore - setSelectedHeader(customHeaderEl.data); + setSelectedHeader(key); } else { requestSort(value); setSelectedHeader(value); @@ -142,6 +162,26 @@ export const Table = ({ ); }; + const getHeaderValues = () => { + if (customHeaderLabels && isListOfStrings(customHeaderLabels)) { + return customHeaderLabels.filter( + (label: string) => !columnsToOmit?.includes(label) + ); + } + + if (customHeaderLabels) { + return Array.from( + customHeaderLabels.filter( + (c: CustomHeaderLabel) => + !columnsToOmit?.includes(c.data) && c.omit !== true + ), + (c: CustomHeaderLabel) => c.label + ); + } + + return keys(items, columnsToOmit); + }; + return ( <> {withSearch && ( @@ -156,11 +196,7 @@ export const Table = ({ theadClassName={theadClassName} trClassName={trClassName} thClassName={thClassName} - values={ - customHeaderLabels - ? Array.from(customHeaderLabels, c => c.label) - : keys(items, columnsToOmit) - } + values={getHeaderValues()} keySorted={getClassNamesFor(selectedHeader)} sortAscIcon={sortAscIcon} sortDescIcon={sortDescIcon} diff --git a/src/table/__tests__/Table.test.tsx b/src/table/__tests__/Table.test.tsx index 7c37224c..ad465af4 100644 --- a/src/table/__tests__/Table.test.tsx +++ b/src/table/__tests__/Table.test.tsx @@ -52,6 +52,50 @@ describe('Table', () => { expect(row.innerHTML).toContain('1'); }); + it('should perform the order with short custom headers', () => { + render( +
+ ); + //body + const { id } = values[0]; + const row: any = screen.getByText(id).closest('tr'); + expect(row.innerHTML).toContain('2'); + //header + const rows: any = screen.getAllByRole('row'); + const idHeader = rows[0].children[0]; + fireEvent.click(idHeader); + expect(row.innerHTML).toContain('1'); + }); + + it('should not perform the order when data can not be found', () => { + render( +
+ ); + //body + const { id } = values[0]; + const row: any = screen.getByText(id).closest('tr'); + expect(row.innerHTML).toContain('2'); + //header + const rows: any = screen.getAllByRole('row'); + const idHeader = rows[0].children[0]; + fireEvent.click(idHeader); + expect(row.innerHTML).toContain('2'); + }); + it('should allow to search and return 0 elements', () => { render(
{ expect(tbody[1].children).toHaveLength(0); }); + it('should not display omitted table headers', () => { + render(
); + const row: any = screen.getAllByRole('row'); + expect(row[1].innerHTML).toContain('id'); + expect(row[2].innerHTML).toContain('actions'); + }); + + it('should not display omitted table headers with given custom headers', () => { + render( +
+ ); + const row: any = screen.getAllByRole('row'); + expect(row[1].innerHTML).toContain('Test'); + expect(row[2].innerHTML).toContain('actions'); + }); + + it('should not display omitted table headers with given custom headers with omit flag', () => { + render( +
+ ); + const row: any = screen.getAllByRole('row'); + expect(row[1].innerHTML).toContain('Test'); + expect(row[2].innerHTML).toContain('actions'); + }); + + it('should not display omitted table headers with given shorted custom headers', () => { + render( +
+ ); + const row: any = screen.getAllByRole('row'); + expect(row[1].innerHTML).toContain('Test'); + expect(row[2].innerHTML).toContain('actions'); + }); + + it('should display only the table header', () => { + render( +
+ ); + const row: any = screen.getAllByRole('row'); + expect(row[1].innerHTML).toContain('Test'); + expect(row[2].innerHTML).toContain('position'); + expect(row[3].innerHTML).toContain('actions'); + const tbody = screen.getAllByRole('rowgroup'); + expect(tbody[1].children).toHaveLength(0); + }); + it('should allow to reorder the data if the customHeader is specified', () => { render(
{ expect(row.innerHTML).toContain('1'); }); - it('should not reorder the data if the customHeader specified doesn\'t have a match data', () => { + it("should not reorder the data if the customHeader specified doesn't have a match data", () => { render(
{
Delete }, ]; - return
; + return
; }}