diff --git a/tests/test_search_retry.mjs b/tests/test_search_retry.mjs index 8a7c109..5a442bf 100644 --- a/tests/test_search_retry.mjs +++ b/tests/test_search_retry.mjs @@ -29,11 +29,13 @@ function extractMethod(source, methodName, paramsPattern = '[^)]*') { } globalThis.getSvgIcon = () => ''; +const renderSearchProgressItem = eval(`(${extractMethod(searchPanelSource, 'renderSearchProgressItem', '[\\s\\S]*?')})`); const renderSearchProgress = eval(`(${extractMethod(searchPanelSource, 'renderSearchProgress')})`); const wireSearchProgressRetryButtons = eval(`(${extractMethod(searchPanelSource, 'wireSearchProgressRetryButtons', '[\\s\\S]*?')})`); function createDialog() { return { + renderSearchProgressItem, renderSearchProgress, wireSearchProgressRetryButtons, getSearchSourceLabel(source) { @@ -84,6 +86,56 @@ test('retryable provider failures render a source-specific retry action', () => assert.doesNotMatch(html, /data-source="civitai"[^>]*>.*Retry/); }); +test('active search progress renders the full progress rows', () => { + const dialog = createDialog(); + const html = dialog.renderSearchProgress({ + activeSearchRunId: 'run-42', + sourceProgress: { + civitai: { + status: 'running', + percent: 42, + message: 'Querying models', + }, + civarchive: { + status: 'error', + message: 'Search failed', + error: 'HTTP 522', + retryable: true, + }, + }, + }); + + assert.match(html, /class="mr-search-progress-list"/); + assert.doesNotMatch(html, /mr-search-progress-list-compact/); + assert.match(html, /aria-valuenow="42"/); + assert.match(html, /width: 42%/); + assert.match(html, /mr-search-progress-cancel/); + assert.match(html, /data-run-id="run-42"/); + assert.match(html, /mr-search-progress-retry/); +}); + +test('inactive search progress renders compact rows without progress controls', () => { + const dialog = createDialog(); + const html = dialog.renderSearchProgress({ + sourceProgress: { + civitai: { + status: 'found', + message: 'Found', + }, + civarchive: { + status: 'error', + message: 'Search failed', + retryable: true, + }, + }, + }); + + assert.match(html, /mr-search-progress-list-compact/); + assert.doesNotMatch(html, /mr-search-progress-bar/); + assert.doesNotMatch(html, /mr-search-progress-cancel/); + assert.match(html, /mr-search-progress-retry/); +}); + test('source retry action searches only the failed provider with force search', () => { const dialog = createDialog(); const listeners = {}; diff --git a/web/resolver/search/search_panel.js b/web/resolver/search/search_panel.js index 0f7a1d6..76a4174 100644 --- a/web/resolver/search/search_panel.js +++ b/web/resolver/search/search_panel.js @@ -1122,6 +1122,59 @@ export const searchPanelMethods = { )); }, + renderSearchProgressItem(source, progress, { + compact = false, + activeSearchRunId = null, + statusLabels = {}, + } = {}) { + const status = progress?.status || 'pending'; + const statusClass = String(status).replace(/[^a-z0-9_-]/gi, ''); + const label = this.getSearchSourceLabel(source); + const retryButton = status === 'error' && progress?.retryable + ? `` + : ''; + + if (compact) { + const statusLabel = progress?.message || statusLabels[status] || status; + const title = `${label}: ${progress?.error || statusLabel}`; + return ` +