Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 56s
Forgejo Docker Build / Root app tests (push) Successful in 45s
Forgejo Android APK / Build signed APK (push) Successful in 1m53s
Forgejo Docker Build / Build Docker image (push) Successful in 8s
Forgejo Docker Build / Deploy to the host (push) Failing after 2s
The first pass at this covered the two admin cards. It missed the pickers that matter most: the per-tab model selectors in app.js, which every clinical tab uses, and the My Resources model dropdown. Both were filled once at page load, so a model added in Admin was still invisible where people actually choose one. app.js's boot fetch is now a named loadModelList() that also runs on models-changed; My Resources re-runs loadOptions(), which is the same call that decides whether the model row is shown at all. Both rebuilds keep a choice already made. These selects can be rebuilt while someone is halfway through a form, and silently moving them off the model they picked would be worse than not refreshing. Verified against a mutation: removing the app.js listener fails the test that says every picker listens. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
75 lines
3.8 KiB
JavaScript
75 lines
3.8 KiB
JavaScript
// Adding a model in the Models card has to reach every picker that lists models.
|
|
// It used to refresh the default-model dropdown alone, so a newly added model
|
|
// was invisible in the Clinical Assistant, review-model and image pickers until
|
|
// the page was reloaded — which is not obvious, and looks like the add failed.
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const read = f => fs.readFileSync(path.join(__dirname, '..', f), 'utf8');
|
|
|
|
test('every mutation of the model roster announces it', () => {
|
|
const admin = read('public/js/admin.js');
|
|
assert.match(admin, /function announceModelsChanged\(\)/);
|
|
assert.match(admin, /new CustomEvent\('models-changed'\)/);
|
|
|
|
// Add, remove, clear-all and enable/disable each change what is selectable.
|
|
const announced = (admin.match(/announceModelsChanged\(\);/g) || []).length;
|
|
assert.ok(announced >= 4, 'expected an announcement on add, remove, clear and toggle; found ' + announced);
|
|
|
|
for (const [label, marker] of [
|
|
['add-discovered', "config/models/add-discovered"],
|
|
['remove custom', "config/models/custom/"],
|
|
['clear all', "config/models/clear-all"],
|
|
['toggle', "config/models/toggle"]
|
|
]) {
|
|
const at = admin.indexOf(marker);
|
|
assert.ok(at > -1, label + ' call site missing');
|
|
// The announcement lives inside that call's success branch.
|
|
assert.ok(admin.slice(at, at + 900).includes('announceModelsChanged()'),
|
|
label + ' does not announce the change');
|
|
}
|
|
});
|
|
|
|
test('the pickers listen, and re-run a loader that is otherwise once-per-visit', () => {
|
|
const assistant = read('public/js/admin/clinicalAssistant.js');
|
|
assert.match(assistant, /document\.addEventListener\('models-changed'/);
|
|
// Its guard must be cleared, or the listener fires and the loader returns early.
|
|
assert.match(assistant, /configState = 'idle';\s*\n\s*loadAssistantAdmin\(\);/);
|
|
assert.match(assistant, /if \(configState === 'loading'\) return;/, 'no reload mid-flight');
|
|
|
|
const images = read('public/js/admin/imageSettings.js');
|
|
assert.match(images, /document\.addEventListener\('models-changed'/);
|
|
assert.match(images, /loaded = false;\s*\n\s*load\(\);/);
|
|
assert.match(images, /if \(loading\) return;/, 'no reload mid-flight');
|
|
});
|
|
|
|
test('every model picker listens, not only the two in the admin cards', () => {
|
|
// The point is that a model added in Admin is selectable wherever models are
|
|
// chosen — the per-tab selectors and My Resources included, not just the
|
|
// admin's own dropdowns.
|
|
const app = read('public/js/app.js');
|
|
assert.match(app, /document\.addEventListener\('models-changed'/);
|
|
assert.match(app, /function loadModelList\(\)/, 'the boot fetch is reusable, not inline');
|
|
|
|
const resources = read('public/js/myResources.js');
|
|
assert.match(resources, /document\.addEventListener\('models-changed'/);
|
|
assert.match(resources, /if \(inited\) loadOptions\(\);/, 'nothing to refresh before the tab has initialised');
|
|
});
|
|
|
|
test('a refresh never moves someone off the model they picked', () => {
|
|
// These selects are rebuilt while a form may be half filled in.
|
|
const app = read('public/js/app.js');
|
|
assert.match(app, /var chosen = sel\.value;[\s\S]{0,260}sel\.value = chosen;/);
|
|
const resources = read('public/js/myResources.js');
|
|
assert.match(resources, /var chosen = select\.value;[\s\S]{0,420}select\.value = chosen;/);
|
|
});
|
|
|
|
test('the toast no longer tells people to go and set a default', () => {
|
|
// It used to say "now select it as default and click Set Default", which was
|
|
// advice for the one dropdown that did refresh.
|
|
const admin = read('public/js/admin.js');
|
|
assert.doesNotMatch(admin, /now select it as default and click Set Default/);
|
|
assert.match(admin, /selectable everywhere models are chosen/);
|
|
});
|