Files
cacti/tests/js/selectmenu_scroll.test.js
Thomas Vincent f28071eb67 fix(theme): [develop] restore Midwinter login controls, form state and notifications (#8326)
* fix(theme): preserve Midwinter authentication controls

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* docs(changelog): describe theme fix

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(ui): preserve form state and notification lifecycles

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: replace the blinking timer during skin setup

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-10-12 15:40:56 -07:00

151 lines
4.5 KiB
JavaScript

/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
| |
| This program is free software; you can redistribute it and/or |
| modify it under the terms of the GNU General Public License |
| as published by the Free Software Foundation; either version 2 |
| of the License, or (at your option) any later version. |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
*/
'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const vm = require('node:vm');
const layoutSource = fs.readFileSync(
path.join(__dirname, '..', '..', 'include', 'layout.js'),
'utf8'
);
function extractFunction(name) {
const start = layoutSource.indexOf(`function ${name}(`);
assert.notEqual(start, -1, `${name}() must exist`);
const bodyStart = layoutSource.indexOf('{', start);
let depth = 0;
for (let offset = bodyStart; offset < layoutSource.length; offset++) {
if (layoutSource[offset] === '{') {
depth++;
} else if (layoutSource[offset] === '}') {
depth--;
if (depth === 0) {
return layoutSource.slice(start, offset + 1);
}
}
}
throw new Error(`Unable to extract ${name}()`);
}
test('scroll handlers close initialized select menus without duplicate bindings', () => {
const windowObject = {};
const selects = [{ initialized: true, closes: 0 }, { initialized: false, closes: 0 }];
let boundEvent;
let menuOpen = true;
let selectScans = 0;
let scrollHandler;
let selectedContainers;
function jquery(selector) {
if (typeof selector === 'string' && selector.startsWith('.cactiConsoleContentArea')) {
selectedContainers = selector;
return {
add: (target) => {
assert.equal(target, windowObject);
return {
rebind: (bound, handler) => {
assert.equal(bound, 'scroll.cactiSelectmenu');
boundEvent = bound;
scrollHandler = handler;
}
};
}
};
}
if (selector === 'select') {
selectScans++;
return {
each: (callback) => selects.forEach(select => callback.call(select))
};
}
if (selector === '.ui-selectmenu-open') {
return { length: menuOpen ? 1 : 0 };
}
return {
selectmenu: (action) => {
if (action === 'instance') {
return selector.initialized ? {} : undefined;
}
assert.equal(action, 'close');
selector.closes++;
}
};
}
const context = vm.createContext({ $: jquery, window: windowObject });
vm.runInContext(extractFunction('setupSelectmenuScrollClose'), context, { filename: 'include/layout.js' });
context.setupSelectmenuScrollClose();
assert.match(selectedContainers, /\.cactiConsoleContentArea/);
assert.match(selectedContainers, /\.cactiGraphContentArea/);
assert.match(selectedContainers, /\.cactiTreeNavigationArea/);
assert.equal(boundEvent, 'scroll.cactiSelectmenu');
assert.equal(typeof scrollHandler, 'function');
scrollHandler();
assert.equal(selects[0].closes, 1);
assert.equal(selects[1].closes, 0);
assert.equal(selectScans, 1);
menuOpen = false;
scrollHandler();
assert.equal(selectScans, 1);
});
test('applySkin binds scroll handling after theme widgets initialize', () => {
const themeReadyPosition = layoutSource.indexOf('themeReady();');
const setupPosition = layoutSource.indexOf('setupSelectmenuScrollClose();', themeReadyPosition);
assert.notEqual(themeReadyPosition, -1);
assert.ok(setupPosition > themeReadyPosition);
});
test('repeated skin setup keeps one blinking interval and preserves other timers', () => {
const start = layoutSource.indexOf('// Setup blinking');
const end = layoutSource.indexOf('/* applySkin() runs more than once', start);
assert.ok(start > 0 && end > start);
const intervals = new Map([[1, () => {}]]);
let nextId = 1;
const context = vm.createContext({
setInterval(callback, delay) {
assert.equal(delay, 1200);
const id = ++nextId;
intervals.set(id, callback);
return id;
},
clearInterval(id) { intervals.delete(id); },
});
vm.runInContext(`function applySkin() { ${layoutSource.slice(start, end)} }`, context);
for (let transition = 0; transition < 100; transition++) context.applySkin();
assert.equal(intervals.size, 2);
assert.equal(intervals.has(1), true);
});