Repository: HospitalRun/hospitalrun-frontend (ts)

Issue:
Create Patient History tab
## 🚀 Feature Proposal
There should be a patient history tab available in the patient profile. 

This history tab should show in chronological order events that have happened to the patient. 

To begin with this will be: 
- labs
- appointments

# Technical Notes
- Should use the `Callout` component
- will need some sort of special query that gets all entities that can be seen in the history timeline 

Pull request:
feat: Add patient history tab
Fixes #2255 

**Changes proposed in this pull request:**

- Add a new history tab for patient's view
- Currently support only histories for Lab and Appointment
- History for a Lab can be one in three types of events: Requested, Canceled and Completed
- History for an Appointment can be one in two types of events: Started or Ended
- History events are sorted in descending order, i.e the latest event should be on the top


Patch:
diff --git a/src/patients/history/HistoryTab.tsx b/src/patients/history/HistoryTab.tsx
new file mode 100644
index 00000000..da28425c
--- /dev/null
+++ b/src/patients/history/HistoryTab.tsx
@@ -0,0 +1,15 @@
+import React from 'react'
+
+import HistoryTable from './HistoryTable'
+
+interface Props {
+  patientId: string
+}
+
+const HistoryTab = ({ patientId }: Props) => (
+  <>
+    <HistoryTable patientId={patientId} />
+  </>
+)
+
+export default HistoryTab
diff --git a/src/patients/history/HistoryTable.tsx b/src/patients/history/HistoryTable.tsx
new file mode 100644
index 00000000..f4c94f10
--- /dev/null
+++ b/src/patients/history/HistoryTable.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 useTranslator from '../../shared/hooks/useTranslator'
+import { HistoryRecordType, PatientHistoryRecord } from '../../shared/model/PatientHistoryRecord'
+import usePatientAppointments from '../hooks/usePatientAppointments'
+import usePatientLabs from '../hooks/usePatientLabs'
+import { mapHistoryRecords } from './mappers/HistoryRecordsMapper'
+
+interface Props {
+  patientId: string
+}
+
+const getRecordPath = (historyRecord: PatientHistoryRecord): string => {
+  if (historyRecord.type === HistoryRecordType.LAB) {
+    return `/labs/${historyRecord.recordId}`
+  }
+  return `/appointments/${historyRecord.recordId}`
+}
+
+const HistoryTable = ({ patientId }: Props) => {
+  const { t } = useTranslator()
+  const history = useHistory()
+
+  const { data: labs } = usePatientLabs(patientId)
+  const { data: appointments } = usePatientAppointments(patientId)
+
+  const mappedHistoryRecords = mapHistoryRecords(labs, appointments)
+
+  if (mappedHistoryRecords.length === 0) {
+    return (
+      <Alert
+        color="warning"
+        title={t('patient.history.noHistoryTitle')}
+        message={t('patient.history.noHistoryMessage')}
+      />
+    )
+  }
+  return (
+    <Table
+      getID={(row) => row.id}
+      data={mappedHistoryRecords}
+      columns={[
+        {
+          label: t('patient.history.eventDate'),
+          key: 'date',
+          formatter: (row) => format(new Date(row.date), 'yyyy-MM-dd hh:mm a'),
+        },
+        {
+          label: t('patient.history.recordType'),
+          key: 'type',
+        },
+        {
+          label: t('patient.history.information'),
+          key: 'info',
+        },
+      ]}
+      actionsHeaderText={t('actions.label')}
+      actions={[
+        {
+          label: t('actions.view'),
+          action: (row) => history.push(getRecordPath(row as PatientHistoryRecord)),
+        },
+      ]}
+    />
+  )
+}
+
+export default HistoryTable
diff --git a/src/patients/history/mappers/HistoryRecordsMapper.tsx b/src/patients/history/mappers/HistoryRecordsMapper.tsx
new file mode 100644
index 00000000..5ccc4797
--- /dev/null
+++ b/src/patients/history/mappers/HistoryRecordsMapper.tsx
@@ -0,0 +1,42 @@
+import Appointment from '../../../shared/model/Appointment'
+import Lab from '../../../shared/model/Lab'
+import { PatientHistoryRecord } from '../../../shared/model/PatientHistoryRecord'
+import { convertLab, convertAppointment } from './helpers'
+
+const mapLabs = (labs: Lab[] | undefined): PatientHistoryRecord[] => {
+  if (!labs) {
+    return []
+  }
+  let flattenedRecords = [] as PatientHistoryRecord[]
+  labs.forEach((lab) => {
+    flattenedRecords = flattenedRecords.concat(convertLab(lab))
+  })
+  return flattenedRecords
+}
+
+const mapAppointments = (appointments: Appointment[] | undefined): PatientHistoryRecord[] => {
+  if (!appointments) {
+    return []
+  }
+  let flattenedRecords = [] as PatientHistoryRecord[]
+  appointments.forEach((appt) => {
+    flattenedRecords = flattenedRecords.concat(convertAppointment(appt))
+  })
+  return flattenedRecords
+}
+
+export const mapHistoryRecords = (
+  labs: Lab[] | undefined,
+  appointments: Appointment[] | undefined,
+): PatientHistoryRecord[] => {
+  const labRecords = mapLabs(labs)
+  const appointmentRecords = mapAppointments(appointments)
+
+  const result = labRecords.concat(appointmentRecords)
+  result.sort(
+    (a: PatientHistoryRecord, b: PatientHistoryRecord): number =>
+      b.date.getTime() - a.date.getTime(),
+  )
+
+  return result
+}
diff --git a/src/patients/history/mappers/helpers.tsx b/src/patients/history/mappers/helpers.tsx
new file mode 100644
index 00000000..1a5ee1f8
--- /dev/null
+++ b/src/patients/history/mappers/helpers.tsx
@@ -0,0 +1,57 @@
+import Appointment from '../../../shared/model/Appointment'
+import Lab from '../../../shared/model/Lab'
+import { PatientHistoryRecord, HistoryRecordType } from '../../../shared/model/PatientHistoryRecord'
+
+export const convertLab = (lab: Lab): PatientHistoryRecord[] => {
+  const labEvents = []
+  if (lab.requestedOn) {
+    labEvents.push({
+      date: new Date(lab.requestedOn),
+      type: HistoryRecordType.LAB,
+      info: `Requested - ${lab.type}`,
+      recordId: lab.id,
+      id: `requestedLab${lab.id}`,
+    })
+  }
+  if (lab.canceledOn) {
+    labEvents.push({
+      date: new Date(lab.canceledOn),
+      type: HistoryRecordType.LAB,
+      info: `Canceled - ${lab.type}`,
+      recordId: lab.id,
+      id: `canceledLab${lab.id}`,
+    })
+  } else if (lab.completedOn) {
+    labEvents.push({
+      date: new Date(lab.completedOn),
+      type: HistoryRecordType.LAB,
+      info: `Completed - ${lab.type}`,
+      recordId: lab.id,
+      id: `completedLab${lab.id}`,
+    })
+  }
+  return labEvents
+}
+
+export const convertAppointment = (appt: Appointment): PatientHistoryRecord[] => {
+  const apptEvents = []
+  if (appt.startDateTime) {
+    apptEvents.push({
+      date: new Date(appt.startDateTime),
+      type: HistoryRecordType.APPOINTMENT,
+      info: `Started - ${appt.type}`,
+      recordId: appt.id,
+      id: `startedAppt${appt.id}`,
+    })
+  }
+  if (appt.endDateTime) {
+    apptEvents.push({
+      date: new Date(appt.endDateTime),
+      type: HistoryRecordType.APPOINTMENT,
+      info: `Ended - ${appt.type}`,
+      recordId: appt.id,
+      id: `endedAppt${appt.id}`,
+    })
+  }
+  return apptEvents
+}
diff --git a/src/patients/view/ViewPatient.tsx b/src/patients/view/ViewPatient.tsx
index d16b9031..dd5bd132 100644
--- a/src/patients/view/ViewPatient.tsx
+++ b/src/patients/view/ViewPatient.tsx
@@ -22,6 +22,7 @@ import CareGoalTab from '../care-goals/CareGoalTab'
 import CarePlanTab from '../care-plans/CarePlanTab'
 import Diagnoses from '../diagnoses/Diagnoses'
 import GeneralInformation from '../GeneralInformation'
