mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-26 07:35:44 -03:00
feat: add modal file path resolution and synchronization
- Add getActiveModalFilePath function to resolve current file path from DOM state - Add updateModalFilePathReferences function to synchronize file path across all modal controls - Refactor existing code to use new path resolution functions - Ensure metadata interactions remain in sync after file renames or moves - Improve robustness by handling cases where DOM state hasn't been initialized yet
This commit is contained in:
@@ -7,6 +7,88 @@ import { BASE_MODEL_CATEGORIES } from '../../utils/constants.js';
|
|||||||
import { showToast } from '../../utils/uiHelpers.js';
|
import { showToast } from '../../utils/uiHelpers.js';
|
||||||
import { getModelApiClient } from '../../api/modelApiFactory.js';
|
import { getModelApiClient } from '../../api/modelApiFactory.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the active file path for the currently open model modal.
|
||||||
|
* Falls back to the provided value when DOM state has not been initialised yet.
|
||||||
|
* @param {string} fallback - Optional fallback path
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function getActiveModalFilePath(fallback = '') {
|
||||||
|
const modalElement = document.getElementById('modelModal');
|
||||||
|
if (modalElement && modalElement.dataset && modalElement.dataset.filePath) {
|
||||||
|
return modalElement.dataset.filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileNameContent = document.querySelector('.file-name-content');
|
||||||
|
if (fileNameContent && fileNameContent.dataset && fileNameContent.dataset.filePath) {
|
||||||
|
return fileNameContent.dataset.filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update all modal controls that cache the current model file path.
|
||||||
|
* Keeps metadata interactions in sync after renames or moves.
|
||||||
|
* @param {string} newFilePath - Updated model file path
|
||||||
|
*/
|
||||||
|
function updateModalFilePathReferences(newFilePath) {
|
||||||
|
if (!newFilePath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalElement = document.getElementById('modelModal');
|
||||||
|
if (modalElement) {
|
||||||
|
modalElement.dataset.filePath = newFilePath;
|
||||||
|
modalElement.setAttribute('data-file-path', newFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelNameContent = document.querySelector('.model-name-content');
|
||||||
|
if (modelNameContent && modelNameContent.dataset) {
|
||||||
|
modelNameContent.dataset.filePath = newFilePath;
|
||||||
|
modelNameContent.setAttribute('data-file-path', newFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseModelContent = document.querySelector('.base-model-content');
|
||||||
|
if (baseModelContent && baseModelContent.dataset) {
|
||||||
|
baseModelContent.dataset.filePath = newFilePath;
|
||||||
|
baseModelContent.setAttribute('data-file-path', newFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileNameContent = document.querySelector('.file-name-content');
|
||||||
|
if (fileNameContent && fileNameContent.dataset) {
|
||||||
|
fileNameContent.dataset.filePath = newFilePath;
|
||||||
|
fileNameContent.setAttribute('data-file-path', newFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const editTagsBtn = document.querySelector('.edit-tags-btn');
|
||||||
|
if (editTagsBtn) {
|
||||||
|
editTagsBtn.dataset.filePath = newFilePath;
|
||||||
|
editTagsBtn.setAttribute('data-file-path', newFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const editTriggerWordsBtn = document.querySelector('.edit-trigger-words-btn');
|
||||||
|
if (editTriggerWordsBtn) {
|
||||||
|
editTriggerWordsBtn.dataset.filePath = newFilePath;
|
||||||
|
editTriggerWordsBtn.setAttribute('data-file-path', newFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-action="open-file-location"]').forEach((el) => {
|
||||||
|
el.dataset.filepath = newFilePath;
|
||||||
|
el.setAttribute('data-filepath', newFilePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-file-path]').forEach((el) => {
|
||||||
|
el.dataset.filePath = newFilePath;
|
||||||
|
el.setAttribute('data-file-path', newFilePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-filepath]').forEach((el) => {
|
||||||
|
el.dataset.filepath = newFilePath;
|
||||||
|
el.setAttribute('data-filepath', newFilePath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up model name editing functionality
|
* Set up model name editing functionality
|
||||||
* @param {string} filePath - File path
|
* @param {string} filePath - File path
|
||||||
@@ -110,8 +192,8 @@ export function setupModelNameEditing(filePath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get the file path from the dataset
|
// Resolve current file path from modal state
|
||||||
const filePath = this.dataset.filePath;
|
const filePath = getActiveModalFilePath(this.dataset.filePath);
|
||||||
|
|
||||||
await getModelApiClient().saveModelMetadata(filePath, { model_name: newModelName });
|
await getModelApiClient().saveModelMetadata(filePath, { model_name: newModelName });
|
||||||
|
|
||||||
@@ -230,11 +312,8 @@ export function setupBaseModelEditing(filePath) {
|
|||||||
|
|
||||||
// Only save if the value has actually changed
|
// Only save if the value has actually changed
|
||||||
if (valueChanged || baseModelContent.textContent.trim() !== originalValue) {
|
if (valueChanged || baseModelContent.textContent.trim() !== originalValue) {
|
||||||
// Get file path from the dataset
|
const resolvedPath = getActiveModalFilePath(baseModelContent.dataset.filePath);
|
||||||
const filePath = baseModelContent.dataset.filePath;
|
saveBaseModel(resolvedPath, originalValue);
|
||||||
|
|
||||||
// Save the changes, passing the original value for comparison
|
|
||||||
saveBaseModel(filePath, originalValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove this event listener
|
// Remove this event listener
|
||||||
@@ -278,8 +357,13 @@ async function saveBaseModel(filePath, originalValue) {
|
|||||||
return; // No change, no need to save
|
return; // No change, no need to save
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolvedPath = getActiveModalFilePath(filePath);
|
||||||
|
if (!resolvedPath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await getModelApiClient().saveModelMetadata(filePath, { base_model: newBaseModel });
|
await getModelApiClient().saveModelMetadata(resolvedPath, { base_model: newBaseModel });
|
||||||
|
|
||||||
showToast('toast.models.baseModelUpdated', {}, 'success');
|
showToast('toast.models.baseModelUpdated', {}, 'success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -396,10 +480,22 @@ export function setupFileNameEditing(filePath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get the file path from the dataset
|
const currentFilePath = getActiveModalFilePath(this.dataset.filePath);
|
||||||
const filePath = this.dataset.filePath;
|
const result = await getModelApiClient().renameModelFile(currentFilePath, newFileName);
|
||||||
|
|
||||||
await getModelApiClient().renameModelFile(filePath, newFileName);
|
if (result && result.success && result.new_file_path) {
|
||||||
|
const newFilePath = result.new_file_path;
|
||||||
|
this.dataset.filePath = newFilePath;
|
||||||
|
this.setAttribute('data-file-path', newFilePath);
|
||||||
|
|
||||||
|
const modalElement = document.getElementById('modelModal');
|
||||||
|
if (modalElement) {
|
||||||
|
modalElement.dataset.filePath = newFilePath;
|
||||||
|
modalElement.setAttribute('data-file-path', newFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateModalFilePathReferences(newFilePath);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error renaming file:', error);
|
console.error('Error renaming file:', error);
|
||||||
this.textContent = originalValue; // Restore original file name
|
this.textContent = originalValue; // Restore original file name
|
||||||
|
|||||||
@@ -21,6 +21,14 @@ import { initVersionsTab } from './ModelVersionsTab.js';
|
|||||||
import { loadRecipesForLora } from './RecipeTab.js';
|
import { loadRecipesForLora } from './RecipeTab.js';
|
||||||
import { translate } from '../../utils/i18nHelpers.js';
|
import { translate } from '../../utils/i18nHelpers.js';
|
||||||
|
|
||||||
|
function getModalFilePath(fallback = '') {
|
||||||
|
const modalElement = document.getElementById('modelModal');
|
||||||
|
if (modalElement && modalElement.dataset && modalElement.dataset.filePath) {
|
||||||
|
return modalElement.dataset.filePath;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the model modal with the given model data
|
* Display the model modal with the given model data
|
||||||
* @param {Object} model - Model data object
|
* @param {Object} model - Model data object
|
||||||
@@ -269,6 +277,10 @@ export async function showModelModal(model, modelType) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
modalManager.showModal(modalId, content, null, onCloseCallback);
|
modalManager.showModal(modalId, content, null, onCloseCallback);
|
||||||
|
const activeModalElement = document.getElementById(modalId);
|
||||||
|
if (activeModalElement) {
|
||||||
|
activeModalElement.dataset.filePath = modelWithFullData.file_path || '';
|
||||||
|
}
|
||||||
const versionsTabController = initVersionsTab({
|
const versionsTabController = initVersionsTab({
|
||||||
modalId,
|
modalId,
|
||||||
modelType,
|
modelType,
|
||||||
@@ -374,7 +386,7 @@ function setupEventHandlers(filePath) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'open-file-location':
|
case 'open-file-location':
|
||||||
const filePath = target.dataset.filepath;
|
const filePath = target.dataset.filepath || getModalFilePath();
|
||||||
if (filePath) {
|
if (filePath) {
|
||||||
openFileLocation(filePath);
|
openFileLocation(filePath);
|
||||||
}
|
}
|
||||||
@@ -423,7 +435,7 @@ function setupEditableFields(filePath, modelType) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
await saveNotes(filePath);
|
await saveNotes();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -439,6 +451,7 @@ function setupLoraSpecificFields(filePath) {
|
|||||||
const presetValue = document.getElementById('preset-value');
|
const presetValue = document.getElementById('preset-value');
|
||||||
const addPresetBtn = document.querySelector('.add-preset-btn');
|
const addPresetBtn = document.querySelector('.add-preset-btn');
|
||||||
const presetTags = document.querySelector('.preset-tags');
|
const presetTags = document.querySelector('.preset-tags');
|
||||||
|
const resolveFilePath = () => getModalFilePath(filePath);
|
||||||
|
|
||||||
if (!presetSelector || !presetValue || !addPresetBtn || !presetTags) return;
|
if (!presetSelector || !presetValue || !addPresetBtn || !presetTags) return;
|
||||||
|
|
||||||
@@ -466,13 +479,16 @@ function setupLoraSpecificFields(filePath) {
|
|||||||
|
|
||||||
if (!key || !value) return;
|
if (!key || !value) return;
|
||||||
|
|
||||||
const loraCard = document.querySelector(`.model-card[data-filepath="${filePath}"]`);
|
const currentPath = resolveFilePath();
|
||||||
|
if (!currentPath) return;
|
||||||
|
const loraCard = document.querySelector(`.model-card[data-filepath="${currentPath}"]`) ||
|
||||||
|
document.querySelector(`.model-card[data-filepath="${filePath}"]`);
|
||||||
const currentPresets = parsePresets(loraCard?.dataset.usage_tips);
|
const currentPresets = parsePresets(loraCard?.dataset.usage_tips);
|
||||||
|
|
||||||
currentPresets[key] = parseFloat(value);
|
currentPresets[key] = parseFloat(value);
|
||||||
const newPresetsJson = JSON.stringify(currentPresets);
|
const newPresetsJson = JSON.stringify(currentPresets);
|
||||||
|
|
||||||
await getModelApiClient().saveModelMetadata(filePath, { usage_tips: newPresetsJson });
|
await getModelApiClient().saveModelMetadata(currentPath, { usage_tips: newPresetsJson });
|
||||||
|
|
||||||
presetTags.innerHTML = renderPresetTags(currentPresets);
|
presetTags.innerHTML = renderPresetTags(currentPresets);
|
||||||
|
|
||||||
@@ -491,10 +507,13 @@ function setupLoraSpecificFields(filePath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save model notes
|
* Save model notes using the current modal file path.
|
||||||
* @param {string} filePath - Path to the model file
|
|
||||||
*/
|
*/
|
||||||
async function saveNotes(filePath) {
|
async function saveNotes() {
|
||||||
|
const filePath = getModalFilePath();
|
||||||
|
if (!filePath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const content = document.querySelector('.notes-content').textContent;
|
const content = document.querySelector('.notes-content').textContent;
|
||||||
try {
|
try {
|
||||||
await getModelApiClient().saveModelMetadata(filePath, { notes: content });
|
await getModelApiClient().saveModelMetadata(filePath, { notes: content });
|
||||||
|
|||||||
196
tests/frontend/components/modelMetadata.renamePath.test.js
Normal file
196
tests/frontend/components/modelMetadata.renamePath.test.js
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest';
|
||||||
|
|
||||||
|
const {
|
||||||
|
METADATA_MODULE,
|
||||||
|
MODAL_MODULE,
|
||||||
|
API_FACTORY,
|
||||||
|
UI_HELPERS_MODULE,
|
||||||
|
MODAL_MANAGER_MODULE,
|
||||||
|
SHOWCASE_MODULE,
|
||||||
|
MODEL_TAGS_MODULE,
|
||||||
|
UTILS_MODULE,
|
||||||
|
TRIGGER_WORDS_MODULE,
|
||||||
|
PRESET_TAGS_MODULE,
|
||||||
|
MODEL_VERSIONS_MODULE,
|
||||||
|
RECIPE_TAB_MODULE,
|
||||||
|
I18N_HELPERS_MODULE,
|
||||||
|
} = vi.hoisted(() => ({
|
||||||
|
METADATA_MODULE: new URL('../../../static/js/components/shared/ModelMetadata.js', import.meta.url).pathname,
|
||||||
|
MODAL_MODULE: new URL('../../../static/js/components/shared/ModelModal.js', import.meta.url).pathname,
|
||||||
|
API_FACTORY: new URL('../../../static/js/api/modelApiFactory.js', import.meta.url).pathname,
|
||||||
|
UI_HELPERS_MODULE: new URL('../../../static/js/utils/uiHelpers.js', import.meta.url).pathname,
|
||||||
|
MODAL_MANAGER_MODULE: new URL('../../../static/js/managers/ModalManager.js', import.meta.url).pathname,
|
||||||
|
SHOWCASE_MODULE: new URL('../../../static/js/components/shared/showcase/ShowcaseView.js', import.meta.url).pathname,
|
||||||
|
MODEL_TAGS_MODULE: new URL('../../../static/js/components/shared/ModelTags.js', import.meta.url).pathname,
|
||||||
|
UTILS_MODULE: new URL('../../../static/js/components/shared/utils.js', import.meta.url).pathname,
|
||||||
|
TRIGGER_WORDS_MODULE: new URL('../../../static/js/components/shared/TriggerWords.js', import.meta.url).pathname,
|
||||||
|
PRESET_TAGS_MODULE: new URL('../../../static/js/components/shared/PresetTags.js', import.meta.url).pathname,
|
||||||
|
MODEL_VERSIONS_MODULE: new URL('../../../static/js/components/shared/ModelVersionsTab.js', import.meta.url).pathname,
|
||||||
|
RECIPE_TAB_MODULE: new URL('../../../static/js/components/shared/RecipeTab.js', import.meta.url).pathname,
|
||||||
|
I18N_HELPERS_MODULE: new URL('../../../static/js/utils/i18nHelpers.js', import.meta.url).pathname,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock(UI_HELPERS_MODULE, () => ({
|
||||||
|
showToast: vi.fn(),
|
||||||
|
openCivitai: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock(MODAL_MANAGER_MODULE, () => ({
|
||||||
|
modalManager: {
|
||||||
|
showModal: vi.fn((id, html) => {
|
||||||
|
document.body.innerHTML = `<div id="${id}">${html}</div>`;
|
||||||
|
}),
|
||||||
|
closeModal: vi.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock(SHOWCASE_MODULE, () => ({
|
||||||
|
toggleShowcase: vi.fn(),
|
||||||
|
setupShowcaseScroll: vi.fn(),
|
||||||
|
scrollToTop: vi.fn(),
|
||||||
|
loadExampleImages: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock(MODEL_TAGS_MODULE, () => ({
|
||||||
|
setupTagEditMode: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock(UTILS_MODULE, () => ({
|
||||||
|
renderCompactTags: vi.fn(() => ''),
|
||||||
|
setupTagTooltip: vi.fn(),
|
||||||
|
formatFileSize: vi.fn(() => '1 MB'),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock(TRIGGER_WORDS_MODULE, () => ({
|
||||||
|
renderTriggerWords: vi.fn(() => ''),
|
||||||
|
setupTriggerWordsEditMode: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock(PRESET_TAGS_MODULE, () => ({
|
||||||
|
parsePresets: vi.fn(() => ({})),
|
||||||
|
renderPresetTags: vi.fn(() => ''),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock(MODEL_VERSIONS_MODULE, () => ({
|
||||||
|
initVersionsTab: vi.fn(() => ({
|
||||||
|
load: vi.fn().mockResolvedValue(undefined),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock(RECIPE_TAB_MODULE, () => ({
|
||||||
|
loadRecipesForLora: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock(I18N_HELPERS_MODULE, () => ({
|
||||||
|
translate: vi.fn((_, __, fallback) => fallback || ''),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock(API_FACTORY, () => ({
|
||||||
|
getModelApiClient: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Model metadata interactions keep file path in sync', () => {
|
||||||
|
let getModelApiClient;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
({ getModelApiClient } = await import(API_FACTORY));
|
||||||
|
getModelApiClient.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates modal references after renaming the model file', async () => {
|
||||||
|
const renameModelFile = vi.fn().mockResolvedValue({
|
||||||
|
success: true,
|
||||||
|
new_file_path: 'new/models/Qwen.testing.safetensors',
|
||||||
|
});
|
||||||
|
|
||||||
|
getModelApiClient.mockReturnValue({
|
||||||
|
renameModelFile,
|
||||||
|
saveModelMetadata: vi.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.innerHTML = `
|
||||||
|
<div id="modelModal" data-file-path="models/Qwen.safetensors"></div>
|
||||||
|
<div class="model-name-header">
|
||||||
|
<h2 class="model-name-content" data-file-path="models/Qwen.safetensors">Qwen</h2>
|
||||||
|
<button class="edit-model-name-btn"></button>
|
||||||
|
</div>
|
||||||
|
<div class="base-model-display">
|
||||||
|
<span class="base-model-content" data-file-path="models/Qwen.safetensors">SDXL</span>
|
||||||
|
<button class="edit-base-model-btn"></button>
|
||||||
|
</div>
|
||||||
|
<div class="file-name-wrapper">
|
||||||
|
<span class="file-name-content" data-file-path="models/Qwen.safetensors">Qwen</span>
|
||||||
|
<button class="edit-file-name-btn"></button>
|
||||||
|
</div>
|
||||||
|
<div class="model-tags-container">
|
||||||
|
<div class="model-tags-compact"></div>
|
||||||
|
<div class="tooltip-content"></div>
|
||||||
|
<button class="edit-tags-btn" data-file-path="models/Qwen.safetensors"></button>
|
||||||
|
</div>
|
||||||
|
<button class="edit-trigger-words-btn" data-file-path="models/Qwen.safetensors"></button>
|
||||||
|
<div data-action="open-file-location" data-filepath="models/Qwen.safetensors"></div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const { setupFileNameEditing } = await import(METADATA_MODULE);
|
||||||
|
|
||||||
|
setupFileNameEditing('models/Qwen.safetensors');
|
||||||
|
|
||||||
|
const fileNameContent = document.querySelector('.file-name-content');
|
||||||
|
fileNameContent.setAttribute('contenteditable', 'true');
|
||||||
|
fileNameContent.dataset.originalValue = 'Qwen';
|
||||||
|
fileNameContent.textContent = 'Qwen.testing';
|
||||||
|
|
||||||
|
fileNameContent.dispatchEvent(new FocusEvent('blur'));
|
||||||
|
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
expect(renameModelFile).toHaveBeenCalledWith('models/Qwen.safetensors', 'Qwen.testing');
|
||||||
|
});
|
||||||
|
await Promise.resolve();
|
||||||
|
await renameModelFile.mock.results[0].value;
|
||||||
|
expect(document.getElementById('modelModal').dataset.filePath).toBe('new/models/Qwen.testing.safetensors');
|
||||||
|
expect(document.querySelector('.model-name-content').dataset.filePath).toBe('new/models/Qwen.testing.safetensors');
|
||||||
|
expect(document.querySelector('.base-model-content').dataset.filePath).toBe('new/models/Qwen.testing.safetensors');
|
||||||
|
expect(document.querySelector('.file-name-content').dataset.filePath).toBe('new/models/Qwen.testing.safetensors');
|
||||||
|
expect(document.querySelector('.edit-tags-btn').dataset.filePath).toBe('new/models/Qwen.testing.safetensors');
|
||||||
|
expect(document.querySelector('.edit-trigger-words-btn').dataset.filePath).toBe('new/models/Qwen.testing.safetensors');
|
||||||
|
expect(document.querySelector('[data-action="open-file-location"]').dataset.filepath).toBe('new/models/Qwen.testing.safetensors');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the latest file path when saving notes', async () => {
|
||||||
|
const saveModelMetadata = vi.fn().mockResolvedValue({ success: true });
|
||||||
|
const fetchModelMetadata = vi.fn().mockResolvedValue(null);
|
||||||
|
|
||||||
|
getModelApiClient.mockReturnValue({
|
||||||
|
fetchModelMetadata,
|
||||||
|
saveModelMetadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { showModelModal } = await import(MODAL_MODULE);
|
||||||
|
|
||||||
|
await showModelModal(
|
||||||
|
{
|
||||||
|
model_name: 'Qwen',
|
||||||
|
file_path: 'models/Qwen.safetensors',
|
||||||
|
file_name: 'Qwen.safetensors',
|
||||||
|
civitai: {},
|
||||||
|
},
|
||||||
|
'loras',
|
||||||
|
);
|
||||||
|
|
||||||
|
const modalElement = document.getElementById('modelModal');
|
||||||
|
modalElement.dataset.filePath = 'models/Qwen.testing.safetensors';
|
||||||
|
|
||||||
|
const notesContent = document.querySelector('.notes-content');
|
||||||
|
notesContent.textContent = 'Updated notes';
|
||||||
|
notesContent.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
||||||
|
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
expect(saveModelMetadata).toHaveBeenCalledWith('models/Qwen.testing.safetensors', { notes: 'Updated notes' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user