diff --git a/src/components/Toolbar.test.tsx b/src/components/Toolbar.test.tsx index 930d22d..d56a72b 100644 --- a/src/components/Toolbar.test.tsx +++ b/src/components/Toolbar.test.tsx @@ -4,6 +4,9 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import Toolbar from './Toolbar'; import { createDefaultGlobalTracks } from '../core/global-track'; import { KGKeySignatureRegion } from '../core/region/KGKeySignatureRegion'; +import { KGProject } from '../core/KGProject'; +import { KGProjectStorage } from '../core/io/KGProjectStorage'; +import { showAlert, showConfirm } from '../util/dialogUtil'; const executeCommandMock = vi.fn(); const storeState = { @@ -41,6 +44,7 @@ const storeState = { toggleKGOnePanel: vi.fn(), toggleEventListPanel: vi.fn(), activateSidePanel: vi.fn(), + setShowSettings: vi.fn(), showKGOnePanel: true, showEventListPanel: false, showChatBox: true, @@ -61,6 +65,7 @@ const storeState = { refreshProjectState: vi.fn(), requestMainContentScroll: vi.fn(), requestPianoRollScroll: vi.fn(), + loadProject: vi.fn(), tracks: [] as unknown[], globalTracks: createDefaultGlobalTracks(), }; @@ -141,7 +146,24 @@ vi.mock('../util/midiUtil', () => ({ vi.mock('../core/audio-interface/KGOfflineRenderer', () => ({ KGOfflineRenderer: { instance: vi.fn(() => ({})) } })); vi.mock('./common/FileImportModal', () => ({ default: () => null })); vi.mock('./common/LoadingOverlay', () => ({ default: () => null })); -vi.mock('./common/OpenProjectModal', () => ({ default: () => null })); +vi.mock('./common/OpenProjectModal', () => ({ + default: ({ + onConfirmOpenProject, + onOpenProject, + }: { + onConfirmOpenProject: (projectName: string) => Promise; + onOpenProject: (projectName: string) => Promise; + }) => ( +
+ + +
+ ), +})); vi.mock('../util/chatUtil', () => ({ clearChatHistoryAndUI: vi.fn() })); vi.mock('./common/icons/PianoIcon', () => ({ default: () => piano })); vi.mock('./common/icons/MetronomeIcon', () => ({ default: () => metro })); @@ -205,7 +227,11 @@ describe('Toolbar settings side-panel behavior', () => { storeState.toggleKGOnePanel.mockClear(); storeState.toggleEventListPanel.mockClear(); storeState.activateSidePanel.mockClear(); + storeState.setShowSettings.mockClear(); storeState.setStatus.mockClear(); + storeState.cleanupProjectState.mockClear(); + storeState.loadProject.mockReset(); + storeState.loadProject.mockResolvedValue(undefined); storeState.showSettings = true; storeState.showChatBox = true; storeState.showKGOnePanel = true; @@ -216,6 +242,9 @@ describe('Toolbar settings side-panel behavior', () => { storeState.setKeySignature.mockClear(); storeState.refreshProjectState.mockClear(); executeCommandMock.mockClear(); + vi.mocked(KGProjectStorage.getInstance).mockReset(); + vi.mocked(showConfirm).mockReset(); + vi.mocked(showAlert).mockReset(); }); it('suppresses active styling for side-panel buttons while Settings is visible', () => { @@ -301,4 +330,60 @@ describe('Toolbar settings side-panel behavior', () => { expect(storeState.refreshProjectState).toHaveBeenCalled(); expect(storeState.setStatus).toHaveBeenCalledWith('Key signature changed to E minor'); }); + + it('closes Settings after a saved project finishes loading successfully', async () => { + const storageMock = { + load: vi.fn().mockResolvedValue(new KGProject()), + cleanupOrphanMedia: vi.fn().mockResolvedValue(undefined), + }; + vi.mocked(KGProjectStorage.getInstance).mockReturnValue(storageMock as unknown as ReturnType); + + render(); + + fireEvent.click(screen.getByTitle('Load')); + fireEvent.click(screen.getByRole('button', { name: 'open-loaded-project' })); + + await waitFor(() => { + expect(storageMock.load).toHaveBeenCalledWith('Loaded Project'); + }); + await waitFor(() => { + expect(storeState.loadProject).toHaveBeenCalled(); + }); + await waitFor(() => { + expect(storeState.setShowSettings).toHaveBeenCalledWith(false); + }); + }); + + it('keeps Settings open when the user cancels project loading', async () => { + vi.mocked(showConfirm).mockResolvedValue(false); + + render(); + + fireEvent.click(screen.getByTitle('Load')); + fireEvent.click(screen.getByRole('button', { name: 'confirm-open-project' })); + + await waitFor(() => { + expect(showConfirm).toHaveBeenCalled(); + }); + expect(storeState.setShowSettings).not.toHaveBeenCalled(); + }); + + it('keeps Settings open when project loading fails', async () => { + const storageMock = { + load: vi.fn().mockResolvedValue(new KGProject()), + cleanupOrphanMedia: vi.fn().mockResolvedValue(undefined), + }; + vi.mocked(KGProjectStorage.getInstance).mockReturnValue(storageMock as unknown as ReturnType); + storeState.loadProject.mockRejectedValueOnce(new Error('load failed')); + + render(); + + fireEvent.click(screen.getByTitle('Load')); + fireEvent.click(screen.getByRole('button', { name: 'open-loaded-project' })); + + await waitFor(() => { + expect(showAlert).toHaveBeenCalled(); + }); + expect(storeState.setShowSettings).not.toHaveBeenCalled(); + }); }); diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index d4d3710..69a057f 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -55,7 +55,7 @@ const Toolbar: React.FC = () => { isLooping, toggleLoop, globalTracks, canUndo, canRedo, undoDescription, redoDescription, undo, redo, - toggleChatBox, toggleSettings, toggleKGOnePanel, toggleEventListPanel, activateSidePanel, showKGOnePanel, showEventListPanel, showChatBox, showSettings, cleanupProjectState, toggleMetronome, isMetronomeEnabled, + toggleChatBox, toggleSettings, toggleKGOnePanel, toggleEventListPanel, activateSidePanel, showKGOnePanel, showEventListPanel, showChatBox, showSettings, setShowSettings, cleanupProjectState, toggleMetronome, isMetronomeEnabled, isRecording, startRecording, stopRecording, // Piano roll state/actions showPianoRoll, setShowPianoRoll, activeRegionId, setActiveRegionId, @@ -220,7 +220,7 @@ const Toolbar: React.FC = () => { }; // Common project loading logic extracted for reuse - const loadProjectFromData = async (project: KGProject, sourceDescription: string, savedName?: string) => { + const loadProjectFromData = async (project: KGProject, sourceDescription: string, savedName?: string): Promise => { try { // Clean up UI state first cleanupProjectState(); @@ -245,10 +245,13 @@ const Toolbar: React.FC = () => { console.log(`project loaded successfully from ${sourceDescription}`); } + return true; + } catch (error) { console.error(`Error loading project from ${sourceDescription}:`, error); setStatus(t('toolbar.status.loadFailed', { error: String(error) })); await showAlert(t('toolbar.load.error', { error: String(error) })); + return false; } }; @@ -299,7 +302,15 @@ const Toolbar: React.FC = () => { return; } - await loadProjectFromData(loadedProject, `Project "${projectNameToLoad}"`, projectNameToLoad); + const didLoadProject = await loadProjectFromData( + loadedProject, + `Project "${projectNameToLoad}"`, + projectNameToLoad + ); + + if (didLoadProject && showSettings) { + setShowSettings(false); + } } catch (error) { console.error("Error loading project:", error); await showAlert(t('toolbar.load.error', { error: String(error) }));