diff --git a/src/components/MainContent.test.tsx b/src/components/MainContent.test.tsx
index c374e5e..5d24208 100644
--- a/src/components/MainContent.test.tsx
+++ b/src/components/MainContent.test.tsx
@@ -3,13 +3,20 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import { fireEvent, render, screen } from '@testing-library/react';
import MainContent from './MainContent';
import { KGMidiRegion } from '../core/region/KGMidiRegion';
+import { KGAudioRegion } from '../core/region/KGAudioRegion';
+import { KGAudioTrack } from '../core/track/KGAudioTrack';
import { createMockMidiTrack } from '../test/utils/mock-data';
const midiRegion = new KGMidiRegion('region-1', '1', 0, 'Region 1', 0, 4);
-const track = createMockMidiTrack({ id: 1, regions: [midiRegion] });
+const anotherMidiRegion = new KGMidiRegion('region-2', '1', 0, 'Region 2', 8, 4);
+const audioRegion = new KGAudioRegion('audio-1', '2', 1, 'Audio 1', 4, 4);
+const midiTrack = createMockMidiTrack({ id: 1, regions: [midiRegion, anotherMidiRegion] });
+const audioTrack = new KGAudioTrack('Audio Track', 2);
+audioTrack.setTrackIndex(1);
+audioTrack.setRegions([audioRegion]);
const storeState = {
- tracks: [track],
+ tracks: [midiTrack, audioTrack],
maxBars: 8,
barWidthMultiplier: 1,
reorderTracks: vi.fn(),
@@ -71,13 +78,16 @@ vi.mock('../stores/projectStore', () => ({
vi.mock('../core/KGCore', () => ({
KGCore: {
instance: () => ({
- addSelectedItems: (items: KGMidiRegion[]) => {
+ addSelectedItems: (items: Array<{ getId(): string }>) => {
storeState.selectedRegionIds = items.map(item => item.getId());
},
clearSelectedItems: () => {
storeState.selectedRegionIds = [];
},
executeCommand: vi.fn(),
+ getCurrentProject: () => ({
+ getTracks: () => storeState.tracks,
+ }),
}),
},
}));
@@ -94,14 +104,26 @@ vi.mock('./track/TrackInfoPanel', () => ({
vi.mock('./track/TrackGridPanel', () => ({
default: ({ onRegionClick }: { onRegionClick?: RegionClickHandler }) => (
-
+ <>
+
+
+
+ >
),
}));
vi.mock('./piano-roll/PianoRoll', () => ({
- default: () =>
,
+ default: ({ onClose }: { onClose?: () => void }) => (
+
+
+
+ ),
}));
describe('MainContent', () => {
@@ -120,11 +142,65 @@ describe('MainContent', () => {
it('updates activeRegionId when selecting a region with piano roll closed', () => {
render();
- fireEvent.click(screen.getByRole('button', { name: 'select-region' }));
+ fireEvent.click(screen.getByRole('button', { name: 'select-midi-region' }));
expect(storeState.activeRegionId).toBe('region-1');
expect(storeState.setActiveRegionId).toHaveBeenCalledWith('region-1');
expect(storeState.showPianoRoll).toBe(false);
expect(storeState.openMidiPianoRoll).not.toHaveBeenCalled();
});
+
+ it('keeps piano roll open and preserves activeRegionId when deselecting all regions', () => {
+ storeState.showPianoRoll = true;
+ storeState.activeRegionId = 'region-1';
+ storeState.selectedRegionIds = ['region-1'];
+
+ const { container } = render();
+
+ fireEvent.click(container.firstChild as HTMLElement);
+
+ expect(storeState.selectedRegionIds).toEqual([]);
+ expect(storeState.showPianoRoll).toBe(true);
+ expect(storeState.activeRegionId).toBe('region-1');
+ expect(storeState.setShowPianoRoll).not.toHaveBeenCalledWith(false);
+ expect(storeState.setActiveRegionId).not.toHaveBeenCalledWith(null);
+ });
+
+ it('auto-switches the open piano roll when selecting another MIDI region', () => {
+ storeState.showPianoRoll = true;
+ storeState.activeRegionId = 'region-1';
+
+ render();
+
+ fireEvent.click(screen.getByRole('button', { name: 'select-second-midi-region' }));
+
+ expect(storeState.activeRegionId).toBe('region-2');
+ expect(storeState.openMidiPianoRoll).toHaveBeenCalledWith('region-2');
+ });
+
+ it('preserves current cross-type behavior when selecting an audio region with the editor open', () => {
+ storeState.showPianoRoll = true;
+ storeState.activeRegionId = 'region-1';
+
+ render();
+
+ fireEvent.click(screen.getByRole('button', { name: 'select-audio-region' }));
+
+ expect(storeState.activeRegionId).toBe('audio-1');
+ expect(storeState.openSpectrogramViewer).toHaveBeenCalledWith('audio-1');
+ });
+
+ it('explicit close still clears piano roll visibility and active region', () => {
+ storeState.showPianoRoll = true;
+ storeState.activeRegionId = 'region-1';
+
+ render();
+
+ fireEvent.click(screen.getByRole('button', { name: 'close-piano-roll' }));
+
+ expect(storeState.showPianoRoll).toBe(false);
+ expect(storeState.activeRegionId).toBeNull();
+ expect(storeState.setShowPianoRoll).toHaveBeenCalledWith(false);
+ expect(storeState.setActiveRegionId).toHaveBeenCalledWith(null);
+ });
});
diff --git a/src/components/MainContent.tsx b/src/components/MainContent.tsx
index ace8c85..ab677ec 100644
--- a/src/components/MainContent.tsx
+++ b/src/components/MainContent.tsx
@@ -336,6 +336,21 @@ const MainContent: React.FC = ({
setRegions(updatedRegions);
}, [tracks, timeSignature]);
+ useEffect(() => {
+ if (!showPianoRoll || !activeRegionId) {
+ return;
+ }
+
+ const activeRegionStillExists = tracks.some(track =>
+ track.getRegions().some(region => region.getId() === activeRegionId)
+ );
+
+ if (!activeRegionStillExists) {
+ setShowPianoRoll(false);
+ setActiveRegionId(null);
+ }
+ }, [tracks, showPianoRoll, activeRegionId, setShowPianoRoll, setActiveRegionId]);
+
// Apply auto-selection for newly created/imported regions after the regions state commits.
useEffect(() => {
const pendingRegionId = pendingAutoSelectionRegionIdRef.current;
@@ -589,7 +604,9 @@ const MainContent: React.FC = ({
: null;
setSelectedRegionId(lastSelectedRegionId);
- setActiveRegionId(lastSelectedRegionId);
+ if (lastSelectedRegionId) {
+ setActiveRegionId(lastSelectedRegionId);
+ }
if (DEBUG_MODE.MAIN_CONTENT) {
console.log(`Selected regions: ${selectedRegions.map(selectedRegion => selectedRegion.getId()).join(', ')}`);
@@ -600,7 +617,6 @@ const MainContent: React.FC = ({
}
if (!lastSelectedRegionId) {
- setShowPianoRoll(false);
return;
}