import React from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import MainContent from './MainContent'; import { KGMidiRegion } from '../core/region/KGMidiRegion'; import { KGAudioRegion } from '../core/region/KGAudioRegion'; import { KGChordRegion } from '../core/region/KGChordRegion'; import { KGTempoRegion } from '../core/region/KGTempoRegion'; import { KGAudioTrack } from '../core/track/KGAudioTrack'; import { createDefaultGlobalTracks, GlobalTrackType } from '../core/global-track'; import { createMockMidiTrack } from '../test/utils/mock-data'; const executeCommandMock = vi.fn(); const midiRegion = new KGMidiRegion('region-1', '1', 0, 'Region 1', 0, 4); 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: [midiTrack, audioTrack], globalTracks: createDefaultGlobalTracks(), maxBars: 8, barWidthMultiplier: 1, reorderTracks: vi.fn(), updateTrack: vi.fn(), updateTrackProperties: vi.fn(), timeSignature: { numerator: 4, denominator: 4 }, setPlayheadPosition: vi.fn(), playheadPosition: 0, isPlaying: false, autoScrollEnabled: false, setAutoScrollEnabled: vi.fn(), clearAllSelections: vi.fn(() => { storeState.selectedRegionIds = []; }), setSelectedTrack: vi.fn(), selectedRegionIds: [] as string[], showPianoRoll: false, activeRegionId: null as string | null, setShowPianoRoll: vi.fn((show: boolean) => { storeState.showPianoRoll = show; }), setActiveRegionId: vi.fn((regionId: string | null) => { storeState.activeRegionId = regionId; }), pianoRollMode: 'midi-edit' as const, requestedSheetMusicViewEnabled: false, pianoRollViewRequestVersion: 0, openMidiPianoRoll: vi.fn(), openAudioWaveformViewer: vi.fn(), openSpectrogramViewer: vi.fn(), openHybridMode: vi.fn(), hybridAudioRegionId: null as string | null, addTrack: vi.fn(), 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, activeTrackAutomationType: null, selectedTrackAutomationPointIds: [], bumpTrackAutomationRedrawVersion: vi.fn(), refreshProjectState: vi.fn(), showInstrumentSelection: false, isLooping: false, loopingRange: [0, 0] as [number, number], }; // eslint-disable-next-line no-unused-vars type StoreSelector = (...args: [typeof storeState]) => unknown; // eslint-disable-next-line no-unused-vars type RegionClickHandler = (...args: [string, { shiftKey: boolean; metaKey: boolean; ctrlKey: boolean }]) => void; const finishTrackCreateDialogClose = () => { const overlay = document.querySelector('.dialog-overlay'); if (overlay) { fireEvent.animationEnd(overlay); } }; const toggleGlobalTracksAndRerender = (rerender: (ui: React.ReactNode) => void) => { fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' })); rerender(); }; vi.mock('../stores/projectStore', () => ({ useProjectStore: (selector?: StoreSelector) => ( selector ? selector(storeState) : storeState ), })); vi.mock('../core/KGCore', () => ({ KGCore: { instance: () => ({ addSelectedItems: (items: Array<{ getId(): string }>) => { storeState.selectedRegionIds = items.map(item => item.getId()); }, clearSelectedItems: () => { storeState.selectedRegionIds = []; }, executeCommand: executeCommandMock, getCurrentProject: () => ({ getTracks: () => storeState.tracks, }), }), }, })); vi.mock('../hooks/useRegionOperations', () => ({ useRegionOperations: () => ({ deleteSelectedRegions: vi.fn(() => false), }), })); vi.mock('./track/TrackInfoPanel', () => ({ default: () =>
, })); vi.mock('./track/TrackGridPanel', () => ({ default: ({ onRegionClick }: { onRegionClick?: RegionClickHandler }) => ( <> ), })); vi.mock('./piano-roll/PianoRoll', () => ({ default: ({ onClose }: { onClose?: () => void }) => (
), })); describe('MainContent', () => { beforeEach(() => { midiTrack.setRegions([midiRegion, anotherMidiRegion]); audioTrack.setRegions([audioRegion]); storeState.tracks = [midiTrack, audioTrack]; storeState.globalTracks = createDefaultGlobalTracks(); storeState.selectedRegionIds = []; storeState.activeRegionId = null; storeState.showPianoRoll = false; storeState.showGlobalTracks = false; storeState.playheadPosition = 0; storeState.timeSignature = { numerator: 4, denominator: 4 }; storeState.clearAllSelections.mockClear(); storeState.setSelectedTrack.mockClear(); storeState.setShowPianoRoll.mockClear(); storeState.setActiveRegionId.mockClear(); storeState.openMidiPianoRoll.mockClear(); storeState.openAudioWaveformViewer.mockClear(); storeState.openSpectrogramViewer.mockClear(); storeState.addTrack.mockClear(); storeState.addAudioTrack.mockClear(); storeState.setShowGlobalTracks.mockClear(); executeCommandMock.mockClear(); }); it('updates activeRegionId when selecting a region with piano roll closed', () => { render(); 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.openAudioWaveformViewer).toHaveBeenCalledWith('audio-1'); expect(storeState.openSpectrogramViewer).not.toHaveBeenCalled(); }); it('cmd-click adds a second regular region without range fill', () => { render(); fireEvent.click(screen.getByRole('button', { name: 'select-midi-region' })); fireEvent.click(screen.getByRole('button', { name: 'meta-select-second-midi-region' })); expect(storeState.selectedRegionIds).toEqual(['region-1', 'region-2']); expect(storeState.activeRegionId).toBe('region-2'); }); it('cmd-click on an already selected regular region removes it', () => { storeState.selectedRegionIds = ['region-1', 'region-2']; render(); fireEvent.click(screen.getByRole('button', { name: 'meta-select-second-midi-region' })); expect(storeState.selectedRegionIds).toEqual(['region-1']); }); it('shift-click on the same track selects the full in-between range and keeps the clicked region primary', () => { const middleMidiRegion = new KGMidiRegion('region-1b', '1', 0, 'Region 1B', 4, 4); midiTrack.setRegions([midiRegion, middleMidiRegion, anotherMidiRegion]); render(); fireEvent.click(screen.getByRole('button', { name: 'select-midi-region' })); fireEvent.click(screen.getByRole('button', { name: 'shift-select-second-midi-region' })); expect(storeState.selectedRegionIds).toEqual(['region-1', 'region-1b', 'region-2']); midiTrack.setRegions([midiRegion, anotherMidiRegion]); }); it('shift-click with no same-track anchor falls back to single selection', () => { render(); fireEvent.click(screen.getByRole('button', { name: 'shift-select-second-midi-region' })); expect(storeState.selectedRegionIds).toEqual(['region-2']); }); it('shift-click preserves already selected regions on other regular tracks', () => { const middleMidiRegion = new KGMidiRegion('region-1b', '1', 0, 'Region 1B', 4, 4); midiTrack.setRegions([midiRegion, middleMidiRegion, anotherMidiRegion]); render(); fireEvent.click(screen.getByRole('button', { name: 'select-midi-region' })); fireEvent.click(screen.getByRole('button', { name: 'meta-select-audio-region' })); fireEvent.click(screen.getByRole('button', { name: 'shift-select-second-midi-region' })); expect(storeState.selectedRegionIds).toEqual(['audio-1', 'region-1', 'region-1b', 'region-2']); midiTrack.setRegions([midiRegion, anotherMidiRegion]); }); it('shift-plus-cmd-click uses additive-toggle behavior instead of range selection', () => { const middleMidiRegion = new KGMidiRegion('region-1b', '1', 0, 'Region 1B', 4, 4); midiTrack.setRegions([midiRegion, middleMidiRegion, anotherMidiRegion]); render(); fireEvent.click(screen.getByRole('button', { name: 'select-midi-region' })); fireEvent.click(screen.getByRole('button', { name: 'shift-meta-select-second-midi-region' })); expect(storeState.selectedRegionIds).toEqual(['region-1', 'region-2']); midiTrack.setRegions([midiRegion, anotherMidiRegion]); }); 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); }); it('renders the two-row timeline ruler with track controls and beat markers', () => { const { container } = render(); expect(screen.getByRole('button', { name: 'Create track' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Show global tracks' })).toBeInTheDocument(); const barCells = container.querySelectorAll('[data-testid="bar-number-cell"]'); expect(barCells).toHaveLength(storeState.maxBars); expect(barCells[0]).toHaveTextContent('1'); expect(barCells[storeState.maxBars - 1]).toHaveTextContent(String(storeState.maxBars)); const beatRows = container.querySelectorAll('[data-testid="bar-beat-markers"]'); expect(beatRows).toHaveLength(storeState.maxBars); expect(beatRows[0].querySelectorAll('.beat-marker')).toHaveLength(storeState.timeSignature.numerator - 1); expect(beatRows[0].querySelector('.bar-boundary-marker')).not.toBeNull(); }); it('updates lower-row beat ticks when the time signature numerator changes', () => { storeState.timeSignature = { numerator: 3, denominator: 4 }; const { container } = render(); const firstBeatRow = container.querySelector('[data-testid="bar-beat-markers"]'); expect(firstBeatRow?.querySelectorAll('.beat-marker')).toHaveLength(2); }); it('opens the track creation modal with MIDI selected by default', () => { render(); fireEvent.click(screen.getByRole('button', { name: 'Create track' })); expect(screen.getByRole('dialog', { name: 'Create New Track' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'MIDI' }).className).toContain('selected'); expect(screen.getByRole('button', { name: 'Audio' }).className).not.toContain('selected'); expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Create' })).toBeInTheDocument(); }); it('creates an audio track when audio is selected in the modal', () => { render(); fireEvent.click(screen.getByRole('button', { name: 'Create track' })); fireEvent.click(screen.getByRole('button', { name: 'Audio' })); fireEvent.click(screen.getByRole('button', { name: 'Create' })); finishTrackCreateDialogClose(); expect(storeState.addAudioTrack).toHaveBeenCalledTimes(1); expect(storeState.addTrack).not.toHaveBeenCalled(); expect(screen.queryByRole('dialog', { name: 'Create New Track' })).not.toBeInTheDocument(); }); it('creates a MIDI track by default when confirming the modal', () => { render(); fireEvent.click(screen.getByRole('button', { name: 'Create track' })); fireEvent.click(screen.getByRole('button', { name: 'Create' })); finishTrackCreateDialogClose(); expect(storeState.addTrack).toHaveBeenCalledTimes(1); expect(storeState.addAudioTrack).not.toHaveBeenCalled(); }); it('closes the track creation modal without creating a track when canceled', () => { render(); fireEvent.click(screen.getByRole('button', { name: 'Create track' })); fireEvent.click(screen.getByRole('button', { name: 'Cancel' })); finishTrackCreateDialogClose(); expect(storeState.addTrack).not.toHaveBeenCalled(); expect(storeState.addAudioTrack).not.toHaveBeenCalled(); expect(screen.queryByRole('dialog', { name: 'Create New Track' })).not.toBeInTheDocument(); }); it('toggles the persisted global tracks button active state through the store', () => { const { rerender } = render(); let globalTracksButton = screen.getByRole('button', { name: 'Show global tracks' }); expect(globalTracksButton.className).not.toContain('active'); fireEvent.click(globalTracksButton); expect(storeState.setShowGlobalTracks).toHaveBeenCalledWith(true); rerender(); globalTracksButton = screen.getByRole('button', { name: 'Show global tracks' }); expect(globalTracksButton.className).toContain('active'); fireEvent.click(globalTracksButton); expect(storeState.setShowGlobalTracks).toHaveBeenCalledWith(false); rerender(); globalTracksButton = screen.getByRole('button', { name: 'Show global tracks' }); expect(globalTracksButton.className).not.toContain('active'); }); it('renders the four global tracks only when the persisted toggle is on', () => { const { rerender } = render(); 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(); toggleGlobalTracksAndRerender(rerender); expect(screen.getByText('Marker')).toBeInTheDocument(); expect(screen.getByText('Tempo')).toBeInTheDocument(); expect(screen.getByText('Key Signature')).toBeInTheDocument(); expect(screen.getByText('Chord')).toBeInTheDocument(); const globalTracksInfoShell = screen.getByRole('button', { name: 'Add Marker global track item' }).closest('.global-tracks-info-shell') as HTMLElement; toggleGlobalTracksAndRerender(rerender); fireEvent.transitionEnd(globalTracksInfoShell); 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(); }); it('routes the tempo and chord global track add buttons through commands', () => { storeState.playheadPosition = 5; const { rerender } = render(); toggleGlobalTracksAndRerender(rerender); fireEvent.click(screen.getByRole('button', { name: 'Add Tempo global track item' })); expect(executeCommandMock).toHaveBeenCalledTimes(1); fireEvent.click(screen.getByRole('button', { name: 'Add Chord global track item' })); expect(storeState.addTrack).not.toHaveBeenCalled(); expect(storeState.addAudioTrack).not.toHaveBeenCalled(); expect(executeCommandMock).toHaveBeenCalledTimes(2); expect((executeCommandMock.mock.calls[1][0] as { startBeat?: number }).startBeat).toBe(5); }); it('selecting a global region clears regular-region selection', () => { const globalTracks = createDefaultGlobalTracks(); const chordTrack = globalTracks.find(track => track.getType() === GlobalTrackType.Chord); chordTrack?.setRegions([ new KGChordRegion('chord-1', chordTrack.getId(), chordTrack.getTrackIndex(), 'Am', 0, 4), ]); storeState.globalTracks = globalTracks; const { rerender } = render(); fireEvent.click(screen.getByRole('button', { name: 'select-midi-region' })); toggleGlobalTracksAndRerender(rerender); fireEvent.click(screen.getByText('Am')); expect(storeState.selectedRegionIds).toEqual(['chord-1']); storeState.globalTracks = createDefaultGlobalTracks(); }); it('shift-click across globals ranges only within the same global lane and keeps the clicked region primary', () => { const globalTracks = createDefaultGlobalTracks(); const chordTrack = globalTracks.find(track => track.getType() === GlobalTrackType.Chord); chordTrack?.setRegions([ new KGChordRegion('chord-1', chordTrack.getId(), chordTrack.getTrackIndex(), 'Am', 0, 4), new KGChordRegion('chord-2', chordTrack.getId(), chordTrack.getTrackIndex(), 'F', 4, 4), new KGChordRegion('chord-3', chordTrack.getId(), chordTrack.getTrackIndex(), 'G', 8, 4), ]); storeState.globalTracks = globalTracks; const { rerender } = render(); toggleGlobalTracksAndRerender(rerender); fireEvent.click(screen.getByText('Am')); fireEvent.click(screen.getByText('G'), { shiftKey: true }); expect(storeState.selectedRegionIds).toEqual(['chord-1', 'chord-2', 'chord-3']); storeState.globalTracks = createDefaultGlobalTracks(); }); it('shift-clicking from the first chord region to the last chord region selects the full chord range', () => { const globalTracks = createDefaultGlobalTracks(); const chordTrack = globalTracks.find(track => track.getType() === GlobalTrackType.Chord); chordTrack?.setRegions([ new KGChordRegion('chord-1', chordTrack.getId(), chordTrack.getTrackIndex(), 'Am', 0, 4), new KGChordRegion('chord-2', chordTrack.getId(), chordTrack.getTrackIndex(), 'F', 4, 4), new KGChordRegion('chord-3', chordTrack.getId(), chordTrack.getTrackIndex(), 'G', 8, 4), ]); storeState.globalTracks = globalTracks; const { rerender } = render(); toggleGlobalTracksAndRerender(rerender); const firstChord = screen.getByText('Am'); const lastChord = screen.getByText('G'); fireEvent.mouseDown(firstChord, { clientX: 10, clientY: 10, button: 0 }); fireEvent.mouseUp(window, { clientX: 10, clientY: 10 }); fireEvent.click(firstChord); fireEvent.mouseDown(lastChord, { clientX: 10, clientY: 10, button: 0, shiftKey: true }); fireEvent.mouseUp(window, { clientX: 10, clientY: 10, shiftKey: true }); fireEvent.click(lastChord, { shiftKey: true }); expect(storeState.selectedRegionIds).toEqual(['chord-1', 'chord-2', 'chord-3']); storeState.globalTracks = createDefaultGlobalTracks(); }); it('cmd-clicking a region in a different global lane clears the previous global selection', () => { const globalTracks = createDefaultGlobalTracks(); const chordTrack = globalTracks.find(track => track.getType() === GlobalTrackType.Chord); const tempoTrack = globalTracks.find(track => track.getType() === GlobalTrackType.Tempo); chordTrack?.setRegions([ new KGChordRegion('chord-1', chordTrack.getId(), chordTrack.getTrackIndex(), 'Am', 0, 4), ]); tempoTrack?.setRegions([ new KGTempoRegion('tempo-1', tempoTrack.getId(), tempoTrack.getTrackIndex(), 128, 0, 4, 4), ]); storeState.globalTracks = globalTracks; const { rerender } = render(); toggleGlobalTracksAndRerender(rerender); fireEvent.click(screen.getByText('Am')); fireEvent.click(screen.getByText('128 BPM'), { metaKey: true }); expect(storeState.selectedRegionIds).toEqual(['tempo-1']); storeState.globalTracks = createDefaultGlobalTracks(); }); it('shift-clicking a region in a different global lane clears the previous global selection', () => { const globalTracks = createDefaultGlobalTracks(); const chordTrack = globalTracks.find(track => track.getType() === GlobalTrackType.Chord); const tempoTrack = globalTracks.find(track => track.getType() === GlobalTrackType.Tempo); chordTrack?.setRegions([ new KGChordRegion('chord-1', chordTrack.getId(), chordTrack.getTrackIndex(), 'Am', 0, 4), ]); tempoTrack?.setRegions([ new KGTempoRegion('tempo-1', tempoTrack.getId(), tempoTrack.getTrackIndex(), 128, 0, 4, 4), new KGTempoRegion('tempo-2', tempoTrack.getId(), tempoTrack.getTrackIndex(), 140, 4, 4, 4), ]); storeState.globalTracks = globalTracks; const { rerender } = render(); toggleGlobalTracksAndRerender(rerender); fireEvent.click(screen.getByText('Am')); fireEvent.click(screen.getByText('140 BPM'), { shiftKey: true }); expect(storeState.selectedRegionIds).toEqual(['tempo-2']); storeState.globalTracks = createDefaultGlobalTracks(); }); it('uses split-insert chord command when the playhead is inside an existing chord region', () => { const globalTracks = createDefaultGlobalTracks(); const chordTrack = globalTracks.find(track => track.getType() === GlobalTrackType.Chord); chordTrack?.addRegion(new KGChordRegion('chord-1', chordTrack.getId(), chordTrack.getTrackIndex(), 'Am', 0, 8)); storeState.globalTracks = globalTracks; storeState.playheadPosition = 3; const { rerender } = render(); toggleGlobalTracksAndRerender(rerender); fireEvent.click(screen.getByRole('button', { name: 'Add Chord global track item' })); expect(executeCommandMock).toHaveBeenCalledTimes(1); expect((executeCommandMock.mock.calls[0][0] as { insertBeat?: number }).insertBeat).toBe(3); storeState.globalTracks = createDefaultGlobalTracks(); }); it('tabs the open chord popup to the next chord region before the next bar', () => { const globalTracks = createDefaultGlobalTracks(); const chordTrack = globalTracks.find(track => track.getType() === GlobalTrackType.Chord); chordTrack?.setRegions([ new KGChordRegion('chord-1', chordTrack.getId(), chordTrack.getTrackIndex(), 'Am', 0, 3), new KGChordRegion('chord-2', chordTrack.getId(), chordTrack.getTrackIndex(), 'G', 2, 2), ]); storeState.globalTracks = globalTracks; const { rerender } = render(); toggleGlobalTracksAndRerender(rerender); fireEvent.doubleClick(screen.getByText('Am')); fireEvent.keyDown(screen.getByRole('textbox'), { key: 'Tab' }); expect(storeState.selectedRegionIds).toEqual(['chord-2']); storeState.globalTracks = createDefaultGlobalTracks(); }); it('tabs the open chord popup to insert at the next exact bar when no chord starts before it', () => { const globalTracks = createDefaultGlobalTracks(); const chordTrack = globalTracks.find(track => track.getType() === GlobalTrackType.Chord); chordTrack?.setRegions([ new KGChordRegion('chord-1', chordTrack.getId(), chordTrack.getTrackIndex(), 'Am', 0, 8), ]); storeState.globalTracks = globalTracks; const { rerender } = render(); toggleGlobalTracksAndRerender(rerender); fireEvent.doubleClick(screen.getByText('Am')); fireEvent.keyDown(screen.getByRole('textbox'), { key: 'Tab' }); expect(executeCommandMock).toHaveBeenCalledTimes(1); expect((executeCommandMock.mock.calls[0][0] as { insertBeat?: number }).insertBeat).toBe(4); storeState.globalTracks = createDefaultGlobalTracks(); }); it('shift-tabs the open chord popup to the previous existing chord without creating a new one', () => { const globalTracks = createDefaultGlobalTracks(); const chordTrack = globalTracks.find(track => track.getType() === GlobalTrackType.Chord); chordTrack?.setRegions([ new KGChordRegion('chord-1', chordTrack.getId(), chordTrack.getTrackIndex(), 'Am', 0, 4), new KGChordRegion('chord-2', chordTrack.getId(), chordTrack.getTrackIndex(), 'G', 8, 4), ]); storeState.globalTracks = globalTracks; const { rerender } = render(); toggleGlobalTracksAndRerender(rerender); fireEvent.doubleClick(screen.getByText('G')); fireEvent.keyDown(screen.getByRole('textbox'), { key: 'Tab', shiftKey: true }); expect(storeState.selectedRegionIds).toEqual(['chord-1']); expect(executeCommandMock).toHaveBeenCalledTimes(0); storeState.globalTracks = createDefaultGlobalTracks(); }); it('routes the signature global track add button through a command', () => { const { rerender } = render(); 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', () => { const { rerender } = render(); toggleGlobalTracksAndRerender(rerender); fireEvent.click(screen.getByRole('button', { name: 'Add Marker global track item' })); expect(executeCommandMock).toHaveBeenCalledTimes(1); expect((executeCommandMock.mock.calls[0][0] as { preferredLength?: number }).preferredLength).toBe( storeState.timeSignature.numerator ); }); it('keeps a double-clicked global region editor visible past the sticky left panel', async () => { const globalTracks = createDefaultGlobalTracks(); const chordTrack = globalTracks.find(track => track.getType() === GlobalTrackType.Chord); chordTrack?.setRegions([ new KGChordRegion('chord-1', chordTrack.getId(), chordTrack.getTrackIndex(), 'Am', 40, 16), ]); storeState.globalTracks = globalTracks; const requestAnimationFrameSpy = vi.spyOn(window, 'requestAnimationFrame').mockImplementation((callback: FrameRequestCallback) => { callback(0); return 1; }); const { container, rerender } = render(); 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; toggleGlobalTracksAndRerender(rerender); fireEvent.doubleClick(screen.getByText('Am')); await waitFor(() => { expect(mainContent.scrollLeft).toBe(188); }); requestAnimationFrameSpy.mockRestore(); storeState.globalTracks = createDefaultGlobalTracks(); }); });