fix: close Settings after successful saved project load
This commit is contained in:
@@ -4,6 +4,9 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|||||||
import Toolbar from './Toolbar';
|
import Toolbar from './Toolbar';
|
||||||
import { createDefaultGlobalTracks } from '../core/global-track';
|
import { createDefaultGlobalTracks } from '../core/global-track';
|
||||||
import { KGKeySignatureRegion } from '../core/region/KGKeySignatureRegion';
|
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 executeCommandMock = vi.fn();
|
||||||
const storeState = {
|
const storeState = {
|
||||||
@@ -41,6 +44,7 @@ const storeState = {
|
|||||||
toggleKGOnePanel: vi.fn(),
|
toggleKGOnePanel: vi.fn(),
|
||||||
toggleEventListPanel: vi.fn(),
|
toggleEventListPanel: vi.fn(),
|
||||||
activateSidePanel: vi.fn(),
|
activateSidePanel: vi.fn(),
|
||||||
|
setShowSettings: vi.fn(),
|
||||||
showKGOnePanel: true,
|
showKGOnePanel: true,
|
||||||
showEventListPanel: false,
|
showEventListPanel: false,
|
||||||
showChatBox: true,
|
showChatBox: true,
|
||||||
@@ -61,6 +65,7 @@ const storeState = {
|
|||||||
refreshProjectState: vi.fn(),
|
refreshProjectState: vi.fn(),
|
||||||
requestMainContentScroll: vi.fn(),
|
requestMainContentScroll: vi.fn(),
|
||||||
requestPianoRollScroll: vi.fn(),
|
requestPianoRollScroll: vi.fn(),
|
||||||
|
loadProject: vi.fn(),
|
||||||
tracks: [] as unknown[],
|
tracks: [] as unknown[],
|
||||||
globalTracks: createDefaultGlobalTracks(),
|
globalTracks: createDefaultGlobalTracks(),
|
||||||
};
|
};
|
||||||
@@ -141,7 +146,24 @@ vi.mock('../util/midiUtil', () => ({
|
|||||||
vi.mock('../core/audio-interface/KGOfflineRenderer', () => ({ KGOfflineRenderer: { instance: vi.fn(() => ({})) } }));
|
vi.mock('../core/audio-interface/KGOfflineRenderer', () => ({ KGOfflineRenderer: { instance: vi.fn(() => ({})) } }));
|
||||||
vi.mock('./common/FileImportModal', () => ({ default: () => null }));
|
vi.mock('./common/FileImportModal', () => ({ default: () => null }));
|
||||||
vi.mock('./common/LoadingOverlay', () => ({ 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<boolean>;
|
||||||
|
onOpenProject: (projectName: string) => Promise<void>;
|
||||||
|
}) => (
|
||||||
|
<div>
|
||||||
|
<button type="button" onClick={() => void onConfirmOpenProject('Loaded Project')}>
|
||||||
|
confirm-open-project
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => void onOpenProject('Loaded Project')}>
|
||||||
|
open-loaded-project
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
vi.mock('../util/chatUtil', () => ({ clearChatHistoryAndUI: vi.fn() }));
|
vi.mock('../util/chatUtil', () => ({ clearChatHistoryAndUI: vi.fn() }));
|
||||||
vi.mock('./common/icons/PianoIcon', () => ({ default: () => <span>piano</span> }));
|
vi.mock('./common/icons/PianoIcon', () => ({ default: () => <span>piano</span> }));
|
||||||
vi.mock('./common/icons/MetronomeIcon', () => ({ default: () => <span>metro</span> }));
|
vi.mock('./common/icons/MetronomeIcon', () => ({ default: () => <span>metro</span> }));
|
||||||
@@ -205,7 +227,11 @@ describe('Toolbar settings side-panel behavior', () => {
|
|||||||
storeState.toggleKGOnePanel.mockClear();
|
storeState.toggleKGOnePanel.mockClear();
|
||||||
storeState.toggleEventListPanel.mockClear();
|
storeState.toggleEventListPanel.mockClear();
|
||||||
storeState.activateSidePanel.mockClear();
|
storeState.activateSidePanel.mockClear();
|
||||||
|
storeState.setShowSettings.mockClear();
|
||||||
storeState.setStatus.mockClear();
|
storeState.setStatus.mockClear();
|
||||||
|
storeState.cleanupProjectState.mockClear();
|
||||||
|
storeState.loadProject.mockReset();
|
||||||
|
storeState.loadProject.mockResolvedValue(undefined);
|
||||||
storeState.showSettings = true;
|
storeState.showSettings = true;
|
||||||
storeState.showChatBox = true;
|
storeState.showChatBox = true;
|
||||||
storeState.showKGOnePanel = true;
|
storeState.showKGOnePanel = true;
|
||||||
@@ -216,6 +242,9 @@ describe('Toolbar settings side-panel behavior', () => {
|
|||||||
storeState.setKeySignature.mockClear();
|
storeState.setKeySignature.mockClear();
|
||||||
storeState.refreshProjectState.mockClear();
|
storeState.refreshProjectState.mockClear();
|
||||||
executeCommandMock.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', () => {
|
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.refreshProjectState).toHaveBeenCalled();
|
||||||
expect(storeState.setStatus).toHaveBeenCalledWith('Key signature changed to E minor');
|
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<typeof KGProjectStorage.getInstance>);
|
||||||
|
|
||||||
|
render(<Toolbar />);
|
||||||
|
|
||||||
|
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(<Toolbar />);
|
||||||
|
|
||||||
|
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<typeof KGProjectStorage.getInstance>);
|
||||||
|
storeState.loadProject.mockRejectedValueOnce(new Error('load failed'));
|
||||||
|
|
||||||
|
render(<Toolbar />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTitle('Load'));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'open-loaded-project' }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(showAlert).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
expect(storeState.setShowSettings).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const Toolbar: React.FC = () => {
|
|||||||
isLooping, toggleLoop,
|
isLooping, toggleLoop,
|
||||||
globalTracks,
|
globalTracks,
|
||||||
canUndo, canRedo, undoDescription, redoDescription, undo, redo,
|
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,
|
isRecording, startRecording, stopRecording,
|
||||||
// Piano roll state/actions
|
// Piano roll state/actions
|
||||||
showPianoRoll, setShowPianoRoll, activeRegionId, setActiveRegionId,
|
showPianoRoll, setShowPianoRoll, activeRegionId, setActiveRegionId,
|
||||||
@@ -220,7 +220,7 @@ const Toolbar: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Common project loading logic extracted for reuse
|
// 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<boolean> => {
|
||||||
try {
|
try {
|
||||||
// Clean up UI state first
|
// Clean up UI state first
|
||||||
cleanupProjectState();
|
cleanupProjectState();
|
||||||
@@ -245,10 +245,13 @@ const Toolbar: React.FC = () => {
|
|||||||
console.log(`project loaded successfully from ${sourceDescription}`);
|
console.log(`project loaded successfully from ${sourceDescription}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error loading project from ${sourceDescription}:`, error);
|
console.error(`Error loading project from ${sourceDescription}:`, error);
|
||||||
setStatus(t('toolbar.status.loadFailed', { error: String(error) }));
|
setStatus(t('toolbar.status.loadFailed', { error: String(error) }));
|
||||||
await showAlert(t('toolbar.load.error', { error: String(error) }));
|
await showAlert(t('toolbar.load.error', { error: String(error) }));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -299,7 +302,15 @@ const Toolbar: React.FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await loadProjectFromData(loadedProject, `Project "${projectNameToLoad}"`, projectNameToLoad);
|
const didLoadProject = await loadProjectFromData(
|
||||||
|
loadedProject,
|
||||||
|
`Project "${projectNameToLoad}"`,
|
||||||
|
projectNameToLoad
|
||||||
|
);
|
||||||
|
|
||||||
|
if (didLoadProject && showSettings) {
|
||||||
|
setShowSettings(false);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading project:", error);
|
console.error("Error loading project:", error);
|
||||||
await showAlert(t('toolbar.load.error', { error: String(error) }));
|
await showAlert(t('toolbar.load.error', { error: String(error) }));
|
||||||
|
|||||||
Reference in New Issue
Block a user