extract learning hub ai panel controller
This commit is contained in:
parent
48931e8024
commit
e5e0435b64
5 changed files with 289 additions and 103 deletions
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from './learningHub/tiptapEditor.js';
|
||||
import { esc, sanitizeHtml } from './learningHub/sanitize.js';
|
||||
import { deleteJson, getJson, sendJson } from './learningHub/api.js';
|
||||
import { createAiPanelController } from './learningHub/aiPanelController.js';
|
||||
import {
|
||||
renderOptionRowShell,
|
||||
renderQuestionBlockShell
|
||||
|
|
@ -44,6 +45,12 @@ import { createWebdavController } from './learningHub/webdavController.js';
|
|||
showToast: function(message, type) { showToast(message, type); }
|
||||
});
|
||||
var webdav = createWebdavController({});
|
||||
var aiPanel = createAiPanelController({
|
||||
browseWebdav: function(path) { webdav.browse(path); },
|
||||
getEditorType: function() { return (document.getElementById('lh-cms-edit-type') || {}).value; },
|
||||
getWebdavPath: function() { return webdav.getCurrentPath(); },
|
||||
setWebdavPath: function(path) { webdav.setCurrentPath(path); }
|
||||
});
|
||||
|
||||
// ── Load when tab activated ────────────────────────────────
|
||||
document.addEventListener('tabChanged', function(e) {
|
||||
|
|
@ -117,8 +124,8 @@ import { createWebdavController } from './learningHub/webdavController.js';
|
|||
if (e.target.closest('#btn-lh-download-pptx')) { slides.downloadPptx(); return; }
|
||||
|
||||
// AI panel
|
||||
if (e.target.closest('#btn-lh-ai-open')) { openAiPanel(); return; }
|
||||
if (e.target.closest('#btn-lh-ai-close')) { closeAiPanel(); return; }
|
||||
if (e.target.closest('#btn-lh-ai-open')) { aiPanel.open(); return; }
|
||||
if (e.target.closest('#btn-lh-ai-close')) { aiPanel.close(); return; }
|
||||
if (e.target.closest('#btn-lh-ai-generate')) { runAiGenerate(); return; }
|
||||
if (e.target.closest('#btn-lh-ai-refine-body')) { toggleRefineBar(); return; }
|
||||
if (e.target.closest('#btn-lh-refine-submit')) { submitRefineBody(); return; }
|
||||
|
|
@ -128,7 +135,7 @@ import { createWebdavController } from './learningHub/webdavController.js';
|
|||
|
||||
// AI tabs
|
||||
var aiTab = e.target.closest('.lh-ai-tab');
|
||||
if (aiTab && aiTab.dataset.aitab) { switchAiTab(aiTab.dataset.aitab); return; }
|
||||
if (aiTab && aiTab.dataset.aitab) { aiPanel.switchTab(aiTab.dataset.aitab); return; }
|
||||
|
||||
// WebDAV list items
|
||||
var wdItem = e.target.closest('.lh-webdav-item');
|
||||
|
|
@ -438,104 +445,6 @@ import { createWebdavController } from './learningHub/webdavController.js';
|
|||
// AI GENERATE PANEL
|
||||
// ============================================================
|
||||
|
||||
function openAiPanel() {
|
||||
var panel = document.getElementById('lh-ai-panel');
|
||||
if (!panel) return;
|
||||
panel.classList.remove('hidden');
|
||||
populateAiModelSelect();
|
||||
|
||||
// Sync content type selector with the editor's current type
|
||||
var editorType = (document.getElementById('lh-cms-edit-type') || {}).value || 'article';
|
||||
var ctype = document.getElementById('lh-ai-ctype');
|
||||
if (ctype) ctype.value = editorType;
|
||||
updateAiOptions(editorType);
|
||||
|
||||
// Hide WebDAV tab if Nextcloud not configured
|
||||
getJson('/api/auth/me')
|
||||
.then(function(d) {
|
||||
var tab = document.getElementById('lh-ai-tab-webdav');
|
||||
if (tab) tab.style.display = (d.user && d.user.nextcloud_url) ? '' : 'none';
|
||||
if (d.user && d.user.webdav_learning_path) webdav.setCurrentPath(d.user.webdav_learning_path || '/');
|
||||
});
|
||||
|
||||
// Wire ctype change once (use a flag so it doesn't stack on re-opens)
|
||||
var ctypeEl = document.getElementById('lh-ai-ctype');
|
||||
var toggleEl = document.getElementById('lh-ai-q-toggle');
|
||||
if (ctypeEl && !ctypeEl._wired) {
|
||||
ctypeEl.addEventListener('change', function() { updateAiOptions(ctypeEl.value); });
|
||||
ctypeEl._wired = true;
|
||||
}
|
||||
if (toggleEl && !toggleEl._wired) {
|
||||
toggleEl.addEventListener('change', function() { updateAiOptions((document.getElementById('lh-ai-ctype') || {}).value); });
|
||||
toggleEl._wired = true;
|
||||
}
|
||||
|
||||
panel.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
|
||||
function updateAiOptions(type) {
|
||||
var wordsField = document.getElementById('lh-ai-words-field');
|
||||
var slidesField = document.getElementById('lh-ai-slides-field');
|
||||
var quizRow = document.getElementById('lh-ai-quiz-row');
|
||||
var cardTitle = document.getElementById('lh-ai-quiz-card-title');
|
||||
var toggleLabel = document.getElementById('lh-ai-q-toggle-label');
|
||||
var toggleCb = document.getElementById('lh-ai-q-toggle');
|
||||
var countWrap = document.getElementById('lh-ai-q-count-wrap');
|
||||
|
||||
// Use style.display directly — classList.toggle('hidden') doesn't work because
|
||||
// .lh-ai-opt-field { display:flex } is defined later in CSS and overrides .hidden
|
||||
if (wordsField) wordsField.style.display = (type === 'quiz' || type === 'presentation') ? 'none' : 'flex';
|
||||
if (slidesField) slidesField.style.display = (type === 'presentation') ? 'flex' : 'none';
|
||||
|
||||
if (type === 'quiz') {
|
||||
// Quiz: questions are the whole point — show the card prominently, no toggle needed
|
||||
if (quizRow) quizRow.style.display = 'block';
|
||||
if (cardTitle) cardTitle.textContent = 'Quiz Questions';
|
||||
if (toggleLabel) toggleLabel.style.display = 'none';
|
||||
if (toggleCb) { toggleCb.checked = true; toggleCb.disabled = true; }
|
||||
if (countWrap) countWrap.style.display = 'flex';
|
||||
} else if (type === 'presentation') {
|
||||
// Presentation: optional questions (same as article)
|
||||
if (quizRow) quizRow.style.display = 'block';
|
||||
if (cardTitle) cardTitle.textContent = 'Quiz Questions (optional)';
|
||||
if (toggleLabel) toggleLabel.style.display = 'flex';
|
||||
if (toggleCb) toggleCb.disabled = false;
|
||||
var checkedP = toggleCb ? toggleCb.checked : false;
|
||||
if (countWrap) countWrap.style.display = checkedP ? 'flex' : 'none';
|
||||
} else {
|
||||
// Article / pearl: optional questions via toggle
|
||||
if (quizRow) quizRow.style.display = 'block';
|
||||
if (cardTitle) cardTitle.textContent = 'Quiz Questions (optional)';
|
||||
if (toggleLabel) toggleLabel.style.display = 'flex';
|
||||
if (toggleCb) toggleCb.disabled = false;
|
||||
var checked = toggleCb ? toggleCb.checked : false;
|
||||
if (countWrap) countWrap.style.display = checked ? 'flex' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function closeAiPanel() {
|
||||
var panel = document.getElementById('lh-ai-panel');
|
||||
if (panel) panel.classList.add('hidden');
|
||||
}
|
||||
|
||||
function populateAiModelSelect() {
|
||||
var sel = document.getElementById('lh-ai-model');
|
||||
if (!sel || sel.options.length > 1) return;
|
||||
if (typeof window._buildModelOptions === 'function') {
|
||||
window._buildModelOptions(sel);
|
||||
}
|
||||
}
|
||||
|
||||
function switchAiTab(tabName) {
|
||||
document.querySelectorAll('.lh-ai-tab').forEach(function(t) { t.classList.toggle('active', t.dataset.aitab === tabName); });
|
||||
document.querySelectorAll('.lh-ai-tabpanel').forEach(function(p) { p.classList.add('hidden'); });
|
||||
var tp = document.getElementById('lh-ai-tp-' + tabName);
|
||||
if (tp) tp.classList.remove('hidden');
|
||||
if (tabName === 'webdav' && !document.querySelector('#lh-ai-webdav-list .lh-webdav-item')) {
|
||||
webdav.browse(webdav.getCurrentPath());
|
||||
}
|
||||
}
|
||||
|
||||
// ── Generate ────────────────────────────────────────────────
|
||||
function runAiGenerate() {
|
||||
var activeTab = document.querySelector('.lh-ai-tab.active');
|
||||
|
|
@ -596,7 +505,7 @@ import { createWebdavController } from './learningHub/webdavController.js';
|
|||
// Presentation returns marpMarkdown directly; others return content{}
|
||||
var payload = data.contentType === 'presentation' ? data : data.content;
|
||||
return applyAiContent(payload, contentType).then(function() {
|
||||
closeAiPanel();
|
||||
aiPanel.close();
|
||||
showToast('Content generated! Review and save.', 'success');
|
||||
});
|
||||
})
|
||||
|
|
|
|||
103
public/js/learningHub/aiPanelController.js
Normal file
103
public/js/learningHub/aiPanelController.js
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import { getJson } from './api.js';
|
||||
|
||||
export function createAiPanelController(deps) {
|
||||
function open() {
|
||||
var panel = document.getElementById('lh-ai-panel');
|
||||
if (!panel) return;
|
||||
panel.classList.remove('hidden');
|
||||
populateModelSelect();
|
||||
|
||||
var editorType = deps.getEditorType() || 'article';
|
||||
var ctype = document.getElementById('lh-ai-ctype');
|
||||
if (ctype) ctype.value = editorType;
|
||||
updateOptions(editorType);
|
||||
|
||||
getJson('/api/auth/me')
|
||||
.then(function(d) {
|
||||
var tab = document.getElementById('lh-ai-tab-webdav');
|
||||
if (tab) tab.style.display = (d.user && d.user.nextcloud_url) ? '' : 'none';
|
||||
if (d.user && d.user.webdav_learning_path) deps.setWebdavPath(d.user.webdav_learning_path || '/');
|
||||
});
|
||||
|
||||
wireOptionControls();
|
||||
panel.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
|
||||
function close() {
|
||||
var panel = document.getElementById('lh-ai-panel');
|
||||
if (panel) panel.classList.add('hidden');
|
||||
}
|
||||
|
||||
function populateModelSelect() {
|
||||
var sel = document.getElementById('lh-ai-model');
|
||||
if (!sel || sel.options.length > 1) return;
|
||||
if (typeof window._buildModelOptions === 'function') window._buildModelOptions(sel);
|
||||
}
|
||||
|
||||
function switchTab(tabName) {
|
||||
document.querySelectorAll('.lh-ai-tab').forEach(function(t) {
|
||||
t.classList.toggle('active', t.dataset.aitab === tabName);
|
||||
});
|
||||
document.querySelectorAll('.lh-ai-tabpanel').forEach(function(p) { p.classList.add('hidden'); });
|
||||
var tp = document.getElementById('lh-ai-tp-' + tabName);
|
||||
if (tp) tp.classList.remove('hidden');
|
||||
if (tabName === 'webdav' && !document.querySelector('#lh-ai-webdav-list .lh-webdav-item')) {
|
||||
deps.browseWebdav(deps.getWebdavPath());
|
||||
}
|
||||
}
|
||||
|
||||
function updateOptions(type) {
|
||||
applyAiOptionState(type, {
|
||||
wordsField: document.getElementById('lh-ai-words-field'),
|
||||
slidesField: document.getElementById('lh-ai-slides-field'),
|
||||
quizRow: document.getElementById('lh-ai-quiz-row'),
|
||||
cardTitle: document.getElementById('lh-ai-quiz-card-title'),
|
||||
toggleLabel: document.getElementById('lh-ai-q-toggle-label'),
|
||||
toggleCb: document.getElementById('lh-ai-q-toggle'),
|
||||
countWrap: document.getElementById('lh-ai-q-count-wrap')
|
||||
});
|
||||
}
|
||||
|
||||
function wireOptionControls() {
|
||||
var ctypeEl = document.getElementById('lh-ai-ctype');
|
||||
var toggleEl = document.getElementById('lh-ai-q-toggle');
|
||||
if (ctypeEl && !ctypeEl._wired) {
|
||||
ctypeEl.addEventListener('change', function() { updateOptions(ctypeEl.value); });
|
||||
ctypeEl._wired = true;
|
||||
}
|
||||
if (toggleEl && !toggleEl._wired) {
|
||||
toggleEl.addEventListener('change', function() {
|
||||
updateOptions((document.getElementById('lh-ai-ctype') || {}).value);
|
||||
});
|
||||
toggleEl._wired = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
close: close,
|
||||
open: open,
|
||||
switchTab: switchTab,
|
||||
updateOptions: updateOptions
|
||||
};
|
||||
}
|
||||
|
||||
export function applyAiOptionState(type, els) {
|
||||
if (els.wordsField) els.wordsField.style.display = (type === 'quiz' || type === 'presentation') ? 'none' : 'flex';
|
||||
if (els.slidesField) els.slidesField.style.display = type === 'presentation' ? 'flex' : 'none';
|
||||
|
||||
if (type === 'quiz') {
|
||||
if (els.quizRow) els.quizRow.style.display = 'block';
|
||||
if (els.cardTitle) els.cardTitle.textContent = 'Quiz Questions';
|
||||
if (els.toggleLabel) els.toggleLabel.style.display = 'none';
|
||||
if (els.toggleCb) { els.toggleCb.checked = true; els.toggleCb.disabled = true; }
|
||||
if (els.countWrap) els.countWrap.style.display = 'flex';
|
||||
return;
|
||||
}
|
||||
|
||||
if (els.quizRow) els.quizRow.style.display = 'block';
|
||||
if (els.cardTitle) els.cardTitle.textContent = 'Quiz Questions (optional)';
|
||||
if (els.toggleLabel) els.toggleLabel.style.display = 'flex';
|
||||
if (els.toggleCb) els.toggleCb.disabled = false;
|
||||
var checked = els.toggleCb ? els.toggleCb.checked : false;
|
||||
if (els.countWrap) els.countWrap.style.display = checked ? 'flex' : 'none';
|
||||
}
|
||||
156
test/learning-hub-ai-panel-controller.test.js
Normal file
156
test/learning-hub-ai-panel-controller.test.js
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
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']);
|
||||
});
|
||||
});
|
||||
|
|
@ -9,6 +9,7 @@ function read(relativePath) {
|
|||
|
||||
const source = read('public/js/learningHub.js');
|
||||
const apiModule = read('public/js/learningHub/api.js');
|
||||
const aiPanelControllerModule = read('public/js/learningHub/aiPanelController.js');
|
||||
const cmsControllerModule = read('public/js/learningHub/cmsController.js');
|
||||
const cmsRendererModule = read('public/js/learningHub/cmsRenderer.js');
|
||||
const editorModule = read('public/js/learningHub/tiptapEditor.js');
|
||||
|
|
@ -164,6 +165,21 @@ test('Learning Hub WebDAV controller owns browser state and rendering', () => {
|
|||
assert.doesNotMatch(webdavControllerModule, /window\.Tiptap|ai-generate|FormData/);
|
||||
});
|
||||
|
||||
test('Learning Hub AI panel controller owns panel options and source tabs', () => {
|
||||
assert.match(source, /from '\.\/learningHub\/aiPanelController\.js'/);
|
||||
assert.match(source, /var aiPanel = createAiPanelController\(/);
|
||||
assert.match(source, /aiPanel\.open\(\)/);
|
||||
assert.match(source, /aiPanel\.close\(\)/);
|
||||
assert.match(source, /aiPanel\.switchTab\(aiTab\.dataset\.aitab\)/);
|
||||
assert.match(aiPanelControllerModule, /export function createAiPanelController\(deps\)/);
|
||||
assert.match(aiPanelControllerModule, /export function applyAiOptionState\(type, els\)/);
|
||||
assert.match(aiPanelControllerModule, /window\._buildModelOptions\(sel\)/);
|
||||
assert.match(aiPanelControllerModule, /getJson\('\/api\/auth\/me'\)/);
|
||||
assert.match(aiPanelControllerModule, /deps\.browseWebdav\(deps\.getWebdavPath\(\)\)/);
|
||||
assert.doesNotMatch(source, /function openAiPanel\(\)|function updateAiOptions\(|function switchAiTab\(/);
|
||||
assert.doesNotMatch(aiPanelControllerModule, /window\.Tiptap|ai-generate|FormData|generate-pptx/);
|
||||
});
|
||||
|
||||
test('Learning Hub viewer renderers are isolated from API and slide loading', () => {
|
||||
assert.match(source, /from '\.\/learningHub\/viewerRenderer\.js'/);
|
||||
assert.match(viewerRendererModule, /export function buildViewerMeta\(item\)/);
|
||||
|
|
|
|||
|
|
@ -36,7 +36,9 @@ test('top bar model selector is removed and omitted selections use backend defau
|
|||
|
||||
test('learning hub model selector uses shared tab model options', () => {
|
||||
const learningHub = read('public/js/learningHub.js');
|
||||
assert.match(learningHub, /window\._buildModelOptions\(sel\)/);
|
||||
const aiPanelController = read('public/js/learningHub/aiPanelController.js');
|
||||
assert.match(learningHub, /createAiPanelController\(/);
|
||||
assert.match(aiPanelController, /window\._buildModelOptions\(sel\)/);
|
||||
assert.doesNotMatch(learningHub, /global-model-select|globalSel\.innerHTML/);
|
||||
assert.match(learningHub, /if \(model\) formData\.append\('model', model\)/);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue