Earlier WellVisit was a single-pane skeleton — the comment in that
file even admitted "Milestones and SSHADESS sub-tabs land in a
follow-up". This commit lands them, faithful to public/components/
wellvisit.html and the three modules behind it (wellVisit.js,
milestones.js, shadess.js, all @be14578).
WellVisit.tsx is now an 83-line shell that lazy-loads four panels:
client/src/pages/wellvisit/
ByVisitAge.tsx — per-visit AAP Bright Futures recommendations
(billing codes, measurements, vaccines,
sensory/dev/proc/oral screens, growth +
feeding, expected reflexes, BMI table,
notes). Status buttons mirror vanilla
(Given/Refused/Deferred/Already-Done for
vaccines; Done/Refused/N-A for screens).
Statuses persist to localStorage under
ped_visit_statuses (same key as vanilla).
Milestones.tsx — checklist per age group, three-state toggle
(✓/✗/blank=not assessed), All-Yes/Clear,
Generate → /api/generate-milestone-narrative,
3-sentence summary → /api/generate-milestone-
summary, Copy-to-Note bridge.
Shadess.tsx — 8 SSHADESS domains verbatim from shadess.js
(Strengths, School, Home, Activities, Drugs,
Emotions/Eating, Sexuality, Safety) with
concern_if auto-flag, skip toggle, manual
concern toggle, optional Listen-In recorder,
Generate → /api/well-visit/shadess.
VisitNote.tsx — pre-existing note generator + carry-over
pickup from sessionStorage so the user can
flow Milestones → SSHADESS → Note without
retyping. Now also includes the recorder.
Server: GET /api/schedule-data now also returns wellVisitCodes,
growthReference, reflexesReference, and bmiClassification so the
React side has everything it needs without a second round trip.
Tests:
shared/clinical/visit-status.ts (+ .test.ts) — pure helpers
for the visitId → growth/reflex key mapping (the vanilla
tables collapse 6y/7y/8y/9y/10y onto one window, etc.) and
the reflex-status color rules. Lives in shared/ so the root
vitest config picks it up; ByVisitAge.tsx imports from there.
105 lines
3.9 KiB
TypeScript
105 lines
3.9 KiB
TypeScript
// Tests for shared/clinical/visit-status.ts. The mappings below mirror
|
|
// the dictionaries baked into public/js/wellVisit.js (@be14578) — if the
|
|
// vanilla schedule changes, both files update in the same commit.
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
mapToGrowthKey,
|
|
mapToReflexKey,
|
|
reflexStatusColor,
|
|
getStatusValue,
|
|
isSshadessVisit,
|
|
SSHADESS_VISITS,
|
|
} from './visit-status';
|
|
|
|
const G_KEYS = {
|
|
'newborn': {}, '1mo': {}, '2mo': {}, '4mo': {}, '6mo': {}, '9mo': {},
|
|
'12mo': {}, '15mo': {}, '18mo': {}, '24mo': {}, '30mo': {},
|
|
'3y': {}, '4y': {}, '5y': {},
|
|
'6y_to_10y': {}, '11y_to_14y': {}, '15y_to_21y': {},
|
|
};
|
|
const R_KEYS = {
|
|
'newborn': {}, '1mo': {}, '2mo': {}, '4mo': {}, '6mo': {}, '9mo': {},
|
|
'12mo': {}, '15mo': {}, '18mo': {}, '24mo': {}, '30mo': {},
|
|
'3y_to_5y': {}, '6y_to_10y': {}, '11y_to_14y': {}, '15y_to_21y': {},
|
|
};
|
|
|
|
describe('mapToGrowthKey', () => {
|
|
it('returns the visitId itself when it is a direct key', () => {
|
|
expect(mapToGrowthKey('newborn', G_KEYS)).toBe('newborn');
|
|
expect(mapToGrowthKey('6mo', G_KEYS)).toBe('6mo');
|
|
expect(mapToGrowthKey('3y', G_KEYS)).toBe('3y');
|
|
});
|
|
it('aliases 3-5d to newborn (vanilla parity)', () => {
|
|
expect(mapToGrowthKey('3-5d', G_KEYS)).toBe('newborn');
|
|
});
|
|
it('collapses 6y..10y onto a single growth window', () => {
|
|
for (const v of ['6y','7y','8y','9y','10y']) {
|
|
expect(mapToGrowthKey(v, G_KEYS)).toBe('6y_to_10y');
|
|
}
|
|
});
|
|
it('collapses adolescence into 11y_to_14y / 15y_to_21y', () => {
|
|
for (const v of ['11y','12y','13y','14y']) {
|
|
expect(mapToGrowthKey(v, G_KEYS)).toBe('11y_to_14y');
|
|
}
|
|
for (const v of ['15y','16y','17y','18y','19y','20y','21y']) {
|
|
expect(mapToGrowthKey(v, G_KEYS)).toBe('15y_to_21y');
|
|
}
|
|
});
|
|
it('returns null when nothing matches', () => {
|
|
expect(mapToGrowthKey('totally-bogus', G_KEYS)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('mapToReflexKey', () => {
|
|
it('groups 3y/4y/5y onto 3y_to_5y (different from growth)', () => {
|
|
for (const v of ['3y','4y','5y']) {
|
|
expect(mapToReflexKey(v, R_KEYS)).toBe('3y_to_5y');
|
|
}
|
|
});
|
|
it('keeps the same 11y_to_14y / 15y_to_21y windows as growth', () => {
|
|
expect(mapToReflexKey('13y', R_KEYS)).toBe('11y_to_14y');
|
|
expect(mapToReflexKey('18y', R_KEYS)).toBe('15y_to_21y');
|
|
});
|
|
});
|
|
|
|
describe('reflexStatusColor', () => {
|
|
it('greens out resolved / integrated reflexes', () => {
|
|
expect(reflexStatusColor('Absent / integrated')).toBe('#059669');
|
|
expect(reflexStatusColor('Present (lifelong)')).toBe('#059669');
|
|
expect(reflexStatusColor('Down-going')).toBe('#059669');
|
|
});
|
|
it('blues out present-and-expected', () => {
|
|
expect(reflexStatusColor('Present')).toBe('#2563eb');
|
|
expect(reflexStatusColor('Peak')).toBe('#2563eb');
|
|
expect(reflexStatusColor('2+ symmetric')).toBe('#2563eb');
|
|
});
|
|
it('purples emerging, ambers transitional', () => {
|
|
expect(reflexStatusColor('Emerging')).toBe('#7c3aed');
|
|
expect(reflexStatusColor('Fading')).toBe('#d97706');
|
|
expect(reflexStatusColor('In transition')).toBe('#d97706');
|
|
expect(reflexStatusColor('Up-going')).toBe('#d97706');
|
|
});
|
|
it('falls back to slate for unknowns', () => {
|
|
expect(reflexStatusColor('whatever')).toBe('#475569');
|
|
expect(reflexStatusColor('')).toBe('#475569');
|
|
});
|
|
});
|
|
|
|
describe('getStatusValue', () => {
|
|
it('handles undefined, bare strings, and object form', () => {
|
|
expect(getStatusValue(undefined)).toBe('');
|
|
expect(getStatusValue('Given')).toBe('Given');
|
|
expect(getStatusValue({ status: 'Refused', note: '' })).toBe('Refused');
|
|
expect(getStatusValue({ status: '', note: 'context' })).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('isSshadessVisit', () => {
|
|
it('matches all 12+ visits exactly', () => {
|
|
for (const v of SSHADESS_VISITS) expect(isSshadessVisit(v)).toBe(true);
|
|
});
|
|
it('rejects under-12 visits', () => {
|
|
for (const v of ['newborn','6mo','11y','5y']) expect(isSshadessVisit(v)).toBe(false);
|
|
});
|
|
});
|