mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
Merge pull request #1167 from nowcommunity/fix/79-table-data-cy-selectors
Add stable table data-cy selectors
This commit is contained in:
@@ -6,6 +6,18 @@ import { applyDefaultSpeciesOrdering, DetailTabTable, hasActiveSortingInSearch }
|
||||
const tableViewMock = jest.fn<(props: Record<string, unknown>) => JSX.Element>()
|
||||
const useMaterialReactTableMock = jest.fn<(options: Record<string, unknown>) => Record<string, unknown>>()
|
||||
|
||||
type CapturedMrtOptions = {
|
||||
muiTableProps?: {
|
||||
'data-cy'?: string
|
||||
}
|
||||
muiTableHeadCellProps?: (args: { column: { id: string } }) => {
|
||||
'data-cy'?: string
|
||||
}
|
||||
muiTableBodyCellProps?: (args: { cell: { column: { id: string } } }) => {
|
||||
'data-cy'?: string
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('@/components/TableView/TableView', () => ({
|
||||
TableView: (props: Record<string, unknown>) => tableViewMock(props),
|
||||
}))
|
||||
@@ -64,6 +76,7 @@ describe('DetailTabTable', () => {
|
||||
columns={[{ accessorKey: 'name', header: 'Name' }]}
|
||||
enableSorting={false}
|
||||
enableColumnActions={false}
|
||||
tableName="Editable table"
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -75,6 +88,11 @@ describe('DetailTabTable', () => {
|
||||
enablePagination: true,
|
||||
})
|
||||
)
|
||||
|
||||
const options = useMaterialReactTableMock.mock.calls[0]?.[0] as CapturedMrtOptions | undefined
|
||||
expect(options?.muiTableProps?.['data-cy']).toEqual('Editable-table-table')
|
||||
expect(options?.muiTableHeadCellProps?.({ column: { id: 'name' } })['data-cy']).toEqual('table-header-name')
|
||||
expect(options?.muiTableBodyCellProps?.({ cell: { column: { id: 'name' } } })['data-cy']).toEqual('table-cell-name')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -20,6 +20,18 @@ const defaultEditPagination: MRT_PaginationState = { pageIndex: 0, pageSize: 15
|
||||
|
||||
const speciesDefaultSortFields = ['order_name', 'family_name', 'genus_name', 'species_name'] as const
|
||||
|
||||
const toDataCyPart = (value: unknown): string => {
|
||||
const normalized = String(value)
|
||||
.trim()
|
||||
.replace(/[^A-Za-z0-9_-]+/g, '-')
|
||||
return normalized || 'unknown'
|
||||
}
|
||||
|
||||
const withDataCy = <TProps extends object>(props: TProps, dataCy: string): TProps & { 'data-cy': string } => ({
|
||||
...props,
|
||||
'data-cy': dataCy,
|
||||
})
|
||||
|
||||
const getNestedValue = (row: MRT_RowData, path: string): unknown => {
|
||||
return path.split('.').reduce<unknown>((acc, segment) => {
|
||||
if (typeof acc !== 'object' || acc === null || !(segment in acc)) {
|
||||
@@ -213,6 +225,9 @@ const DetailTabEditableTable = <T extends MRT_RowData>({
|
||||
const table = useMaterialReactTable({
|
||||
columns,
|
||||
data,
|
||||
muiTableProps: withDataCy({ sx: undefined }, `${toDataCyPart(tableName)}-table`),
|
||||
muiTableHeadCellProps: ({ column }) => withDataCy({ sx: undefined }, `table-header-${toDataCyPart(column.id)}`),
|
||||
muiTableBodyCellProps: ({ cell }) => withDataCy({ sx: undefined }, `table-cell-${toDataCyPart(cell.column.id)}`),
|
||||
enableTopToolbar: false,
|
||||
enableColumnActions,
|
||||
enableSorting,
|
||||
|
||||
@@ -64,6 +64,18 @@ const toTableCellTitle = (value: unknown): string | undefined => {
|
||||
return
|
||||
}
|
||||
|
||||
const toDataCyPart = (value: unknown): string => {
|
||||
const normalized = String(value)
|
||||
.trim()
|
||||
.replace(/[^A-Za-z0-9_-]+/g, '-')
|
||||
return normalized || 'unknown'
|
||||
}
|
||||
|
||||
const withDataCy = <TProps extends object>(props: TProps, dataCy: string): TProps & { 'data-cy': string } => ({
|
||||
...props,
|
||||
'data-cy': dataCy,
|
||||
})
|
||||
|
||||
const sanitizeColumnFilters = (filters: MRT_ColumnFiltersState): MRT_ColumnFiltersState => {
|
||||
return filters.filter(filter => !isEmptyFilterValue(filter.value))
|
||||
}
|
||||
@@ -438,41 +450,52 @@ export const TableView = <T extends MRT_RowData>({
|
||||
const table = useMaterialReactTable({
|
||||
columns: preparedColumns,
|
||||
data: data || [],
|
||||
muiTableProps: {
|
||||
sx: {
|
||||
tableLayout: 'fixed',
|
||||
},
|
||||
},
|
||||
muiTableHeadCellProps: {
|
||||
sx: {
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
'& .MuiTableSortLabel-root': {
|
||||
// Reserve space for the sort icon so toggling sorting doesn't shift column widths.
|
||||
position: 'relative',
|
||||
paddingRight: '1.25em',
|
||||
},
|
||||
'& .MuiTableSortLabel-icon': {
|
||||
// Keep the icon from affecting layout when it appears.
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
margin: 0,
|
||||
muiTableProps: withDataCy(
|
||||
{
|
||||
sx: {
|
||||
tableLayout: 'fixed',
|
||||
},
|
||||
},
|
||||
},
|
||||
muiTableBodyCellProps: ({ cell }) => ({
|
||||
...(cell.column.id === 'mrt-row-actions' ? {} : { title: toTableCellTitle(cell.getValue()) }),
|
||||
sx: {
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
'&.row-actions-cell': {
|
||||
overflow: 'visible',
|
||||
textOverflow: 'clip',
|
||||
`${toDataCyPart(title)}-table`
|
||||
),
|
||||
muiTableHeadCellProps: ({ column }) =>
|
||||
withDataCy(
|
||||
{
|
||||
sx: {
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
'& .MuiTableSortLabel-root': {
|
||||
// Reserve space for the sort icon so toggling sorting doesn't shift column widths.
|
||||
position: 'relative',
|
||||
paddingRight: '1.25em',
|
||||
},
|
||||
'& .MuiTableSortLabel-icon': {
|
||||
// Keep the icon from affecting layout when it appears.
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
margin: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
`table-header-${toDataCyPart(column.id)}`
|
||||
),
|
||||
muiTableBodyCellProps: ({ cell }) =>
|
||||
withDataCy(
|
||||
{
|
||||
...(cell.column.id === 'mrt-row-actions' ? {} : { title: toTableCellTitle(cell.getValue()) }),
|
||||
sx: {
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
'&.row-actions-cell': {
|
||||
overflow: 'visible',
|
||||
textOverflow: 'clip',
|
||||
},
|
||||
},
|
||||
},
|
||||
`table-cell-${toDataCyPart(cell.column.id)}`
|
||||
),
|
||||
muiTableBodyRowProps: clickableRows || selectorFn ? muiTableBodyRowProps : undefined,
|
||||
muiTableContainerProps: tableContainerMaxHeight
|
||||
? {
|
||||
@@ -565,13 +588,11 @@ export const TableView = <T extends MRT_RowData>({
|
||||
'mrt-row-actions': {
|
||||
size: 36,
|
||||
header: '',
|
||||
muiTableHeadCellProps: {
|
||||
align: 'right',
|
||||
},
|
||||
muiTableBodyCellProps: {
|
||||
align: 'right',
|
||||
className: 'row-actions-cell',
|
||||
},
|
||||
muiTableHeadCellProps: withDataCy({ align: 'right' as const }, 'table-header-row-actions'),
|
||||
muiTableBodyCellProps: withDataCy(
|
||||
{ align: 'right' as const, className: 'row-actions-cell' },
|
||||
'table-cell-row-actions'
|
||||
),
|
||||
},
|
||||
},
|
||||
positionActionsColumn: 'last',
|
||||
|
||||
@@ -12,6 +12,22 @@ import { NotificationContextProvider } from '@/components/Notification'
|
||||
type CapturedMrtOptions = {
|
||||
enableColumnFilterModes?: boolean
|
||||
columnFilterModeOptions?: string[]
|
||||
muiTableProps?: {
|
||||
'data-cy'?: string
|
||||
}
|
||||
muiTableHeadCellProps?: (args: { column: { id: string } }) => {
|
||||
'data-cy'?: string
|
||||
}
|
||||
muiTableBodyCellProps?: (args: { cell: { column: { id: string }; getValue: () => unknown } }) => {
|
||||
'data-cy'?: string
|
||||
title?: string
|
||||
}
|
||||
displayColumnDefOptions?: {
|
||||
'mrt-row-actions'?: {
|
||||
muiTableHeadCellProps?: { 'data-cy'?: string }
|
||||
muiTableBodyCellProps?: { 'data-cy'?: string }
|
||||
}
|
||||
}
|
||||
columns: Array<{
|
||||
accessorKey?: string
|
||||
enableColumnFilterModes?: boolean
|
||||
@@ -94,4 +110,27 @@ describe('TableView filter modes', () => {
|
||||
expect(options.initialState?.columnFilterFns?.title).toEqual('equals')
|
||||
expect(options.initialState?.columnFilterFns?.year).toBeUndefined()
|
||||
})
|
||||
|
||||
it('adds stable data-cy selectors to reusable table elements', () => {
|
||||
renderTable([
|
||||
{ accessorKey: 'rid', header: 'Id' },
|
||||
{ accessorKey: 'title', header: 'Title' },
|
||||
])
|
||||
|
||||
const options = getLastMaterialReactTableOptions<CapturedMrtOptions>()
|
||||
expect(options).toBeTruthy()
|
||||
if (!options) return
|
||||
|
||||
expect(options.muiTableProps?.['data-cy']).toEqual('Test-table')
|
||||
expect(options.muiTableHeadCellProps?.({ column: { id: 'title' } })['data-cy']).toEqual('table-header-title')
|
||||
expect(
|
||||
options.muiTableBodyCellProps?.({ cell: { column: { id: 'title' }, getValue: () => 'Example' } })['data-cy']
|
||||
).toEqual('table-cell-title')
|
||||
expect(options.displayColumnDefOptions?.['mrt-row-actions']?.muiTableHeadCellProps?.['data-cy']).toEqual(
|
||||
'table-header-row-actions'
|
||||
)
|
||||
expect(options.displayColumnDefOptions?.['mrt-row-actions']?.muiTableBodyCellProps?.['data-cy']).toEqual(
|
||||
'table-cell-row-actions'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user