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');
});