Validate MW fields availability in species locality rows

This commit is contained in:
Kari Lintulaakso
2026-02-10 16:15:10 +02:00
parent db5e940cc4
commit ce34ef9693
3 changed files with 111 additions and 3 deletions
@@ -4,10 +4,21 @@ import { EditingModal } from '@/components/DetailView/common/EditingModal'
import { Grouped } from '@/components/DetailView/common/tabLayoutHelpers'
import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
import { Box, TextField } from '@mui/material'
import { MRT_ColumnDef } from 'material-react-table'
import { MRT_ColumnDef, MRT_Row } from 'material-react-table'
import { matchesCountryOrContinent } from '@/shared/validators/countryContinents'
import { useForm } from 'react-hook-form'
const hasMesowearScoreInputs = (row: SpeciesLocality) => {
return (
'mw_scale_min' in row &&
'mw_scale_max' in row &&
'mw_value' in row &&
row.mw_scale_min !== undefined &&
row.mw_scale_max !== undefined &&
row.mw_value !== undefined
)
}
export const LocalitySpeciesTab = () => {
const { mode } = useDetailContext<SpeciesDetailsType>()
const {
@@ -104,7 +115,13 @@ export const LocalitySpeciesTab = () => {
},
{
header: 'MW Score',
Cell: () => <Box>not implemented</Box>,
Cell: ({ row }: { row: MRT_Row<SpeciesLocality> }) => {
if (!hasMesowearScoreInputs(row.original)) {
return <Box color="warning.main">MW fields missing</Box>
}
return <Box>not implemented</Box>
},
},
{
accessorKey: 'microwear',
+8 -1
View File
@@ -21,7 +21,14 @@ export type CollectingMethodValues = Prisma.now_coll_meth_values
export type LocalityProject = Prisma.now_plr & { now_proj: Prisma.now_proj }
export type LocalitySpecies = FixBigInt<Prisma.now_ls> & { com_species: SpeciesType }
export type LocalitySpeciesDetailsType = FixBigInt<Prisma.now_ls> & { com_species: SpeciesDetailsType }
export type SpeciesLocality = FixBigInt<Prisma.now_ls> & { now_loc: Prisma.now_loc }
export type SpeciesLocality = FixBigInt<Prisma.now_ls> & {
now_loc: Prisma.now_loc
// Explicitly required in the Species locality payload because MW Score is
// calculated client-side from these values in the Locality-Species table.
mw_scale_min: number | null
mw_scale_max: number | null
mw_value: number | null
}
export type LocalityUpdate = Prisma.now_lau & { now_lr: LocalityReference[] } & { updates: UpdateLog[] }
export type SpeciesUpdate = Prisma.now_sau & { now_sr: SpeciesReference[] } & { updates: UpdateLog[] }
export type Museum = Omit<Prisma.com_mlist, 'used_morph' | 'used_now' | 'used_gene'>
@@ -0,0 +1,84 @@
import type { ReactNode } from 'react'
import { beforeEach, describe, expect, it, jest } from '@jest/globals'
import { render } from '@testing-library/react'
import { LocalitySpeciesTab } from '@/components/Species/Tabs/LocalitySpeciesTab'
import { modeOptionToMode, useDetailContext } from '@/components/DetailView/Context/DetailContext'
import type { SpeciesLocality } from '@/shared/types'
import type { MRT_ColumnDef, MRT_Row } from 'material-react-table'
jest.mock('@/components/DetailView/Context/DetailContext', () => ({
useDetailContext: jest.fn(),
modeOptionToMode: {
new: { read: false, staging: false, new: true, option: 'new' },
read: { read: true, staging: false, new: false, option: 'read' },
edit: { read: false, staging: false, new: false, option: 'edit' },
'staging-edit': { read: false, staging: true, new: false, option: 'staging-edit' },
'staging-new': { read: false, staging: true, new: true, option: 'staging-new' },
},
}))
type EditableTableProps = {
columns: MRT_ColumnDef<SpeciesLocality>[]
field: string
}
const editableTableMock = jest.fn<(props: EditableTableProps) => JSX.Element>()
editableTableMock.mockImplementation(() => <div data-testid="editable-table" />)
jest.mock('@/components/DetailView/common/EditableTable', () => ({
EditableTable: (props: EditableTableProps) => editableTableMock(props),
}))
jest.mock('@/components/DetailView/common/EditingModal', () => ({
EditingModal: ({ children }: { children: ReactNode }) => <div>{children}</div>,
}))
jest.mock('@/components/DetailView/common/tabLayoutHelpers', () => ({
Grouped: ({ children }: { children: ReactNode }) => <div>{children}</div>,
}))
const mockUseDetailContext = useDetailContext as jest.MockedFunction<typeof useDetailContext>
describe('LocalitySpeciesTab MW score prerequisites', () => {
beforeEach(() => {
jest.clearAllMocks()
mockUseDetailContext.mockReturnValue({
mode: modeOptionToMode.read,
} as never)
})
it('passes MW scale/value columns and MW Score cell can read values from row data', () => {
render(<LocalitySpeciesTab />)
const firstCall = editableTableMock.mock.calls[0]
expect(firstCall).toBeDefined()
const editableTableProps = firstCall?.[0]
expect(editableTableProps?.field).toBe('now_ls')
const accessorKeys = (editableTableProps?.columns ?? [])
.map(column => ('accessorKey' in column ? column.accessorKey : undefined))
.filter(Boolean)
expect(accessorKeys).toEqual(expect.arrayContaining(['mw_scale_min', 'mw_scale_max', 'mw_value']))
const mwScoreColumn = editableTableProps?.columns.find(column => column.header === 'MW Score')
expect(mwScoreColumn).toBeDefined()
const cellRenderer = mwScoreColumn?.Cell as ((args: { row: MRT_Row<SpeciesLocality> }) => ReactNode) | undefined
const row = {
original: {
lid: 1,
species_id: 25056,
mw_scale_min: 1,
mw_scale_max: 5,
mw_value: 3,
now_loc: {},
},
} as MRT_Row<SpeciesLocality>
const cellResult = cellRenderer?.({ row })
expect(cellResult).toBeTruthy()
})
})