128 lines
6.4 KiB
JavaScript
128 lines
6.4 KiB
JavaScript
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs/promises');
|
|
const path = require('node:path');
|
|
|
|
let modulePromise;
|
|
|
|
async function loadCitationModule() {
|
|
if (!modulePromise) {
|
|
modulePromise = fs.readFile(path.join(__dirname, '..', 'public', 'js', 'assistant', 'citations.js'), 'utf8')
|
|
.then((source) => import('data:text/javascript;charset=utf-8,' + encodeURIComponent(source)));
|
|
}
|
|
return modulePromise;
|
|
}
|
|
|
|
const sources = [
|
|
{ title: 'Nelson Textbook of Pediatrics' },
|
|
{ title: 'Pediatric Asthma Guideline' },
|
|
{ resource: 'Emergency Medicine Review' }
|
|
];
|
|
|
|
test('renders citation clusters as links to matching source cards', async () => {
|
|
const { renderAssistantMarkdown } = await loadCitationModule();
|
|
const html = renderAssistantMarkdown('Give magnesium for severe exacerbation [1, 2].', sources);
|
|
assert.match(html, /href="#assistant-source-1"/);
|
|
assert.match(html, /href="#assistant-source-2"/);
|
|
assert.match(html, /title="Nelson Textbook of Pediatrics"/);
|
|
assert.match(html, /\[<a class="assistant-cite"[^>]*>1<\/a>, <a class="assistant-cite"[^>]*>2<\/a>\]/);
|
|
});
|
|
|
|
test('leaves unknown citation clusters untouched rather than guessing', async () => {
|
|
const { renderAssistantMarkdown } = await loadCitationModule();
|
|
const html = renderAssistantMarkdown('Dose statement [4].', sources);
|
|
assert.match(html, /\[4\]/);
|
|
assert.doesNotMatch(html, /assistant-source-4/);
|
|
});
|
|
|
|
test('does not merge separate citations across separate claims', async () => {
|
|
const { renderAssistantMarkdown } = await loadCitationModule();
|
|
const html = renderAssistantMarkdown('Use oxygen [1]. Give bronchodilator [2].', sources);
|
|
assert.match(html, /oxygen \[<a class="assistant-cite"[^>]*>1<\/a>\]/);
|
|
assert.match(html, /bronchodilator \[<a class="assistant-cite"[^>]*>2<\/a>\]/);
|
|
assert.doesNotMatch(html, />1<\/a>, <a class="assistant-cite"[^>]*>2<\/a>/);
|
|
});
|
|
|
|
test('sorts adjacent citation tokens into one safe Vancouver cluster', async () => {
|
|
const { renderAssistantMarkdown } = await loadCitationModule();
|
|
const html = renderAssistantMarkdown('Deteriorating course [1][4][2][3].', [
|
|
{ title: 'A' }, { title: 'B' }, { title: 'C' }, { title: 'D' }
|
|
]);
|
|
assert.match(html, />1<\/a>, <a class="assistant-cite"[^>]*>2<\/a>, <a class="assistant-cite"[^>]*>3<\/a>, <a class="assistant-cite"[^>]*>4<\/a>/);
|
|
assert.doesNotMatch(html, /\]<span|\]\[/);
|
|
});
|
|
|
|
test('repairs a clearly adjacent trailing citation missing its closing bracket', async () => {
|
|
const { renderAssistantMarkdown } = await loadCitationModule();
|
|
const html = renderAssistantMarkdown('Deteriorating course [1][4][2][3', [
|
|
{ title: 'A' }, { title: 'B' }, { title: 'C' }, { title: 'D' }
|
|
]);
|
|
assert.match(html, />1<\/a>, <a class="assistant-cite"[^>]*>2<\/a>, <a class="assistant-cite"[^>]*>3<\/a>, <a class="assistant-cite"[^>]*>4<\/a>/);
|
|
});
|
|
|
|
test('does not normalize adjacent citations if any source number is unknown', async () => {
|
|
const { renderAssistantMarkdown } = await loadCitationModule();
|
|
const html = renderAssistantMarkdown('Unsupported claim [1][9].', sources);
|
|
assert.match(html, /\[9\]/);
|
|
assert.doesNotMatch(html, /assistant-source-9/);
|
|
});
|
|
|
|
test('escapes source titles inside citation link titles', async () => {
|
|
const { renderAssistantMarkdown } = await loadCitationModule();
|
|
const html = renderAssistantMarkdown('Unsafe title [1].', [{ title: 'Bad "Title" <script>' }]);
|
|
assert.match(html, /title="Bad "Title" <script>"/);
|
|
assert.doesNotMatch(html, /<script>/);
|
|
});
|
|
|
|
test('strips model-generated Sources and References sections', async () => {
|
|
const { renderAssistantMarkdown } = await loadCitationModule();
|
|
const html = renderAssistantMarkdown('Answer body [1].\n\n## Sources\n[1] duplicate source', sources);
|
|
assert.match(html, /Answer body/);
|
|
assert.doesNotMatch(html, /duplicate source/);
|
|
});
|
|
|
|
test('strips colon-style References sections', async () => {
|
|
const { renderAssistantMarkdown } = await loadCitationModule();
|
|
const html = renderAssistantMarkdown('Answer body [1].\n\n--- References:\n[1] duplicate source', sources);
|
|
assert.match(html, /Answer body/);
|
|
assert.doesNotMatch(html, /duplicate source/);
|
|
});
|
|
|
|
test('normalizes inline markdown headings before rendering', async () => {
|
|
const { renderAssistantMarkdown } = await loadCitationModule();
|
|
const html = renderAssistantMarkdown('Intro ### Management\n- oxygen', sources);
|
|
assert.match(html, /<h3>Management<\/h3>/);
|
|
assert.match(html, /<li>oxygen<\/li>/);
|
|
});
|
|
|
|
test('normalizes old saved assistant answer formatting', async () => {
|
|
const { renderAssistantMarkdown } = await loadCitationModule();
|
|
const html = renderAssistantMarkdown('Comparison of Bronchiolitis and Asthma Management in Infants ### Bronchiolitis Management - Mainstay: Supportive care [1, 2]. - Medications: bronchodilators are not routine [2]. ### Key Differences | Aspect | Bronchiolitis | Asthma | |---|---|---| | Therapy | Supportive | SABA | --- References: [1] duplicate', sources);
|
|
assert.match(html, /<h3>Bronchiolitis Management<\/h3>/);
|
|
assert.match(html, /<li>Mainstay: Supportive care/);
|
|
assert.match(html, /<table>/);
|
|
assert.doesNotMatch(html, /duplicate/);
|
|
});
|
|
|
|
test('strips inline model References after a cited sentence', async () => {
|
|
const { renderAssistantMarkdown } = await loadCitationModule();
|
|
const html = renderAssistantMarkdown('Infants <2 years often do not respond [2, 3]. --- References: [2] Book p.1 [3] Other p.2', sources);
|
|
assert.match(html, /Infants <2 years/);
|
|
assert.doesNotMatch(html, /References/);
|
|
assert.doesNotMatch(html, /Book p\.1/);
|
|
});
|
|
|
|
test('repairs smashed admission bullet list after citations', async () => {
|
|
const { renderAssistantMarkdown } = await loadCitationModule();
|
|
const html = renderAssistantMarkdown('Admission is indicated for: Apnoea [1, 2]- **Persistent oxygen saturation <90%** [1, 2]- **Severe respiratory distress** [1]', sources);
|
|
assert.match(html, /<li><strong>Persistent oxygen saturation <90%<\/strong>/);
|
|
assert.match(html, /<li><strong>Severe respiratory distress<\/strong>/);
|
|
assert.doesNotMatch(html, /\]-/);
|
|
});
|
|
|
|
test('preserves code block contents without creating citation links inside code', async () => {
|
|
const { renderAssistantMarkdown } = await loadCitationModule();
|
|
const html = renderAssistantMarkdown('```js\nconst ref = "[1]";\n```', sources);
|
|
assert.match(html, /<pre><code>const ref = "\[1\]"/);
|
|
assert.doesNotMatch(html, /assistant-source-1/);
|
|
});
|