diff --git a/tests/test_downloads_tab_workflow_route.mjs b/tests/test_downloads_tab_workflow_route.mjs index 0b9b545..0cd83c2 100644 --- a/tests/test_downloads_tab_workflow_route.mjs +++ b/tests/test_downloads_tab_workflow_route.mjs @@ -4259,6 +4259,45 @@ test('progress polling explicitly handles cancelling as a non-interactive state' ); }); +test('Missing Models download progress preserves hovered controls between polling updates', () => { + const renderDownloadSnapshot = extractMethod( + resolveDownloadMethodsSource, + 'renderDownloadSnapshot' + ); + const renderDownloadProgressGroupForMissing = extractMethod( + resolveDownloadMethodsSource, + 'renderDownloadProgressGroupForMissing' + ); + const patchDownloadProgressContent = extractMethod( + resolveDownloadMethodsSource, + 'patchDownloadProgressContent' + ); + const pollDownloadProgress = extractMethod( + resolveDownloadMethodsSource, + 'pollDownloadProgress' + ); + + assert.match(renderDownloadSnapshot, /patchDownloadProgressContent\(/); + assert.doesNotMatch(renderDownloadSnapshot, /progressDiv\.innerHTML\s*=/); + assert.match(renderDownloadProgressGroupForMissing, /patchDownloadProgressContent\(/); + assert.doesNotMatch(renderDownloadProgressGroupForMissing, /progressDiv\.innerHTML\s*=/); + assert.match( + patchDownloadProgressContent, + /patchDownloadsPanelElement\(progressDiv,\s*nextProgressDiv\)/ + ); + + const activeProgressBranch = pollDownloadProgress.match( + /if \(progress\.status === 'downloading'[\s\S]*?\} else if \(progress\.status === 'cancelling'\)/ + )?.[0] || ''; + const cancellingBranch = pollDownloadProgress.match( + /else if \(progress\.status === 'cancelling'\)[\s\S]*?\} else if \(progress\.status === 'completed'\)/ + )?.[0] || ''; + assert.ok(activeProgressBranch); + assert.ok(cancellingBranch); + assert.doesNotMatch(activeProgressBranch, /refreshLocalMatchesUiForMissing/); + assert.doesNotMatch(cancellingBranch, /refreshLocalMatchesUiForMissing/); +}); + test('native Xet progress polling refreshes every 200 milliseconds', () => { const getDownloadProgressPollDelay = eval(`(${extractMethod(resolveDownloadMethodsSource, 'getDownloadProgressPollDelay')})`); diff --git a/web/resolver/actions/resolve_download_methods.js b/web/resolver/actions/resolve_download_methods.js index 504191c..a834849 100644 --- a/web/resolver/actions/resolve_download_methods.js +++ b/web/resolver/actions/resolve_download_methods.js @@ -1253,6 +1253,25 @@ export const resolveDownloadMethods = { `; }, + patchDownloadProgressContent(progressDiv, html) { + if (!progressDiv) return; + + const nextHtml = String(html || ''); + if ( + typeof progressDiv.cloneNode !== 'function' + || typeof this.patchDownloadsPanelElement !== 'function' + ) { + if (progressDiv.innerHTML !== nextHtml) { + progressDiv.innerHTML = nextHtml; + } + return; + } + + const nextProgressDiv = progressDiv.cloneNode(false); + nextProgressDiv.innerHTML = nextHtml; + this.patchDownloadsPanelElement(progressDiv, nextProgressDiv); + }, + renderDownloadProgressGroupForMissing(missing, progressDiv, { includeDownloadId = '', includeSnapshot = null } = {}) { if (!missing || !progressDiv) return false; @@ -1271,11 +1290,24 @@ export const resolveDownloadMethods = { progressDiv.classList.remove('mr-is-hidden'); progressDiv.classList.add('mr-is-visible'); - progressDiv.innerHTML = `