diff --git a/tests/test_downloads_tab_workflow_route.mjs b/tests/test_downloads_tab_workflow_route.mjs index 42ece6f..aa7d7a4 100644 --- a/tests/test_downloads_tab_workflow_route.mjs +++ b/tests/test_downloads_tab_workflow_route.mjs @@ -6,6 +6,7 @@ import { Window } from 'happy-dom'; import { html, normalizePathIdentity, + pollBackgroundTask, safeStorage, } from '../web/resolver/utils/html_utils.js'; import { getModelCardUrl, getSourceKeyFromUrl } from '../web/resolver/utils/url_utils.js'; @@ -55,6 +56,7 @@ void collectNormalizedCategoryValues; void CATEGORY_ALIASES; void normalizeCategoryToken; void normalizeSha256; +void pollBackgroundTask; const projectRoot = path.resolve(import.meta.dirname, '..'); const queueMethodsSource = fs.readFileSync( @@ -2037,6 +2039,7 @@ test('background Loaded Models refresh keeps the current view until new data is }); test('Loaded Models progress patches the stable progress container', () => { + const pollWorkflowProgress = extractMethod(renderFormatMethodsSource, 'pollWorkflowProgress'); const patchLoadedModelsProgress = extractMethod( renderFormatMethodsSource, 'patchLoadedModelsProgress' @@ -2049,17 +2052,81 @@ test('Loaded Models progress patches the stable progress container', () => { const patchAnalysisProgress = extractMethod(renderFormatMethodsSource, 'patchAnalysisProgress'); const pollAnalysisProgress = extractMethod(renderFormatMethodsSource, 'pollAnalysisProgress'); + assert.match(pollWorkflowProgress, /pollBackgroundTask\(/); assert.match(patchLoadedModelsProgress, /mr-download-section/); assert.match(patchLoadedModelsProgress, /mr-progress-fill/); assert.match(patchLoadedModelsProgress, /currentInfo/); + assert.match(pollLoadedModelsProgress, /this\.pollWorkflowProgress\(/); + assert.doesNotMatch(pollLoadedModelsProgress, /pollBackgroundTask\(/); assert.match(pollLoadedModelsProgress, /patchLoadedModelsProgress\(/); assert.doesNotMatch(pollLoadedModelsProgress, /contentElement\.innerHTML\s*=/); assert.match(loadLoadedModels, /patchLoadedModelsProgress\(/); assert.match(patchAnalysisProgress, /patchLoadedModelsProgress\(/); + assert.match(pollAnalysisProgress, /this\.pollWorkflowProgress\(/); + assert.doesNotMatch(pollAnalysisProgress, /pollBackgroundTask\(/); assert.match(pollAnalysisProgress, /patchAnalysisProgress\(/); assert.doesNotMatch(pollAnalysisProgress, /contentElement\.innerHTML\s*=/); }); +test('workflow progress polling preserves endpoint, rendering, and token cleanup', async () => { + const pollWorkflowProgress = eval(`(${extractMethod(renderFormatMethodsSource, 'pollWorkflowProgress')})`); + const pollAnalysisProgress = eval(`(${extractMethod(renderFormatMethodsSource, 'pollAnalysisProgress')})`); + const pollLoadedModelsProgress = eval(`(${extractMethod(renderFormatMethodsSource, 'pollLoadedModelsProgress')})`); + const cases = [ + { + method: pollAnalysisProgress, + activeTab: 'missing', + tokenProperty: '_analysisProgressToken', + id: 'analysis-1', + endpoint: '/model_resolver/analyze-progress/analysis-1', + responses: [{ status: 'completed' }], + renderMethod: 'renderAnalysisProgress', + patchMethod: 'patchAnalysisProgress', + }, + { + method: pollLoadedModelsProgress, + activeTab: 'loaded', + tokenProperty: '_loadedModelsProgressToken', + id: 'loaded/model', + endpoint: '/model_resolver/loaded-progress/loaded%2Fmodel', + responses: [{ status: 'unknown' }, { status: 'completed' }], + renderMethod: 'renderLoadedModelsProgress', + patchMethod: 'patchLoadedModelsProgress', + }, + ]; + + for (const testCase of cases) { + const requests = []; + const rendered = []; + const patched = []; + let responseIndex = 0; + const dialog = { + activeTab: testCase.activeTab, + contentElement: {}, + [testCase.tokenProperty]: testCase.id, + pollWorkflowProgress, + fetchJson: async endpoint => { + requests.push(endpoint); + return testCase.responses[responseIndex++]; + }, + [testCase.renderMethod]: progress => { + rendered.push(progress.status); + return `
${progress.status}
`; + }, + [testCase.patchMethod]: (_container, content) => { + patched.push(content); + }, + }; + + await testCase.method.call(dialog, testCase.id, testCase.id); + + assert.deepEqual(requests, Array(testCase.responses.length).fill(testCase.endpoint)); + assert.equal(dialog[testCase.tokenProperty], null); + assert.deepEqual(rendered, ['completed']); + assert.deepEqual(patched, ['
completed
']); + } +}); + test('Power Lora Loader strength updates only its Loaded Models chip and cache', () => { const updateLoadedModelStrengthsFromNode = eval( `(${extractMethod(tabsLoadedMethodsSource, 'updateLoadedModelStrengthsFromNode')})` diff --git a/web/resolver/utils/render_format_methods.js b/web/resolver/utils/render_format_methods.js index 1a71239..8e68de4 100644 --- a/web/resolver/utils/render_format_methods.js +++ b/web/resolver/utils/render_format_methods.js @@ -280,8 +280,29 @@ export const renderFormatMethods = { this.patchLoadedModelsProgress(container, renderedHtml); }, - async pollAnalysisProgress(analysisId, token) { + async pollWorkflowProgress({ + endpoint, + tokenCheck, + onProgress, + onTerminal, + onError, + filterIgnoredStatus, + }) { await pollBackgroundTask({ + endpoint, + tokenCheck, + onProgress, + isTerminal: (progress) => progress.status === 'completed' || progress.status === 'error', + onTerminal, + onError, + intervalMs: 250, + fetchJson: this.fetchJson.bind(this), + filterIgnoredStatus, + }); + }, + + async pollAnalysisProgress(analysisId, token) { + await this.pollWorkflowProgress({ endpoint: `/model_resolver/analyze-progress/${analysisId}`, tokenCheck: () => this._analysisProgressToken === token, onProgress: (progress) => { @@ -301,13 +322,11 @@ export const renderFormatMethods = { onError: (error) => { console.warn('Model Resolver: analysis progress polling failed', error); }, - intervalMs: 250, - fetchJson: this.fetchJson.bind(this) }); }, async pollLoadedModelsProgress(loadedId, token) { - await pollBackgroundTask({ + await this.pollWorkflowProgress({ endpoint: `/model_resolver/loaded-progress/${encodeURIComponent(loadedId)}`, tokenCheck: () => this._loadedModelsProgressToken === token, filterIgnoredStatus: (progress) => progress?.status === 'unknown', @@ -328,8 +347,6 @@ export const renderFormatMethods = { onError: (error) => { console.warn('Model Resolver: loaded models progress polling failed', error); }, - intervalMs: 250, - fetchJson: this.fetchJson.bind(this) }); },