test clinical note entrypoints
This commit is contained in:
parent
f8b563e642
commit
ed45822cb6
8 changed files with 131 additions and 7 deletions
|
|
@ -490,7 +490,7 @@
|
|||
<script defer src="/js/hospitalCourse.js"></script>
|
||||
<script defer src="/js/chartReview.js"></script>
|
||||
<script defer src="/js/soap.js"></script>
|
||||
<script defer src="/js/milestones.js"></script>
|
||||
<script type="module" src="/js/milestones.js"></script>
|
||||
<script defer src="/js/peGuide.js"></script>
|
||||
<script defer src="/js/extensions.js"></script>
|
||||
<script type="module" src="/js/notes.js"></script>
|
||||
|
|
|
|||
|
|
@ -80,7 +80,8 @@
|
|||
var entry = document.createElement('div');
|
||||
entry.className = 'lab-entry';
|
||||
entry.innerHTML = '<input type="date" class="lab-date"><textarea class="lab-values" placeholder="Lab results..." rows="3"></textarea>' +
|
||||
'<button class="btn-sm btn-ghost" onclick="this.parentElement.remove()"><i class="fas fa-trash"></i></button>';
|
||||
'<button class="btn-sm btn-ghost remove-lab-btn"><i class="fas fa-trash"></i></button>';
|
||||
entry.querySelector('.remove-lab-btn').addEventListener('click', function() { entry.remove(); });
|
||||
labsContainer.appendChild(entry);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@
|
|||
if (e.results[i].isFinal) _liveTranscript += e.results[i][0].transcript + ' ';
|
||||
else interim = e.results[i][0].transcript;
|
||||
}
|
||||
if (transcriptEl) transcriptEl.innerHTML = _liveTranscript + (interim ? '<span style="color:#9ca3af;">' + interim + '</span>' : '');
|
||||
if (transcriptEl) transcriptEl.innerHTML = escHtml(_liveTranscript) + (interim ? '<span style="color:#9ca3af;">' + escHtml(interim) + '</span>' : '');
|
||||
};
|
||||
_recognition.onend = function() { if (_recording && !_paused) try { _recognition.start(); } catch(e) {} };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@
|
|||
var entry = document.createElement('div');
|
||||
entry.className = 'lab-entry';
|
||||
entry.innerHTML = '<input type="date" class="lab-date"><textarea class="lab-values" placeholder="Lab results..." rows="3"></textarea>' +
|
||||
'<button class="btn-sm btn-ghost" onclick="this.parentElement.remove()"><i class="fas fa-trash"></i></button>';
|
||||
'<button class="btn-sm btn-ghost remove-lab-btn"><i class="fas fa-trash"></i></button>';
|
||||
entry.querySelector('.remove-lab-btn').addEventListener('click', function() { entry.remove(); });
|
||||
labsContainer.appendChild(entry);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
(function() {
|
||||
var _inited = false;
|
||||
var MILESTONES_DATA = {}; // Will be loaded from API
|
||||
|
||||
|
|
@ -237,4 +236,3 @@
|
|||
|
||||
console.log('✅ Milestones module loaded');
|
||||
} // end initMilestones
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@
|
|||
if (e.results[i].isFinal) _sickTranscript += e.results[i][0].transcript + ' ';
|
||||
else interim = e.results[i][0].transcript;
|
||||
}
|
||||
if (transcriptEl) transcriptEl.innerHTML = _sickTranscript + (interim ? '<span style="color:#9ca3af;">' + interim + '</span>' : '');
|
||||
if (transcriptEl) transcriptEl.innerHTML = esc(_sickTranscript) + (interim ? '<span style="color:#9ca3af;">' + esc(interim) + '</span>' : '');
|
||||
};
|
||||
_sickRecognition.onend = function() { if (_sickRecording && !_sickPaused) try { _sickRecognition.start(); } catch(e) {} };
|
||||
}
|
||||
|
|
|
|||
123
test/clinical-notes-entrypoints.test.js
Normal file
123
test/clinical-notes-entrypoints.test.js
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
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('<script\\b[^>]*\\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 \? '<span[^']*>' \+ 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 \? '<span[^']*>' \+ 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`);
|
||||
}
|
||||
});
|
||||
|
|
@ -8,6 +8,7 @@ const moduleEntrypoints = [
|
|||
'public/js/clinicalAssistant.js',
|
||||
'public/js/admin.js',
|
||||
'public/js/diagrams.js',
|
||||
'public/js/milestones.js',
|
||||
'public/js/notes.js',
|
||||
'public/js/wellVisit.js'
|
||||
];
|
||||
|
|
|
|||
Loading…
Reference in a new issue