mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-21 11:11:26 -03:00
5adfa3be36
Standalone users previously had to hand-edit settings.json to configure primary folder_paths. Add a standalone-only Model Paths section to the settings modal: - Backend exposes standalone_mode, folder_paths (with template placeholder values filtered out) and a data-driven folder_path_schema derived from OTHER_MODEL_FOLDER_SUBTYPES via GET /api/lm/settings - The new section renders multi-path editors per model type from the schema, with inline enable_other_models / sub-type controls so other model types are configured without leaving the tab - Persistent restart-required cues after a save: nav dot, inline notice and a global banner (unique id per change so dismissals don't mute future reminders) - The missing-model-paths startup banner and the Other Models no-paths empty state now deep-link into the new section instead of pointing at settings.json
173 lines
5.9 KiB
JavaScript
173 lines
5.9 KiB
JavaScript
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
|
|
const initializeAppMock = vi.fn();
|
|
const showToastMock = vi.fn();
|
|
|
|
vi.mock('../../../static/js/core.js', () => ({
|
|
appCore: {
|
|
initialize: initializeAppMock,
|
|
},
|
|
}));
|
|
|
|
vi.mock('../../../static/js/utils/uiHelpers.js', () => ({
|
|
showToast: showToastMock,
|
|
}));
|
|
|
|
describe('Other Models disabled page', () => {
|
|
const originalLocation = window.location;
|
|
let enableOtherModels;
|
|
let initializeOtherDisabledPage;
|
|
|
|
beforeEach(async () => {
|
|
vi.resetModules();
|
|
vi.clearAllMocks();
|
|
initializeAppMock.mockResolvedValue(undefined);
|
|
document.body.innerHTML = [
|
|
'<button id="enableOtherModelsBtn"></button>',
|
|
'<button id="openOtherModelsSettingsBtn"></button>',
|
|
'<button id="openModelPathsSettingsBtn"></button>',
|
|
'<button id="openSettingsFolderBtn"></button>',
|
|
].join('');
|
|
|
|
Object.defineProperty(window, 'location', {
|
|
value: { ...originalLocation, reload: vi.fn() },
|
|
configurable: true,
|
|
writable: true,
|
|
});
|
|
|
|
({ enableOtherModels, initializeOtherDisabledPage } = await import(
|
|
'../../../static/js/other_disabled.js'
|
|
));
|
|
await initializeOtherDisabledPage();
|
|
});
|
|
|
|
afterEach(() => {
|
|
Object.defineProperty(window, 'location', {
|
|
value: originalLocation,
|
|
configurable: true,
|
|
writable: true,
|
|
});
|
|
delete global.fetch;
|
|
delete window.modalManager;
|
|
});
|
|
|
|
it('boots the shared app core so the header stays usable', () => {
|
|
expect(initializeAppMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('opens the Library settings from the no-folders state', () => {
|
|
const showModal = vi.fn();
|
|
window.modalManager = { showModal };
|
|
|
|
document.getElementById('openOtherModelsSettingsBtn').dispatchEvent(
|
|
new MouseEvent('click', { bubbles: true }),
|
|
);
|
|
|
|
expect(showModal).toHaveBeenCalledWith('settingsModal');
|
|
});
|
|
|
|
it('opens the Model Paths settings from the standalone no-folders state', async () => {
|
|
const showModal = vi.fn();
|
|
window.modalManager = { showModal };
|
|
|
|
const navItem = document.createElement('button');
|
|
navItem.className = 'settings-nav-item';
|
|
navItem.dataset.section = 'modelPaths';
|
|
const navClick = vi.fn();
|
|
navItem.addEventListener('click', navClick);
|
|
document.body.appendChild(navItem);
|
|
|
|
document.getElementById('openModelPathsSettingsBtn').dispatchEvent(
|
|
new MouseEvent('click', { bubbles: true }),
|
|
);
|
|
|
|
expect(showModal).toHaveBeenCalledWith('settingsModal');
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
expect(navClick).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('reveals the settings.json location from the standalone no-folders state', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ success: true, message: 'Opened settings folder' }),
|
|
});
|
|
|
|
const button = document.getElementById('openSettingsFolderBtn');
|
|
button.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
await vi.waitFor(() => expect(showToastMock).toHaveBeenCalled());
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
'/api/lm/settings/open-location',
|
|
expect.objectContaining({ method: 'POST' }),
|
|
);
|
|
expect(showToastMock).toHaveBeenCalledWith(
|
|
'settings.openSettingsFileLocation.success',
|
|
{},
|
|
'success',
|
|
);
|
|
expect(button.disabled).toBe(false);
|
|
});
|
|
|
|
it('copies the settings path to the clipboard in Docker mode', async () => {
|
|
const writeText = vi.fn().mockResolvedValue(undefined);
|
|
Object.defineProperty(navigator, 'clipboard', {
|
|
value: { writeText },
|
|
configurable: true,
|
|
});
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ success: true, mode: 'clipboard', path: '/data/settings.json' }),
|
|
});
|
|
|
|
document.getElementById('openSettingsFolderBtn').dispatchEvent(
|
|
new MouseEvent('click', { bubbles: true }),
|
|
);
|
|
await vi.waitFor(() => expect(showToastMock).toHaveBeenCalled());
|
|
|
|
expect(writeText).toHaveBeenCalledWith('/data/settings.json');
|
|
expect(showToastMock).toHaveBeenCalledWith(
|
|
'settings.openSettingsFileLocation.copied',
|
|
{ path: '/data/settings.json' },
|
|
'success',
|
|
);
|
|
});
|
|
|
|
it('enables Other Models through the settings API and reloads', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ success: true }),
|
|
});
|
|
|
|
await enableOtherModels();
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
'/api/lm/settings',
|
|
expect.objectContaining({ method: 'POST' }),
|
|
);
|
|
expect(JSON.parse(global.fetch.mock.calls[0][1].body)).toEqual({
|
|
enable_other_models: true,
|
|
});
|
|
expect(window.location.reload).toHaveBeenCalledTimes(1);
|
|
expect(showToastMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('re-enables the button and toasts when enabling fails', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
status: 500,
|
|
json: async () => ({ success: false, error: 'boom' }),
|
|
});
|
|
|
|
await enableOtherModels();
|
|
|
|
expect(window.location.reload).not.toHaveBeenCalled();
|
|
expect(showToastMock).toHaveBeenCalledWith(
|
|
'other.disabled.enableFailed',
|
|
expect.objectContaining({ message: 'boom' }),
|
|
'error',
|
|
);
|
|
expect(document.getElementById('enableOtherModelsBtn').disabled).toBe(false);
|
|
});
|
|
});
|