blindspot / frontend /src /reference /guard.test.ts
yashwrites's picture
Signs on the film, draw tool, My search off by default, example films and Radiopaedia links (part 2)
2e6a666 verified
Raw History Blame Contribute Delete
7.47 kB
import { describe, expect, it } from 'vitest';
import { guardReference, guardReferenceLabel, pickExamples, safeExternalUrl, uiTerms } from './guard';
describe('guardReference', () => {
it('returns empty lists for anything that is not the payload', () => {
for (const v of [null, undefined, 3, 'x', [], { labels: 'no' }]) expect(guardReference(v)).toEqual({ labels: [], normal_examples: [] });
});
it('keeps a complete label and its examples', () => {
const r = guardReference({
labels: [{
label: 'nodule', display: 'Nodule', kind: 'focal', one_liner: 'A small round spot.', key_signs: ['Round'], mimics: ['Nipple shadow'],
commonly_confused_with: ['mass'], search_tip: 'Use the loupe.', radiopaedia_url: 'https://radiopaedia.org/articles/pulmonary-nodule', review_status: 'ai_draft',
examples: [{ case_id: 'cxd_1', image_url: '/api/cases/cxd_1/image', width: 1024, height: 1024, finding: { finding_id: 'cxd_1#F1', polygon: [[1, 2], [3, 4], [5, 6]], bbox: [1, 2, 5, 6], relative_location: 'right upper zone', side: 'right' } }],
}],
normal_examples: [{ case_id: 'cxd_2', image_url: '/api/cases/cxd_2/image', width: 1024, height: 1024 }],
});
expect(r.labels).toHaveLength(1);
expect(r.labels[0].examples[0].finding?.polygon).toHaveLength(3);
expect(r.labels[0].examples[0].finding?.relative_location).toBe('right upper zone');
expect(r.normal_examples).toHaveLength(1);
});
it('fills missing fields with empty values instead of throwing', () => {
const r = guardReference({ labels: [{ label: 'pleural_thickening' }, { nope: 1 }, null], normal_examples: [{ case_id: 'x' }] });
expect(r.labels).toHaveLength(1);
expect(r.labels[0]).toMatchObject({ display: 'pleural thickening', kind: 'focal', one_liner: '', key_signs: [], mimics: [], examples: [], radiopaedia_url: null });
expect(r.normal_examples).toEqual([]); // no image, no size: nothing to draw
});
it('drops examples without an image and tolerates a missing outline', () => {
const r = guardReference({ labels: [{ label: 'mass', examples: [{ case_id: 'a' }, { case_id: 'b', image_url: '/i.png', width: 10, height: 10, finding: { polygon: [[1, 1]], bbox: [0, 0, 1] } }] }] });
expect(r.labels[0].examples).toHaveLength(1);
expect(r.labels[0].examples[0].finding).toMatchObject({ polygon: null, bbox: null, relative_location: '' });
});
it('reads the nested review status of a raw teaching card', () => {
expect(guardReference({ labels: [{ label: 'mass', review: { status: 'student_reviewed' } }] }).labels[0].review_status).toBe('student_reviewed');
});
});
describe('safeExternalUrl', () => {
it('accepts https only', () => {
expect(safeExternalUrl('https://radiopaedia.org/articles/pulmonary-nodule')).toBe('https://radiopaedia.org/articles/pulmonary-nodule');
for (const bad of ['javascript:alert(1)', 'http://radiopaedia.org/x', 'radiopaedia.org', '', null, 4]) expect(safeExternalUrl(bad)).toBeNull();
});
});
describe('uiTerms', () => {
it('calls the lens the magnifier', () => {
expect(uiTerms('Use the loupe on the apices. Loupe off.')).toBe('Use the magnifier on the apices. Magnifier off.');
});
});
describe('guardReference — volumetric examples (round 4)', () => {
it('reads a CT example with its volume block, the measure slice and the finding values', () => {
const r = guardReference({ labels: [{
label: 'pancreatic_tumour', display: 'Pancreatic tumour', kind: 'focal',
examples: [{
case_id: 'vol_001', image_url: '/api/cases/vol_001/image', width: 64, height: 64, modality: 'ct',
volume_url: '/api/cases/vol_001/volume', mask_url: '/api/cases/vol_001/maskvol', shape: [16, 64, 64], spacing: [3, 1.5, 1.5], window: { wc: 50, ww: 400 },
provenance: { dataset: 'Medical Segmentation Decathlon, Task07 Pancreas', segmented_by: 'an abdominal radiologist (single reader)' },
finding: { finding_id: 'vol_001#F1', bbox: [20, 32, 29, 41], relative_location: 'middle slices of the volume', slice_range: [6, 10], measure: { long_mm: 13.5, slice: 8 }, label_values: [2] },
}],
}] });
const l = r.labels[0];
expect(l.modality).toBe('ct');
const ex = l.examples[0];
expect(ex.modality).toBe('ct');
expect(ex.volume).toEqual({ volume_url: '/api/cases/vol_001/volume', mask_url: '/api/cases/vol_001/maskvol', shape: [16, 64, 64], spacing: [3, 1.5, 1.5], window: { wc: 50, ww: 400 }, slice: 8, label_values: [2] });
expect(ex.finding?.slice_range).toEqual([6, 10]);
expect(ex.provenance).toMatchObject({ dataset: 'Medical Segmentation Decathlon, Task07 Pancreas' });
});
it('a volume example without a preview image still renders (the slice is drawn from the voxels); a broken volume block is dropped', () => {
const r = guardReference({ labels: [{ label: 'brain_tumour', examples: [
{ case_id: 'v', volume_url: '/v.gz', mask_url: '/m.gz', shape: [8, 32, 32] },
{ case_id: 'w', volume_url: '/v.gz', shape: [8, 32, 32] },
] }] });
expect(r.labels[0].modality).toBe('mr');
expect(r.labels[0].examples).toHaveLength(1);
const ex = r.labels[0].examples[0];
expect(ex.volume?.slice).toBe(4);
expect(ex.volume?.spacing).toEqual([1, 1, 1]);
expect(ex.volume?.window.ww).toBe(1000);
});
it('an X-ray example without a modality stays cxr and carries no volume', () => {
const r = guardReference({ labels: [{ label: 'nodule', examples: [{ case_id: 'c', image_url: '/i.png', width: 10, height: 10 }] }] });
expect(r.labels[0].modality).toBe('cxr');
expect(r.labels[0].examples[0]).toMatchObject({ modality: 'cxr', volume: null, provenance: null });
});
});
describe('pickExamples (round 5: the two thumbnails under a debrief row)', () => {
const ex = (case_id: string, outline: boolean, volume = false) => ({
case_id, image_url: `/i/${case_id}.png`, width: 10, height: 10, modality: 'cxr' as const, provenance: null,
volume: volume ? { volume_url: '/v', mask_url: '/m', shape: [4, 4, 4] as [number, number, number], spacing: [1, 1, 1] as [number, number, number], window: { wc: 0, ww: 1 }, slice: 1, label_values: [] } : null,
finding: outline ? { finding_id: `${case_id}#F1`, polygon: [[1, 1], [2, 2], [3, 1]] as [number, number][], bbox: null, relative_location: 'right upper zone', side: 'right', slice_range: null } : null,
});
it('prefers examples with an outline to draw, distinct cases, at most n', () => {
const got = pickExamples([ex('a', false), ex('b', true), ex('b', true), ex('c', true), ex('d', true)], 2);
expect(got.map((e) => e.case_id)).toEqual(['b', 'c']);
});
it('falls back to plain films when no outline exists, and never shows the case being read', () => {
expect(pickExamples([ex('a', false), ex('b', false)], 2).map((e) => e.case_id)).toEqual(['a', 'b']);
expect(pickExamples([ex('a', true), ex('b', true), ex('c', true)], 2, 'a').map((e) => e.case_id)).toEqual(['b', 'c']);
expect(pickExamples([], 2)).toEqual([]);
});
it('counts a volume example as drawable', () => {
expect(pickExamples([ex('a', false), ex('v', false, true)], 1).map((e) => e.case_id)).toEqual(['v']);
});
it('guardReferenceLabel reads one entry with its sign ids', () => {
const l = guardReferenceLabel({ label: 'effusion', signs: ['meniscus', 3, ''], examples: [] });
expect(l?.signs).toEqual(['meniscus']);
expect(guardReferenceLabel(null)).toBeNull();
});
});