Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Table - fix typing for onSort function #739

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions src/components/table/__tests__/table.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ type TestDataT = {
const SAMPLE_DATA_NUM_ROWS = 10;
const SAMPLE_DATA_NUM_COLUMNS = 5;

jest.mock('../table-sortable-head-cell/table-sortable-head-cell', () =>
jest.fn(({ name, columnID, onSort }) => (
<th data-testid="sortable-head-cell" onClick={() => onSort(columnID)}>
{name}
</th>
))
);

const SAMPLE_ROWS: Array<TestDataT> = Array.from(
{ length: SAMPLE_DATA_NUM_ROWS },
(_, rowIndex) => ({ value: `test_${rowIndex}` })
Expand Down Expand Up @@ -56,24 +64,46 @@ describe('Table', () => {
const { mockOnSort } = setup({ shouldShowResults: true });

const columnElements = await screen.findAllByText(/Column Name \d+/);
expect(columnElements.length).toEqual(5);

const sortableColumnHeadCells =
await screen.findAllByTestId('sortable-head-cell');
expect(sortableColumnHeadCells.length).toEqual(5);

act(() => {
fireEvent.click(columnElements[0]);
});

expect(mockOnSort).toHaveBeenCalledWith('column_id_0');
});

it('should render plain head cells for sortable columns when onSort is missing', async () => {
setup({ shouldShowResults: true, omitOnSort: true });

const columnElements = await screen.findAllByText(/Column Name \d+/);
expect(columnElements.length).toEqual(5);

const sortableColumnHeadCells =
screen.queryAllByTestId('sortable-head-cell');
expect(sortableColumnHeadCells.length).toEqual(0);
});
});

