pediatric-ai-scribe-v3/test/learning-hub-ai-panel-controller.test.js
2026-05-08 16:54:48 +02:00

156 lines
6.3 KiB
JavaScript

const { test } = require('node:test');
const assert = require('node:assert/strict');
const path = require('node:path');
const { pathToFileURL } = require('node:url');
const { JSDOM } = require('jsdom');
async function loadAiPanelController() {
return import(pathToFileURL(path.join(__dirname, '..', 'public/js/learningHub/aiPanelController.js')).href);
}
function makeDom() {
return new JSDOM(`<!doctype html>
<div id="lh-ai-panel" class="hidden"></div>
<select id="lh-ai-model"><option value="">Default</option></select>
<select id="lh-ai-ctype"><option value="article">Article</option><option value="quiz">Quiz</option><option value="presentation">Presentation</option></select>
<div id="lh-ai-words-field"></div>
<div id="lh-ai-slides-field"></div>
<div id="lh-ai-quiz-row"></div>
<div id="lh-ai-quiz-card-title"></div>
<label id="lh-ai-q-toggle-label"><input id="lh-ai-q-toggle" type="checkbox"></label>
<div id="lh-ai-q-count-wrap"></div>
<button id="lh-ai-tab-prompt" class="lh-ai-tab active" data-aitab="prompt"></button>
<button id="lh-ai-tab-webdav" class="lh-ai-tab" data-aitab="webdav"></button>
<div id="lh-ai-tp-prompt" class="lh-ai-tabpanel"></div>
<div id="lh-ai-tp-webdav" class="lh-ai-tabpanel hidden"></div>
<div id="lh-ai-webdav-list"></div>`);
}
function withGlobals(dom, fn) {
const previousDocument = global.document;
const previousWindow = global.window;
const previousFetch = global.fetch;
const previousGetAuthHeaders = global.getAuthHeaders;
global.document = dom.window.document;
global.window = dom.window;
global.getAuthHeaders = function() { return {}; };
return Promise.resolve()
.then(fn)
.finally(function() {
global.document = previousDocument;
global.window = previousWindow;
global.fetch = previousFetch;
global.getAuthHeaders = previousGetAuthHeaders;
});
}
test('Learning Hub AI panel option state matches content type semantics', async () => {
const { applyAiOptionState } = await loadAiPanelController();
const els = {
wordsField: { style: {} },
slidesField: { style: {} },
quizRow: { style: {} },
cardTitle: { textContent: '' },
toggleLabel: { style: {} },
toggleCb: { checked: false, disabled: false },
countWrap: { style: {} }
};
applyAiOptionState('article', els);
assert.equal(els.wordsField.style.display, 'flex');
assert.equal(els.slidesField.style.display, 'none');
assert.equal(els.cardTitle.textContent, 'Quiz Questions (optional)');
assert.equal(els.countWrap.style.display, 'none');
els.toggleCb.checked = true;
applyAiOptionState('presentation', els);
assert.equal(els.wordsField.style.display, 'none');
assert.equal(els.slidesField.style.display, 'flex');
assert.equal(els.toggleCb.disabled, false);
assert.equal(els.countWrap.style.display, 'flex');
els.toggleCb.checked = false;
applyAiOptionState('quiz', els);
assert.equal(els.wordsField.style.display, 'none');
assert.equal(els.slidesField.style.display, 'none');
assert.equal(els.cardTitle.textContent, 'Quiz Questions');
assert.equal(els.toggleLabel.style.display, 'none');
assert.equal(els.toggleCb.checked, true);
assert.equal(els.toggleCb.disabled, true);
assert.equal(els.countWrap.style.display, 'flex');
});
test('Learning Hub AI panel opens with defaults, models, auth state, and stable handlers', async () => {
const dom = makeDom();
await withGlobals(dom, async function() {
const { createAiPanelController } = await loadAiPanelController();
let modelBuilds = 0;
let fetches = 0;
const paths = [];
const panel = document.getElementById('lh-ai-panel');
panel.scrollIntoView = function() {};
window._buildModelOptions = function(sel) {
modelBuilds += 1;
const opt = document.createElement('option');
opt.value = 'model-a';
opt.textContent = 'Model A';
sel.appendChild(opt);
};
global.fetch = function(url) {
fetches += 1;
assert.equal(url, '/api/auth/me');
return Promise.resolve({ json: function() { return Promise.resolve({ user: { nextcloud_url: 'https://cloud.example', webdav_learning_path: '/Learning' } }); } });
};
const controller = createAiPanelController({
browseWebdav: function() {},
getEditorType: function() { return 'presentation'; },
getWebdavPath: function() { return '/Learning'; },
setWebdavPath: function(pathValue) { paths.push(pathValue); }
});
controller.open();
controller.open();
await new Promise(function(resolve) { setImmediate(resolve); });
assert.equal(panel.classList.contains('hidden'), false);
assert.equal(document.getElementById('lh-ai-ctype').value, 'presentation');
assert.equal(document.getElementById('lh-ai-words-field').style.display, 'none');
assert.equal(document.getElementById('lh-ai-slides-field').style.display, 'flex');
assert.equal(document.getElementById('lh-ai-tab-webdav').style.display, '');
assert.deepEqual(paths, ['/Learning', '/Learning']);
assert.equal(modelBuilds, 1);
assert.equal(fetches, 2);
const ctype = document.getElementById('lh-ai-ctype');
ctype.value = 'quiz';
ctype.dispatchEvent(new window.Event('change'));
assert.equal(document.getElementById('lh-ai-q-toggle').disabled, true);
assert.equal(document.getElementById('lh-ai-quiz-card-title').textContent, 'Quiz Questions');
});
});
test('Learning Hub AI panel tab switch lazily browses empty WebDAV panel', async () => {
const dom = makeDom();
await withGlobals(dom, async function() {
const { createAiPanelController } = await loadAiPanelController();
const browsedPaths = [];
const controller = createAiPanelController({
browseWebdav: function(pathValue) { browsedPaths.push(pathValue); },
getEditorType: function() { return 'article'; },
getWebdavPath: function() { return '/Learning/Sub'; },
setWebdavPath: function() {}
});
controller.switchTab('webdav');
assert.equal(document.getElementById('lh-ai-tab-webdav').classList.contains('active'), true);
assert.equal(document.getElementById('lh-ai-tp-webdav').classList.contains('hidden'), false);
assert.deepEqual(browsedPaths, ['/Learning/Sub']);
document.getElementById('lh-ai-webdav-list').innerHTML = '<button class="lh-webdav-item"></button>';
controller.switchTab('webdav');
assert.deepEqual(browsedPaths, ['/Learning/Sub']);
});
});