const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs/promises');
const path = require('node:path');
const MarkdownIt = require('markdown-it');
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="Source 1: Nelson Textbook of Pediatrics"/);
assert.match(html, /data-source-number="1"/);
assert.match(html, /data-source-number="2"/);
assert.match(html, /]*>src<\/a> ]*>src<\/a>/);
});
test('can render citation labels as numbers for PDF export', async () => {
const { renderAssistantMarkdown } = await loadCitationModule();
const html = renderAssistantMarkdown('Give magnesium for severe exacerbation [1, 2].', sources, { citationLabel: 'number' });
assert.match(html, /]*>1<\/a> ]*>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 ]*data-source-number="1"[^>]*>src<\/a>/);
assert.match(html, /bronchodilator ]*data-source-number="2"[^>]*>src<\/a>/);
assert.doesNotMatch(html, /data-source-number="1"[^>]*>src<\/a>]*data-source-number="2"/);
});
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, /data-source-number="1"[^>]*>src<\/a> ]*data-source-number="2"[^>]*>src<\/a> ]*data-source-number="3"[^>]*>src<\/a> ]*data-source-number="4"[^>]*>src<\/a>/);
assert.doesNotMatch(html, /\] {
const { renderAssistantMarkdown } = await loadCitationModule();
const html = renderAssistantMarkdown('Deteriorating course [1][4][2][3', [
{ title: 'A' }, { title: 'B' }, { title: 'C' }, { title: 'D' }
]);
assert.match(html, /data-source-number="1"[^>]*>src<\/a> ]*data-source-number="2"[^>]*>src<\/a> ]*data-source-number="3"[^>]*>src<\/a> ]*data-source-number="4"[^>]*>src<\/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"