diff --git a/static/js/core.js b/static/js/core.js index 9c0bd377..80e0ec69 100644 --- a/static/js/core.js +++ b/static/js/core.js @@ -12,7 +12,6 @@ import { helpManager } from './managers/HelpManager.js'; import { bannerService } from './managers/BannerService.js'; import { initTheme, initBackToTop } from './utils/uiHelpers.js'; import { initializeInfiniteScroll } from './utils/infiniteScroll.js'; -import { migrateStorageItems } from './utils/storageHelpers.js'; import { i18n } from './i18n/index.js'; import { onboardingManager } from './managers/OnboardingManager.js'; import { BulkContextMenu } from './components/ContextMenu/BulkContextMenu.js'; @@ -123,10 +122,5 @@ export class AppCore { } } -document.addEventListener('DOMContentLoaded', () => { - // Migrate localStorage items to use the namespace prefix - migrateStorageItems(); -}); - // Create and export a singleton instance export const appCore = new AppCore(); \ No newline at end of file diff --git a/static/js/core.test.js b/static/js/core.test.js deleted file mode 100644 index e39d5af0..00000000 --- a/static/js/core.test.js +++ /dev/null @@ -1,277 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; - -const migrateStorageItemsMock = vi.fn(); -const initializeInfiniteScrollMock = vi.fn(); -const initThemeMock = vi.fn(); -const initBackToTopMock = vi.fn(); -const initializeEventManagementMock = vi.fn(); -const createPageContextMenuMock = vi.fn().mockReturnValue('page-menu'); -const createGlobalContextMenuMock = vi.fn().mockReturnValue('global-menu'); -const bulkManagerInitializeMock = vi.fn(); -const setBulkContextMenuMock = vi.fn(); -const helpManagerInitializeMock = vi.fn(); -const updateServiceInitializeMock = vi.fn(); -const bannerServiceInitializeMock = vi.fn(); -const isBannerVisibleMock = vi.fn().mockReturnValue(false); -const onboardingStartMock = vi.fn(); -const settingsWaitMock = vi.fn().mockResolvedValue(); -const i18nWaitMock = vi.fn().mockResolvedValue(); -const i18nLocaleMock = vi.fn().mockReturnValue('en'); - -const mockState = { - currentPageType: 'loras', - global: { - settings: { - card_info_display: 'hover' - } - } -}; - -const mockModalManager = { initialize: vi.fn() }; -const mockBulkManager = { - initialize: bulkManagerInitializeMock, - setBulkContextMenu: setBulkContextMenuMock -}; -const mockHelpManager = { initialize: helpManagerInitializeMock }; -const mockUpdateService = { initialize: updateServiceInitializeMock }; -const mockBannerService = { - initialize: bannerServiceInitializeMock, - isBannerVisible: isBannerVisibleMock -}; -const mockOnboardingManager = { start: onboardingStartMock }; -const mockSettingsManager = { waitForInitialization: settingsWaitMock }; -const mockMoveManager = {}; -const mockI18n = { - waitForReady: i18nWaitMock, - getCurrentLocale: i18nLocaleMock -}; - -const loadingManagerInstances = []; -const HeaderManagerInstances = []; -const bulkContextMenuInstances = []; -const exampleImagesManagerInitializeMock = vi.fn(); - -const LoadingManagerMock = vi.fn(() => { - const instance = { id: Symbol('LoadingManager') }; - loadingManagerInstances.push(instance); - return instance; -}); - -const HeaderManagerMock = vi.fn(() => { - const instance = { id: Symbol('HeaderManager') }; - HeaderManagerInstances.push(instance); - return instance; -}); - -const BulkContextMenuMock = vi.fn(() => { - const instance = { id: Symbol('BulkContextMenu') }; - bulkContextMenuInstances.push(instance); - return instance; -}); - -const ExampleImagesManagerMock = vi.fn(() => { - const instance = { initialize: exampleImagesManagerInitializeMock }; - globalThis.exampleImagesManager = instance; - return instance; -}); - -vi.stubGlobal('exampleImagesManager', null); - -vi.mock('./utils/storageHelpers.js', () => ({ - migrateStorageItems: migrateStorageItemsMock -})); - -vi.mock('./state/index.js', () => ({ - state: mockState -})); - -vi.mock('./managers/LoadingManager.js', () => ({ - LoadingManager: LoadingManagerMock -})); - -vi.mock('./managers/ModalManager.js', () => ({ - ModalManager: vi.fn(), - modalManager: mockModalManager -})); - -vi.mock('./managers/UpdateService.js', () => ({ - updateService: mockUpdateService -})); - -vi.mock('./components/Header.js', () => ({ - HeaderManager: HeaderManagerMock -})); - -vi.mock('./managers/SettingsManager.js', () => ({ - settingsManager: mockSettingsManager -})); - -vi.mock('./managers/MoveManager.js', () => ({ - moveManager: mockMoveManager -})); - -vi.mock('./managers/BulkManager.js', () => ({ - bulkManager: mockBulkManager -})); - -vi.mock('./managers/ExampleImagesManager.js', () => ({ - ExampleImagesManager: ExampleImagesManagerMock -})); - -vi.mock('./managers/HelpManager.js', () => ({ - helpManager: mockHelpManager -})); - -vi.mock('./managers/BannerService.js', () => ({ - bannerService: mockBannerService -})); - -vi.mock('./utils/uiHelpers.js', () => ({ - initTheme: initThemeMock, - initBackToTop: initBackToTopMock -})); - -vi.mock('./utils/infiniteScroll.js', () => ({ - initializeInfiniteScroll: initializeInfiniteScrollMock -})); - -vi.mock('./i18n/index.js', () => ({ - i18n: mockI18n -})); - -vi.mock('./managers/OnboardingManager.js', () => ({ - onboardingManager: mockOnboardingManager -})); - -vi.mock('./components/ContextMenu/BulkContextMenu.js', () => ({ - BulkContextMenu: BulkContextMenuMock -})); - -vi.mock('./components/ContextMenu/index.js', () => ({ - createPageContextMenu: createPageContextMenuMock, - createGlobalContextMenu: createGlobalContextMenuMock -})); - -vi.mock('./utils/eventManagementInit.js', () => ({ - initializeEventManagement: initializeEventManagementMock -})); - -beforeEach(() => { - vi.clearAllMocks(); - document.body.innerHTML = ''; - document.body.removeAttribute('data-page'); - mockState.currentPageType = 'loras'; - mockState.global.settings.card_info_display = 'hover'; - isBannerVisibleMock.mockReturnValue(false); - loadingManagerInstances.length = 0; - HeaderManagerInstances.length = 0; - bulkContextMenuInstances.length = 0; - delete window.pageContextMenu; - delete window.globalContextMenuInstance; -}); - -afterEach(() => { - vi.useRealTimers(); -}); - -const loadCoreModule = async () => { - vi.resetModules(); - return import('./core.js'); -}; - -describe('AppCore module bootstrapping', () => { - it('registers storage migration on DOMContentLoaded', async () => { - const addEventListenerSpy = vi.spyOn(document, 'addEventListener'); - await loadCoreModule(); - - expect(addEventListenerSpy).toHaveBeenCalledWith('DOMContentLoaded', expect.any(Function)); - const listener = addEventListenerSpy.mock.calls.find(([event]) => event === 'DOMContentLoaded')[1]; - listener(); - expect(migrateStorageItemsMock).toHaveBeenCalledTimes(1); - }); - - it('initializes managers, UI helpers, and onboarding sequence', async () => { - vi.useFakeTimers(); - const { AppCore } = await loadCoreModule(); - const appCore = new AppCore(); - - document.body.dataset.page = 'loras'; - - const result = await appCore.initialize(); - - expect(result).toBe(appCore); - expect(i18nWaitMock).toHaveBeenCalled(); - expect(i18nLocaleMock).toHaveBeenCalled(); - expect(settingsWaitMock).toHaveBeenCalled(); - - expect(LoadingManagerMock).toHaveBeenCalledTimes(1); - const loadingInstance = LoadingManagerMock.mock.results[0].value; - expect(mockState.loadingManager).toBe(loadingInstance); - - expect(mockModalManager.initialize).toHaveBeenCalled(); - expect(updateServiceInitializeMock).toHaveBeenCalled(); - expect(bannerServiceInitializeMock).toHaveBeenCalled(); - expect(window.modalManager).toBe(mockModalManager); - expect(window.settingsManager).toBe(mockSettingsManager); - expect(window.bulkManager).toBe(mockBulkManager); - expect(window.helpManager).toBe(mockHelpManager); - - expect(HeaderManagerMock).toHaveBeenCalledTimes(1); - expect(initThemeMock).toHaveBeenCalled(); - expect(initBackToTopMock).toHaveBeenCalled(); - - expect(bulkManagerInitializeMock).toHaveBeenCalled(); - expect(BulkContextMenuMock).toHaveBeenCalledTimes(1); - expect(setBulkContextMenuMock).toHaveBeenCalledWith(bulkContextMenuInstances[0]); - - expect(ExampleImagesManagerMock).toHaveBeenCalledTimes(1); - expect(exampleImagesManagerInitializeMock).toHaveBeenCalled(); - expect(helpManagerInitializeMock).toHaveBeenCalled(); - - expect(document.body.classList.contains('hover-reveal')).toBe(true); - expect(initializeEventManagementMock).toHaveBeenCalled(); - - vi.runAllTimers(); - expect(onboardingStartMock).toHaveBeenCalled(); - }); - - it('skips bulk manager when recipes page is active', async () => { - const { AppCore } = await loadCoreModule(); - const appCore = new AppCore(); - mockState.currentPageType = 'recipes'; - - await appCore.initialize(); - - expect(bulkManagerInitializeMock).not.toHaveBeenCalled(); - expect(BulkContextMenuMock).not.toHaveBeenCalled(); - expect(setBulkContextMenuMock).not.toHaveBeenCalled(); - }); - - it('initializes page features with context menus and infinite scroll', async () => { - const { AppCore } = await loadCoreModule(); - const appCore = new AppCore(); - document.body.dataset.page = 'loras'; - - appCore.initializeContextMenus = vi.fn(appCore.initializeContextMenus.bind(appCore)); - - appCore.initializePageFeatures(); - - expect(appCore.initializeContextMenus).toHaveBeenCalledWith('loras'); - expect(initializeInfiniteScrollMock).toHaveBeenCalledWith('loras'); - expect(createPageContextMenuMock).toHaveBeenCalledWith('loras'); - expect(window.pageContextMenu).toBe('page-menu'); - expect(createGlobalContextMenuMock).toHaveBeenCalled(); - expect(window.globalContextMenuInstance).toBe('global-menu'); - }); - - it('does not reinitialize once initialized', async () => { - const { AppCore } = await loadCoreModule(); - const appCore = new AppCore(); - - await appCore.initialize(); - await appCore.initialize(); - - expect(i18nWaitMock).toHaveBeenCalledTimes(1); - expect(settingsWaitMock).toHaveBeenCalledTimes(1); - }); -}); diff --git a/static/js/utils/storageHelpers.js b/static/js/utils/storageHelpers.js index 317d19d7..6b7a8f4f 100644 --- a/static/js/utils/storageHelpers.js +++ b/static/js/utils/storageHelpers.js @@ -116,64 +116,6 @@ export function removeSessionItem(key) { sessionStorage.removeItem(STORAGE_PREFIX + key); } -/** - * Migrate all existing localStorage items to use the prefix - * This should be called once during application initialization - */ -export function migrateStorageItems() { - // Check if migration has already been performed - if (localStorage.getItem(STORAGE_PREFIX + 'migration_completed')) { - console.log('Lora Manager: Storage migration already completed'); - return; - } - - // List of known keys used in the application - const knownKeys = [ - 'nsfwBlurLevel', - 'theme', - 'activeFolder', - 'folderTagsCollapsed', - 'settings', - 'loras_filters', - 'recipes_filters', - 'checkpoints_filters', - 'loras_search_prefs', - 'recipes_search_prefs', - 'checkpoints_search_prefs', - 'show_update_notifications', - 'last_update_check', - 'dismissed_banners' - ]; - - // Migrate each known key - knownKeys.forEach(key => { - const prefixedKey = STORAGE_PREFIX + key; - - // Only migrate if the prefixed key doesn't already exist - if (localStorage.getItem(prefixedKey) === null) { - const value = localStorage.getItem(key); - if (value !== null) { - try { - // Try to parse as JSON first - const parsedValue = JSON.parse(value); - setStorageItem(key, parsedValue); - } catch (e) { - // If not JSON, store as is - setStorageItem(key, value); - } - - // We can optionally remove the old key after migration - localStorage.removeItem(key); - } - } - }); - - // Mark migration as completed - localStorage.setItem(STORAGE_PREFIX + 'migration_completed', 'true'); - - console.log('Lora Manager: Storage migration completed'); -} - /** * Save a Map to localStorage * @param {string} key - The localStorage key diff --git a/static/js/utils/storageHelpers.test.js b/static/js/utils/storageHelpers.test.js index ed19b967..8603deb1 100644 --- a/static/js/utils/storageHelpers.test.js +++ b/static/js/utils/storageHelpers.test.js @@ -8,7 +8,6 @@ const { getSessionItem, setSessionItem, removeSessionItem, - migrateStorageItems } = storageHelpers; const createFakeStorage = () => { @@ -110,33 +109,3 @@ describe('storageHelpers namespace utilities', () => { expect(sessionStorage.getItem('lora_manager_flag')).toBeNull(); }); }); - -describe('migrateStorageItems', () => { - it('migrates known keys and logs completion', () => { - const setStorageSpy = vi.spyOn(storageHelpers, 'setStorageItem'); - localStorage.setItem('theme', '"light"'); - localStorage.setItem('loras_filters', JSON.stringify({ sort: 'asc' })); - localStorage.setItem('nsfwBlurLevel', '3'); - - migrateStorageItems(); - - expect(setStorageSpy).toHaveBeenCalledTimes(3); - expect(localStorage.getItem('lora_manager_theme')).toBe('light'); - expect(localStorage.getItem('lora_manager_loras_filters')).toBe(JSON.stringify({ sort: 'asc' })); - expect(localStorage.getItem('loras_filters')).toBeNull(); - expect(localStorage.getItem('lora_manager_nsfwBlurLevel')).toBe('3'); - expect(localStorage.getItem('nsfwBlurLevel')).toBeNull(); - expect(localStorage.getItem('lora_manager_migration_completed')).toBe('true'); - expect(consoleLogMock).toHaveBeenCalledWith('Lora Manager: Storage migration completed'); - }); - - it('skips migration when already completed and logs notice', () => { - const setStorageSpy = vi.spyOn(storageHelpers, 'setStorageItem'); - localStorage.setItem('lora_manager_migration_completed', 'true'); - - migrateStorageItems(); - - expect(setStorageSpy).not.toHaveBeenCalled(); - expect(consoleLogMock).toHaveBeenCalledWith('Lora Manager: Storage migration already completed'); - }); -});