diff --git a/tests/test_downloads_tab_workflow_route.mjs b/tests/test_downloads_tab_workflow_route.mjs index 434397c..d26d821 100644 --- a/tests/test_downloads_tab_workflow_route.mjs +++ b/tests/test_downloads_tab_workflow_route.mjs @@ -5667,6 +5667,7 @@ test('local match status labels hash matches distinctly from exact matches', () test('local match refreshes preserve keyed rows and button handlers', () => { const renderLocalMatchesContent = extractMethod(searchPanelMethodsSource, 'renderLocalMatchesContent'); + const renderLocalMatchRow = extractMethod(searchPanelMethodsSource, 'renderLocalMatchRow'); const patchLocalMatchesContainer = extractMethod( resolveDownloadMethodsSource, 'patchLocalMatchesContainer' @@ -5678,8 +5679,9 @@ test('local match refreshes preserve keyed rows and button handlers', () => { const wireLocalMatchButtons = extractMethod(searchPanelMethodsSource, 'wireLocalMatchButtons'); const refreshMissingListRow = extractMethod(missingBrowserMethodsSource, 'refreshMissingListRow'); - assert.match(renderLocalMatchesContent, /data-local-match-key/); - assert.match(renderLocalMatchesContent, /encodeURIComponent/); + assert.match(renderLocalMatchesContent, /renderLocalMatchRow/); + assert.match(renderLocalMatchRow, /data-local-match-key/); + assert.match(renderLocalMatchRow, /encodeURIComponent/); assert.match(patchLocalMatchesContainer, /local-match-key|localMatchKey/); assert.match(patchLocalMatchesContainer, /mr-local-alternatives-toggle/); assert.match(refreshLocalMatchesUiForMissing, /patchLocalMatchesContainer/); @@ -5692,6 +5694,76 @@ test('local match refreshes preserve keyed rows and button handlers', () => { assert.match(refreshMissingListRow, /innerHTML !== renderedSources/); }); +test('local match rows preserve best-match and alternative rendering contracts', () => { + globalThis.getSvgIcon = () => ''; + const renderLocalMatchRow = eval(`(${extractMethod(searchPanelMethodsSource, 'renderLocalMatchRow')})`); + const renderLocalMatchesContent = eval(`(${extractMethod(searchPanelMethodsSource, 'renderLocalMatchesContent')})`); + const dialog = { + renderLocalMatchRow, + getHashMatchLabelMap() { + return null; + }, + buildContextMenuModelData() { + return { model: 'context' }; + }, + getLocalMatchContextData() { + return { source: 'local' }; + }, + getLocalMatchIdentity(match) { + return match.identity; + }, + escapeHtml(value) { + return String(value).replace(/[&<>"']/g, char => ({ + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + }[char])); + }, + getContextMenuAttrs() { + return ' data-context-menu="true"'; + }, + getConfidenceBadge(confidence) { + return `${confidence}`; + }, + getModelPreviewTooltipAttrs() { + return ''; + }, + renderLocalMatchStatusGroup() { + return 'Status'; + }, + areLocalMatchAlternativesCollapsed() { + return false; + }, + }; + const html = renderLocalMatchesContent.call(dialog, { + node_id: 1, + widget_index: 2, + matches: [ + { + confidence: 100, + identity: 'path:/models/best.safetensors', + model: { relative_path: 'models/best.safetensors' }, + }, + { + confidence: 80, + identity: 'path:/models/alternative.safetensors', + model: { relative_path: 'models/alternative.safetensors' }, + }, + ], + }, 0); + + assert.match(html, /class="mr-match-row mr-best-match"/); + assert.match(html, /id="resolve-0-1-2-0"/); + assert.match(html, /data-local-match-key="path%3A%2Fmodels%2Fbest\.safetensors"/); + assert.match(html, /data-context-menu="true"/g); + assert.match(html, /data-local-match-alternative="true"/); + assert.match(html, /id="resolve-alt-0-1-2-0"/); + assert.match(html, /data-local-match-key="path%3A%2Fmodels%2Falternative\.safetensors"/); + assert.equal((html.match(/class="status-group"/g) || []).length, 2); +}); + test('URN resolution preserves the download panel container', () => { const resolveUrnAsync = extractMethod(resolveDownloadMethodsSource, 'resolveUrnAsync'); diff --git a/web/resolver/search/search_panel.js b/web/resolver/search/search_panel.js index 76a4174..be0d4ca 100644 --- a/web/resolver/search/search_panel.js +++ b/web/resolver/search/search_panel.js @@ -2358,6 +2358,45 @@ export const searchPanelMethods = { } catch (_e) {} }, + renderLocalMatchRow(missing, match, { + missingIndex = 0, + matchIndex = 0, + hashLabelMap = null, + alternative = false, + bestMatch = false, + } = {}) { + const buttonPrefix = alternative ? 'resolve-alt' : 'resolve'; + const buttonId = `${buttonPrefix}-${missingIndex}-${missing.node_id}-${missing.widget_index}-${matchIndex}`; + const matchPath = match.model?.relative_path || match.model?.path || match.path || match.filename || ''; + const contextModel = this.buildContextMenuModelData( + match.model || {}, + match.filename || '', + this.getLocalMatchContextData(missing, match) + ); + const localMatchIdentity = this.getLocalMatchIdentity?.(match) || ''; + const identityAttr = localMatchIdentity + ? ` data-local-match-identity="${this.escapeHtml(localMatchIdentity)}"` + : ''; + const fallbackKey = alternative ? `alternative-${matchIndex}` : `match-${matchIndex}`; + const localMatchKey = localMatchIdentity + || matchPath + || match.filename + || fallbackKey; + const localMatchKeyAttr = ` data-local-match-key="${this.escapeHtml(encodeURIComponent(String(localMatchKey)))}"`; + const alternativeAttr = alternative ? ' data-local-match-alternative="true"' : ''; + const rowClass = `mr-match-row${bestMatch ? ' mr-best-match' : ''}`; + + let html = `
`; + html += this.getConfidenceBadge(match.confidence); + html += `${this.escapeHtml(matchPath)}`; + html += this.renderLocalMatchStatusGroup(missing, match, hashLabelMap); + html += ``; + html += `
`; + return html; + }, + renderLocalMatchesContent(missing, missingIndex = 0) { const allMatches = missing.matches || []; const filteredMatches = allMatches.filter(m => m.confidence >= 70); @@ -2381,33 +2420,13 @@ export const searchPanelMethods = { for (let matchIndex = 0; matchIndex < sortedMatches.length; matchIndex++) { const match = sortedMatches[matchIndex]; - const buttonId = `resolve-${missingIndex}-${missing.node_id}-${missing.widget_index}-${matchIndex}`; - const matchPath = match.model?.relative_path || match.model?.path || match.path || match.filename || ''; const isBestMatch = matchIndex === 0 && match.confidence >= 95; - const contextModel = this.buildContextMenuModelData( - match.model || {}, - match.filename || '', - this.getLocalMatchContextData(missing, match) - ); - const localMatchIdentity = this.getLocalMatchIdentity?.(match) || ''; - const identityAttr = localMatchIdentity - ? ` data-local-match-identity="${this.escapeHtml(localMatchIdentity)}"` - : ''; - const localMatchKey = localMatchIdentity - || matchPath - || match.filename - || `match-${matchIndex}`; - const localMatchKeyAttr = ` data-local-match-key="${this.escapeHtml(encodeURIComponent(String(localMatchKey)))}"`; - const rowClass = `mr-match-row${isBestMatch ? ' mr-best-match' : ''}`; - - html += `
`; - html += this.getConfidenceBadge(match.confidence); - html += `${this.escapeHtml(matchPath)}`; - html += this.renderLocalMatchStatusGroup(missing, match, hashLabelMap); - html += ``; - html += `
`; + html += this.renderLocalMatchRow(missing, match, { + missingIndex, + matchIndex, + hashLabelMap, + bestMatch: isBestMatch, + }); } if (perfectMatches.length > 0 && otherMatches.length > 0) { @@ -2422,28 +2441,12 @@ export const searchPanelMethods = { html += `
`; for (let mIdx = 0; mIdx < otherMatches.length; mIdx++) { const match = otherMatches[mIdx]; - const altBtnId = `resolve-alt-${missingIndex}-${missing.node_id}-${missing.widget_index}-${mIdx}`; - const contextModel = this.buildContextMenuModelData( - match.model || {}, - match.filename || '', - this.getLocalMatchContextData(missing, match) - ); - const matchPath = match.model?.relative_path || match.model?.path || match.path || match.filename || ''; - const localMatchIdentity = this.getLocalMatchIdentity?.(match) || ''; - const identityAttr = localMatchIdentity - ? ` data-local-match-identity="${this.escapeHtml(localMatchIdentity)}"` - : ''; - const localMatchKey = localMatchIdentity - || matchPath - || match.filename - || `alternative-${mIdx}`; - const localMatchKeyAttr = ` data-local-match-key="${this.escapeHtml(encodeURIComponent(String(localMatchKey)))}"`; - html += `
`; - html += this.getConfidenceBadge(match.confidence); - html += `${this.escapeHtml(matchPath)}`; - html += this.renderLocalMatchStatusGroup(missing, match, hashLabelMap); - html += ``; - html += `
`; + html += this.renderLocalMatchRow(missing, match, { + missingIndex, + matchIndex: mIdx, + hashLabelMap, + alternative: true, + }); } html += `
`; }