From 699365f336ac9747ea75178ae1189d5d48fb5553 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Sun, 2 Aug 2026 14:46:58 +0200 Subject: [PATCH] test(frontend): cover node context menu policies --- tests/test_node_context_menu.mjs | 121 +++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 tests/test_node_context_menu.mjs diff --git a/tests/test_node_context_menu.mjs b/tests/test_node_context_menu.mjs new file mode 100644 index 0000000..57a7398 --- /dev/null +++ b/tests/test_node_context_menu.mjs @@ -0,0 +1,121 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + buildModelResolverNodeMenu, + getImmediateModelsForNode, + getResolvedModelsForNode, + isExistingResolvedModel, + matchesWorkflowModelReference, + toResolverContextModel, +} from '../web/resolver/node_context_menu.js'; + +test('node context menu filters existing models by path and workflow scope', () => { + const model = { + node_id: 11, + widget_index: 3, + original_path: 'Folder\\Model.safetensors', + is_top_level: false, + subgraph_id: 'subgraph-a', + exists: true, + }; + + assert.equal(isExistingResolvedModel({ exists: true, full_path: 'C:\\model' }), true); + assert.equal(isExistingResolvedModel({ exists: false, full_path: 'C:\\model' }), false); + assert.equal(isExistingResolvedModel({ exists: true }), false); + assert.equal(matchesWorkflowModelReference(model, { + node_id: '11', + widget_index: 3, + original_path: 'folder/model.safetensors', + is_top_level: false, + subgraph_id: 'subgraph-a', + }), true); + assert.equal(matchesWorkflowModelReference(model, { node_id: 12 }), false); + assert.equal(matchesWorkflowModelReference(model, { node_id: 11, widget_index: 4 }), false); + assert.equal(matchesWorkflowModelReference(model, { node_id: 11, is_top_level: true }), false); + assert.equal(matchesWorkflowModelReference(model, { node_id: 11, subgraph_id: 'other' }), false); + assert.equal(matchesWorkflowModelReference(model, { node_id: 11, original_path: 'other.safetensors' }), false); + + const models = getResolvedModelsForNode({ + resolved_models: [ + { ...model, widget_index: 2, full_path: 'C:\\b', category: 'vae' }, + { ...model, widget_index: 1, full_path: 'C:\\a', category: 'checkpoints' }, + { ...model, exists: false, widget_index: 0 }, + ], + }, 11, { is_top_level: false, subgraph_id: 'subgraph-a' }); + assert.deepEqual(models.map(item => item.widget_index), [1, 2]); +}); + +test('node context menu resolves immediate widget selections and ignores invalid references', () => { + const node = { + id: 7, + type: 'CheckpointLoader', + widgets: [ + { value: 'object-model.safetensors', options: { values: [{ name: 'object-model.safetensors' }] } }, + { value: 'old.safetensors', options: { values: ['old.safetensors'] } }, + { value: 42, options: { values: [42] } }, + { value: 'pending.safetensors' }, + ], + }; + const models = getImmediateModelsForNode({ + resolved_models: [ + { node_id: 7, is_top_level: true, node_type: 'CheckpointLoader', widget_index: 0, original_path: 'object-model.safetensors', exists: true, full_path: 'C:\\object' }, + { node_id: 7, is_top_level: true, node_type: 'OtherLoader', widget_index: 1, original_path: 'old.safetensors', exists: true, full_path: 'C:\\old' }, + { node_id: 7, is_top_level: true, widget_index: 1, original_path: 'old.safetensors', exists: true, full_path: 'C:\\old' }, + { node_id: 7, is_top_level: true, widget_index: 2, original_path: '42', exists: true, full_path: 'C:\\number' }, + { node_id: 7, is_top_level: true, widget_index: 3, original_path: 'other.safetensors', exists: true, full_path: 'C:\\other' }, + ], + missing_models: [ + { node_id: 7, is_top_level: true, widget_index: 3, original_path: 'old-pending.safetensors', node_type: 'CheckpointLoader' }, + ], + }, node, { is_top_level: true }); + + assert.equal(models.length, 3); + assert.equal(models[0].widget_index, 0); + assert.equal(models[0].full_path, 'C:\\object'); + assert.equal(models[2].resolution_pending, true); + assert.equal(models[2].original_path, 'pending.safetensors'); +}); + +test('node context menu builds empty, single, and grouped menus with optional handlers', () => { + assert.equal(buildModelResolverNodeMenu([]), null); + assert.equal(buildModelResolverNodeMenu([{ exists: false, full_path: '' }]), null); + + const calls = []; + const pending = { resolution_pending: true, category: 'diffusion_models', original_path: 'pending.gguf' }; + const single = buildModelResolverNodeMenu([pending], { + showInResolver: model => calls.push(['resolver', model.original_path]), + }, { + formatCategory: () => 'Diffusion models', + }); + assert.equal(single.submenu.options.length, 3); + single.submenu.options[0].callback(); + single.submenu.options[1].callback(); + single.submenu.options[2].callback(); + assert.deepEqual(calls, [['resolver', 'pending.gguf']]); + + const grouped = buildModelResolverNodeMenu([ + { exists: true, full_path: 'C:\\a', category: 'checkpoints', name: 'a.safetensors' }, + { exists: true, full_path: 'C:\\b', category: 'vae', name: 'b.safetensors' }, + ]); + assert.equal(grouped.submenu.options.length, 2); + assert.equal(grouped.submenu.options[0].has_submenu, true); + assert.equal(grouped.submenu.options[0].submenu.options.length, 3); +}); + +test('node context conversion prefers resolved paths and derives safe display fields', () => { + const model = { + original_path: 'folder/model.gguf', + resolved_path: 'C:\\resolved\\model.gguf', + relative_path: 'relative/model.gguf', + category: 'diffusion_models', + }; + const context = toResolverContextModel(model); + + assert.equal(context.name, 'model.gguf'); + assert.equal(context.filename, 'model.gguf'); + assert.equal(context.path, model.resolved_path); + assert.equal(context.resolved_path, model.resolved_path); + assert.equal(context.relative_path, model.relative_path); + assert.equal(context.context_scope, 'local_model'); +});