fix: finish all patient related upgrades

This commit is contained in:
voltsrage
2026-06-23 18:17:13 +08:00
parent 7751d6df06
commit 5d46200941
20 changed files with 985 additions and 19 deletions
@@ -0,0 +1,60 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { mount } from '@vue/test-utils'
import EncounterTimeline from '@/components/patient/EncounterTimeline.vue'
const events = [
{
type: 'observation',
timestamp: '2026-06-23T10:00:00Z',
observationCode: 'TEMP_C',
value: 38.8,
unit: 'C',
},
{
type: 'alert',
timestamp: '2026-06-23T11:00:00Z',
alertType: 'QsofaScreen',
severity: 'Warning',
status: 'Open',
},
{
type: 'medication',
timestamp: '2026-06-23T12:00:00Z',
drugName: 'Ceftriaxone',
dose: 1,
doseUnit: 'g',
route: 'IV',
},
]
describe('EncounterTimeline', () => {
beforeEach(() => {
vi.useFakeTimers()
vi.setSystemTime(new Date('2026-06-23T14:00:00Z'))
})
afterEach(() => {
vi.useRealTimers()
})
it('rendersEvents', () => {
const wrapper = mount(EncounterTimeline, { props: { events } })
expect(wrapper.text()).toContain('Encounter Timeline')
expect(wrapper.text()).toContain('Ceftriaxone')
expect(wrapper.text()).toContain('qSOFA Screen')
})
it('filtersByEventType', async () => {
const wrapper = mount(EncounterTimeline, { props: { events } })
const medToggle = wrapper.findAll('button').find(b => b.text() === 'Medications')
await medToggle.trigger('click')
expect(wrapper.text()).not.toContain('Ceftriaxone')
expect(wrapper.text()).toContain('Temperature')
})
it('collapsesWhenHeaderClicked', async () => {
const wrapper = mount(EncounterTimeline, { props: { events } })
await wrapper.find('button').trigger('click')
expect(wrapper.text()).not.toContain('Ceftriaxone')
})
})
@@ -0,0 +1,56 @@
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import PatientBanner from '@/components/patient/PatientBanner.vue'
const encounter = {
roomBed: 'ICU-3C',
department: 'Icu',
attendingPhysician: 'Dr. Smith',
admissionReason: 'Sepsis workup',
patient: {
mrn: 'MRN-000123',
firstName: 'Jane',
lastName: 'Doe',
dateOfBirth: '1985-03-20',
gender: 'F',
bloodType: 'AbNegative',
allergies: 'Penicillin, Latex',
emergencyContactName: 'John Doe',
emergencyContactPhone: '555-0100',
},
}
describe('PatientBanner', () => {
it('showsDemographicsAndEncounterFields', () => {
const wrapper = mount(PatientBanner, { props: { encounter } })
expect(wrapper.text()).toContain('Jane Doe')
expect(wrapper.text()).toContain('MRN-000123')
expect(wrapper.text()).toContain('AB-')
expect(wrapper.text()).toContain('ICU')
expect(wrapper.text()).toContain('ICU-3C')
expect(wrapper.text()).toContain('Dr. Smith')
expect(wrapper.text()).toContain('Sepsis workup')
expect(wrapper.text()).toContain('John Doe')
})
it('highlightsKnownAllergies', () => {
const wrapper = mount(PatientBanner, { props: { encounter } })
expect(wrapper.text()).toContain('Penicillin, Latex')
const strip = wrapper.find('[role="status"]')
expect(strip.classes().join(' ')).toMatch(/bg-red-600/)
})
it('showsNkdaWhenNoAllergies', () => {
const wrapper = mount(PatientBanner, {
props: {
encounter: {
...encounter,
patient: { ...encounter.patient, allergies: null },
},
},
})
expect(wrapper.text()).toContain('NKDA')
const strip = wrapper.find('[role="status"]')
expect(strip.classes().join(' ')).not.toMatch(/bg-red-600/)
})
})
@@ -0,0 +1,57 @@
import { describe, it, expect } from 'vitest'
import {
filterMedicationsForWindow,
formatMedicationTooltip,
getMedicationsNearTimestamp,
xPixelForMedicationTime,
} from '@/composables/chartMedications'
const observations = [
{ observationCode: 'HEART_RATE', value: 72, recordedAt: '2026-06-19T11:00:00Z' },
{ observationCode: 'HEART_RATE', value: 80, recordedAt: '2026-06-19T13:00:00Z' },
{ observationCode: 'RESP_RATE', value: 18, recordedAt: '2026-06-19T12:00:00Z' },
]
const medications = [
{
id: '1',
drugName: 'Metoprolol',
dose: 5,
doseUnit: 'mg',
route: 'IV',
administeredAt: '2026-06-19T12:30:00Z',
},
{
id: '2',
drugName: 'Saline',
dose: 500,
doseUnit: 'mL',
route: 'IV',
administeredAt: '2026-06-19T10:00:00Z',
},
]
describe('chartMedications', () => {
it('filtersMedicationsToObservationWindow', () => {
const result = filterMedicationsForWindow(observations, 'HEART_RATE', medications)
expect(result).toHaveLength(1)
expect(result[0].drugName).toBe('Metoprolol')
})
it('formatsMedicationTooltip', () => {
expect(formatMedicationTooltip(medications[0])).toContain('Metoprolol 5mg (IV)')
})
it('findsMedicationsNearTimestamp', () => {
const target = new Date('2026-06-19T12:35:00Z').getTime()
const near = getMedicationsNearTimestamp(medications, target, 10 * 60 * 1000)
expect(near).toHaveLength(1)
})
it('mapsMedicationTimeToChartPixel', () => {
const chartArea = { left: 10, right: 110, top: 0, bottom: 100, width: 100, height: 100 }
const timestamps = ['2026-06-19T11:00:00Z', '2026-06-19T13:00:00Z']
const x = xPixelForMedicationTime('2026-06-19T12:00:00Z', timestamps, chartArea)
expect(x).toBe(60)
})
})
@@ -0,0 +1,38 @@
import { describe, it, expect } from 'vitest'
import {
computeAge,
formatAllergiesDisplay,
formatBloodType,
formatDepartment,
formatDobWithAge,
hasKnownAllergies,
} from '@/composables/patientFormat'
describe('patientFormat', () => {
it('computesAgeFromDob', () => {
expect(computeAge('1990-06-15', new Date('2026-06-23'))).toBe(36)
})
it('formatsBloodType', () => {
expect(formatBloodType('AbNegative')).toBe('AB-')
expect(formatBloodType('AB-')).toBe('AB-')
})
it('formatsDepartment', () => {
expect(formatDepartment('Icu')).toBe('ICU')
expect(formatDepartment('GENERAL_MEDICINE')).toBe('General Medicine')
})
it('showsNkdaWhenNoAllergies', () => {
expect(hasKnownAllergies('')).toBe(false)
expect(hasKnownAllergies(null)).toBe(false)
expect(formatAllergiesDisplay('')).toBe('NKDA')
expect(formatAllergiesDisplay(' Penicillin ')).toBe('Penicillin')
})
it('formatsDobWithAge', () => {
const text = formatDobWithAge('1990-06-15')
expect(text).toContain('1990')
expect(text).toMatch(/\(\d+y\)/)
})
})
@@ -0,0 +1,41 @@
import { describe, it, expect } from 'vitest'
import {
formatTimelineEvent,
timelineTypeLabel,
TIMELINE_EVENT_TYPES,
} from '@/composables/timelineFormat'
describe('timelineFormat', () => {
it('formatsObservationEvent', () => {
const text = formatTimelineEvent({
type: 'observation',
observationCode: 'HEART_RATE',
value: 110,
unit: 'bpm',
})
expect(text).toContain('Heart Rate')
expect(text).toContain('110')
})
it('formatsMedicationEvent', () => {
const text = formatTimelineEvent({
type: 'medication',
drugName: 'Metoprolol',
dose: 5,
doseUnit: 'mg',
route: 'IV',
})
expect(text).toBe('Metoprolol 5mg (IV)')
})
it('listsAllEventTypes', () => {
expect(TIMELINE_EVENT_TYPES.map(t => t.id)).toEqual([
'observation',
'alert',
'order',
'medication',
'status',
])
expect(timelineTypeLabel('order')).toBe('Orders')
})
})
@@ -26,6 +26,10 @@ describe('useChartData', () => {
const { chartData } = mountChartData(observations, 'HEART_RATE')
expect(chartData.value.datasets[0].data).toEqual([72, 80])
expect(chartData.value.labels).toHaveLength(2)
expect(chartData.value.timestamps).toEqual([
'2026-06-19T11:00:00Z',
'2026-06-19T12:00:00Z',
])
})
it('sortsChronologically', () => {