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, '..'); const read = f => fs.readFileSync(path.join(root, f), 'utf8'); // The Create image popup showed "Generating image…" forever even though the job // had finished. Cause: the status poll called fetchAssistantImageJob, which was // never imported, so every tick threw ReferenceError — and the catch treated it // as a transient failure and rescheduled, permanently. test('every api.js function the assistant calls is actually imported', () => { const src = read('public/js/clinicalAssistant.js'); const api = read('public/js/assistant/api.js'); const marker = "from './assistant/api.js';"; const importBlock = src.slice(0, src.indexOf(marker)); const body = src.slice(importBlock.length); const exported = [...api.matchAll(/export function (\w+)/g)].map(m => m[1]); assert.ok(exported.length > 5, 'found the api surface'); const missing = exported.filter(name => new RegExp('\\b' + name + '\\s*\\(').test(body) && !new RegExp('\\b' + name + '\\b').test(importBlock)); assert.deepEqual(missing, [], 'called without being imported: ' + missing.join(', ')); }); test('a broken poll surfaces instead of retrying forever', () => { const src = read('public/js/clinicalAssistant.js'); const poll = src.slice(src.indexOf('var pollAttempts = 0;'), src.indexOf('// ── Images gallery')); // A programming error must not look like a slow image. assert.match(poll, /pollError instanceof ReferenceError \|\| pollError instanceof TypeError/); assert.match(poll, /hooks\.onError.*Cannot check image status/, 'and the user is told'); assert.match(poll, /console\.error\('\[clinical-assistant\] image polling is broken'/); // Even genuine transient failures cannot retry indefinitely. assert.match(poll, /if \(\+\+pollAttempts > 240\)/); assert.match(poll, /the image may still finish/, 'and the message says the work was not lost'); }); test('the done handler still reports success when a later step fails', () => { const src = read('public/js/clinicalAssistant.js'); // Rendering history or opening a preview must not turn a finished image into // an error state. assert.match(src, /catch \(doneError\) \{\s*\n\s*if \(typeof hooks\.onError === 'function'\) hooks\.onError\('Image ready: '/); }); test('extension cards put the name first and never wrap a number mid-digit', () => { const js = read('public/js/extensions.js'); const css = read('public/css/styles.css'); // The number led at 20px with word-break:break-all, so "5616/3764/5619" // wrapped as "5616/3764/56 19" — unreadable, and unsafe to dial. // Scope to the card renderer; other handlers reference these classes too. const card = js.slice(js.indexOf("html += '
/); assert.ok(card.indexOf('ext-name') < card.indexOf('ext-number'), 'the name is the scan target and leads'); assert.match(card, //, 'numbers may only break between groups'); assert.doesNotMatch(css, /\.ext-number\{[^}]*word-break:break-all/, 'never inside a run of digits'); assert.match(css, /\.ext-number\{[^}]*word-break:normal/); // Uniform boxes so a grid reads as rows rather than a ragged mosaic. assert.match(css, /\.ext-card\{[\s\S]*?min-height:96px;/); assert.match(css, /@media\(max-width:640px\)\{ \.ext-card\{ min-height:0; \}/, 'except on a phone'); }); test('a saved chat title keeps whole words', () => { const src = read('public/js/clinicalAssistant.js'); // slice(0, 60) cut mid-word, and every view inherited that one arbitrary cut. assert.doesNotMatch(src, /function deriveChatTitle\(\)[\s\S]{0,240}?slice\(0, 60\)/); assert.match(src, /truncateOnWord\(String\(first && first\.content/); assert.match(src, /if \(lastSpace > limit \* 0\.5\) cut = cut\.slice\(0, lastSpace\);/, 'a single very long token still falls back to a hard cut'); });