Use getMissingModelDomKey for search UI element IDs
Replace inline node_id/widget_index DOM key construction with the centralized getMissingModelDomKey helper across search panel and missing browser methods. This keeps search results and button IDs stable when the same model appears in multiple workflows/widgets. Also adds a regression test verifying that search progress stays with its model when switching between shared widgets.
This commit is contained in:
@@ -7785,6 +7785,7 @@ test('info dialog size formatting preserves numeric, nested and label values', (
|
||||
test('link/name controls place the add or search action before the input switch', () => {
|
||||
const renderSearchControls = eval(`(${extractMethod(searchPanelMethodsSource, 'renderSearchControls')})`);
|
||||
const dialog = {
|
||||
getMissingModelDomKey() { return 'model-key'; },
|
||||
getSearchState() {
|
||||
return {
|
||||
selectedSource: 'all',
|
||||
@@ -7838,6 +7839,42 @@ test('link/name controls place the add or search action before the input switch'
|
||||
assert.ok(modeIndex > inputIndex);
|
||||
});
|
||||
|
||||
test('search progress stays with its model when switching a shared widget', async () => {
|
||||
const refresh = eval(`(${extractMethod(searchPanelMethodsSource.replaceAll('workflowKey = this.getWorkflowScopedQueueKey()', "workflowKey = 'workflow'"), 'refreshSearchUiForMissingNow')})`);
|
||||
const window = new Window();
|
||||
const root = window.document.createElement('div');
|
||||
window.document.body.append(root);
|
||||
const first = { node_id: 232, widget_index: 0, original_path: 'first' };
|
||||
const second = { ...first, original_path: 'second' };
|
||||
const state = { activeSearchRunId: 'run-a' };
|
||||
const dialog = {
|
||||
...workflowIdentityMethods,
|
||||
contentElement: root,
|
||||
activeTab: 'missing',
|
||||
getWorkflowScopedQueueKey: () => 'workflow',
|
||||
getMissingSearchKey: model => model.original_path,
|
||||
refreshMissingSourcesSummary() {},
|
||||
hasRenderableSearchState: () => true,
|
||||
displaySearchResults(model, currentState, container) { container.textContent = model.original_path; },
|
||||
renderSearchButtonContent: text => text,
|
||||
};
|
||||
const mount = model => {
|
||||
dialog.selectedMissingModelKey = dialog.getMissingModelKey(model);
|
||||
const key = dialog.getMissingModelDomKey(model);
|
||||
root.innerHTML = `<div id="search-results-${key}"></div><button id="search-${key}">Search</button>`;
|
||||
};
|
||||
mount(second);
|
||||
refresh.call(dialog, first, state, { workflowKey: 'workflow' });
|
||||
assert.equal(root.querySelector('div').textContent, '');
|
||||
assert.equal(root.querySelector('button').disabled, false);
|
||||
mount(first);
|
||||
refresh.call(dialog, first, state, { workflowKey: 'workflow' });
|
||||
assert.equal(root.querySelector('div').textContent, 'first');
|
||||
assert.equal(root.querySelector('button').disabled, true);
|
||||
assert.notEqual(dialog.getMissingModelDomKey(first), dialog.getMissingModelDomKey(second));
|
||||
await window.happyDOM.close();
|
||||
});
|
||||
|
||||
test('search result refreshes preserve keyed progress and result DOM', () => {
|
||||
const displaySearchResults = extractMethod(resolveDownloadMethodsSource, 'displaySearchResults');
|
||||
const patchSearchResultsContainer = extractMethod(
|
||||
|
||||
@@ -2645,13 +2645,13 @@ export const resolveDownloadMethods = {
|
||||
}
|
||||
|
||||
const isUrn = Boolean(missing.is_urn && !requestedSearchQuery && !searchSha256);
|
||||
const resultsId = `search-results-${missing.node_id}-${missing.widget_index}`;
|
||||
const resultsId = `search-results-${this.getMissingModelDomKey(missing)}`;
|
||||
const canUpdateCurrentWorkflow = workflowKey === this.getWorkflowScopedQueueKey();
|
||||
const resultsDiv = canUpdateCurrentWorkflow
|
||||
? this.contentElement?.querySelector(`#${resultsId}`)
|
||||
: null;
|
||||
const searchBtn = canUpdateCurrentWorkflow
|
||||
? this.contentElement?.querySelector(`#search-${missing.node_id}-${missing.widget_index}`)
|
||||
? this.contentElement?.querySelector(`#search-${this.getMissingModelDomKey(missing)}`)
|
||||
: null;
|
||||
let searchRunId = null;
|
||||
let completedSearchRun = false;
|
||||
@@ -3192,13 +3192,13 @@ export const resolveDownloadMethods = {
|
||||
wireDownloadSearchPanel(container, missing) {
|
||||
if (!container) return;
|
||||
|
||||
const searchResultsId = `search-results-${missing.node_id}-${missing.widget_index}`;
|
||||
const searchResultsId = `search-results-${this.getMissingModelDomKey(missing)}`;
|
||||
const searchResultsDiv = container.querySelector(`#${searchResultsId}`);
|
||||
if (searchResultsDiv) {
|
||||
this.wireSearchDownloadButtons(searchResultsDiv, missing);
|
||||
}
|
||||
|
||||
const searchBtn = container.querySelector(`#search-${missing.node_id}-${missing.widget_index}`);
|
||||
const searchBtn = container.querySelector(`#search-${this.getMissingModelDomKey(missing)}`);
|
||||
if (searchBtn && searchBtn.dataset.mlSearchBound !== 'true') {
|
||||
searchBtn.dataset.mlSearchBound = 'true';
|
||||
searchBtn.addEventListener('click', () => {
|
||||
|
||||
@@ -596,8 +596,9 @@ export const searchPanelMethods = {
|
||||
this.updateBatchFooterButtons?.();
|
||||
|
||||
if (!this.contentElement || this.activeTab !== 'missing') return;
|
||||
if (this.selectedMissingModelKey && this.getMissingModelKey(missing) !== this.selectedMissingModelKey) return;
|
||||
|
||||
const resultsId = `search-results-${missing.node_id}-${missing.widget_index}`;
|
||||
const resultsId = `search-results-${this.getMissingModelDomKey(missing)}`;
|
||||
const resultsDiv = this.contentElement.querySelector(`#${resultsId}`);
|
||||
if (resultsDiv && currentState && this.hasRenderableSearchState(currentState)) {
|
||||
resultsDiv.classList.remove('mr-is-hidden');
|
||||
@@ -605,7 +606,7 @@ export const searchPanelMethods = {
|
||||
this.displaySearchResults(missing, currentState, resultsDiv);
|
||||
}
|
||||
|
||||
const searchBtn = this.contentElement.querySelector(`#search-${missing.node_id}-${missing.widget_index}`);
|
||||
const searchBtn = this.contentElement.querySelector(`#search-${this.getMissingModelDomKey(missing)}`);
|
||||
if (searchBtn && currentState) {
|
||||
const isRunning = Boolean(currentState.activeSearchRunId);
|
||||
const hasSearchAttempt = this.hasRenderableSearchState(currentState);
|
||||
@@ -1915,7 +1916,7 @@ export const searchPanelMethods = {
|
||||
html += `</div>`;
|
||||
|
||||
const downloadSourceRow = this.getDownloadSourceTableRow(missing, downloadSource);
|
||||
html += `<div id="search-results-${missing.node_id}-${missing.widget_index}" class="mr-search-results mr-is-visible">`;
|
||||
html += `<div id="search-results-${this.getMissingModelDomKey(missing)}" class="mr-search-results mr-is-visible">`;
|
||||
html += this.renderSearchResultsTable(downloadSourceRow ? [downloadSourceRow] : []);
|
||||
html += `</div>`;
|
||||
return html;
|
||||
@@ -2082,8 +2083,8 @@ export const searchPanelMethods = {
|
||||
|
||||
let html = `<div id="${searchSourcesId}" class="mr-search-source-bar">`;
|
||||
html += `<div class="mr-search-source-picker mr-search-button-picker">`;
|
||||
html += `<label class="mr-search-source-picker-label" for="search-${missing.node_id}-${missing.widget_index}">Search</label>`;
|
||||
html += `<button id="search-${missing.node_id}-${missing.widget_index}" class="mr-btn mr-btn-link" type="button">`;
|
||||
html += `<label class="mr-search-source-picker-label" for="search-${this.getMissingModelDomKey(missing)}">Search</label>`;
|
||||
html += `<button id="search-${this.getMissingModelDomKey(missing)}" class="mr-btn mr-btn-link" type="button">`;
|
||||
html += this.renderSearchButtonContent(buttonText);
|
||||
html += `</button>`;
|
||||
html += `</div>`;
|
||||
|
||||
@@ -2260,7 +2260,7 @@ export const missingBrowserMethods = {
|
||||
}
|
||||
|
||||
const state = this.searchResultCache.get(this.getMissingSearchKey(missing));
|
||||
const searchResultsDiv = container.querySelector(`#search-results-${missing.node_id}-${missing.widget_index}`);
|
||||
const searchResultsDiv = container.querySelector(`#search-results-${this.getMissingModelDomKey(missing)}`);
|
||||
if (state && searchResultsDiv && this.hasRenderableSearchState(state)) {
|
||||
searchResultsDiv.classList.remove('mr-is-hidden');
|
||||
searchResultsDiv.classList.add('mr-is-visible');
|
||||
@@ -2732,7 +2732,7 @@ export const missingBrowserMethods = {
|
||||
});
|
||||
html += this.renderDownloadTargetControls(missing, missing.category || 'checkpoints');
|
||||
html += `</div>`;
|
||||
html += `<div id="search-results-${missing.node_id}-${missing.widget_index}" class="mr-search-results"></div>`;
|
||||
html += `<div id="search-results-${this.getMissingModelDomKey(missing)}" class="mr-search-results"></div>`;
|
||||
} else if (missing.is_urn) {
|
||||
html += `<div id="${urnDownloadId}" class="mr-download-section">`;
|
||||
html += `<div class="mr-download-info">Resolving CivitAI download for this URN...</div>`;
|
||||
@@ -2743,7 +2743,7 @@ export const missingBrowserMethods = {
|
||||
html += this.renderSearchControls(missing);
|
||||
html += this.renderDownloadTargetControls(missing, missing.category || 'checkpoints');
|
||||
html += `</div>`;
|
||||
html += `<div id="search-results-${missing.node_id}-${missing.widget_index}" class="mr-search-results"></div>`;
|
||||
html += `<div id="search-results-${this.getMissingModelDomKey(missing)}" class="mr-search-results"></div>`;
|
||||
}
|
||||
|
||||
// Progress container (for downloads). Use the complete model identity,
|
||||
|
||||
Reference in New Issue
Block a user