import test from 'node:test'; import assert from 'node:assert/strict'; import { imageMetadataMethods, extractCivitaiImageId } from '../web/resolver/views/tabs_image_metadata_methods.js'; import { tabsLoadedMethods } from '../web/resolver/views/tabs_loaded_methods.js'; globalThis.window = globalThis; function createMockContext() { const memory = new Map(); const mockStorage = { getItem: (k) => (memory.has(k) ? memory.get(k) : null), setItem: (k, v) => memory.set(k, String(v)), removeItem: (k) => memory.delete(k), }; return { ...imageMetadataMethods, ...tabsLoadedMethods, escapeHtml: (s) => String(s ?? ''), escapeJsString: (s) => JSON.stringify(s), getMetadataTooltipBadge: (help, label) => `?`, getCategoryDisplayName: (cat) => cat, getModelToken: (model) => model.name || '', getContextMenuAttrs: () => '', stripModelExtension: (name) => String(name || '').replace(/\.[^/.]+$/, ''), getMissingModelKey: (model) => model.name || 'key', getModelPreviewTooltipAttrs: () => '', bindLoadedModelSelection: () => {}, renderStatusMessage: (msg, type = 'info') => `
${msg}
`, storage: mockStorage, }; } test('image metadata sections default to expanded', () => { const ctx = createMockContext(); assert.strictEqual(ctx.isImageInspectorSectionCollapsed('tools'), false); assert.strictEqual(ctx.isImageInspectorSectionCollapsed('summary'), false); assert.strictEqual(ctx.isImageInspectorSectionCollapsed('prompt-transfer'), false); assert.strictEqual(ctx.isImageInspectorSectionCollapsed('model-transfer'), false); assert.strictEqual(ctx.isImageInspectorSectionCollapsed('models'), false); }); test('toggling section marks it collapsed and persists state', () => { const ctx = createMockContext(); const state = {}; ctx.toggleImageInspectorSection('prompt-transfer'); assert.strictEqual(ctx.isImageInspectorSectionCollapsed('prompt-transfer'), true); assert.strictEqual(ctx.isImageInspectorSectionCollapsed('model-transfer'), false); ctx.toggleImageInspectorSection('prompt-transfer'); assert.strictEqual(ctx.isImageInspectorSectionCollapsed('prompt-transfer'), false); }); test('renderMetadataPromptTransferPanel renders collapse button with chevron and aria-expanded', () => { const ctx = createMockContext(); const state = { promptTransfer: { loading: false, applying: false, error: '', pairs: {}, prompts: { source: [], target: [] }, }, }; const expandedHtml = ctx.renderMetadataPromptTransferPanel(state); assert.ok(expandedHtml.includes('data-image-inspector-collapse="prompt-transfer"')); assert.ok(expandedHtml.includes('aria-expanded="true"')); assert.ok(!expandedHtml.includes('mr-image-prompt-transfer is-collapsed')); ctx.toggleImageInspectorSection('prompt-transfer'); const collapsedHtml = ctx.renderMetadataPromptTransferPanel(state); assert.ok(collapsedHtml.includes('aria-expanded="false"')); assert.ok(collapsedHtml.includes('mr-image-prompt-transfer is-collapsed')); }); test('renderMetadataTransferPanel renders collapse button with chevron and aria-expanded', () => { const ctx = createMockContext(); const state = { transfer: { loading: false, refreshing: false, applying: false, undoing: false, error: '', targetGroups: [], selectedNodeKeys: new Set(), }, }; const expandedHtml = ctx.renderMetadataTransferPanel(state); assert.ok(expandedHtml.includes('data-image-inspector-collapse="model-transfer"')); assert.ok(expandedHtml.includes('aria-expanded="true"')); assert.ok(!expandedHtml.includes('mr-image-transfer is-collapsed')); ctx.toggleImageInspectorSection('model-transfer'); const collapsedHtml = ctx.renderMetadataTransferPanel(state); assert.ok(collapsedHtml.includes('aria-expanded="false"')); assert.ok(collapsedHtml.includes('mr-image-transfer is-collapsed')); }); test('displayLoadedModels renders collapse button and tooltip badge, hiding subtitle when collapsible', () => { const ctx = createMockContext(); const container = { innerHTML: '', dataset: {} }; const data = { total: 1, loaded_models: [ { name: 'model.safetensors', category: 'checkpoints', active: true } ], }; // Non-collapsible (Loaded Models tab) - shows subtitle, no tooltip badge ctx.displayLoadedModels(container, data, { collapsible: false }); assert.ok(!container.innerHTML.includes('data-image-inspector-collapse')); assert.ok(container.innerHTML.includes('

Loaded Models')); assert.ok(container.innerHTML.includes('class="mr-loaded-models-subtitle"')); // Collapsible (Metadata tab) - shows tooltip badge, hides subtitle paragraph ctx.displayLoadedModels(container, data, { collapsible: true, isCollapsed: false, sectionId: 'models', title: 'Referenced Models', tooltip: 'Models reported by embedded Civitai metadata.', }); assert.ok(container.innerHTML.includes('data-image-inspector-collapse="models"')); assert.ok(container.innerHTML.includes('aria-expanded="true"')); assert.ok(container.innerHTML.includes('Referenced Models')); assert.ok(container.innerHTML.includes('class="mr-tooltip-badge"')); assert.ok(container.innerHTML.includes('Models reported by embedded Civitai metadata.')); assert.ok(!container.innerHTML.includes('class="mr-loaded-models-subtitle"')); // Collapsible and collapsed ctx.displayLoadedModels(container, data, { collapsible: true, isCollapsed: true, sectionId: 'models', title: 'Referenced Models', tooltip: 'Models reported by embedded Civitai metadata.', }); assert.ok(container.innerHTML.includes('data-image-inspector-collapse="models"')); assert.ok(container.innerHTML.includes('aria-expanded="false"')); }); test('renderImageMetadataResult renders Extracted Metadata with tooltip badge and no subtitle paragraph', () => { const ctx = createMockContext(); const resultEl = { innerHTML: '', querySelector: () => ({ innerHTML: '', dataset: {} }), }; ctx.contentElement = { querySelector: (sel) => { if (sel === '[data-image-inspector-result]') { return resultEl; } return null; }, }; ctx.imageInspectorState = { metadata: { filename: 'test.png', image: { width: 512, height: 512, format: 'PNG' }, }, loadedModels: null, loading: false, error: '', }; ctx.renderMetadataPromptTransferPanel = () => ''; ctx.renderMetadataTransferPanel = () => ''; ctx.renderImageMetadataResult(); assert.ok(resultEl.innerHTML.includes('Extracted Metadata')); assert.ok(resultEl.innerHTML.includes('data-image-inspector-collapse="summary"')); assert.ok(resultEl.innerHTML.includes('Read-only information from the selected source.')); assert.ok(resultEl.innerHTML.includes('class="mr-tooltip-badge"')); assert.ok(!resultEl.innerHTML.includes('class="mr-loaded-models-subtitle"')); }); test('renderImageMetadataResult without metadata renders prompt transfer panel first', () => { const ctx = createMockContext(); const resultEl = { innerHTML: '', querySelector: () => null, }; ctx.contentElement = { querySelector: (sel) => (sel === '[data-image-inspector-result]' ? resultEl : null), }; ctx.imageInspectorState = { metadata: null, loadedModels: null, loading: false, error: '', }; ctx.renderMetadataPromptTransferPanel = () => ''; ctx.renderMetadataTransferPanel = () => ''; ctx.renderImageMetadataResult(); assert.ok(resultEl.innerHTML.startsWith('
link'), 145110824); assert.strictEqual(extractCivitaiImageId('photo'), 145110824); assert.strictEqual( extractCivitaiImageId(''), 145110824, ); assert.strictEqual(extractCivitaiImageId('
'), 145110824); assert.strictEqual(extractCivitaiImageId('https://example.com/foo.png'), null); assert.strictEqual(extractCivitaiImageId('just some text'), null); assert.strictEqual(extractCivitaiImageId(''), null); assert.strictEqual(extractCivitaiImageId(null), null); }); test('handleImageMetadataPaste inspects Civitai URL from plain text clipboard', () => { const ctx = createMockContext(); const { root, textarea } = createMockPasteTarget(ctx); let civitaiImageId = null; let civitaiSourceUrl = null; ctx.inspectCivitaiImage = (id, url) => { civitaiImageId = id; civitaiSourceUrl = url; }; let defaultPrevented = false; let propagationStopped = false; const civitaiUrl = 'https://civitai.red/images/145110824'; const event = { target: root, preventDefault: () => { defaultPrevented = true; }, stopPropagation: () => { propagationStopped = true; }, clipboardData: { files: [], items: [], getData: (type) => (type === 'text/plain' ? civitaiUrl : ''), }, }; assert.strictEqual(ctx.handleImageMetadataPaste(event), true); assert.strictEqual(defaultPrevented, true); assert.strictEqual(propagationStopped, true); assert.strictEqual(civitaiImageId, 145110824); assert.strictEqual(civitaiSourceUrl, civitaiUrl); assert.strictEqual(textarea.value, civitaiUrl); }); test('handleImageMetadataPaste inspects Civitai image from HTML clipboard', () => { const ctx = createMockContext(); const { root, textarea } = createMockPasteTarget(ctx); let civitaiImageId = null; let civitaiSourceUrl = null; ctx.inspectCivitaiImage = (id, url) => { civitaiImageId = id; civitaiSourceUrl = url; }; let defaultPrevented = false; const htmlSnippet = ''; const event = { target: root, preventDefault: () => { defaultPrevented = true; }, stopPropagation: () => {}, clipboardData: { files: [], items: [], getData: (type) => (type === 'text/html' ? htmlSnippet : (type === 'text/plain' ? 'Civitai Image' : '')), }, }; assert.strictEqual(ctx.handleImageMetadataPaste(event), true); assert.strictEqual(defaultPrevented, true); assert.strictEqual(civitaiImageId, 145110824); assert.ok(civitaiSourceUrl.includes('145110824')); assert.ok(textarea.value.includes('145110824')); }); test('inspectPastedWorkflow delegates to inspectCivitaiImage when input is a Civitai URL', () => { const ctx = createMockContext(); let civitaiImageId = null; let civitaiSourceUrl = null; ctx.inspectCivitaiImage = (id, url) => { civitaiImageId = id; civitaiSourceUrl = url; }; const civitaiUrl = 'https://civitai.com/images/145110824'; ctx.inspectPastedWorkflow(civitaiUrl); assert.strictEqual(civitaiImageId, 145110824); assert.strictEqual(civitaiSourceUrl, civitaiUrl); }); test('getMetadataPromptTransferData falls back to civitai_metadata when workflow prompts are empty', () => { const ctx = createMockContext(); const state = { workflow: null, metadata: { civitai_metadata: { prompt: 'a cinematic portrait of an astronaut', negativePrompt: 'blurry, bad quality', }, }, }; const transferData = ctx.getMetadataPromptTransferData(state); assert.strictEqual(transferData.sourcePrompts.length, 2); assert.strictEqual(transferData.sourcePrompts[0].role, 'positive'); assert.strictEqual(transferData.sourcePrompts[0].text, 'a cinematic portrait of an astronaut'); assert.strictEqual(transferData.sourcePrompts[1].role, 'negative'); assert.strictEqual(transferData.sourcePrompts[1].text, 'blurry, bad quality'); }); test('getMetadataTransferSourceModels includes models with name and category even when original_path is empty', () => { const ctx = createMockContext(); const loadedModels = { loaded_models: [ { name: 'SDXL Base 1.0', category: 'checkpoints', original_path: '', source: 'civitai_metadata', }, { name: 'Detail Tweaker LoRA', category: 'loras', original_path: '', source: 'civitai_metadata', }, { name: '', category: 'checkpoints', original_path: '', }, { name: 'URN model', category: 'checkpoints', is_urn: true, original_path: 'urn:air:sdxl:...', }, ], }; const sourceEntries = ctx.getMetadataTransferSourceModels(loadedModels); assert.strictEqual(sourceEntries.length, 2); assert.strictEqual(sourceEntries[0].model.name, 'SDXL Base 1.0'); assert.strictEqual(sourceEntries[1].model.name, 'Detail Tweaker LoRA'); }); test('getMetadataTransferModelLabel appends friendly model name in brackets when available', () => { const ctx = createMockContext(); const modelWithFriendly = { name: 'Cinematic_Krea2_krea2_3283603_epoch_10.safetensors', original_path: 'Cinematic_Krea2_krea2_3283603_epoch_10.safetensors', model_name: 'Cinematic Krea2', }; const label = ctx.getMetadataTransferModelLabel(modelWithFriendly); assert.strictEqual(label, 'Cinematic_Krea2_krea2_3283603_epoch_10 (Cinematic Krea2)'); const modelSameName = { name: 'Cinematic Krea2.safetensors', original_path: 'Cinematic Krea2.safetensors', model_name: 'Cinematic Krea2', }; assert.strictEqual(ctx.getMetadataTransferModelLabel(modelSameName), 'Cinematic Krea2'); });