mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-08-14 09:43:22 -03:00
refactor(delete): make undo unconditional, remove undo toggle and button delay
- Remove delete_undo_enabled setting (backend default, frontend state, settings modal UI, 10 locales); staged deletes with 30s undo are now the only delete path and stale settings keys are silently ignored - Remove the 1500ms delete-button arm delay (armDeleteButton) from all delete modals; misclicks are recoverable via the undo toast - Delete modal always shows the recoverable warning - Log the first staged file path in staging log lines for easier support
This commit is contained in:
@@ -332,41 +332,3 @@ describe('BulkManager.confirmBulkDelete undo flows', () => {
|
||||
expect(resetAndReloadMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('BulkManager.showBulkDeleteModal delay-activate', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
stateStub.currentPageType = 'loras';
|
||||
stateStub.selectedModels.clear();
|
||||
stateStub.selectedModels.add('/models/a.safetensors');
|
||||
document.body.innerHTML = `
|
||||
<div id="bulkDeleteModal" class="modal delete-modal">
|
||||
<h2></h2>
|
||||
<p class="delete-message"></p>
|
||||
<div class="delete-model-info"><p></p></div>
|
||||
<button class="cancel-btn">Cancel</button>
|
||||
<button class="delete-btn">Delete</button>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
it('opens with the delete button disabled and enables it after 1500ms', async () => {
|
||||
const { BulkManager } = await import('../../../static/js/managers/BulkManager.js');
|
||||
const bulk = new BulkManager();
|
||||
bulk.showBulkDeleteModal();
|
||||
|
||||
const deleteBtn = document.querySelector('#bulkDeleteModal .delete-btn');
|
||||
expect(deleteBtn.disabled).toBe(true);
|
||||
|
||||
deleteBtn.click();
|
||||
expect(bulkDeleteModelsMock).not.toHaveBeenCalled();
|
||||
|
||||
vi.advanceTimersByTime(1500);
|
||||
expect(deleteBtn.disabled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
|
||||
vi.mock('../../../static/js/managers/ModalManager.js', () => ({
|
||||
modalManager: {
|
||||
closeModal: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../../static/js/utils/uiHelpers.js', () => ({
|
||||
showToast: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../../static/js/state/index.js', () => {
|
||||
const settings = {};
|
||||
return {
|
||||
state: {
|
||||
global: {
|
||||
settings,
|
||||
},
|
||||
loadingManager: {
|
||||
showSimpleLoading: vi.fn(),
|
||||
hide: vi.fn(),
|
||||
},
|
||||
},
|
||||
createDefaultSettings: () => ({
|
||||
language: 'en',
|
||||
delete_undo_enabled: true,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../../../static/js/api/modelApiFactory.js', () => ({
|
||||
resetAndReload: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../../static/js/utils/constants.js', () => ({
|
||||
DOWNLOAD_PATH_TEMPLATES: {},
|
||||
DEFAULT_PATH_TEMPLATES: {},
|
||||
MAPPABLE_BASE_MODELS: [],
|
||||
PATH_TEMPLATE_PLACEHOLDERS: {},
|
||||
DEFAULT_PRIORITY_TAG_CONFIG: {
|
||||
lora: 'character, style',
|
||||
checkpoint: 'base, guide',
|
||||
embedding: 'hint',
|
||||
},
|
||||
getMappableBaseModelsDynamic: () => [],
|
||||
}));
|
||||
|
||||
vi.mock('../../../static/js/utils/i18nHelpers.js', () => ({
|
||||
translate: (_key, _params, fallback) => fallback ?? '',
|
||||
}));
|
||||
|
||||
vi.mock('../../../static/js/i18n/index.js', () => ({
|
||||
i18n: {
|
||||
getCurrentLocale: () => 'en',
|
||||
setLanguage: vi.fn().mockResolvedValue(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../../static/js/components/shared/ModelCard.js', () => ({
|
||||
configureModelCardVideo: vi.fn(),
|
||||
}));
|
||||
|
||||
import { SettingsManager } from '../../../static/js/managers/SettingsManager.js';
|
||||
import { showToast } from '../../../static/js/utils/uiHelpers.js';
|
||||
import { state } from '../../../static/js/state/index.js';
|
||||
|
||||
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../../..');
|
||||
|
||||
const createManager = () => {
|
||||
state.global.settings = {};
|
||||
const initSettingsSpy = vi
|
||||
.spyOn(SettingsManager.prototype, 'initializeSettings')
|
||||
.mockResolvedValue();
|
||||
const initializeSpy = vi
|
||||
.spyOn(SettingsManager.prototype, 'initialize')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
const manager = new SettingsManager();
|
||||
|
||||
initSettingsSpy.mockRestore();
|
||||
initializeSpy.mockRestore();
|
||||
|
||||
return manager;
|
||||
};
|
||||
|
||||
const appendDeleteUndoCheckbox = () => {
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.id = 'deleteUndoEnabled';
|
||||
document.body.appendChild(checkbox);
|
||||
return checkbox;
|
||||
};
|
||||
|
||||
const stubLoadSettingsSubloaders = (manager) => {
|
||||
vi.spyOn(manager, 'loadMetadataArchiveSettings').mockResolvedValue();
|
||||
vi.spyOn(manager, 'loadBackupSettings').mockResolvedValue();
|
||||
vi.spyOn(manager, 'loadLibraries').mockResolvedValue();
|
||||
vi.spyOn(manager, 'loadLoraRoots').mockResolvedValue();
|
||||
vi.spyOn(manager, 'loadCheckpointRoots').mockResolvedValue();
|
||||
vi.spyOn(manager, 'loadUnetRoots').mockResolvedValue();
|
||||
vi.spyOn(manager, 'loadEmbeddingRoots').mockResolvedValue();
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = '';
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete global.fetch;
|
||||
});
|
||||
|
||||
describe('SettingsManager delete undo toggle', () => {
|
||||
it('renders the checkbox markup with a resolvable i18n label', () => {
|
||||
const template = readFileSync(
|
||||
resolve(repoRoot, 'templates/components/modals/settings_modal.html'),
|
||||
'utf8',
|
||||
);
|
||||
const locales = JSON.parse(
|
||||
readFileSync(resolve(repoRoot, 'locales/en.json'), 'utf8'),
|
||||
);
|
||||
|
||||
// The label must resolve to real text, not fall back to the raw key.
|
||||
expect(locales.settings.deleteUndoEnabled).toBe(
|
||||
'Keep deleted items recoverable for 30 seconds (undo)',
|
||||
);
|
||||
expect(template).toContain('id="deleteUndoEnabled"');
|
||||
expect(template).toContain("t('settings.deleteUndoEnabled')");
|
||||
expect(template).toContain(
|
||||
"settingsManager.saveToggleSetting('deleteUndoEnabled', 'delete_undo_enabled')",
|
||||
);
|
||||
});
|
||||
|
||||
it('restores the checkbox as unchecked when the saved setting is false', async () => {
|
||||
const manager = createManager();
|
||||
const checkbox = appendDeleteUndoCheckbox();
|
||||
checkbox.checked = true;
|
||||
|
||||
stubLoadSettingsSubloaders(manager);
|
||||
global.fetch = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ success: true }),
|
||||
});
|
||||
|
||||
state.global.settings = { delete_undo_enabled: false };
|
||||
|
||||
await manager.loadSettingsToUI();
|
||||
|
||||
expect(checkbox.checked).toBe(false);
|
||||
});
|
||||
|
||||
it('restores the checkbox as checked when the saved setting is true or absent', async () => {
|
||||
const manager = createManager();
|
||||
const checkbox = appendDeleteUndoCheckbox();
|
||||
|
||||
stubLoadSettingsSubloaders(manager);
|
||||
global.fetch = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ success: true }),
|
||||
});
|
||||
|
||||
state.global.settings = { delete_undo_enabled: true };
|
||||
await manager.loadSettingsToUI();
|
||||
expect(checkbox.checked).toBe(true);
|
||||
|
||||
checkbox.checked = false;
|
||||
state.global.settings = {};
|
||||
await manager.loadSettingsToUI();
|
||||
expect(checkbox.checked).toBe(true);
|
||||
});
|
||||
|
||||
it('saves delete_undo_enabled to the backend when the checkbox is toggled', async () => {
|
||||
const manager = createManager();
|
||||
const checkbox = appendDeleteUndoCheckbox();
|
||||
checkbox.checked = false;
|
||||
|
||||
state.global.settings = { delete_undo_enabled: true };
|
||||
|
||||
global.fetch = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ success: true }),
|
||||
});
|
||||
|
||||
await manager.saveToggleSetting('deleteUndoEnabled', 'delete_undo_enabled');
|
||||
|
||||
expect(state.global.settings.delete_undo_enabled).toBe(false);
|
||||
expect(global.fetch).toHaveBeenCalledWith('/api/lm/settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ delete_undo_enabled: false }),
|
||||
});
|
||||
expect(showToast).toHaveBeenCalledWith(
|
||||
'toast.settings.settingsUpdated',
|
||||
{ setting: 'delete undo enabled' },
|
||||
'success',
|
||||
);
|
||||
});
|
||||
|
||||
it('saves delete_undo_enabled as true when re-enabled', async () => {
|
||||
const manager = createManager();
|
||||
const checkbox = appendDeleteUndoCheckbox();
|
||||
checkbox.checked = true;
|
||||
|
||||
state.global.settings = { delete_undo_enabled: false };
|
||||
|
||||
global.fetch = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ success: true }),
|
||||
});
|
||||
|
||||
await manager.saveToggleSetting('deleteUndoEnabled', 'delete_undo_enabled');
|
||||
|
||||
expect(state.global.settings.delete_undo_enabled).toBe(true);
|
||||
expect(global.fetch).toHaveBeenCalledWith('/api/lm/settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ delete_undo_enabled: true }),
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user