Unify metadata workflow cache and category handling
This commit is contained in:
@@ -2096,6 +2096,40 @@ test('background Loaded Models refresh keeps the current view until new data is
|
||||
assert.equal(contentElement.scrollTop, 48);
|
||||
});
|
||||
|
||||
test('Loaded Models renders the shared cache without starting another scan', async () => {
|
||||
const loadLoadedModels = eval(`(${extractMethod(tabsLoadedMethodsSource, 'loadLoadedModels')})`);
|
||||
const getCachedLoadedModelsForSignature = eval(`(${extractMethod(
|
||||
workflowStateMethodsSource,
|
||||
'getCachedLoadedModelsForSignature'
|
||||
)})`);
|
||||
const cachedData = { loaded_models: [{ strength: 0.5 }], total: 1 };
|
||||
const contentElement = { innerHTML: '' };
|
||||
let fetchCount = 0;
|
||||
let renderCount = 0;
|
||||
const dialog = {
|
||||
activeTab: 'loaded',
|
||||
contentElement,
|
||||
cachedLoadedModelsSignature: 'workflow-a',
|
||||
cachedLoadedModelsData: cachedData,
|
||||
syncWorkflowScopedQueue() {},
|
||||
getWorkflowSignature: () => 'workflow-a',
|
||||
getCachedLoadedModelsForSignature,
|
||||
fetchJson: async () => {
|
||||
fetchCount += 1;
|
||||
throw new Error('The shared cache was not used');
|
||||
},
|
||||
displayLoadedModels: (_container, data) => {
|
||||
renderCount += 1;
|
||||
assert.equal(data, cachedData);
|
||||
},
|
||||
};
|
||||
|
||||
await loadLoadedModels.call(dialog, { nodes: [] });
|
||||
|
||||
assert.equal(fetchCount, 0);
|
||||
assert.equal(renderCount, 1);
|
||||
});
|
||||
|
||||
test('Loaded Models progress patches the stable progress container', () => {
|
||||
const pollWorkflowProgress = extractMethod(renderFormatMethodsSource, 'pollWorkflowProgress');
|
||||
const patchLoadedModelsProgress = extractMethod(
|
||||
@@ -2730,6 +2764,31 @@ test('workflow analysis and loaded model caches stay independent and cloned', ()
|
||||
assert.equal(dialog.workflowAnalysisCaches.get(dialog.workflowKey).data.missing_models.length, 1);
|
||||
});
|
||||
|
||||
test('shared loaded model cache requires a matching workflow signature unless forced', () => {
|
||||
const getCachedLoadedModelsForSignature = eval(`(${extractMethod(
|
||||
workflowStateMethodsSource,
|
||||
'getCachedLoadedModelsForSignature'
|
||||
)})`);
|
||||
const cachedData = { loaded_models: [{ filename: 'loaded.safetensors' }] };
|
||||
const dialog = {
|
||||
cachedLoadedModelsSignature: 'workflow-a',
|
||||
cachedLoadedModelsData: cachedData,
|
||||
};
|
||||
|
||||
assert.equal(
|
||||
getCachedLoadedModelsForSignature.call(dialog, 'workflow-a'),
|
||||
cachedData
|
||||
);
|
||||
assert.equal(
|
||||
getCachedLoadedModelsForSignature.call(dialog, 'workflow-b'),
|
||||
null
|
||||
);
|
||||
assert.equal(
|
||||
getCachedLoadedModelsForSignature.call(dialog, 'workflow-a', { force: true }),
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
test('Missing Models filter toggles persist state and rerender once per change', () => {
|
||||
const bindMissingFilterToggle = eval(
|
||||
`(${extractMethod(missingBrowserMethodsSource, 'bindMissingFilterToggle')})`
|
||||
|
||||
@@ -147,6 +147,123 @@ function createLoraTransferState(sourceModels, activityScope = 'all') {
|
||||
};
|
||||
}
|
||||
|
||||
test('metadata transfer persists only mode and activity scope preferences', () => {
|
||||
const context = createTransferContext();
|
||||
|
||||
assert.deepEqual(context.getMetadataTransferPreferences(), {
|
||||
mode: 'replace',
|
||||
activityScope: 'all',
|
||||
});
|
||||
|
||||
assert.equal(context.setMetadataTransferMode('merge'), 'merge');
|
||||
assert.equal(context.setMetadataTransferActivityScope('inactive'), 'inactive');
|
||||
assert.deepEqual(context.getMetadataTransferPreferences(), {
|
||||
mode: 'merge',
|
||||
activityScope: 'inactive',
|
||||
});
|
||||
});
|
||||
|
||||
test('metadata tab reuses transfer state for the unchanged workflow', () => {
|
||||
const context = createTransferContext();
|
||||
context.getWorkflowSignature = workflow => workflow.signature;
|
||||
const state = { transfer: { workflowSignature: 'workflow-a' } };
|
||||
|
||||
assert.equal(
|
||||
context.isMetadataTransferCurrentWorkflow(state, { signature: 'workflow-a' }),
|
||||
true
|
||||
);
|
||||
assert.equal(
|
||||
context.isMetadataTransferCurrentWorkflow(state, { signature: 'workflow-b' }),
|
||||
false
|
||||
);
|
||||
assert.equal(
|
||||
context.isMetadataTransferCurrentWorkflow({ transfer: { workflowSignature: '' } }, null),
|
||||
true
|
||||
);
|
||||
assert.equal(context.isMetadataTransferCurrentWorkflow(null, null), false);
|
||||
});
|
||||
|
||||
test('metadata transfer reuses the shared Loaded Models cache for the same workflow', async () => {
|
||||
const cachedData = {
|
||||
loaded_models: [{
|
||||
category: 'text_encoders',
|
||||
node_id: 1,
|
||||
node_type: 'CLIPLoader',
|
||||
widget_index: 0,
|
||||
original_path: 'current.safetensors',
|
||||
active: true,
|
||||
connected: true,
|
||||
is_top_level: true,
|
||||
}],
|
||||
total: 1,
|
||||
};
|
||||
let fetchCount = 0;
|
||||
const context = {
|
||||
...imageMetadataMethods,
|
||||
getCurrentWorkflow: () => ({ signature: 'workflow-a' }),
|
||||
getWorkflowSignature: workflow => workflow.signature,
|
||||
cachedLoadedModelsSignature: 'workflow-a',
|
||||
cachedLoadedModelsData: cachedData,
|
||||
getCachedLoadedModelsForSignature(signature, { force = false } = {}) {
|
||||
return !force && signature === this.cachedLoadedModelsSignature
|
||||
? this.cachedLoadedModelsData
|
||||
: null;
|
||||
},
|
||||
fetchJson: async () => {
|
||||
fetchCount += 1;
|
||||
throw new Error('The shared cache was not used');
|
||||
},
|
||||
renderImageMetadataResult() {},
|
||||
};
|
||||
|
||||
const result = await context.openMetadataTransfer();
|
||||
|
||||
assert.equal(fetchCount, 0);
|
||||
assert.equal(result, cachedData);
|
||||
assert.equal(context.imageInspectorState.transfer.targetModels, cachedData);
|
||||
});
|
||||
|
||||
test('metadata transfer stores a fresh target scan in the shared Loaded Models cache', async () => {
|
||||
const freshData = { loaded_models: [], total: 0 };
|
||||
let saveCount = 0;
|
||||
const context = {
|
||||
...imageMetadataMethods,
|
||||
getCurrentWorkflow: () => ({ signature: 'workflow-b' }),
|
||||
getWorkflowSignature: workflow => workflow.signature,
|
||||
cachedLoadedModelsSignature: null,
|
||||
cachedLoadedModelsData: null,
|
||||
getCachedLoadedModelsForSignature() {
|
||||
return null;
|
||||
},
|
||||
fetchJson: async () => freshData,
|
||||
saveLoadedModelsCacheForActiveWorkflow() {
|
||||
saveCount += 1;
|
||||
},
|
||||
renderImageMetadataResult() {},
|
||||
};
|
||||
|
||||
const result = await context.openMetadataTransfer();
|
||||
|
||||
assert.equal(result, freshData);
|
||||
assert.equal(context.cachedLoadedModelsSignature, 'workflow-b');
|
||||
assert.equal(context.cachedLoadedModelsData, freshData);
|
||||
assert.equal(saveCount, 1);
|
||||
});
|
||||
|
||||
test('metadata transfer uses the shared canonical category aliases', () => {
|
||||
const context = createTransferContext();
|
||||
const state = createTransferState([
|
||||
{ category: 'clip_gguf', original_path: 'clip.safetensors' },
|
||||
]);
|
||||
state.transfer.targetGroups[0].category = 'clips';
|
||||
state.transfer.targetGroups[0].nodes[0].refs[0].category = 'clips';
|
||||
|
||||
const [row] = context.getMetadataTransferPreviewRows(state);
|
||||
|
||||
assert.equal(row.sourceUnavailable, false);
|
||||
assert.equal(row.nextValue, 'clip.safetensors');
|
||||
});
|
||||
|
||||
test('metadata transfer hides imported models with an unsupported file extension', () => {
|
||||
const context = createTransferContext();
|
||||
const state = createTransferState([
|
||||
|
||||
@@ -4,6 +4,7 @@ from types import SimpleNamespace
|
||||
import pytest
|
||||
|
||||
from core.contracts import ModelReference
|
||||
from core.type_utils import CATEGORY_MAP
|
||||
from core.workflow_transfer import (
|
||||
normalize_transfer_category,
|
||||
transfer_models_to_workflow,
|
||||
@@ -48,9 +49,15 @@ def _selector(reference):
|
||||
def test_normalize_transfer_category_covers_workflow_aliases():
|
||||
assert normalize_transfer_category("UPSCALE-MODEL") == "upscale_models"
|
||||
assert normalize_transfer_category("checkpoint") == "checkpoints"
|
||||
assert normalize_transfer_category("CLIP / GGUF") == "text_encoders"
|
||||
assert normalize_transfer_category("unknown") == ""
|
||||
|
||||
|
||||
def test_normalize_transfer_category_matches_the_canonical_category_map():
|
||||
for raw_category, canonical_category in CATEGORY_MAP.items():
|
||||
assert normalize_transfer_category(raw_category) == canonical_category
|
||||
|
||||
|
||||
def test_replace_fans_one_source_to_selected_slots_and_syncs_named_values():
|
||||
workflow = {
|
||||
"nodes": [
|
||||
|
||||
Reference in New Issue
Block a user