const { test } = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const root = path.join(__dirname, '..'); function read(relativePath) { return fs.readFileSync(path.join(root, relativePath), 'utf8'); } function scriptTagSource(indexHtml, src) { const escaped = src.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const match = indexHtml.match(new RegExp(']*\\bsrc="' + escaped + '(?:\\?v=[^"]+)?"[^>]*><\\/script>')); return match ? match[0] : ''; } const clinicalScripts = [ '/js/hospitalCourse.js', '/js/chartReview.js', '/js/soap.js', '/js/milestones.js', '/js/sickVisit.js', '/js/ed-encounters.js', '/js/encounters.js' ]; test('clinical note-generation scripts are loaded once from index', () => { const indexHtml = read('public/index.html'); clinicalScripts.forEach((src) => { const matches = indexHtml.match(new RegExp('src="' + src.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '(?:\\?v=[^"]+)?"', 'g')) || []; assert.equal(matches.length, 1, `${src} should be loaded exactly once`); }); }); test('clinical note generators are wired to their expected tabs', () => { const expectations = { 'public/js/hospitalCourse.js': 'hospital', 'public/js/chartReview.js': 'chart', 'public/js/soap.js': 'soap', 'public/js/milestones.js': 'wellvisit', 'public/js/sickVisit.js': 'sickvisit', 'public/js/ed-encounters.js': 'ed' }; for (const [file, tab] of Object.entries(expectations)) { const source = read(file); assert.match(source, new RegExp("e\\.detail\\.tab !== '" + tab + "'|e\\.detail\\.tab === '" + tab + "'"), `${file} should listen for ${tab} tab activation`); } }); test('saved encounter infrastructure exposes required globals', () => { const encounters = read('public/js/encounters.js'); ['wirePauseButton', 'registerEncounterLoadHandler', 'saveEncounter', 'loadSavedEncountersList'].forEach((name) => { assert.match(encounters, new RegExp('window\\.' + name + '\\s*='), `encounters.js should expose window.${name}`); }); assert.match(encounters, /sessionStorage\.setItem\('_savedEncId_' \+ type, data\.id\)/, 'saved encounter IDs should survive refresh within the tab'); }); test('clinical note generators preserve save/load integration points', () => { const loadHandlers = { 'public/js/hospitalCourse.js': { type: 'hospital', note: 'hc-course-text' }, 'public/js/chartReview.js': { type: 'chart', note: 'cr-review-text' }, 'public/js/soap.js': { type: 'soap', note: 'soap-text' }, 'public/js/sickVisit.js': { type: 'sickvisit', note: 'sick-note-text' }, 'public/js/ed-encounters.js': { type: 'ed', note: 'composeFinalNoteForSave' } }; for (const [file, expected] of Object.entries(loadHandlers)) { const source = read(file); assert.match(source, new RegExp("registerEncounterLoadHandler\\('" + expected.type + "'"), `${file} should register a load handler for ${expected.type}`); assert.match(source, new RegExp(expected.note.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), `${file} should reference its generated note target`); } const encounters = read('public/js/encounters.js'); ['hospital', 'chart', 'soap', 'sickvisit', 'wellvisit', 'encounter', 'dictation'].forEach((type) => { assert.match(encounters, new RegExp("saveFromTab\\('" + type + "'|enc_type:\\s*type"), `encounters.js should support saving ${type}`); }); assert.match(read('public/js/sickVisit.js'), /enc_type:\s*'sickvisit'/, 'sick visit has an inline save flow'); assert.match(read('public/js/ed-encounters.js'), /enc_type:\s*'ed'/, 'ED has an inline draft/final save flow'); }); test('clinical note scripts avoid browser-native prompt and confirm UI', () => { const files = clinicalScripts.map((src) => 'public' + src); files.forEach((file) => { const source = read(file); assert.doesNotMatch(source, /\bprompt\s*\(/, `${file} should not use prompt()`); assert.doesNotMatch(source, /\balert\s*\(/, `${file} should not use alert()`); assert.doesNotMatch(source, /\bconfirm\s*\(/, `${file} should not use confirm()`); }); }); test('clinical note scripts avoid inline DOM event handlers', () => { const files = clinicalScripts.map((src) => 'public' + src); files.forEach((file) => { const source = read(file); assert.doesNotMatch(source, /\son[a-z]+\s*=/i, `${file} should wire events with addEventListener instead of inline handlers`); }); }); test('live speech transcript rendering escapes interim browser text', () => { assert.match( read('public/js/sickVisit.js'), /transcriptEl\.innerHTML = esc\(_sickTranscript\) \+ \(interim \? '' \+ esc\(interim\)/, 'sick visit live transcript should escape browser speech text before innerHTML' ); assert.match( read('public/js/ed-encounters.js'), /transcriptEl\.innerHTML = escHtml\(_liveTranscript\) \+ \(interim \? '' \+ escHtml\(interim\)/, 'ED live transcript should escape browser speech text before innerHTML' ); }); test('clinical AI outputs are inserted as text or sanitized output helpers', () => { const expectations = { 'public/js/hospitalCourse.js': /setOutputText\(courseText, data\.hospitalCourse\)/, 'public/js/chartReview.js': /setOutputText\(reviewText, data\.review\)/, 'public/js/soap.js': /setOutputText\(soapText, data\.soap\)/, 'public/js/sickVisit.js': /setOutputText\(noteEl, data\.note\)/, 'public/js/ed-encounters.js': /textEl\.textContent = stage\.note|t\.textContent = note/ }; for (const [file, pattern] of Object.entries(expectations)) { assert.match(read(file), pattern, `${file} should not inject generated clinical text as raw HTML`); } });