mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-08-28 08:21:27 -03:00
fix(recipes): restore batch import modal on reopen and log recipe ingest progress (#1084)
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { renderTemplate } from '../utils/domFixtures.js';
|
||||
|
||||
vi.mock('../../../static/js/utils/uiHelpers.js', () => ({
|
||||
showToast: vi.fn(),
|
||||
setupAutoNewlineOnPaste: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../../static/js/utils/i18nHelpers.js', () => ({
|
||||
translate: (key, params = {}, fallback = null) => fallback ?? key,
|
||||
}));
|
||||
|
||||
vi.mock('../../../static/js/api/apiConfig.js', () => ({
|
||||
WS_ENDPOINTS: {},
|
||||
}));
|
||||
|
||||
vi.mock('../../../static/js/utils/storageHelpers.js', () => ({
|
||||
getStorageItem: vi.fn(() => true),
|
||||
setStorageItem: vi.fn(),
|
||||
}));
|
||||
|
||||
// jsdom has no WebSocket; the manager only needs open/connecting/close states.
|
||||
class FakeWebSocket {
|
||||
constructor(url) {
|
||||
this.url = url;
|
||||
this.readyState = 0;
|
||||
}
|
||||
close() {
|
||||
this.readyState = 3;
|
||||
}
|
||||
}
|
||||
FakeWebSocket.OPEN = 1;
|
||||
FakeWebSocket.CONNECTING = 0;
|
||||
|
||||
const RUNNING_PROGRESS = {
|
||||
status: 'running',
|
||||
total: 2,
|
||||
completed: 1,
|
||||
success: 1,
|
||||
failed: 0,
|
||||
skipped: 0,
|
||||
progress_percent: 50,
|
||||
current_item: 'image-1.png',
|
||||
};
|
||||
|
||||
const COMPLETED_PROGRESS = {
|
||||
status: 'completed',
|
||||
total: 2,
|
||||
completed: 2,
|
||||
success: 2,
|
||||
failed: 0,
|
||||
skipped: 0,
|
||||
progress_percent: 100,
|
||||
current_item: '',
|
||||
};
|
||||
|
||||
describe('BatchImportManager reopen behavior (#1084)', () => {
|
||||
let modalManager;
|
||||
let batchImportManager;
|
||||
let fetchMock;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
vi.resetModules();
|
||||
|
||||
document.body.innerHTML = '';
|
||||
renderTemplate('components/batch_import_modal.html');
|
||||
|
||||
// jsdom does not implement window.scrollTo; ModalManager calls it on close.
|
||||
window.scrollTo = vi.fn();
|
||||
|
||||
vi.stubGlobal('WebSocket', FakeWebSocket);
|
||||
fetchMock = vi.fn(async () => ({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({}),
|
||||
}));
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
const modalModule = await import('../../../static/js/managers/ModalManager.js');
|
||||
modalManager = modalModule.modalManager;
|
||||
modalManager.initialize();
|
||||
|
||||
const batchModule = await import('../../../static/js/managers/BatchImportManager.js');
|
||||
batchImportManager = new batchModule.BatchImportManager();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (batchImportManager) {
|
||||
batchImportManager.cleanupConnections();
|
||||
}
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
async function startImportViaUrls(urls) {
|
||||
batchImportManager.showModal();
|
||||
document.getElementById('batchUrlInput').value = urls.join('\n');
|
||||
fetchMock.mockImplementation(async (url) => ({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () =>
|
||||
url.includes('/batch-import/start')
|
||||
? { success: true, operation_id: 'op-123' }
|
||||
: { success: true, progress: RUNNING_PROGRESS },
|
||||
}));
|
||||
await batchImportManager.startImport();
|
||||
}
|
||||
|
||||
it('opens a fresh input form when no operation exists', () => {
|
||||
batchImportManager.showModal();
|
||||
expect(document.getElementById('batchImportModal').style.display).toBe('block');
|
||||
expect(document.getElementById('batchInputStep').style.display).toBe('block');
|
||||
expect(document.getElementById('batchProgressStep').style.display).toBe('none');
|
||||
});
|
||||
|
||||
it('reopens into the progress view while an import keeps running in the background', async () => {
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
|
||||
await startImportViaUrls([
|
||||
'https://civitai.com/images/1',
|
||||
'https://civitai.com/images/2',
|
||||
]);
|
||||
|
||||
expect(batchImportManager.isImporting).toBe(true);
|
||||
expect(batchImportManager.operationId).toBe('op-123');
|
||||
expect(document.getElementById('batchProgressStep').style.display).toBe('block');
|
||||
|
||||
// Close the modal the same way the X button does.
|
||||
modalManager.closeModal('batchImportModal');
|
||||
expect(document.getElementById('batchImportModal').style.display).toBe('none');
|
||||
|
||||
// Closing while running must be visible in the console (#1084).
|
||||
const closedWhileRunning = logSpy.mock.calls.some((call) =>
|
||||
String(call[0]).includes('Modal closed while import op-123 is still running')
|
||||
);
|
||||
expect(closedWhileRunning).toBe(true);
|
||||
|
||||
// Reopen: the in-flight operation must be restored, not discarded.
|
||||
batchImportManager.showModal();
|
||||
expect(document.getElementById('batchImportModal').style.display).toBe('block');
|
||||
expect(batchImportManager.operationId).toBe('op-123');
|
||||
expect(batchImportManager.isImporting).toBe(true);
|
||||
expect(document.getElementById('batchProgressStep').style.display).toBe('block');
|
||||
expect(document.getElementById('batchInputStep').style.display).toBe('none');
|
||||
|
||||
logSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('reopens into the results view after a background import completes', async () => {
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
|
||||
await startImportViaUrls([
|
||||
'https://civitai.com/images/1',
|
||||
'https://civitai.com/images/2',
|
||||
]);
|
||||
|
||||
// Operation finishes while the modal stays closed.
|
||||
modalManager.closeModal('batchImportModal');
|
||||
batchImportManager.handleProgressUpdate(RUNNING_PROGRESS);
|
||||
batchImportManager.handleProgressUpdate(COMPLETED_PROGRESS);
|
||||
|
||||
expect(batchImportManager.isImporting).toBe(false);
|
||||
expect(batchImportManager.results.status).toBe('completed');
|
||||
|
||||
// Reopening shows the finished results instead of a blank form.
|
||||
batchImportManager.showModal();
|
||||
expect(document.getElementById('batchImportModal').style.display).toBe('block');
|
||||
expect(document.getElementById('batchResultsStep').style.display).toBe('block');
|
||||
expect(document.getElementById('batchInputStep').style.display).toBe('none');
|
||||
|
||||
logSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('does not close when clicking the backdrop (stateful workflow, #1084)', async () => {
|
||||
await startImportViaUrls(['https://civitai.com/images/1']);
|
||||
|
||||
const modalEl = document.getElementById('batchImportModal');
|
||||
expect(modalEl.style.display).toBe('block');
|
||||
|
||||
// Simulate a backdrop click: mousedown + mouseup on the modal shell.
|
||||
// Because batch import is a stateful, multi-step workflow, the modal must
|
||||
// not dismiss on stray outside clicks.
|
||||
modalEl.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
|
||||
modalEl.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
|
||||
|
||||
expect(modalEl.style.display).toBe('block');
|
||||
expect(modalManager.isAnyModalOpen()).toBe('batchImportModal');
|
||||
});
|
||||
|
||||
it('logs start, progress and completion to the console', async () => {
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
|
||||
await startImportViaUrls(['https://civitai.com/images/1']);
|
||||
|
||||
// force a polled progress update
|
||||
batchImportManager.handleProgressUpdate(RUNNING_PROGRESS);
|
||||
batchImportManager.handleProgressUpdate(COMPLETED_PROGRESS);
|
||||
|
||||
const messages = logSpy.mock.calls.map((call) => String(call[0]));
|
||||
expect(messages.some((m) => m.includes('[BatchImport] Import started, operation_id=op-123'))).toBe(true);
|
||||
expect(messages.some((m) => m.includes('[BatchImport] Progress 50%'))).toBe(true);
|
||||
expect(messages.some((m) => m.includes('[BatchImport] Import finished: status=completed'))).toBe(true);
|
||||
|
||||
logSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user