Repository: HospitalRun/hospitalrun-frontend (ts)

Issue:
Ability to view a diagnosis
## 🚀 Feature Proposal

Currently, the list of diagnosis are not clickable. This issue is to make them clickable and create a component that allows a user to view the diagnoses. 

## Technical notes
* the routing should work similar to how care plan routes work


Pull request:
feat(diagnoses): adds ability to view a diagnosis
Fixes #2129.

**Changes proposed in this pull request:**

- Added a diagnosis table component, where each row/diagnosis has a "view" button that leads to the corresponding form
- Added a new react-query handler for the diagnosis routing
- Changed Diagnoses and ViewPatient tests
- Added new tests for the components and routing
- Added asynchronous data loading for diagnoses (similar to care plans)

**Newly added dependencies with [Bundlephobia](https://bundlephobia.com/) links:**

- No dependencies added.


Patch:
diff --git a/src/patients/diagnoses/Diagnoses.tsx b/src/patients/diagnoses/Diagnoses.tsx
index 12ee0418..a7b099ee 100644
--- a/src/patients/diagnoses/Diagnoses.tsx
+++ b/src/patients/diagnoses/Diagnoses.tsx
@@ -1,29 +1,29 @@
 import { Button } from '@hospitalrun/components'
 import React, { useState } from 'react'
 import { useSelector } from 'react-redux'
+import { Route, Switch, useParams } from 'react-router-dom'
 
 import useAddBreadcrumbs from '../../page-header/breadcrumbs/useAddBreadcrumbs'
+import Loading from '../../shared/components/Loading'
 import useTranslator from '../../shared/hooks/useTranslator'
-import Patient from '../../shared/model/Patient'
 import Permissions from '../../shared/model/Permissions'
 import { RootState } from '../../shared/store'
+import usePatient from '../hooks/usePatient'
 import AddDiagnosisModal from './AddDiagnosisModal'
-import DiagnosesList from './DiagnosesList'
+import ViewDiagnoses from './ViewDiagnoses'
+import ViewDiagnosis from './ViewDiagnosis'
 
-interface Props {
-  patient: Patient
-}
-
-const Diagnoses = (props: Props) => {
-  const { patient } = props
+const Diagnoses = () => {
+  const { id: patientId } = useParams()
   const { t } = useTranslator()
   const { permissions } = useSelector((state: RootState) => state.user)
+  const { data, status } = usePatient(patientId)
   const [showDiagnosisModal, setShowDiagnosisModal] = useState(false)
 
   const breadcrumbs = [
     {
       i18nKey: 'patient.diagnoses.label',
-      location: `/patients/${patient.id}/diagnoses`,
+      location: `/patients/${patientId}/diagnoses`,
     },
   ]
   useAddBreadcrumbs(breadcrumbs)
@@ -32,6 +32,10 @@ const Diagnoses = (props: Props) => {
     setShowDiagnosisModal(false)
   }
 
+  if (data === undefined || status === 'loading') {
+    return <Loading />
+  }
+
   return (
     <>
       <div className="row">
@@ -50,11 +54,18 @@ const Diagnoses = (props: Props) => {
         </div>
       </div>
       <br />
-      <DiagnosesList patientId={patient.id} />
+      <Switch>
+        <Route exact path="/patients/:id/diagnoses">
+          <ViewDiagnoses />
+        </Route>
+        <Route exact path="/patients/:id/diagnoses/:diagnosisId">
+          <ViewDiagnosis />
+        </Route>
+      </Switch>
       <AddDiagnosisModal
         show={showDiagnosisModal}
         onCloseButtonClick={onAddDiagnosisModalClose}
-        patient={patient}
+        patient={data}
       />
     </>
   )
diff --git a/src/patients/diagnoses/DiagnosesList.tsx b/src/patients/diagnoses/DiagnosesList.tsx
deleted file mode 100644
index 42736e66..00000000
--- a/src/patients/diagnoses/DiagnosesList.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { Alert, List, ListItem } from '@hospitalrun/components'
-import React from 'react'
-
-import Loading from '../../shared/components/Loading'
-import useTranslator from '../../shared/hooks/useTranslator'
-import Diagnosis from '../../shared/model/Diagnosis'
-import usePatientDiagnoses from '../hooks/usePatientDiagnoses'
-
-interface Props {
-  patientId: string
-}
-
-const DiagnosesList = (props: Props) => {
-  const { patientId } = props
-  const { t } = useTranslator()
-  const { data, status } = usePatientDiagnoses(patientId)
-
-  if (data === undefined || status === 'loading') {
-    return <Loading />
-  }
-
-  if (data.length === 0) {
-    return (
-      <Alert
-        color="warning"
-        title={t('patient.diagnoses.warning.noDiagnoses')}
-        message={t('patient.diagnoses.addDiagnosisAbove')}
-      />
-    )
-  }
-
-  return (
-    <List>
-      {data.map((diagnosis: Diagnosis) => (
-        <ListItem key={diagnosis.id}>{diagnosis.name}</ListItem>
-      ))}
-    </List>
-  )
-}
-
-export default DiagnosesList
diff --git a/src/patients/diagnoses/DiagnosisTable.tsx b/src/patients/diagnoses/DiagnosisTable.tsx
new file mode 100644
index 00000000..13ea52f4
--- /dev/null
+++ b/src/patients/diagnoses/DiagnosisTable.tsx
@@ -0,0 +1,71 @@
+import { Alert, Table } from '@hospitalrun/components'
+import format from 'date-fns/format'
+import React from 'react'
+import { useHistory } from 'react-router-dom'
+
+import Loading from '../../shared/components/Loading'
+import useTranslator from '../../shared/hooks/useTranslator'
+import usePatientDiagnoses from '../hooks/usePatientDiagnoses'
+
+interface Props {
+  patientId: string
+}
+
+const DiagnosisTable = (props: Props) => {
+  const { patientId } = props
+  const history = useHistory()
+  const { t } = useTranslator()
+  const { data, status } = usePatientDiagnoses(patientId)
+
+  if (data === undefined || status === 'loading') {
+    return <Loading />
+  }
+
+  if (data.length === 0) {
+    return (
+      <Alert
+        color="warning"
+        title={t('patient.diagnoses.warning.noDiagnoses')}
+        message={t('patient.diagnoses.addDiagnosisAbove')}
+      />
+    )
+  }
+
+  return (
+    <Table
+      getID={(row) => row.id}
+      data={data}
+      columns={[
+        { label: t('patient.diagnoses.diagnosisName'), key: 'name' },
+        {
+          label: t('patient.diagnoses.diagnosisDate'),
+          key: 'diagnosisDate',
+          formatter: (row) =>
+            format(row.diagnosisDate ? new Date(row.diagnosisDate) : new Date(0), 'yyyy-MM-dd'),
+        },
+        {
+          label: t('patient.diagnoses.onsetDate'),
+          key: 'onsetDate',
+          formatter: (row) =>
+            format(row.onsetDate ? new Date(row.onsetDate) : new Date(0), 'yyyy-MM-dd'),
+        },
+        {
+          label: t('patient.diagnoses.abatementDate'),
+          key: 'abatementDate',
+          formatter: (row) =>
+            format(row.abatementDate ? new Date(row.abatementDate) : new Date(0), 'yyyy-MM-dd'),
+        },
+        { label: t('patient.diagnoses.status'), key: 'status' },
+      ]}
+      actionsHeaderText={t('actions.label')}
+      actions={[
+        {
+          label: t('actions.view'),
+          action: (row) => history.push(`/patients/${patientId}/diagnoses/${row.id}`),
+        },
+      ]}
+    />
+  )
+}
+
+export default DiagnosisTable
diff --git a/src/patients/diagnoses/ViewDiagnoses.tsx b/src/patients/diagnoses/ViewDiagnoses.tsx
new file mode 100644
index 00000000..8885edcd
--- /dev/null
+++ b/src/patients/diagnoses/ViewDiagnoses.tsx
@@ -0,0 +1,12 @@
+import React from 'react'
+import { useParams } from 'react-router-dom'
+
+import DiagnosisTable from './DiagnosisTable'
+
+const ViewDiagnoses = () => {
+  const { id: patientId } = useParams()
+
+  return <DiagnosisTable patientId={patientId} />
+}
+
+export default ViewDiagnoses
diff --git a/src/patients/diagnoses/ViewDiagnosis.tsx b/src/patients/diagnoses/ViewDiagnosis.tsx
new file mode 100644
index 00000000..0d57edad
--- /dev/null
+++ b/src/patients/diagnoses/ViewDiagnosis.tsx
@@ -0,0 +1,31 @@
+import React from 'react'
+import { useParams } from 'react-router-dom'
+
+import Loading from '../../shared/components/Loading'
+import useDiagnosis from '../hooks/useDiagnosis'
+import usePatient from '../hooks/usePatient'
+import DiagnosisForm from './DiagnosisForm'
+
+const ViewDiagnosis = () => {
+  const { diagnosisId, id: patientId } = useParams()
+  const { data: patient, status: patientStatus } = usePatient(patientId)
+  const { data: diagnosis, status: diagnosisStatus } = useDiagnosis(patientId, diagnosisId)
+
+  if (
+    patient === undefined ||
+    diagnosis === undefined ||
+    patientStatus === 'loading' ||
+    diagnosisStatus === 'loading'
+  ) {
+    return <Loading />
+  }
+
+  return (
+    <>
+      <h2>{diagnosis.name}</h2>
+      <DiagnosisForm patient={patient} diagnosis={diagnosis} disabled />
+    </>
+  )
+}
+
+export default ViewDiagnosis
diff --git a/src/patients/hooks/useDiagnosis.tsx b/src/patients/hooks/useDiagnosis.tsx
new file mode 100644
index 00000000..dd6b856a
--- /dev/null
+++ b/src/patients/hooks/useDiagnosis.tsx
@@ -0,0 +1,18 @@
+import { useQuery } from 'react-query'
+
+import PatientRepository from '../../shared/db/PatientRepository'
+import Diagnosis from '../../shared/model/Diagnosis'
+
+async function getDiagnosis(_: string, patientId: string, diagnosisId: string): Promise<Diagnosis> {
+  const patient = await PatientRepository.find(patientId)
+  const maybeDiagnosis = patient.diagnoses?.find((d) => d.id === diagnosisId)
+  if (!maybeDiagnosis) {
+    throw new Error('Diagnosis not found')
+  }
+
+  return maybeDiagnosis
+}
+
+export default function useDiagnosis(patientId: string, diagnosisId: string) {
+  return useQuery(['diagnoses', patientId, diagnosisId], getDiagnosis, { retry: false })
+}
diff --git a/src/patients/view/ImportantPatientInfo.tsx b/src/patients/view/ImportantPatientInfo.tsx
index 1aab395a..0783be8a 100644
--- a/src/patients/view/ImportantPatientInfo.tsx
+++ b/src/patients/view/ImportantPatientInfo.tsx
@@ -161,7 +161,7 @@ const ImportantPatientInfo = (props: Props) => {
           <div className="border border-primary" style={tableContainerStyle}>
             <Table
               tableClassName="table table-hover table-sm m-0"
-              onRowClick={() => history.push(`/patients/${patient.id}/diagnoses`)}
+              onRowClick={(row) => history.push(`/patients/${patient.id}/diagnoses/${row.id}`)}
               getID={(row) => row.id}
               columns={[
                 { label: t('patient.diagnoses.diagnosisName'), key: 'name' },
diff --git a/src/patients/view/ViewPatient.tsx b/src/patients/view/ViewPatient.tsx
index dd5bd132..29acd8d0 100644
--- a/src/patients/view/ViewPatient.tsx
+++ b/src/patients/view/ViewPatient.tsx
@@ -110,7 +110,7 @@ const ViewPatient = () => {
             onClick={() => history.push(`/patients/${patient.id}/allergies`)}
           />
           <Tab
-            active={location.pathname === `/patients/${patient.id}/diagnoses`}
+            active={location.pathname.startsWith(`/patients/${patient.id}/diagnoses`)}
             label={t('patient.diagnoses.label')}
             onClick={() => history.push(`/patients/${patient.id}/diagnoses`)}
           />
@@ -163,8 +163,8 @@ const ViewPatient = () => {
           <Route path={`${path}/allergies`}>
             <Allergies patient={patient} />
           </Route>
-          <Route exact path={`${path}/diagnoses`}>
-            <Diagnoses patient={patient} />
+          <Route path={`${path}/diagnoses`}>
+            <Diagnoses />
           </Route>
           <Route exact path={`${path}/notes`}>
             <Note patient={patient} />


Test patch:
diff --git a/src/__tests__/patients/diagnoses/Diagnoses.test.tsx b/src/__tests__/patients/diagnoses/Diagnoses.test.tsx
index d239fade..5318059b 100644
--- a/src/__tests__/patients/diagnoses/Diagnoses.test.tsx
+++ b/src/__tests__/patients/diagnoses/Diagnoses.test.tsx
@@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event'
 import { createMemoryHistory } from 'history'
 import React from 'react'
 import { Provider } from 'react-redux'
-import { Router } from 'react-router-dom'
+import { Route, Router } from 'react-router-dom'
 import createMockStore from 'redux-mock-store'
 import thunk from 'redux-thunk'
 
@@ -22,20 +22,21 @@ const expectedPatient = {
 } as Patient
 
 const mockStore = createMockStore<RootState, any>([thunk])
-const history = createMemoryHistory()
 
-let user: any
-let store: any
+const setup = async (patient = expectedPatient, permissions = [Permissions.AddDiagnosis]) => {
+  jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient)
+  const store = mockStore({ user: { permissions } } as any)
+  const history = createMemoryHistory()
+  history.push(`/patients/${patient.id}/diagnoses`)
 
-const setup = (patient = expectedPatient, permissions = [Permissions.AddDiagnosis]) => {
-  user = { permissions }
-  store = mockStore({ patient, user } as any)
   return render(
-    <Router history={history}>
-      <Provider store={store}>
-        <Diagnoses patient={patient} />
-      </Provider>
-    </Router>,
+    <Provider store={store}>
+      <Router history={history}>
+        <Route path="/patients/:id/diagnoses">
+          <Diagnoses />
+        </Route>
+      </Router>
+    </Provider>,
   )
 }
 
@@ -46,31 +47,32 @@ describe('Diagnoses', () => {
       jest.spyOn(PatientRepository, 'saveOrUpdate')
     })
 
-    it('should render a add diagnoses button', () => {
+    it('should render an add diagnoses button', async () => {
       setup()
 
       expect(
-        screen.getByRole('button', {
-          name: /patient\.diagnoses\.new/i,
+        await screen.findByRole('button', {
+          name: /patient.diagnoses.new/i,
         }),
       ).toBeInTheDocument()
     })
 
     it('should not render a diagnoses button if the user does not have permissions', () => {
       setup(expectedPatient, [])
+
       expect(
         screen.queryByRole('button', {
-          name: /patient\.diagnoses\.new/i,
+          name: /patient.diagnoses.new/i,
         }),
       ).not.toBeInTheDocument()
     })
 
-    it('should open the Add Diagnosis Modal', () => {
+    it('should open the Add Diagnosis Modal', async () => {
       setup()
 
       userEvent.click(
-        screen.getByRole('button', {
-          name: /patient\.diagnoses\.new/i,
+        await screen.findByRole('button', {
+          name: /patient.diagnoses.new/i,
         }),
       )
 
diff --git a/src/__tests__/patients/diagnoses/DiagnosesList.test.tsx b/src/__tests__/patients/diagnoses/DiagnosesList.test.tsx
deleted file mode 100644
index 5c4bf8fd..00000000
--- a/src/__tests__/patients/diagnoses/DiagnosesList.test.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { render, screen, waitFor } from '@testing-library/react'
-import React from 'react'
-
-import DiagnosesList from '../../../patients/diagnoses/DiagnosesList'
-import PatientRepository from '../../../shared/db/PatientRepository'
-import Diagnosis from '../../../shared/model/Diagnosis'
-import Patient from '../../../shared/model/Patient'
-
-const expectedDiagnoses = [
-  { id: '123', name: 'diagnosis1', diagnosisDate: new Date().toISOString() } as Diagnosis,
-]
-
-describe('Diagnoses list', () => {
-  const setup = async (diagnoses: Diagnosis[]) => {
-    jest.resetAllMocks()
-    const mockPatient = { id: '123', diagnoses } as Patient
-    jest.spyOn(PatientRepository, 'find').mockResolvedValueOnce(mockPatient)
-
-    return render(<DiagnosesList patientId={mockPatient.id} />)
-  }
-
-  it('should list the patients diagnoses', async () => {
-    const diagnoses = expectedDiagnoses as Diagnosis[]
-    const { container } = await setup(diagnoses)
-    await waitFor(() => {
-      expect(container.querySelector('.list-group')).toBeInTheDocument()
-    })
-    const listItems = container.querySelectorAll('.list-group-item')
-    expect(listItems).toHaveLength(expectedDiagnoses.length)
-    expect(listItems[0]).toHaveTextContent(expectedDiagnoses[0].name)
-  })
-
-  it('should render a warning message if the patient does not have any diagnoses', async () => {
-    const { container } = await setup([])
-    const alert = await screen.findByRole('alert')
-    expect(alert).toBeInTheDocument()
-    expect(container.querySelector('.list-group')).not.toBeInTheDocument()
-    expect(alert).toHaveClass('alert-warning')
-    expect(screen.getByText(/patient.diagnoses.warning.noDiagnoses/i)).toBeInTheDocument()
-    expect(screen.getByText(/patient.diagnoses.addDiagnosisAbove/i)).toBeInTheDocument()
-  })
-})
diff --git a/src/__tests__/patients/diagnoses/DiagnosisTable.test.tsx b/src/__tests__/patients/diagnoses/DiagnosisTable.test.tsx
new file mode 100644
index 00000000..7b3b59f1
--- /dev/null
+++ b/src/__tests__/patients/diagnoses/DiagnosisTable.test.tsx
@@ -0,0 +1,86 @@
+import { render, screen, within } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { createMemoryHistory } from 'history'
+import React from 'react'
+import { Route, Router, Switch } from 'react-router-dom'
+
+import DiagnosisTable from '../../../patients/diagnoses/DiagnosisTable'
+import ViewDiagnosis from '../../../patients/diagnoses/ViewDiagnosis'
+import PatientRepository from '../../../shared/db/PatientRepository'
+import Diagnosis, { DiagnosisStatus } from '../../../shared/model/Diagnosis'
+import Patient from '../../../shared/model/Patient'
+
+const diagnosis = {
+  id: '456',
+  name: 'diagnosis name',
+  diagnosisDate: new Date(2020, 12, 2).toISOString(),
+  onsetDate: new Date(2020, 12, 3).toISOString(),
+  abatementDate: new Date(2020, 12, 4).toISOString(),
+  status: DiagnosisStatus.Active,
+  note: 'note',
+  visit: 'visit',
+} as Diagnosis
+
+const expectedPatient = {
+  id: '123',
+  diagnoses: [diagnosis],
+} as Patient
+
+const setup = async (patient = expectedPatient) => {
+  jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient)
+  const history = createMemoryHistory()
+  history.push(`/patients/${patient.id}/diagnoses/${diagnosis.id}`)
+
+  return {
+    history,
+    ...render(
+      <Router history={history}>
+        <DiagnosisTable patientId={patient.id} />
+        <Switch>
+          <Route exact path="/patients/:id/diagnoses/:diagnosisId">
+            <ViewDiagnosis />
+          </Route>
+        </Switch>
+      </Router>,
+    ),
+  }
+}
+
+describe('Diagnosis Table', () => {
+  it('should render a table', async () => {
+    setup()
+    await screen.findByRole('table')
+
+    const columnHeaders = screen.getAllByRole('columnheader')
+    expect(columnHeaders[0]).toHaveTextContent(/patient.diagnoses.diagnosisName/i)
+    expect(columnHeaders[1]).toHaveTextContent(/patient.diagnoses.diagnosisDate/i)
+    expect(columnHeaders[2]).toHaveTextContent(/patient.diagnoses.onsetDate/i)
+    expect(columnHeaders[3]).toHaveTextContent(/patient.diagnoses.abatementDate/i)
+    expect(columnHeaders[4]).toHaveTextContent(/patient.diagnoses.status/i)
+    expect(columnHeaders[5]).toHaveTextContent(/actions.label/i)
+
+    expect(await screen.findByRole('button', { name: /actions.view/i })).toBeInTheDocument()
+  })
+
+  it('should navigate to the diagnosis view when the view details button is clicked', async () => {
+    const { history } = await setup()
+    await screen.findByRole('table')
+
+    userEvent.click(await screen.findByRole('button', { name: /actions.view/i }))
+    expect(history.location.pathname).toEqual(
+      `/patients/${expectedPatient.id}/diagnoses/${diagnosis.id}`,
+    )
+
+    const form = await screen.findByRole('form')
+    expect(
+      within(form).getByPlaceholderText(/patient.diagnoses.diagnosisName/i),
+    ).toBeInTheDocument()
+  })
+
+  it('should display a warning if there are no diagnoses', async () => {
+    await setup({ ...expectedPatient, diagnoses: [] })
+
+    expect(await screen.findByText(/patient.diagnoses.warning.noDiagnoses/i)).toBeInTheDocument()
+    expect(await screen.findByText(/patient.diagnoses.addDiagnosisAbove/i)).toBeInTheDocument()
+  })
+})
diff --git a/src/__tests__/patients/diagnoses/ViewDiagnoses.test.tsx b/src/__tests__/patients/diagnoses/ViewDiagnoses.test.tsx
new file mode 100644
index 00000000..e5487b99
--- /dev/null
+++ b/src/__tests__/patients/diagnoses/ViewDiagnoses.test.tsx
@@ -0,0 +1,48 @@
+import { render, screen } from '@testing-library/react'
+import { createMemoryHistory } from 'history'
+import React from 'react'
+import { Route, Router } from 'react-router-dom'
+
+import ViewDiagnoses from '../../../patients/diagnoses/ViewDiagnoses'
+import PatientRepository from '../../../shared/db/PatientRepository'
+import Diagnosis, { DiagnosisStatus } from '../../../shared/model/Diagnosis'
+import Patient from '../../../shared/model/Patient'
+
+const diagnosis = {
+  id: '456',
+  name: 'diagnosis name',
+  diagnosisDate: new Date(2020, 12, 2).toISOString(),
+  onsetDate: new Date(2020, 12, 3).toISOString(),
+  abatementDate: new Date(2020, 12, 4).toISOString(),
+  status: DiagnosisStatus.Active,
+  note: 'note',
+  visit: 'visit',
+} as Diagnosis
+
+const expectedPatient = {
+  id: '123',
+  diagnoses: [diagnosis],
+} as Patient
+
+const setup = async () => {
+  jest.resetAllMocks()
+  jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient)
+
+  const history = createMemoryHistory()
+  history.push(`/patients/${expectedPatient.id}/diagnoses`)
+
+  return render(
+    <Router history={history}>
+      <Route path="/patients/:id/diagnoses">
+        <ViewDiagnoses />
+      </Route>
+    </Router>,
+  )
+}
+
+describe('View Diagnoses', () => {
+  it('should render a diagnoses table with the patient id', async () => {
+    setup()
+    expect(await screen.findByRole('table')).toBeInTheDocument()
+  })
+})
diff --git a/src/__tests__/patients/diagnoses/ViewDiagnosis.test.tsx b/src/__tests__/patients/diagnoses/ViewDiagnosis.test.tsx
new file mode 100644
index 00000000..c1cfd10b
--- /dev/null
+++ b/src/__tests__/patients/diagnoses/ViewDiagnosis.test.tsx
@@ -0,0 +1,57 @@
+import { render, screen, waitForElementToBeRemoved } from '@testing-library/react'
+import { createMemoryHistory } from 'history'
+import React from 'react'
+import { Route, Router } from 'react-router-dom'
+
+import ViewDiagnosis from '../../../patients/diagnoses/ViewDiagnosis'
+import PatientRepository from '../../../shared/db/PatientRepository'
+import Diagnosis from '../../../shared/model/Diagnosis'
+import Patient from '../../../shared/model/Patient'
+
+const diagnosis = {
+  id: '123',
+  name: 'some name',
+  diagnosisDate: new Date().toISOString(),
+} as Diagnosis
+
+const expectedPatient = {
+  id: 'patientId',
+  diagnoses: [diagnosis],
+} as Patient
+
+const setup = () => {
+  jest.resetAllMocks()
+  jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient)
+  const history = createMemoryHistory()
+  history.push(`/patients/${expectedPatient.id}/diagnoses/${diagnosis.id}`)
+
+  return render(
+    <Router history={history}>
+      <Route path="/patients/:id/diagnoses/:diagnosisId">
+        <ViewDiagnosis />
+      </Route>
+    </Router>,
+  )
+}
+
+describe('View Diagnosis', () => {
+  it('should render the loading spinner only while diagnosis data is being fetched', async () => {
+    const { container } = setup()
+
+    expect(container.querySelector(`[class^='css-']`)).toBeInTheDocument()
+    await waitForElementToBeRemoved(container.querySelector('.css-0'))
+    expect(container.querySelector(`[class^='css-']`)).not.toBeInTheDocument()
+  })
+
+  it('should render the diagnosis name', async () => {
+    setup()
+
+    expect(await screen.findByRole('heading', { name: diagnosis.name })).toBeInTheDocument()
+  })
+
+  it('should render a diagnosis form', async () => {
+    setup()
+
+    expect(await screen.findByRole('form')).toBeInTheDocument()
+  })
+})
diff --git a/src/__tests__/patients/hooks/useDiagnosis.test.tsx b/src/__tests__/patients/hooks/useDiagnosis.test.tsx
new file mode 100644
index 00000000..8527c783
--- /dev/null
+++ b/src/__tests__/patients/hooks/useDiagnosis.test.tsx
@@ -0,0 +1,65 @@
+import useDiagnosis from '../../../patients/hooks/useDiagnosis'
+import PatientRepository from '../../../shared/db/PatientRepository'
+import Diagnosis from '../../../shared/model/Diagnosis'
+import Patient from '../../../shared/model/Patient'
+import executeQuery from '../../test-utils/use-query.util'
+
+describe('useDiagnosis', () => {
+  let errorMock: jest.SpyInstance
+
+  beforeEach(() => {
+    jest.resetAllMocks()
+    errorMock = jest.spyOn(console, 'error').mockImplementation()
+  })
+
+  afterEach(() => {
+    errorMock.mockRestore()
+  })
+
+  it('should return a diagnosis successfully given a valid patient id and diagnosis id', async () => {
+    const expectedPatientId = 'patientId'
+    const expectedDiagnosis = { id: 'diagnosisId', name: 'diagnosis name' } as Diagnosis
+    const expectedPatient = { id: expectedPatientId, diagnoses: [expectedDiagnosis] } as Patient
+    jest.spyOn(PatientRepository, 'find').mockResolvedValueOnce(expectedPatient)
+
+    const actualDiagnosis = await executeQuery(() =>
+      useDiagnosis(expectedPatientId, expectedDiagnosis.id),
+    )
+
+    expect(PatientRepository.find).toHaveBeenCalledTimes(1)
+    expect(PatientRepository.find).toHaveBeenCalledWith(expectedPatientId)
+    expect(actualDiagnosis).toEqual(expectedDiagnosis)
+  })
+
+  it('should throw an error if patient id is not valid', async () => {
+    const expectedPatientId = 'patientId'
+    const expectedDiagnosisId = 'diagnosisId'
+    jest.spyOn(PatientRepository, 'find').mockRejectedValueOnce(new Error('Patient not found'))
+
+    try {
+      await executeQuery(
+        () => useDiagnosis(expectedPatientId, expectedDiagnosisId),
+        (queryResult) => queryResult.isError,
+      )
+    } catch (e) {
+      expect(e).toEqual(new Error('Patient not found'))
+    }
+  })
+
+  it('should throw an error if patient id is valid but diagnosis id is not', async () => {
+    const expectedPatientId = 'patientId'
+    const expectedDiagnosisId = 'diagnosisId'
+    const actualDiagnosis = { id: 'actual diagnosisId', name: 'actual diagnosis name' } as Diagnosis
+    const expectedPatient = { id: expectedPatientId, diagnoses: [actualDiagnosis] } as Patient
+    jest.spyOn(PatientRepository, 'find').mockResolvedValueOnce(expectedPatient)
+
+    try {
+      await executeQuery(
+        () => useDiagnosis(expectedPatientId, expectedDiagnosisId),
+        (queryResult) => queryResult.isError,
+      )
+    } catch (e) {
+      expect(e).toEqual(new Error('Diagnosis not found'))
+    }
+  })
+})
diff --git a/src/__tests__/patients/view/ImportantPatientInfo.test.tsx b/src/__tests__/patients/view/ImportantPatientInfo.test.tsx
index 815effb7..0c319d68 100644
--- a/src/__tests__/patients/view/ImportantPatientInfo.test.tsx
+++ b/src/__tests__/patients/view/ImportantPatientInfo.test.tsx
@@ -4,10 +4,11 @@ import format from 'date-fns/format'
 import { createMemoryHistory } from 'history'
 import React from 'react'
 import { Provider } from 'react-redux'
-import { Router } from 'react-router-dom'
+import { Router, Route, Switch } from 'react-router-dom'
 import createMockStore from 'redux-mock-store'
 import thunk from 'redux-thunk'
 
+import ViewDiagnosis from '../../../patients/diagnoses/ViewDiagnosis'
 import ImportantPatientInfo from '../../../patients/view/ImportantPatientInfo'
 import PatientRepository from '../../../shared/db/PatientRepository'
 import CarePlan from '../../../shared/model/CarePlan'
@@ -23,15 +24,19 @@ describe('Important Patient Info Panel', () => {
   let user: any
   let store: any
 
+  const diagnosis = {
+    id: 'diagnosisId',
+    name: 'diagnosis1',
+    diagnosisDate: new Date().toISOString(),
+  } as Diagnosis
+
   const expectedPatient = {
     id: '123',
     sex: 'male',
     fullName: 'full Name',
     code: 'P-123',
     dateOfBirth: format(new Date(), 'MM/dd/yyyy'),
-    diagnoses: [
-      { id: '123', name: 'diagnosis1', diagnosisDate: new Date().toISOString() } as Diagnosis,
-    ],
+    diagnoses: [diagnosis],
     allergies: [
       { id: '1', name: 'allergy1' },
       { id: '2', name: 'allergy2' },
@@ -63,6 +68,11 @@ describe('Important Patient Info Panel', () => {
       <Provider store={store}>
         <Router history={history}>
           <ImportantPatientInfo patient={patient} />
+          <Switch>
+            <Route exact path="/patients/:id/diagnoses/:diagnosisId">
+              <ViewDiagnosis />
+            </Route>
+          </Switch>
         </Router>
       </Provider>,
     )
@@ -182,4 +192,21 @@ describe('Important Patient Info Panel', () => {
       ).not.toBeInTheDocument()
     })
   })
+
+  describe('patient diagnosis routing', () => {
+    it('should render the diagnosis form when the table row is clicked', async () => {
+      setup(expectedPatient, [])
+
+      const rows = await screen.findAllByRole('row')
+      userEvent.click(rows[2])
+      expect(history.location.pathname).toEqual(
+        `/patients/${expectedPatient.id}/diagnoses/${diagnosis.id}`,
+      )
+
+      const form = await screen.findByRole('form')
+      expect(
+        within(form).getByPlaceholderText(/patient.diagnoses.diagnosisName/i),
+      ).toBeInTheDocument()
+    })
+  })
 })