+import HistoryTab from '../history/HistoryTab'
 import usePatient from '../hooks/usePatient'
 import Labs from '../labs/Labs'
 import Medications from '../medications/Medications'
@@ -143,6 +144,11 @@ const ViewPatient = () => {
             label={t('patient.visits.label')}
             onClick={() => history.push(`/patients/${patient.id}/visits`)}
           />
+          <Tab
+            active={location.pathname.startsWith(`/patients/${patient.id}/history`)}
+            label={t('patient.history.label')}
+            onClick={() => history.push(`/patients/${patient.id}/history`)}
+          />
         </TabsHeader>
         <Panel>
           <Route exact path={path}>
@@ -178,6 +184,9 @@ const ViewPatient = () => {
           <Route path={`${path}/visits`}>
             <VisitTab patientId={patient.id} />
           </Route>
+          <Route path={`${path}/history`}>
+            <HistoryTab patientId={patient.id} />
+          </Route>
         </Panel>
       </div>
     </div>
diff --git a/src/shared/locales/enUs/translations/patient/index.ts b/src/shared/locales/enUs/translations/patient/index.ts
index d3227438..7b291b6d 100644
--- a/src/shared/locales/enUs/translations/patient/index.ts
+++ b/src/shared/locales/enUs/translations/patient/index.ts
@@ -190,6 +190,14 @@ export default {
       },
     },
     visit: 'Visit',
+    history: {
+      label: 'History',
+      eventDate: 'Event Date',
+      recordType: 'Record Type',
+      information: 'Information',
+      noHistoryTitle: 'No History',
+      noHistoryMessage: 'There is no histories for this patient',
+    },
     visits: {
       new: 'Add Visit',
       label: 'Visits',
diff --git a/src/shared/model/PatientHistoryRecord.tsx b/src/shared/model/PatientHistoryRecord.tsx
new file mode 100644
index 00000000..a4d5ddfc
--- /dev/null
+++ b/src/shared/model/PatientHistoryRecord.tsx
@@ -0,0 +1,12 @@
+export enum HistoryRecordType {
+  APPOINTMENT = 'Appointment',
+  LAB = 'Lab',
+}
+
+export interface PatientHistoryRecord {
+  id: string
+  date: Date
+  type: HistoryRecordType
+  info: string
+  recordId: string
+}


Test patch:
diff --git a/src/__tests__/patients/history/HistoryTab.test.tsx b/src/__tests__/patients/history/HistoryTab.test.tsx
new file mode 100644
index 00000000..a31791f3
--- /dev/null
+++ b/src/__tests__/patients/history/HistoryTab.test.tsx
@@ -0,0 +1,99 @@
+import { render, screen } from '@testing-library/react'
+import { createMemoryHistory } from 'history'
+import React from 'react'
+import { Provider } from 'react-redux'
+import { Router } from 'react-router-dom'
+import createMockStore from 'redux-mock-store'
+import thunk from 'redux-thunk'
+
+import HistoryTab from '../../../patients/history/HistoryTab'
+import * as HistoryRecordsMapper from '../../../patients/history/mappers/HistoryRecordsMapper'
+import PatientRepository from '../../../shared/db/PatientRepository'
+import Appointment from '../../../shared/model/Appointment'
+import Lab from '../../../shared/model/Lab'
+import Patient from '../../../shared/model/Patient'
+import { PatientHistoryRecord, HistoryRecordType } from '../../../shared/model/PatientHistoryRecord'
+import Permissions from '../../../shared/model/Permissions'
+import { RootState } from '../../../shared/store'
+
+const mockStore = createMockStore<RootState, any>([thunk])
+const history = createMemoryHistory()
+const expectedPatient = {
+  id: '123',
+} as Patient
+
+const mockLabs = [
+  {
+    id: '123',
+    code: 'dummy',
+    patient: 'dummy patient',
+    type: 'emergency incident',
+    status: 'completed',
+    requestedOn: '01/01/2021',
+    completedOn: '02/01/2021',
+  },
+] as Lab[]
+const mockAppts = [
+  {
+    id: '234',
+    startDateTime: '01/04/2021',
+    endDateTime: '01/05/2021',
+    patient: 'dummy patient',
+    reason: 'high blood pressure',
+    type: 'annual checkup',
+  },
+] as Appointment[]
+
+const mockHistoryRecords = [
+  {
+    date: new Date('01/01/2021'),
+    type: HistoryRecordType.LAB,
+    info: 'Requested - emergency incident',
+    recordId: '123',
+    id: 'requestedLab123',
+  },
+  {
+    date: new Date('02/01/2021'),
+    type: HistoryRecordType.APPOINTMENT,
+    info: 'Started - emergency accident',
+    recordId: '456',
+    id: 'startedAppt456',
+  },
+] as PatientHistoryRecord[]
+
+let store: any
+const setup = async (
+  patient = expectedPatient,
+  permissions = [Permissions.ViewPatientHistory],
+  route = '/patients/123/history',
+) => {
+  store = mockStore({ patient: { patient }, user: { permissions } } as any)
+  history.push(route)
+
+  return render(
+    <Router history={history}>
+      <Provider store={store}>
+        <HistoryTab patientId={patient.id} />
+      </Provider>
+    </Router>,
+  )
+}
+
+describe('HistoryTab', () => {
+  beforeEach(() => {
+    jest.resetAllMocks()
+    jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient)
+    jest.spyOn(PatientRepository, 'getLabs').mockResolvedValue(mockLabs)
+    jest.spyOn(PatientRepository, 'getAppointments').mockResolvedValue(mockAppts)
+    jest.spyOn(HistoryRecordsMapper, 'mapHistoryRecords').mockReturnValue(mockHistoryRecords)
+  })
+
+  describe('patient history list', () => {
+    it('should render patient history', async () => {
+      setup()
+      expect(await screen.findByRole('table')).toBeInTheDocument()
+      expect(screen.getByRole('cell', { name: mockHistoryRecords[0].info })).toBeInTheDocument()
+      expect(screen.getByRole('cell', { name: mockHistoryRecords[1].info })).toBeInTheDocument()
+    })
+  })
+})
diff --git a/src/__tests__/patients/history/HistoryTable.test.tsx b/src/__tests__/patients/history/HistoryTable.test.tsx
new file mode 100644
index 00000000..6196ca54
--- /dev/null
+++ b/src/__tests__/patients/history/HistoryTable.test.tsx
@@ -0,0 +1,154 @@
+import { render, screen, waitFor, within } from '@testing-library/react'
+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 createMockStore from 'redux-mock-store'
+import thunk from 'redux-thunk'
+
+import HistoryTable from '../../../patients/history/HistoryTable'
+import * as HistoryRecordsMapper from '../../../patients/history/mappers/HistoryRecordsMapper'
+import PatientRepository from '../../../shared/db/PatientRepository'
+import Appointment from '../../../shared/model/Appointment'
+import Lab from '../../../shared/model/Lab'
+import Patient from '../../../shared/model/Patient'
+import { PatientHistoryRecord, HistoryRecordType } from '../../../shared/model/PatientHistoryRecord'
+import Permissions from '../../../shared/model/Permissions'
+import { RootState } from '../../../shared/store'
+
+const mockStore = createMockStore<RootState, any>([thunk])
+const history = createMemoryHistory()
+const expectedPatient = {
+  id: '123',
+} as Patient
+
+const mockLabs = [
+  {
+    id: '123',
+    code: 'dummy',
+    patient: 'dummy patient',
+    type: 'emergency incident',
+    status: 'completed',
+    requestedOn: '01/01/2021',
+    completedOn: '02/01/2021',
+  },
+] as Lab[]
+const mockAppts = [
+  {
+    id: '234',
+    startDateTime: '01/04/2021',
+    endDateTime: '01/05/2021',
+    patient: 'dummy patient',
+    reason: 'high blood pressure',
+    type: 'annual checkup',
+  },
+] as Appointment[]
+
+const mockHistoryRecords = [
+  {
+    date: new Date('01/01/2021'),
+    type: HistoryRecordType.LAB,
+    info: 'Requested - emergency incident',
+    recordId: '123',
+    id: 'requestedLab123',
+  },
+  {
+    date: new Date('02/01/2021'),
+    type: HistoryRecordType.APPOINTMENT,
+    info: 'Started - emergency accident',
+    recordId: '456',
+    id: 'startedAppt456',
+  },
+] as PatientHistoryRecord[]
+
+let store: any
+const setup = async (
+  patient = expectedPatient,
+  permissions = [Permissions.ViewPatientHistory],
+  route = '/patients/123/history',
+) => {
+  store = mockStore({ patient: { patient }, user: { permissions } } as any)
+  history.push(route)
+
+  return render(
+    <Router history={history}>
+      <Provider store={store}>
+        <HistoryTable patientId={patient.id} />
+      </Provider>
+    </Router>,
+  )
+}
+describe('HistoryTable', () => {
+  beforeEach(() => {
+    jest.resetAllMocks()
+    jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient)
+    jest.spyOn(PatientRepository, 'getLabs').mockResolvedValue(mockLabs)
+    jest.spyOn(PatientRepository, 'getAppointments').mockResolvedValue(mockAppts)
+  })
+
+  describe('patient history table', () => {
+    it('should render the history table headers', async () => {
+      setup()
+      jest.spyOn(HistoryRecordsMapper, 'mapHistoryRecords').mockReturnValue(mockHistoryRecords)
+      expect(await screen.findByRole('table')).toBeInTheDocument()
+      const headers = screen.getAllByRole('columnheader')
+      expect(headers[0]).toHaveTextContent(/patient\.history\.eventDate/i)
+      expect(headers[1]).toHaveTextContent(/patient\.history\.recordType/i)
+      expect(headers[2]).toHaveTextContent(/patient\.history\.information/i)
+      expect(headers[3]).toHaveTextContent(/actions\.label/i)
+    })
+
+    it('should render correct patient histories', async () => {
+      setup()
+      jest.spyOn(HistoryRecordsMapper, 'mapHistoryRecords').mockReturnValue(mockHistoryRecords)
+
+      await screen.findByRole('table')
+
+      const cells = screen.getAllByRole('cell')
+      expect(cells[0]).toHaveTextContent('2021-01-01 12:00 AM')
+      expect(cells[1]).toHaveTextContent(HistoryRecordType.LAB)
+      expect(cells[2]).toHaveTextContent('Requested - emergency incident')
+    })
+
+    it('render an action button', async () => {
+      setup()
+
+      await waitFor(() => {
+        const row = screen.getAllByRole('row')
+
+        expect(
+          within(row[1]).getByRole('button', {
+            name: /actions\.view/i,
+          }),
+        ).toBeInTheDocument()
+      })
+    })
+
+    it('should navigate to lab record on lab history click', async () => {
+      setup()
+      jest.spyOn(HistoryRecordsMapper, 'mapHistoryRecords').mockReturnValue(mockHistoryRecords)
+      expect(await screen.findByRole('table')).toBeInTheDocument()
+      userEvent.click(screen.getAllByRole('button', { name: /actions.view/i })[0])
+      expect(history.location.pathname).toEqual('/labs/123')
+    })
+
+    it('should navigate to appointment record on appointment history click', async () => {
+      setup()
+      jest.spyOn(HistoryRecordsMapper, 'mapHistoryRecords').mockReturnValue(mockHistoryRecords)
+      expect(await screen.findByRole('table')).toBeInTheDocument()
+      userEvent.click(screen.getAllByRole('button', { name: /actions.view/i })[1])
+      expect(history.location.pathname).toEqual('/appointments/456')
+    })
+  })
+
+  describe('no patient histories', () => {
+    it('should render a warning message if there are no histories', async () => {
+      setup()
+      jest.spyOn(HistoryRecordsMapper, 'mapHistoryRecords').mockReturnValue([])
+      expect(await screen.findByRole('alert')).toBeInTheDocument()
+      expect(screen.getByText(/patient.history.noHistoryTitle/i)).toBeInTheDocument()
+      expect(screen.getByText(/patient.history.noHistoryMessage/i)).toBeInTheDocument()
+    })
+  })
+})
diff --git a/src/__tests__/patients/history/mappers/HistoryRecordsMapper.test.tsx b/src/__tests__/patients/history/mappers/HistoryRecordsMapper.test.tsx
new file mode 100644
index 00000000..61766146
--- /dev/null
+++ b/src/__tests__/patients/history/mappers/HistoryRecordsMapper.test.tsx
@@ -0,0 +1,84 @@
+import * as helpers from '../../../../patients/history/mappers/helpers'
+import { mapHistoryRecords } from '../../../../patients/history/mappers/HistoryRecordsMapper'
+import Appointment from '../../../../shared/model/Appointment'
+import Lab from '../../../../shared/model/Lab'
+import {
+  PatientHistoryRecord,
+  HistoryRecordType,
+} from '../../../../shared/model/PatientHistoryRecord'
+
+describe('test mapHistoryRecords', () => {
+  beforeEach(() => {
+    jest.resetAllMocks()
+  })
+
+  it('should map history records correctly', () => {
+    const mockLabs = [
+      {
+        id: '123',
+        code: 'dummy',
+        patient: 'dummy patient',
+        type: 'emergency incident',
+        status: 'completed',
+        requestedOn: '01/01/2021',
+        completedOn: '02/01/2021',
+      },
+    ] as Lab[]
+    const mockAppts = [
+      {
+        id: '234',
+        startDateTime: '01/04/2021',
+        endDateTime: '01/05/2021',
+        patient: 'dummy patient',
+        reason: 'high blood pressure',
+        type: 'annual checkup',
+      },
+    ] as Appointment[]
+    const mockLabHistoryRecords = [
+      {
+        date: new Date('01/01/2021'),
+        type: HistoryRecordType.LAB,
+        info: 'Requested - emergency incident',
+        recordId: '123',
+        id: 'requestedLab123',
+      },
+      {
+        date: new Date('02/01/2021'),
+        type: HistoryRecordType.LAB,
+        info: 'Canceled - emergency incident',
+        recordId: '123',
+        id: 'canceledLab123',
+      },
+    ] as PatientHistoryRecord[]
+    const mockApptHistoryRecords = [
+      {
+        date: new Date('01/03/2021'),
+        type: HistoryRecordType.APPOINTMENT,
+        info: 'Started - regular checkup',
+        recordId: '123',
+        id: 'startedAppt123',
+      },
+      {
+        date: new Date('02/03/2021'),
+        type: HistoryRecordType.APPOINTMENT,
+        info: 'Ended - regular checkup',
+        recordId: '123',
+        id: 'endedAppt123',
+      },
+    ] as PatientHistoryRecord[]
+    jest.spyOn(helpers, 'convertLab').mockReturnValue(mockLabHistoryRecords)
+    jest.spyOn(helpers, 'convertAppointment').mockReturnValue(mockApptHistoryRecords)
+    const expectedMappedHistoryRecords = [
+      mockApptHistoryRecords[1],
+      mockLabHistoryRecords[1],
+      mockApptHistoryRecords[0],
+      mockLabHistoryRecords[0],
+    ]
+    const actualMappedHistoryRecords = mapHistoryRecords(mockLabs, mockAppts)
+    expect(helpers.convertLab).toHaveBeenCalledTimes(1)
+    expect(helpers.convertLab).toBeCalledWith(mockLabs[0])
+    expect(helpers.convertAppointment).toHaveBeenCalledTimes(1)
+    expect(helpers.convertAppointment).toBeCalledWith(mockAppts[0])
+    expect(expectedMappedHistoryRecords).toEqual(actualMappedHistoryRecords)
+  })
+})
diff --git a/src/__tests__/patients/history/mappers/helpers.test.tsx b/src/__tests__/patients/history/mappers/helpers.test.tsx
new file mode 100644
index 00000000..70548254
--- /dev/null
+++ b/src/__tests__/patients/history/mappers/helpers.test.tsx
@@ -0,0 +1,132 @@
+import { convertLab, convertAppointment } from '../../../../patients/history/mappers/helpers'
+import Appointment from '../../../../shared/model/Appointment'
+import Lab from '../../../../shared/model/Lab'
+import {
+  PatientHistoryRecord,
+  HistoryRecordType,
+} from '../../../../shared/model/PatientHistoryRecord'
+
+describe('test convertLab', () => {
+  beforeEach(() => {
+    jest.resetAllMocks()
+  })
+
+  it('should convert requested lab events correctly', () => {
+    const mockLab = {
+      id: '123',
+      code: 'dummy',
+      patient: 'dummy patient',
+      type: 'emergency incident',
+      status: 'requested',
+      requestedOn: '01/01/2021',
+    } as Lab
+    const expectedPatientHistoryRecords = [
+      {
+        date: new Date('01/01/2021'),
+        type: HistoryRecordType.LAB,
+        info: 'Requested - emergency incident',
+        recordId: '123',
+        id: 'requestedLab123',
+      },
+    ] as PatientHistoryRecord[]
+
+    const actualPatientHistoryRecords = convertLab(mockLab)
+    expect(actualPatientHistoryRecords).toEqual(expectedPatientHistoryRecords)
+  })
+
+  it('should convert completed lab events correctly', () => {
+    const mockLab = {
+      id: '123',
+      code: 'dummy',
+      patient: 'dummy patient',
+      type: 'emergency incident',
+      status: 'completed',
+      requestedOn: '01/01/2021',
+      completedOn: '02/01/2021',
+    } as Lab
+    const expectedPatientHistoryRecords = [
+      {
+        date: new Date('01/01/2021'),
+        type: HistoryRecordType.LAB,
+        info: 'Requested - emergency incident',
+        recordId: '123',
+        id: 'requestedLab123',
+      },
+      {
+        date: new Date('02/01/2021'),
+        type: HistoryRecordType.LAB,
+        info: 'Completed - emergency incident',
+        recordId: '123',
+        id: 'completedLab123',
+      },
+    ] as PatientHistoryRecord[]
+
+    const actualPatientHistoryRecords = convertLab(mockLab)
+    expect(actualPatientHistoryRecords).toEqual(expectedPatientHistoryRecords)
+  })
+
+  it('should convert canceled lab events correctly', () => {
+    const mockLab = {
+      id: '123',
+      code: 'dummy',
+      patient: 'dummy patient',
+      type: 'emergency incident',
+      status: 'canceled',
+      requestedOn: '01/01/2021',
+      canceledOn: '02/01/2021',
+    } as Lab
+    const expectedPatientHistoryRecords = [
+      {
+        date: new Date('01/01/2021'),
+        type: HistoryRecordType.LAB,
+        info: 'Requested - emergency incident',
+        recordId: '123',
+        id: 'requestedLab123',
+      },
+      {
+        date: new Date('02/01/2021'),
+        type: HistoryRecordType.LAB,
+        info: 'Canceled - emergency incident',
+        recordId: '123',
+        id: 'canceledLab123',
+      },
+    ] as PatientHistoryRecord[]
+
+    const actualPatientHistoryRecords = convertLab(mockLab)
+    expect(actualPatientHistoryRecords).toEqual(expectedPatientHistoryRecords)
+  })
+})
+
+describe('test convertAppointment', () => {
+  beforeEach(() => {
+    jest.resetAllMocks()
+  })
+  it('should convert appointment events correctly', () => {
+    const mockAppointment = {
+      id: '123',
+      startDateTime: '01/01/2021',
+      endDateTime: '01/02/2021',
+      patient: 'John Doe',
+      reason: 'blood test',
+      type: 'regular checkup',
+    } as Appointment
+    const expectedPatientHistoryRecords = [
+      {
+        date: new Date('01/01/2021'),
+        type: HistoryRecordType.APPOINTMENT,
+        info: 'Started - regular checkup',
+        recordId: '123',
+        id: 'startedAppt123',
+      },
+      {
+        date: new Date('01/02/2021'),
+        type: HistoryRecordType.APPOINTMENT,
+        info: 'Ended - regular checkup',
+        recordId: '123',
+        id: 'endedAppt123',
+      },
+    ] as PatientHistoryRecord[]
+    const actualPatientHistoryRecords = convertAppointment(mockAppointment)
+    expect(actualPatientHistoryRecords).toEqual(expectedPatientHistoryRecords)
+  })
+})
diff --git a/src/__tests__/patients/view/ViewPatient.test.tsx b/src/__tests__/patients/view/ViewPatient.test.tsx
index ab46713b..7883bbf8 100644
--- a/src/__tests__/patients/view/ViewPatient.test.tsx
+++ b/src/__tests__/patients/view/ViewPatient.test.tsx
@@ -100,7 +100,7 @@ describe('ViewPatient', () => {
     setup()
 
     await waitFor(() => {
-      expect(screen.getAllByRole('tab')).toHaveLength(11)
+      expect(screen.getAllByRole('tab')).toHaveLength(12)
     })
     expect(screen.getByRole('tab', { name: /patient\.generalInformation/i })).toBeInTheDocument()
     expect(screen.getByRole('tab', { name: /patient\.relatedPersons\.label/i })).toBeInTheDocument()
@@ -115,6 +115,7 @@ describe('ViewPatient', () => {
     expect(screen.getByRole('tab', { name: /patient\.carePlan\.label/i })).toBeInTheDocument()
     expect(screen.getByRole('tab', { name: /patient\.careGoal\.label/i })).toBeInTheDocument()
     expect(screen.getByRole('tab', { name: /patient\.visits\.label/i })).toBeInTheDocument()
+    expect(screen.getByRole('tab', { name: /patient\.history\.label/i })).toBeInTheDocument()
   })
 
   it('should mark the general information tab as active and render the general information component when route is /patients/:id', async () => {