import { describe, it, expect, beforeEach, vi } from 'vitest'; 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, }, }, createDefaultSettings: () => ({ language: 'en', download_filename_templates: { lora: '', checkpoint: '', embedding: '' }, }), }; }); vi.mock('../../../static/js/api/modelApiFactory.js', () => ({ resetAndReload: vi.fn(), getModelApiClient: vi.fn(), })); vi.mock('../../../static/js/utils/constants.js', () => ({ DOWNLOAD_PATH_TEMPLATES: {}, DEFAULT_PATH_TEMPLATES: {}, MAPPABLE_BASE_MODELS: [], PATH_TEMPLATE_PLACEHOLDERS: [], FILENAME_TEMPLATE_PLACEHOLDERS: [ '{model_name}', '{version_name}', '{base_model}', '{author}', '{first_tag}', '{hash_short}', '{original_name}', ], DEFAULT_FILENAME_TEMPLATES: { lora: '', checkpoint: '', embedding: '' }, DEFAULT_PRIORITY_TAG_CONFIG: { lora: 'character, style', checkpoint: 'base, guide', embedding: 'hint', }, getMappableBaseModelsDynamic: () => [], })); vi.mock('../../../static/js/utils/i18nHelpers.js', () => ({ translate: (key, params, fallback) => { if (params && fallback) { return fallback.replace(/\{(\w+)\}/g, (match, name) => params[name] ?? match); } return 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(), })); vi.mock('../../../static/js/managers/BannerService.js', () => ({ bannerService: { registerBanner: vi.fn(), }, })); import { SettingsManager } from '../../../static/js/managers/SettingsManager.js'; import { state } from '../../../static/js/state/index.js'; import { showToast } from '../../../static/js/utils/uiHelpers.js'; import { resetAndReload, getModelApiClient } from '../../../static/js/api/modelApiFactory.js'; const createManager = () => { 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 appendFilenameTemplateUi = (modelType = 'lora') => { document.body.innerHTML = `
`; }; describe('SettingsManager filename templates', () => { beforeEach(() => { document.body.innerHTML = ''; vi.clearAllMocks(); state.global.settings = { download_filename_templates: { lora: '', checkpoint: '', embedding: '' }, }; }); it('treats an empty template as valid (keep original filename)', () => { appendFilenameTemplateUi(); const manager = createManager(); expect(manager.validateFilenameTemplate('lora', '')).toBe(true); const validation = document.getElementById('loraFilenameValidation'); expect(validation.classList.contains('valid')).toBe(true); expect(validation.textContent).toContain('keep original filename'); }); it('rejects templates with path separators or OS-illegal characters', () => { appendFilenameTemplateUi(); const manager = createManager(); expect(manager.validateFilenameTemplate('lora', '{base_model}/{model_name}')).toBe(false); expect(manager.validateFilenameTemplate('lora', 'a:b')).toBe(false); const validation = document.getElementById('loraFilenameValidation'); expect(validation.classList.contains('invalid')).toBe(true); }); it('rejects unknown placeholders', () => { appendFilenameTemplateUi(); const manager = createManager(); expect(manager.validateFilenameTemplate('lora', '{bogus}-{model_name}')).toBe(false); const validation = document.getElementById('loraFilenameValidation'); expect(validation.textContent).toContain('{bogus}'); }); it('accepts a template using only known placeholders', () => { appendFilenameTemplateUi(); const manager = createManager(); const template = '{base_model}-{model_name}-{version_name}-{hash_short}'; expect(manager.validateFilenameTemplate('lora', template)).toBe(true); expect(document.getElementById('loraFilenameValidation').classList.contains('valid')).toBe(true); }); it('saves a valid template via saveSetting with the merged dict', () => { appendFilenameTemplateUi(); const manager = createManager(); manager.saveSetting = vi.fn().mockResolvedValue(); manager.updateFilenameTemplate('lora', '{model_name}'); expect(state.global.settings.download_filename_templates.lora).toBe('{model_name}'); expect(manager.saveSetting).toHaveBeenCalledWith( 'download_filename_templates', { lora: '{model_name}', checkpoint: '', embedding: '' }, ); }); it('does not save an invalid template', () => { appendFilenameTemplateUi(); const manager = createManager(); manager.saveSetting = vi.fn().mockResolvedValue(); manager.updateFilenameTemplate('lora', '{unknown_placeholder}'); expect(state.global.settings.download_filename_templates.lora).toBe(''); expect(manager.saveSetting).not.toHaveBeenCalled(); }); it('previews the original filename when the template is empty', () => { appendFilenameTemplateUi(); const manager = createManager(); manager.updateFilenamePreview('lora', ''); expect(document.getElementById('loraFilenamePreview').textContent).toBe('V1.safetensors'); }); it('renders a preview with example placeholder values', () => { appendFilenameTemplateUi(); const manager = createManager(); manager.updateFilenamePreview('lora', '{base_model}-{model_name}-{version_name}-{hash_short}'); expect(document.getElementById('loraFilenamePreview').textContent) .toBe('Flux.1 D-model-name-v3-a1b2c3d4e5.safetensors'); }); it('disables the apply button when the template is empty', () => { appendFilenameTemplateUi(); const manager = createManager(); manager.updateFilenameTemplateApplyButton('lora', ''); expect(document.getElementById('loraApplyFilenameTemplate').disabled).toBe(true); manager.updateFilenameTemplateApplyButton('lora', '{model_name}'); expect(document.getElementById('loraApplyFilenameTemplate').disabled).toBe(false); }); it('merges backend download_filename_templates over defaults', () => { const manager = createManager(); const merged = manager.mergeSettingsWithDefaults({ download_filename_templates: { lora: '{model_name}' }, }); expect(merged.download_filename_templates).toEqual({ lora: '{model_name}', checkpoint: '', embedding: '', }); const fromString = manager.mergeSettingsWithDefaults({ download_filename_templates: '{"checkpoint":"{hash_short}"}', }); expect(fromString.download_filename_templates).toEqual({ lora: '', checkpoint: '{hash_short}', embedding: '', }); }); it('shows an info toast and does nothing when applying an empty template', async () => { appendFilenameTemplateUi(); const manager = createManager(); vi.stubGlobal('confirm', vi.fn(() => true)); await manager.applyFilenameTemplate('lora'); expect(showToast).toHaveBeenCalledWith('settings.filenameTemplates.emptyTemplateInfo', {}, 'info'); expect(getModelApiClient).not.toHaveBeenCalled(); }); it('applies the template through the model API client and reloads', async () => { appendFilenameTemplateUi(); state.global.settings.download_filename_templates.lora = '{model_name}'; const manager = createManager(); vi.stubGlobal('confirm', vi.fn(() => true)); const apiClient = { applyFilenameTemplate: vi.fn().mockResolvedValue() }; getModelApiClient.mockReturnValue(apiClient); await manager.applyFilenameTemplate('lora'); expect(getModelApiClient).toHaveBeenCalledWith('loras'); expect(apiClient.applyFilenameTemplate).toHaveBeenCalledWith(); expect(resetAndReload).toHaveBeenCalledWith(true); }); it('does not apply when the confirm dialog is declined', async () => { appendFilenameTemplateUi(); state.global.settings.download_filename_templates.lora = '{model_name}'; const manager = createManager(); vi.stubGlobal('confirm', vi.fn(() => false)); await manager.applyFilenameTemplate('lora'); expect(getModelApiClient).not.toHaveBeenCalled(); expect(resetAndReload).not.toHaveBeenCalled(); }); });