function setup({ shouldShowResults }: { shouldShowResults: boolean }) {
function setup({
shouldShowResults,
omitOnSort,
}: {
shouldShowResults: boolean;
omitOnSort?: boolean;
}) {
const mockOnSort = jest.fn();
render(
<Table
data={SAMPLE_ROWS}
columns={SAMPLE_COLUMNS}
shouldShowResults={shouldShowResults}
endMessage={<div>Sample end message</div>}
onSort={mockOnSort}
{...(!omitOnSort && { onSort: mockOnSort })}
sortColumn={SAMPLE_COLUMNS[SAMPLE_DATA_NUM_COLUMNS - 1].id}
sortOrder="DESC"
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';

import { render, screen, userEvent } from '@/test-utils/rtl';

import { type SortOrder } from '@/utils/sort-by';

import TableSortableHeadCell from '../table-sortable-head-cell';

describe(TableSortableHeadCell.name, () => {
it('should render unsorted without error', async () => {
setup({ sortColumn: 'column_2', sortOrder: 'DESC' });

expect(
await screen.findByLabelText('Column 1, not sorted')
).toBeInTheDocument();
});

it('should call onSort when clicked', async () => {
const { mockOnSort, user } = setup({
sortColumn: 'column_2',
sortOrder: 'DESC',
});

const cell = await screen.findByLabelText('Column 1, not sorted');

await user.click(cell);
expect(mockOnSort).toHaveBeenCalledWith('column_1');
});

it('should render sorted ASC without error', async () => {
setup({ sortColumn: 'column_1', sortOrder: 'ASC' });

expect(
await screen.findByLabelText('Column 1, ascending sorting')
).toBeInTheDocument();
});

it('should render sorted DESC without error', async () => {
setup({ sortColumn: 'column_1', sortOrder: 'DESC' });

expect(
await screen.findByLabelText('Column 1, descending sorting')
).toBeInTheDocument();
});
});

function setup({
sortColumn,
sortOrder,
}: {
sortColumn: string;
sortOrder: SortOrder;
}) {
const user = userEvent.setup();
const mockOnSort = jest.fn();
render(
<TableSortableHeadCell
name="Column 1"
columnID="column_1"
width="20%"
onSort={mockOnSort}
sortColumn={sortColumn}
sortOrder={sortOrder}
/>
);
return { mockOnSort, user };
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { type SortOrder } from '@/utils/sort-by';

export type Props = {
name: string;
columnID: string;
width: string;
sortColumn?: string;
sortOrder?: string;
sortOrder?: SortOrder;
onSort: (column: string) => void;
};
16 changes: 10 additions & 6 deletions src/components/table/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,32 @@ import {

import TableSortableHeadCell from './table-sortable-head-cell/table-sortable-head-cell';
import { styled } from './table.styles';
import type { Props } from './table.types';
import type { Props, TableConfig } from './table.types';

export default function Table<T extends object>({
export default function Table<T extends object, C extends TableConfig<T>>({
data,
columns,
shouldShowResults,
endMessage,
...sortParams
}: Props<T>) {
onSort,
sortColumn,
sortOrder,
}: Props<T, C>) {
return (
<styled.TableRoot>
<StyledTable>
<StyledTableHead>
<StyledTableHeadRow>
{columns.map((column) =>
column.sortable ? (
column.sortable && typeof onSort === 'function' ? (
<TableSortableHeadCell
key={column.id}
name={column.name}
columnID={column.id}
width={column.width}
{...sortParams}
onSort={onSort}
sortColumn={sortColumn}
sortOrder={sortOrder}
/>
) : (
<styled.TableHeadCell
Expand Down
21 changes: 16 additions & 5 deletions src/components/table/table.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,24 @@ export type TableColumn<T> = {
sortable?: boolean;
};

export type Props<T> = {
export type TableConfig<T> = Array<TableColumn<T>>;

type AreAnyColumnsSortable<T, C extends TableConfig<T>> = true extends {
[K in keyof C]: C[K] extends { sortable: true } ? true : false;
}[number]
? true
: false;

type OnSortFunctionOptional<T, C extends TableConfig<T>> =
AreAnyColumnsSortable<T, C> extends true
? { onSort: (column: string) => void }
: { onSort?: (column: string) => void };

export type Props<T, C extends TableConfig<T>> = {
data: Array<T>;
columns: Array<TableColumn<T>>;
columns: C;
shouldShowResults: boolean;
endMessage: React.ReactNode;
// Sort params
onSort: (column: string) => void;
sortColumn?: string;
sortOrder?: SortOrder;
};
} & OnSortFunctionOptional<T, C>;
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createElement } from 'react';

import FormattedDate from '@/components/formatted-date/formatted-date';
import Link from '@/components/link/link';
import { type TableColumn } from '@/components/table/table.types';
import { type TableConfig } from '@/components/table/table.types';
import { type DomainWorkflow } from '@/views/domain-page/domain-page.types';
import WorkflowStatusTag from '@/views/shared/workflow-status-tag/workflow-status-tag';

Expand Down Expand Up @@ -55,6 +55,6 @@ const domainWorkflowsTableConfig = [
width: '12.5%',
sortable: true,
},
] as const satisfies Array<TableColumn<DomainWorkflow>>;
] as const satisfies TableConfig<DomainWorkflow>;

export default domainWorkflowsTableConfig;
6 changes: 3 additions & 3 deletions src/views/domains-page/config/domains-table-columns.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { type TableColumn } from '@/components/table/table.types';
import { type TableConfig } from '@/components/table/table.types';

import { type DomainData } from '../domains-page.types';
import DomainsTableClusterCell from '../domains-table-cluster-cell/domains-table-cluster-cell';
import DomainsTableDomainNameCell from '../domains-table-domain-name-cell/domains-table-domain-name-cell';

const domainsTableColumnsConfig: Array<TableColumn<DomainData>> = [
const domainsTableColumnsConfig = [
{
name: 'Domain Name',
id: 'name',
Expand All @@ -18,6 +18,6 @@ const domainsTableColumnsConfig: Array<TableColumn<DomainData>> = [
renderCell: DomainsTableClusterCell,
width: '20%',
},
];
] as const satisfies TableConfig<DomainData>;

export default domainsTableColumnsConfig;
4 changes: 2 additions & 2 deletions src/views/domains-page/domains-table/domains-table.types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { TableColumn } from '@/components/table/table.types';
import type { TableConfig } from '@/components/table/table.types';

import type { DomainData } from '../domains-page.types';

export type DomainsTableColumns = Array<TableColumn<DomainData>>;
export type DomainsTableColumns = TableConfig<DomainData>;

export type Props = {
domains: Array<DomainData>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createElement } from 'react';

import FormattedDate from '@/components/formatted-date/formatted-date';
import { type TableColumn } from '@/components/table/table.types';
import { type TableConfig } from '@/components/table/table.types';
import { type Worker } from '@/route-handlers/describe-task-list/describe-task-list.types';

import TaskListWorkersTableHandlerIcon from '../task-list-workers-table-handler-icon/task-list-workers-table-handler-icon';
Expand Down Expand Up @@ -40,6 +40,6 @@ const taskListWorkersTableConfig = [
}),
width: '10%',
},
] as const satisfies Array<TableColumn<Worker>>;
] as const satisfies TableConfig<Worker>;

export default taskListWorkersTableConfig;
Loading