feat: persist global track visibility status and metronome status

This commit is contained in:
Xiaohan-Tian
2026-06-02 23:25:30 -07:00
parent 6b0de74acd
commit 528e4ca7f9
9 changed files with 219 additions and 43 deletions
+50 -33
View File
@@ -58,6 +58,10 @@ const storeState = {
addAudioTrack: vi.fn(),
projectName: 'Test Project',
savedProjectName: 'Test Project',
showGlobalTracks: false,
setShowGlobalTracks: vi.fn((show: boolean) => {
storeState.showGlobalTracks = show;
}),
requestPianoRollScroll: vi.fn(),
mainContentScrollRequest: null,
activeTrackAutomationTrackId: null,
@@ -82,6 +86,11 @@ const finishTrackCreateDialogClose = () => {
}
};
const toggleGlobalTracksAndRerender = (rerender: (ui: React.ReactNode) => void) => {
fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' }));
rerender(<MainContent />);
};
vi.mock('../stores/projectStore', () => ({
useProjectStore: (selector?: StoreSelector) => (
selector ? selector(storeState) : storeState
@@ -160,6 +169,7 @@ describe('MainContent', () => {
storeState.selectedRegionIds = [];
storeState.activeRegionId = null;
storeState.showPianoRoll = false;
storeState.showGlobalTracks = false;
storeState.playheadPosition = 0;
storeState.timeSignature = { numerator: 4, denominator: 4 };
storeState.clearAllSelections.mockClear();
@@ -171,6 +181,7 @@ describe('MainContent', () => {
storeState.openSpectrogramViewer.mockClear();
storeState.addTrack.mockClear();
storeState.addAudioTrack.mockClear();
storeState.setShowGlobalTracks.mockClear();
executeCommandMock.mockClear();
});
@@ -385,29 +396,35 @@ describe('MainContent', () => {
expect(screen.queryByRole('dialog', { name: 'Create New Track' })).not.toBeInTheDocument();
});
it('toggles the mock global tracks button active state locally', () => {
render(<MainContent />);
it('toggles the persisted global tracks button active state through the store', () => {
const { rerender } = render(<MainContent />);
const globalTracksButton = screen.getByRole('button', { name: 'Show global tracks' });
let globalTracksButton = screen.getByRole('button', { name: 'Show global tracks' });
expect(globalTracksButton.className).not.toContain('active');
fireEvent.click(globalTracksButton);
expect(storeState.setShowGlobalTracks).toHaveBeenCalledWith(true);
rerender(<MainContent />);
globalTracksButton = screen.getByRole('button', { name: 'Show global tracks' });
expect(globalTracksButton.className).toContain('active');
fireEvent.click(globalTracksButton);
expect(storeState.setShowGlobalTracks).toHaveBeenCalledWith(false);
rerender(<MainContent />);
globalTracksButton = screen.getByRole('button', { name: 'Show global tracks' });
expect(globalTracksButton.className).not.toContain('active');
});
it('renders the four mock global tracks only when toggled on', () => {
render(<MainContent />);
it('renders the four global tracks only when the persisted toggle is on', () => {
const { rerender } = render(<MainContent />);
expect(screen.queryByText('Marker')).not.toBeInTheDocument();
expect(screen.queryByText('Tempo')).not.toBeInTheDocument();
expect(screen.queryByText('Key Signature')).not.toBeInTheDocument();
expect(screen.queryByText('Chord')).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' }));
toggleGlobalTracksAndRerender(rerender);
expect(screen.getByText('Marker')).toBeInTheDocument();
expect(screen.getByText('Tempo')).toBeInTheDocument();
@@ -416,7 +433,7 @@ describe('MainContent', () => {
const globalTracksInfoShell = screen.getByRole('button', { name: 'Add Marker global track item' }).closest('.global-tracks-info-shell') as HTMLElement;
fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' }));
toggleGlobalTracksAndRerender(rerender);
fireEvent.transitionEnd(globalTracksInfoShell);
expect(screen.queryByText('Marker')).not.toBeInTheDocument();
@@ -427,9 +444,9 @@ describe('MainContent', () => {
it('routes the tempo and chord global track add buttons through commands', () => {
storeState.playheadPosition = 5;
render(<MainContent />);
const { rerender } = render(<MainContent />);
fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' }));
toggleGlobalTracksAndRerender(rerender);
fireEvent.click(screen.getByRole('button', { name: 'Add Tempo global track item' }));
expect(executeCommandMock).toHaveBeenCalledTimes(1);
@@ -450,10 +467,10 @@ describe('MainContent', () => {
]);
storeState.globalTracks = globalTracks;
render(<MainContent />);
const { rerender } = render(<MainContent />);
fireEvent.click(screen.getByRole('button', { name: 'select-midi-region' }));
fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' }));
toggleGlobalTracksAndRerender(rerender);
fireEvent.click(screen.getByText('Am'));
expect(storeState.selectedRegionIds).toEqual(['chord-1']);
@@ -471,9 +488,9 @@ describe('MainContent', () => {
]);
storeState.globalTracks = globalTracks;
render(<MainContent />);
const { rerender } = render(<MainContent />);
fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' }));
toggleGlobalTracksAndRerender(rerender);
fireEvent.click(screen.getByText('Am'));
fireEvent.click(screen.getByText('G'), { shiftKey: true });
@@ -492,9 +509,9 @@ describe('MainContent', () => {
]);
storeState.globalTracks = globalTracks;
render(<MainContent />);
const { rerender } = render(<MainContent />);
fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' }));
toggleGlobalTracksAndRerender(rerender);
const firstChord = screen.getByText('Am');
const lastChord = screen.getByText('G');
@@ -524,9 +541,9 @@ describe('MainContent', () => {
]);
storeState.globalTracks = globalTracks;
render(<MainContent />);
const { rerender } = render(<MainContent />);
fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' }));
toggleGlobalTracksAndRerender(rerender);
fireEvent.click(screen.getByText('Am'));
fireEvent.click(screen.getByText('128 BPM'), { metaKey: true });
@@ -548,9 +565,9 @@ describe('MainContent', () => {
]);
storeState.globalTracks = globalTracks;
render(<MainContent />);
const { rerender } = render(<MainContent />);
fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' }));
toggleGlobalTracksAndRerender(rerender);
fireEvent.click(screen.getByText('Am'));
fireEvent.click(screen.getByText('140 BPM'), { shiftKey: true });
@@ -566,9 +583,9 @@ describe('MainContent', () => {
storeState.globalTracks = globalTracks;
storeState.playheadPosition = 3;
render(<MainContent />);
const { rerender } = render(<MainContent />);
fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' }));
toggleGlobalTracksAndRerender(rerender);
fireEvent.click(screen.getByRole('button', { name: 'Add Chord global track item' }));
expect(executeCommandMock).toHaveBeenCalledTimes(1);
@@ -586,9 +603,9 @@ describe('MainContent', () => {
]);
storeState.globalTracks = globalTracks;
render(<MainContent />);
const { rerender } = render(<MainContent />);
fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' }));
toggleGlobalTracksAndRerender(rerender);
fireEvent.doubleClick(screen.getByText('Am'));
fireEvent.keyDown(screen.getByRole('textbox'), { key: 'Tab' });
@@ -605,9 +622,9 @@ describe('MainContent', () => {
]);
storeState.globalTracks = globalTracks;
render(<MainContent />);
const { rerender } = render(<MainContent />);
fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' }));
toggleGlobalTracksAndRerender(rerender);
fireEvent.doubleClick(screen.getByText('Am'));
fireEvent.keyDown(screen.getByRole('textbox'), { key: 'Tab' });
@@ -626,9 +643,9 @@ describe('MainContent', () => {
]);
storeState.globalTracks = globalTracks;
render(<MainContent />);
const { rerender } = render(<MainContent />);
fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' }));
toggleGlobalTracksAndRerender(rerender);
fireEvent.doubleClick(screen.getByText('G'));
fireEvent.keyDown(screen.getByRole('textbox'), { key: 'Tab', shiftKey: true });
@@ -639,18 +656,18 @@ describe('MainContent', () => {
});
it('routes the signature global track add button through a command', () => {
render(<MainContent />);
const { rerender } = render(<MainContent />);
fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' }));
toggleGlobalTracksAndRerender(rerender);
fireEvent.click(screen.getByRole('button', { name: 'Add Key Signature global track item' }));
expect(executeCommandMock).toHaveBeenCalledTimes(1);
});
it('creates new marker regions with a one-bar default length', () => {
render(<MainContent />);
const { rerender } = render(<MainContent />);
fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' }));
toggleGlobalTracksAndRerender(rerender);
fireEvent.click(screen.getByRole('button', { name: 'Add Marker global track item' }));
expect(executeCommandMock).toHaveBeenCalledTimes(1);
@@ -672,13 +689,13 @@ describe('MainContent', () => {
return 1;
});
const { container } = render(<MainContent />);
const { container, rerender } = render(<MainContent />);
const mainContent = container.querySelector('.main-content') as HTMLDivElement;
Object.defineProperty(mainContent, 'scrollWidth', { configurable: true, value: 4000 });
Object.defineProperty(mainContent, 'clientWidth', { configurable: true, value: 900 });
mainContent.scrollLeft = 500;
fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' }));
toggleGlobalTracksAndRerender(rerender);
fireEvent.doubleClick(screen.getByText('Am'));
await waitFor(() => {
+8 -7
View File
@@ -68,6 +68,8 @@ const MainContent: React.FC<MainContentProps> = ({
addAudioTrack,
projectName,
savedProjectName,
showGlobalTracks,
setShowGlobalTracks,
requestPianoRollScroll,
mainContentScrollRequest,
activeTrackAutomationTrackId,
@@ -84,7 +86,6 @@ const MainContent: React.FC<MainContentProps> = ({
const [draggedTrackIndex, setDraggedTrackIndex] = useState<number | null>(null);
const [dragOverTrackIndex, setDragOverTrackIndex] = useState<number | null>(null);
const [showCreateTrackModal, setShowCreateTrackModal] = useState(false);
const [showGlobalTracksMock, setShowGlobalTracksMock] = useState(false);
const mainContentRegions = useMainContentRegions({
tracks,
@@ -252,10 +253,10 @@ const MainContent: React.FC<MainContentProps> = ({
setShowCreateTrackModal(true);
}, []);
const handleToggleGlobalTracksMock = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
const handleToggleGlobalTracks = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
event.stopPropagation();
setShowGlobalTracksMock(previous => !previous);
}, []);
setShowGlobalTracks(!showGlobalTracks);
}, [setShowGlobalTracks, showGlobalTracks]);
const handleGlobalChordDropToTrack = useCallback(async (draggedRegionId: string, trackIndex: number) => {
const targetTrack = tracks[trackIndex];
@@ -348,10 +349,10 @@ const MainContent: React.FC<MainContentProps> = ({
</button>
<button
type="button"
className={`track-header-button${showGlobalTracksMock ? ' active' : ''}`}
className={`track-header-button${showGlobalTracks ? ' active' : ''}`}
aria-label={t('mainContent.showGlobalTracks')}
title={t('mainContent.showGlobalTracks')}
onClick={handleToggleGlobalTracksMock}
onClick={handleToggleGlobalTracks}
>
<FaSquareArrowUpRight />
</button>
@@ -385,7 +386,7 @@ const MainContent: React.FC<MainContentProps> = ({
</div>
<MainContentGlobalTracksSection
visible={showGlobalTracksMock}
visible={showGlobalTracks}
{...mainContentGlobalTracks.sectionProps}
chordLaneProps={{
...mainContentGlobalTracks.sectionProps.chordLaneProps,