import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { Window } from 'happy-dom'; import { initLoadedModelSelection, updateLoadedModelSelectionState, getVisibleModelChips, getSelectedModelChips, getSelectedModelTokens, clearLoadedModelSelection, } from '../web/resolver/utils/loaded_model_selection.js'; import { groupLoadedModelsByCategory, buildLoadedModelTokenStrings, } from '../web/resolver/utils/loaded_model_utils.js'; const getSvgIcon = () => ''; void groupLoadedModelsByCategory; void buildLoadedModelTokenStrings; void getSvgIcon; function setupDomEnvironment() { const window = new Window(); const document = window.document; globalThis.window = window; globalThis.document = document; try { Object.defineProperty(globalThis, 'navigator', { configurable: true, writable: true, value: window.navigator, }); } catch { // Fallback for environments where navigator cannot be redefined } window.MLSetCopyMode = function(sectionId, mode) { const section = document.getElementById(sectionId); if (!section) return; const normalizedMode = ['all', 'active', 'inactive', 'selected'].includes(mode) ? mode : 'all'; if (normalizedMode !== 'selected') { section.dataset.mlPreviousMode = normalizedMode; } const codeEl = section.querySelector('.mr-copy-code'); const labelEl = section.querySelector('.mr-copy-label'); const metaEl = section.querySelector('.mr-copy-meta'); const dataKey = normalizedMode === 'all' ? 'mlAll' : normalizedMode === 'active' ? 'mlActive' : normalizedMode === 'inactive' ? 'mlInactive' : 'mlSelected'; const countKey = normalizedMode === 'all' ? 'mlAllCount' : normalizedMode === 'active' ? 'mlActiveCount' : normalizedMode === 'inactive' ? 'mlInactiveCount' : 'mlSelectedCount'; const labelMap = { all: 'Copy all', active: 'Copy active', inactive: 'Copy inactive', selected: 'Copy selected' }; if (codeEl) codeEl.textContent = section.dataset[dataKey] || ''; if (labelEl) labelEl.textContent = labelMap[normalizedMode]; if (metaEl) { const count = Number(section.dataset[countKey] || 0); metaEl.textContent = `${count} token${count === 1 ? '' : 's'}`; } section.querySelectorAll('.mr-copy-mode').forEach(btn => { btn.classList.toggle('active', btn.dataset.mlCopyMode === normalizedMode); }); }; window.MLSetCopyButtonFeedback = function(btn, text) { if (!btn) return; btn.textContent = text; }; return { window, document }; } function createLoadedModelsMarkup(copySectionId = 'mr-copy-test') { return `

Loaded Models

base_model
lora_a0.80 lora_b1.00 lora_c0.50
lora_d1.00
Copy all
5 tokens
`; } test('single click selects only clicked chip and switches copy toolbar to selected mode', () => { const { document } = setupDomEnvironment(); const container = document.createElement('div'); container.innerHTML = createLoadedModelsMarkup(); document.body.appendChild(container); const cleanup = initLoadedModelSelection(container); const chips = container.querySelectorAll('.mr-model-chip'); chips[1].dispatchEvent(new window.MouseEvent('click', { bubbles: true, cancelable: true })); assert.equal(chips[1].classList.contains('is-selected'), true); assert.equal(chips[0].classList.contains('is-selected'), false); assert.equal(chips[2].classList.contains('is-selected'), false); const copySection = container.querySelector('.mr-copy-section'); assert.equal(copySection.dataset.mlSelected, ''); assert.equal(copySection.dataset.mlSelectedCount, '1'); const selectedBtn = copySection.querySelector('.mr-copy-mode-selected'); assert.equal(selectedBtn.style.display, ''); assert.equal(selectedBtn.classList.contains('active'), true); const codeEl = copySection.querySelector('.mr-copy-code'); assert.equal(codeEl.textContent, ''); cleanup(); }); test('ctrl+click adds and toggles chips in multi-selection', () => { const { document } = setupDomEnvironment(); const container = document.createElement('div'); container.innerHTML = createLoadedModelsMarkup(); document.body.appendChild(container); const cleanup = initLoadedModelSelection(container); const chips = container.querySelectorAll('.mr-model-chip'); // Select first chip with regular click chips[0].dispatchEvent(new window.MouseEvent('click', { bubbles: true })); assert.deepEqual(getSelectedModelTokens(container), ['']); // Ctrl+click second chip chips[2].dispatchEvent(new window.MouseEvent('click', { bubbles: true, ctrlKey: true })); assert.deepEqual(getSelectedModelTokens(container), [ '', '', ]); const copySection = container.querySelector('.mr-copy-section'); assert.equal(copySection.dataset.mlSelected, ' '); assert.equal(copySection.dataset.mlSelectedCount, '2'); // Ctrl+click to toggle off the first chip chips[0].dispatchEvent(new window.MouseEvent('click', { bubbles: true, ctrlKey: true })); assert.deepEqual(getSelectedModelTokens(container), ['']); assert.equal(copySection.dataset.mlSelected, ''); cleanup(); }); test('shift+click performs range selection from previous anchor', () => { const { document } = setupDomEnvironment(); const container = document.createElement('div'); container.innerHTML = createLoadedModelsMarkup(); document.body.appendChild(container); const cleanup = initLoadedModelSelection(container); const chips = container.querySelectorAll('.mr-model-chip'); // Click on index 1 (lora_a) as anchor chips[1].dispatchEvent(new window.MouseEvent('click', { bubbles: true })); assert.deepEqual(getSelectedModelTokens(container), ['']); // Shift+click on index 3 (lora_c) -> range 1..3 selected chips[3].dispatchEvent(new window.MouseEvent('click', { bubbles: true, shiftKey: true })); assert.deepEqual(getSelectedModelTokens(container), [ '', '', '', ]); // Shift+click again on index 2 (lora_b) from same anchor -> range 1..2 selected chips[2].dispatchEvent(new window.MouseEvent('click', { bubbles: true, shiftKey: true })); assert.deepEqual(getSelectedModelTokens(container), [ '', '', ]); cleanup(); }); test('ctrl+shift+click expands existing selection with range', () => { const { document } = setupDomEnvironment(); const container = document.createElement('div'); container.innerHTML = createLoadedModelsMarkup(); document.body.appendChild(container); const cleanup = initLoadedModelSelection(container); const chips = container.querySelectorAll('.mr-model-chip'); // Select index 0 (checkpoint) chips[0].dispatchEvent(new window.MouseEvent('click', { bubbles: true })); // Click index 2 as anchor with Ctrl chips[2].dispatchEvent(new window.MouseEvent('click', { bubbles: true, ctrlKey: true })); assert.deepEqual(getSelectedModelTokens(container), [ '', '', ]); // Ctrl+Shift+click index 4 (inactive lora_d) -> adds 2..4 to selection without removing index 0 chips[4].dispatchEvent(new window.MouseEvent('click', { bubbles: true, ctrlKey: true, shiftKey: true })); assert.deepEqual(getSelectedModelTokens(container), [ '', '', '', '', ]); cleanup(); }); test('clicking blank area deselects all chips and reverts copy mode', () => { const { document } = setupDomEnvironment(); const container = document.createElement('div'); container.innerHTML = createLoadedModelsMarkup(); document.body.appendChild(container); const cleanup = initLoadedModelSelection(container); const chips = container.querySelectorAll('.mr-model-chip'); // Select a chip chips[1].dispatchEvent(new window.MouseEvent('click', { bubbles: true })); assert.equal(getSelectedModelChips(container).length, 1); // Click on empty list space const list = container.querySelector('.mr-models-list'); list.dispatchEvent(new window.MouseEvent('click', { bubbles: true })); assert.equal(getSelectedModelChips(container).length, 0); const copySection = container.querySelector('.mr-copy-section'); assert.equal(copySection.dataset.mlSelectedCount, '0'); assert.equal(copySection.querySelector('.mr-copy-mode-selected').style.display, 'none'); // Reverted back to 'all' const activeModeBtn = copySection.querySelector('.mr-copy-mode.active'); assert.equal(activeModeBtn.dataset.mlCopyMode, 'all'); cleanup(); }); test('escape key clears selection', () => { const { document } = setupDomEnvironment(); const container = document.createElement('div'); container.innerHTML = createLoadedModelsMarkup(); document.body.appendChild(container); const cleanup = initLoadedModelSelection(container); const chips = container.querySelectorAll('.mr-model-chip'); chips[1].dispatchEvent(new window.MouseEvent('click', { bubbles: true })); assert.equal(getSelectedModelChips(container).length, 1); document.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Escape', bubbles: true })); assert.equal(getSelectedModelChips(container).length, 0); cleanup(); }); test('ctrl+a selects all visible chips', () => { const { document } = setupDomEnvironment(); const container = document.createElement('div'); container.innerHTML = createLoadedModelsMarkup(); document.body.appendChild(container); const cleanup = initLoadedModelSelection(container); const chips = container.querySelectorAll('.mr-model-chip'); // Make inactive section hidden like MLFilterSwitch('active') does const inactiveGroup = container.querySelector('.mr-model-group-inactive'); inactiveGroup.style.display = 'none'; document.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'a', ctrlKey: true, bubbles: true })); const selected = getSelectedModelChips(container); assert.equal(selected.length, 4); // Only the 4 visible active chips assert.equal(chips[4].classList.contains('is-selected'), false); cleanup(); }); test('ctrl+c writes selected tokens to clipboard with feedback', async () => { const { document } = setupDomEnvironment(); const container = document.createElement('div'); container.innerHTML = createLoadedModelsMarkup(); document.body.appendChild(container); let writtenText = ''; Object.defineProperty(globalThis, 'navigator', { configurable: true, writable: true, value: { clipboard: { writeText: async (text) => { writtenText = text; } } } }); const notifications = []; const dialog = { activeTab: 'loaded', showNotification: (msg, type) => notifications.push({ msg, type }) }; const cleanup = initLoadedModelSelection(container, dialog); const chips = container.querySelectorAll('.mr-model-chip'); // Select two chips chips[1].dispatchEvent(new window.MouseEvent('click', { bubbles: true })); chips[2].dispatchEvent(new window.MouseEvent('click', { bubbles: true, ctrlKey: true })); // Press Ctrl+C document.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'c', ctrlKey: true, bubbles: true })); await new Promise(resolve => setTimeout(resolve, 10)); assert.equal(writtenText, ' '); assert.equal(notifications.length, 1); assert.match(notifications[0].msg, /Copied 2 model tokens to clipboard/); cleanup(); }); test('displayLoadedModels renders chips with data-ml-token and includes Selected mode button', () => { const { document } = setupDomEnvironment(); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const source = fs.readFileSync(path.join(__dirname, '../web/resolver/views/tabs_loaded_methods.js'), 'utf-8'); function extractMethod(sourceCode, methodName) { const regex = new RegExp(`\\n\\s+${methodName}\\s*\\(([^)]*\\{[^}]*\\}|[^)]*)\\)\\s*\\{`); const match = regex.exec(sourceCode); assert.ok(match, `Could not find ${methodName}`); const params = match[1]; const braceStart = match.index + match[0].length - 1; let depth = 0; for (let i = braceStart; i < sourceCode.length; i += 1) { if (sourceCode[i] === '{') depth += 1; if (sourceCode[i] === '}') depth -= 1; if (depth === 0) { return `function ${methodName}(${params}) ${sourceCode.slice(braceStart, i + 1)}`; } } throw new Error(`Unmatched braces for ${methodName}`); } const displayLoadedModels = eval(`(${extractMethod(source, 'displayLoadedModels')})`); const container = document.createElement('div'); document.body.appendChild(container); const dialog = { getModelNameAndStrength: model => ({ name: model.name, strength: model.strength !== null && model.strength !== undefined ? model.strength.toFixed(2) : null }), getFilenameFromPath: pathVal => pathVal, stripModelExtension: name => name, getCategoryTokenName: cat => cat.replace(/s$/, ''), getModelToken: (model, cat) => { const str = model.strength !== null && model.strength !== undefined ? model.strength.toFixed(2) : '1.00'; return `<${cat.replace(/s$/, '')}:${model.name}:${str}>`; }, getLoadedModelDomKey: model => model.name, getLoadedModelContext: model => model, getContextMenuAttrs: () => '', getModelPreviewTooltipAttrs: () => '', getCategoryDisplayName: cat => cat, escapeHtml: str => String(str), escapeJsString: str => JSON.stringify(str), bindLoadedModelSelection: () => {} }; displayLoadedModels.call(dialog, container, { loaded_models: [ { category: 'checkpoints', name: 'model_1', active: true, connected: true }, { category: 'loras', name: 'lora_1', strength: 0.8, active: true, connected: true }, { category: 'loras', name: 'lora_2', strength: 1.0, active: false, connected: true }, ], total: 3 }); const chips = container.querySelectorAll('.mr-model-chip'); assert.equal(chips.length, 3); assert.equal(chips[0].getAttribute('data-ml-token'), ''); assert.equal(chips[1].getAttribute('data-ml-token'), ''); assert.equal(chips[2].getAttribute('data-ml-token'), ''); const selectedBtn = container.querySelector('.mr-copy-mode-selected'); assert.ok(selectedBtn, 'Selected mode button should be present in DOM'); assert.equal(selectedBtn.getAttribute('data-ml-copy-mode'), 'selected'); }); test('ctrl+click works reliably in metadata tab even when loaded tab was previously bound', () => { const { document } = setupDomEnvironment(); const contentElement = document.createElement('div'); contentElement.id = 'mr-content-element'; document.body.appendChild(contentElement); const dialog = { activeTab: 'loaded', contentElement, _mlSelectionCleanup: null, bindLoadedModelSelection(container) { if (typeof this._mlSelectionCleanup === 'function') { this._mlSelectionCleanup(); this._mlSelectionCleanup = null; } const cleanup = initLoadedModelSelection(container, this); this._mlSelectionCleanup = cleanup; return cleanup; } }; // 1. User was first on "Loaded Models" tab contentElement.innerHTML = createLoadedModelsMarkup('mr-copy-loaded'); dialog.bindLoadedModelSelection(contentElement); // 2. User switches to "Metadata" tab dialog.activeTab = 'metadata'; const metadataShell = document.createElement('div'); metadataShell.className = 'mr-image-inspector'; const metadataResult = document.createElement('div'); metadataResult.setAttribute('data-image-inspector-result', ''); const modelContainer = document.createElement('div'); modelContainer.id = 'mr-image-inspector-models-12345'; modelContainer.innerHTML = createLoadedModelsMarkup('mr-copy-metadata'); metadataResult.appendChild(modelContainer); metadataShell.appendChild(metadataResult); contentElement.appendChild(metadataShell); dialog.bindLoadedModelSelection(modelContainer); // 3. User Ctrl+clicks chips in Metadata tab const metaChips = modelContainer.querySelectorAll('.mr-model-chip'); assert.equal(metaChips.length, 5); // Ctrl+click first chip metaChips[0].dispatchEvent(new window.MouseEvent('click', { bubbles: true, ctrlKey: true })); assert.equal(metaChips[0].classList.contains('is-selected'), true, 'First chip must remain selected after Ctrl+click in metadata tab'); assert.deepEqual(getSelectedModelTokens(modelContainer), ['']); // Ctrl+click second chip metaChips[1].dispatchEvent(new window.MouseEvent('click', { bubbles: true, ctrlKey: true })); assert.equal(metaChips[1].classList.contains('is-selected'), true, 'Second chip must remain selected after Ctrl+click'); assert.deepEqual(getSelectedModelTokens(modelContainer), [ '', '' ]); // Ctrl+click first chip again to toggle it off metaChips[0].dispatchEvent(new window.MouseEvent('click', { bubbles: true, ctrlKey: true })); assert.equal(metaChips[0].classList.contains('is-selected'), false, 'First chip must be toggled off'); assert.deepEqual(getSelectedModelTokens(modelContainer), ['']); const copySection = modelContainer.querySelector('.mr-copy-section'); assert.equal(copySection.dataset.mlSelected, ''); assert.equal(copySection.dataset.mlSelectedCount, '1'); dialog._mlSelectionCleanup?.(); }); test('chip click event propagation is stopped to prevent parent container interference', () => { const { document } = setupDomEnvironment(); const parentContainer = document.createElement('div'); const childContainer = document.createElement('div'); childContainer.innerHTML = createLoadedModelsMarkup('mr-copy-nested'); parentContainer.appendChild(childContainer); document.body.appendChild(parentContainer); let parentClickFired = false; parentContainer.addEventListener('click', () => { parentClickFired = true; }); const cleanup = initLoadedModelSelection(childContainer); const chip = childContainer.querySelector('.mr-model-chip'); chip.dispatchEvent(new window.MouseEvent('click', { bubbles: true, ctrlKey: true })); assert.equal(chip.classList.contains('is-selected'), true); assert.equal(parentClickFired, false, 'Parent container should not receive click event due to stopPropagation'); cleanup(); });