fix: keep piano roll window showing last selected region if user deselect region instead of closing the piano roll window
This commit is contained in:
@@ -3,13 +3,20 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react';
|
import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
import MainContent from './MainContent';
|
import MainContent from './MainContent';
|
||||||
import { KGMidiRegion } from '../core/region/KGMidiRegion';
|
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';
|
import { createMockMidiTrack } from '../test/utils/mock-data';
|
||||||
|
|
||||||
const midiRegion = new KGMidiRegion('region-1', '1', 0, 'Region 1', 0, 4);
|
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 = {
|
const storeState = {
|
||||||
tracks: [track],
|
tracks: [midiTrack, audioTrack],
|
||||||
maxBars: 8,
|
maxBars: 8,
|
||||||
barWidthMultiplier: 1,
|
barWidthMultiplier: 1,
|
||||||
reorderTracks: vi.fn(),
|
reorderTracks: vi.fn(),
|
||||||
@@ -71,13 +78,16 @@ vi.mock('../stores/projectStore', () => ({
|
|||||||
vi.mock('../core/KGCore', () => ({
|
vi.mock('../core/KGCore', () => ({
|
||||||
KGCore: {
|
KGCore: {
|
||||||
instance: () => ({
|
instance: () => ({
|
||||||
addSelectedItems: (items: KGMidiRegion[]) => {
|
addSelectedItems: (items: Array<{ getId(): string }>) => {
|
||||||
storeState.selectedRegionIds = items.map(item => item.getId());
|
storeState.selectedRegionIds = items.map(item => item.getId());
|
||||||
},
|
},
|
||||||
clearSelectedItems: () => {
|
clearSelectedItems: () => {
|
||||||
storeState.selectedRegionIds = [];
|
storeState.selectedRegionIds = [];
|
||||||
},
|
},
|
||||||
executeCommand: vi.fn(),
|
executeCommand: vi.fn(),
|
||||||
|
getCurrentProject: () => ({
|
||||||
|
getTracks: () => storeState.tracks,
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@@ -94,14 +104,26 @@ vi.mock('./track/TrackInfoPanel', () => ({
|
|||||||
|
|
||||||
vi.mock('./track/TrackGridPanel', () => ({
|
vi.mock('./track/TrackGridPanel', () => ({
|
||||||
default: ({ onRegionClick }: { onRegionClick?: RegionClickHandler }) => (
|
default: ({ onRegionClick }: { onRegionClick?: RegionClickHandler }) => (
|
||||||
<button type="button" onClick={() => onRegionClick?.('region-1', { shiftKey: false })}>
|
<>
|
||||||
select-region
|
<button type="button" onClick={() => onRegionClick?.('region-1', { shiftKey: false })}>
|
||||||
</button>
|
select-midi-region
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => onRegionClick?.('region-2', { shiftKey: false })}>
|
||||||
|
select-second-midi-region
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => onRegionClick?.('audio-1', { shiftKey: false })}>
|
||||||
|
select-audio-region
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('./piano-roll/PianoRoll', () => ({
|
vi.mock('./piano-roll/PianoRoll', () => ({
|
||||||
default: () => <div data-testid="piano-roll" />,
|
default: ({ onClose }: { onClose?: () => void }) => (
|
||||||
|
<div data-testid="piano-roll">
|
||||||
|
<button type="button" onClick={onClose}>close-piano-roll</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('MainContent', () => {
|
describe('MainContent', () => {
|
||||||
@@ -120,11 +142,65 @@ describe('MainContent', () => {
|
|||||||
it('updates activeRegionId when selecting a region with piano roll closed', () => {
|
it('updates activeRegionId when selecting a region with piano roll closed', () => {
|
||||||
render(<MainContent />);
|
render(<MainContent />);
|
||||||
|
|
||||||
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.activeRegionId).toBe('region-1');
|
||||||
expect(storeState.setActiveRegionId).toHaveBeenCalledWith('region-1');
|
expect(storeState.setActiveRegionId).toHaveBeenCalledWith('region-1');
|
||||||
expect(storeState.showPianoRoll).toBe(false);
|
expect(storeState.showPianoRoll).toBe(false);
|
||||||
expect(storeState.openMidiPianoRoll).not.toHaveBeenCalled();
|
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(<MainContent />);
|
||||||
|
|
||||||
|
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(<MainContent />);
|
||||||
|
|
||||||
|
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(<MainContent />);
|
||||||
|
|
||||||
|
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(<MainContent />);
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -336,6 +336,21 @@ const MainContent: React.FC<MainContentProps> = ({
|
|||||||
setRegions(updatedRegions);
|
setRegions(updatedRegions);
|
||||||
}, [tracks, timeSignature]);
|
}, [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.
|
// Apply auto-selection for newly created/imported regions after the regions state commits.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const pendingRegionId = pendingAutoSelectionRegionIdRef.current;
|
const pendingRegionId = pendingAutoSelectionRegionIdRef.current;
|
||||||
@@ -589,7 +604,9 @@ const MainContent: React.FC<MainContentProps> = ({
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
setSelectedRegionId(lastSelectedRegionId);
|
setSelectedRegionId(lastSelectedRegionId);
|
||||||
setActiveRegionId(lastSelectedRegionId);
|
if (lastSelectedRegionId) {
|
||||||
|
setActiveRegionId(lastSelectedRegionId);
|
||||||
|
}
|
||||||
|
|
||||||
if (DEBUG_MODE.MAIN_CONTENT) {
|
if (DEBUG_MODE.MAIN_CONTENT) {
|
||||||
console.log(`Selected regions: ${selectedRegions.map(selectedRegion => selectedRegion.getId()).join(', ')}`);
|
console.log(`Selected regions: ${selectedRegions.map(selectedRegion => selectedRegion.getId()).join(', ')}`);
|
||||||
@@ -600,7 +617,6 @@ const MainContent: React.FC<MainContentProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!lastSelectedRegionId) {
|
if (!lastSelectedRegionId) {
|
||||||
setShowPianoRoll(false);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user