Files
willmiao-ComfyUI-Lora-Manager/tests/frontend/components/initialization.test.js
T
Will Miao 86c0239b8c fix(init): honest error state and per-page completion for initialization
Two initialization-page defects made failures look like progress:

- A cache read exception while rendering a model/recipe page forced
  is_initializing=True, showing an endless spinner; worse, the polling
  fallback then reported the idle scanner as "complete", reloading the
  page straight back into the same exception. Page handlers now pass
  init_error to the initialization template, which renders the error
  state (with retry) server-side, and initialization.js stays on it
  instead of starting WebSocket/polling.

- The WebSocket path reloaded once the page's own scanner finished,
  while the polling fallback waited for every scanner. The polling
  fallback now mirrors the per-page semantics: /api/lm/init-status
  accepts ?page=<pageType> (aggregate behavior preserved without it),
  and ModelScanner records _init_error so a failed initial scan is
  reported as "error" instead of masquerading as "complete". Polling
  stops on error instead of reloading into the failure.
2026-10-25 09:02:57 +08:00

133 lines
4.2 KiB
JavaScript

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
vi.mock('../../../static/js/core.js', () => ({
appCore: {
initialize: vi.fn(() => Promise.resolve()),
},
}));
vi.mock('../../../static/js/utils/storageHelpers.js', () => ({
getSessionItem: vi.fn(() => null),
setSessionItem: vi.fn(),
}));
class MockWebSocket {
static instances = [];
static OPEN = 1;
constructor(url) {
this.url = url;
this.readyState = MockWebSocket.OPEN;
MockWebSocket.instances.push(this);
}
close() {
this.readyState = 3;
}
}
function setupDom({ serverError = null } = {}) {
document.body.innerHTML = `
<div class="initialization-container" id="initializationContainer">
<div class="loading-content"${serverError ? ' hidden' : ''}>
<div class="loading-status" id="progressStatus"></div>
<div class="init-progress-bar" id="initProgressBar"></div>
<span id="progressPercentage">0%</span>
<span id="remainingTime"></span>
</div>
<div class="init-error" id="initError"${serverError ? '' : ' hidden'}>
<p id="initErrorText">${serverError ?? ''}</p>
<button type="button" id="initRetryBtn"></button>
</div>
<div class="tip-item"></div>
<span class="tip-dot"></span>
</div>
`;
}
function jsonResponse(payload) {
return Promise.resolve({ json: () => Promise.resolve(payload) });
}
describe('InitializationManager', () => {
let initManager;
beforeEach(async () => {
vi.useFakeTimers();
MockWebSocket.instances = [];
vi.stubGlobal('WebSocket', MockWebSocket);
({ initManager } = await import('../../../static/js/components/initialization.js'));
initManager.websocket = null;
initManager.pollingActive = false;
initManager.progress = 0;
initManager.pageType = null;
initManager.tipInterval = null;
});
afterEach(() => {
initManager.cleanup();
vi.clearAllTimers();
vi.useRealTimers();
vi.unstubAllGlobals();
vi.restoreAllMocks();
document.body.innerHTML = '';
});
it('stays on a server-rendered error instead of polling into a reload loop', () => {
setupDom({ serverError: 'corrupt snapshot' });
const fetchSpy = vi.fn();
vi.stubGlobal('fetch', fetchSpy);
initManager.initialize();
expect(fetchSpy).not.toHaveBeenCalled();
expect(MockWebSocket.instances).toHaveLength(0);
expect(initManager.tipInterval).toBeNull();
});
it('scopes the polling fallback to the current page type', () => {
setupDom();
initManager.pageType = 'loras';
const fetchSpy = vi.fn(() => jsonResponse({ status: 'initializing' }));
vi.stubGlobal('fetch', fetchSpy);
initManager.pollProgress();
expect(fetchSpy).toHaveBeenCalledWith('/api/lm/init-status?page=loras');
});
it('stops polling when the server reports an error', async () => {
setupDom();
initManager.pageType = 'checkpoints';
const fetchSpy = vi.fn(() =>
jsonResponse({ status: 'error', details: 'Checkpoint cache initialization failed: boom' })
);
vi.stubGlobal('fetch', fetchSpy);
initManager.pollProgress();
await vi.advanceTimersByTimeAsync(0);
// The error state is shown...
expect(document.getElementById('initError').hidden).toBe(false);
expect(document.getElementById('initErrorText').textContent)
.toContain('boom');
// ...and no further poll is scheduled.
await vi.advanceTimersByTimeAsync(10000);
expect(fetchSpy).toHaveBeenCalledTimes(1);
});
it('keeps polling while initialization is in progress', async () => {
setupDom();
initManager.pageType = 'loras';
const fetchSpy = vi.fn(() => jsonResponse({ status: 'initializing' }));
vi.stubGlobal('fetch', fetchSpy);
initManager.pollProgress();
await vi.advanceTimersByTimeAsync(0);
await vi.advanceTimersByTimeAsync(2000);
expect(fetchSpy).toHaveBeenCalledTimes(2);
});
});