From 672e4cff904e3ec6b324d490deb7ba7607313869 Mon Sep 17 00:00:00 2001 From: Will Miao Date: Mon, 2 Mar 2026 23:29:16 +0800 Subject: [PATCH] fix(move): reset manual folder selection when using default path (fixes #836) --- static/js/managers/MoveManager.js | 7 +- tests/frontend/managers/MoveManager.test.js | 159 ++++++++++++++++++++ 2 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 tests/frontend/managers/MoveManager.test.js diff --git a/static/js/managers/MoveManager.js b/static/js/managers/MoveManager.js index 4ffad779..4a7cc856 100644 --- a/static/js/managers/MoveManager.js +++ b/static/js/managers/MoveManager.js @@ -88,6 +88,11 @@ class MoveManager { folderPathInput.value = ''; } + // Reset folder tree selection + if (this.folderTreeManager) { + this.folderTreeManager.clearSelection(); + } + try { // Fetch model roots const modelRootSelect = document.getElementById('moveModelRoot'); @@ -308,7 +313,7 @@ class MoveManager { } // Get selected folder path from folder tree manager - const targetFolder = this.folderTreeManager.getSelectedPath(); + const targetFolder = this.useDefaultPath ? '' : this.folderTreeManager.getSelectedPath(); let targetPath = selectedRoot; if (targetFolder) { diff --git a/tests/frontend/managers/MoveManager.test.js b/tests/frontend/managers/MoveManager.test.js new file mode 100644 index 00000000..ab829493 --- /dev/null +++ b/tests/frontend/managers/MoveManager.test.js @@ -0,0 +1,159 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { moveManager } from '../../../static/js/managers/MoveManager.js'; +import { state } from '../../../static/js/state/index.js'; +import { modalManager } from '../../../static/js/managers/ModalManager.js'; +import { getModelApiClient } from '../../../static/js/api/modelApiFactory.js'; +import * as storageHelpers from '../../../static/js/utils/storageHelpers.js'; + +// Mock dependencies +vi.mock('../../../static/js/state/index.js', () => ({ + state: { + currentPageType: 'loras', + selectedModels: new Set(), + global: { + settings: { + download_path_templates: { + lora: '{base_model}/unstaged' + } + } + } + } +})); + +vi.mock('../../../static/js/managers/ModalManager.js', () => ({ + modalManager: { + showModal: vi.fn(), + closeModal: vi.fn() + } +})); + +vi.mock('../../../static/js/api/modelApiFactory.js', () => ({ + getModelApiClient: vi.fn() +})); + +vi.mock('../../../static/js/utils/storageHelpers.js', () => ({ + getStorageItem: vi.fn(), + setStorageItem: vi.fn() +})); + +vi.mock('../../../static/js/utils/uiHelpers.js', () => ({ + showToast: vi.fn() +})); + +vi.mock('../../../static/js/utils/i18nHelpers.js', () => ({ + translate: vi.fn(key => key) +})); + +describe('MoveManager', () => { + let mockApiClient; + + beforeEach(() => { + vi.clearAllMocks(); + + // Setup DOM + document.body.innerHTML = ` +
+

+ + + +
+ +
+
+
+
+ `; + + mockApiClient = { + apiConfig: { + config: { + displayName: 'LoRA', + supportsMove: true + }, + endpoints: { + moveModel: '/api/move' + } + }, + modelType: 'loras', + fetchModelRoots: vi.fn().mockResolvedValue({ roots: ['/models/loras'] }), + fetchUnifiedFolderTree: vi.fn().mockResolvedValue({ success: true, tree: {} }), + moveSingleModel: vi.fn().mockResolvedValue({ success: true }) + }; + getModelApiClient.mockReturnValue(mockApiClient); + }); + + it('should reset folder selection when showing move modal', async () => { + // Manually set a selected path in folderTreeManager + moveManager.folderTreeManager.selectedPath = 'previous/path'; + + await moveManager.showMoveModal('some/file.safetensors'); + + expect(moveManager.folderTreeManager.getSelectedPath()).toBe(''); + }); + + it('should ignore manual folder selection when useDefaultPath is true', async () => { + // Setup state + moveManager.useDefaultPath = true; + moveManager.currentFilePath = '/models/loras/flux/my-lora.safetensors'; + document.getElementById('moveModelRoot').innerHTML = ''; + document.getElementById('moveModelRoot').value = '/models/loras'; + + // Manually set a selected path despite useDefaultPath being true + moveManager.folderTreeManager.selectedPath = 'wrong/folder'; + + await moveManager.moveModel(); + + // Should call moveSingleModel with the root path, NOT including the 'wrong/folder' + expect(mockApiClient.moveSingleModel).toHaveBeenCalledWith( + '/models/loras/flux/my-lora.safetensors', + '/models/loras', + true + ); + }); + + it('should include manual folder selection when useDefaultPath is false', async () => { + // Setup state + moveManager.useDefaultPath = false; + moveManager.currentFilePath = '/models/loras/flux/my-lora.safetensors'; + document.getElementById('moveModelRoot').innerHTML = ''; + document.getElementById('moveModelRoot').value = '/models/loras'; + + // Set a selected path + moveManager.folderTreeManager.selectedPath = 'my/organized/folder'; + + await moveManager.moveModel(); + + // Should call moveSingleModel with root + selected folder + expect(mockApiClient.moveSingleModel).toHaveBeenCalledWith( + '/models/loras/flux/my-lora.safetensors', + '/models/loras/my/organized/folder', + false + ); + }); + + it('should handle bulk move and ignore manual folder selection when useDefaultPath is true', async () => { + // Setup state + moveManager.useDefaultPath = true; + moveManager.bulkFilePaths = [ + '/models/loras/flux/lora1.safetensors', + '/models/loras/flux/lora2.safetensors' + ]; + document.getElementById('moveModelRoot').innerHTML = ''; + document.getElementById('moveModelRoot').value = '/models/loras'; + + // Manually set a selected path + moveManager.folderTreeManager.selectedPath = 'wrong/folder'; + + mockApiClient.moveBulkModels = vi.fn().mockResolvedValue({ success: true }); + + await moveManager.moveModel(); + + // Should call moveBulkModels with the root path, NOT including the 'wrong/folder' + expect(mockApiClient.moveBulkModels).toHaveBeenCalledWith( + moveManager.bulkFilePaths, + '/models/loras', + true + ); + }); +});