Fix progress polling hover state regression in downloads tab

This commit is contained in:
Dariusz L
2026-07-28 18:59:45 +02:00
parent f8a6e8b626
commit 48cc5c9050
2 changed files with 79 additions and 7 deletions
@@ -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')})`);
@@ -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 = `<div class="mr-download-progress-list">${items.join('')}</div>`;
this.patchDownloadProgressContent(
progressDiv,
`<div class="mr-download-progress-list">${items.join('')}</div>`
);
this.attachDownloadActionHandlers(progressDiv, includeDownloadId);
return true;
},
ensureSearchDownloadButtonIcon(downloadBtn) {
if (
!downloadBtn?.classList?.contains('search-download-btn')
|| downloadBtn.querySelector?.('svg')
) {
return;
}
downloadBtn.innerHTML = getSvgIcon('download');
},
updateDownloadButtonForSnapshot(downloadBtn, progress, status, shouldRenderProgress) {
if (!downloadBtn) return;
@@ -1293,7 +1325,7 @@ export const resolveDownloadMethods = {
? `Paused ${percentLabel}`.trim()
: (percentLabel ? `Downloading ${percentLabel}` : 'Starting download...'));
if (downloadBtn.classList.contains('search-download-btn')) {
downloadBtn.innerHTML = getSvgIcon('download');
this.ensureSearchDownloadButtonIcon(downloadBtn);
downloadBtn.setAttribute('data-tooltip', label);
downloadBtn.setAttribute('aria-label', label);
} else {
@@ -1307,7 +1339,7 @@ export const resolveDownloadMethods = {
downloadBtn.disabled = true;
downloadBtn.classList.remove('mr-is-success-action', 'mr-btn-primary');
if (downloadBtn.classList.contains('search-download-btn')) {
downloadBtn.innerHTML = getSvgIcon('download');
this.ensureSearchDownloadButtonIcon(downloadBtn);
downloadBtn.setAttribute('data-tooltip', 'Cancelling download...');
downloadBtn.setAttribute('aria-label', 'Cancelling download');
} else {
@@ -1317,7 +1349,7 @@ export const resolveDownloadMethods = {
downloadBtn.disabled = false;
downloadBtn.classList.remove('mr-is-success-action', 'mr-btn-primary');
if (downloadBtn.classList.contains('search-download-btn')) {
downloadBtn.innerHTML = getSvgIcon('download');
this.ensureSearchDownloadButtonIcon(downloadBtn);
downloadBtn.setAttribute('data-tooltip', 'Retry download');
downloadBtn.setAttribute('aria-label', 'Retry download');
} else {
@@ -1354,7 +1386,10 @@ export const resolveDownloadMethods = {
if (progressDiv) {
progressDiv.classList.remove('mr-is-hidden');
progressDiv.classList.add('mr-is-visible');
progressDiv.innerHTML = this.renderDownloadSnapshotMarkup(downloadId, snapshot);
this.patchDownloadProgressContent(
progressDiv,
this.renderDownloadSnapshotMarkup(downloadId, snapshot)
);
this.attachDownloadActionHandlers(progressDiv, downloadId);
}
@@ -1873,7 +1908,6 @@ export const resolveDownloadMethods = {
if (progress.status === 'downloading' || progress.status === 'starting' || progress.status === 'paused') {
this.renderDownloadSnapshot(downloadId, snapshot, { progressDiv, downloadBtn });
this.refreshLocalMatchesUiForMissing?.(missing);
if (typeof this.requestQueuePanelUpdate === 'function') {
this.requestQueuePanelUpdate();
} else {
@@ -1886,7 +1920,6 @@ export const resolveDownloadMethods = {
} else if (progress.status === 'cancelling') {
this.renderDownloadSnapshot(downloadId, snapshot, { progressDiv, downloadBtn });
this.refreshLocalMatchesUiForMissing?.(missing);
this.updateQueuePanel?.();
setTimeout(() => this.pollDownloadProgress(downloadId), 500);