feat: implemented user-friendly chord guide editor; added real time chord guide info to the status bar

This commit is contained in:
Xiaohan-Tian
2026-05-29 12:51:58 -07:00
parent 72297cb731
commit cd2938fe15
16 changed files with 1056 additions and 160 deletions
+83
View File
@@ -0,0 +1,83 @@
import React from 'react';
import { act, render, screen } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import StatusBar from './StatusBar';
import { KGPianoRollState } from '../core/state/KGPianoRollState';
import { KGCore } from '../core/KGCore';
import type { ChordGuideData } from '../core/ChordGuideTypes';
import { resolveChordGuideItems } from '../util/chordGuideDataUtil';
import type { ResolvedChordGuideItem } from '../core/ChordGuideTypes';
import chordGuideDataJson from '../../public/resources/modes/chord_guide.json';
const chordGuideData = chordGuideDataJson as ChordGuideData;
const storeState = {
currentStatus: 'Ready',
};
type StoreState = typeof storeState;
type StoreSelector = (state: StoreState) => unknown;
vi.mock('../stores/projectStore', () => ({
useProjectStore: Object.assign(
(selector?: StoreSelector) => (selector ? selector(storeState) : storeState),
{ getState: () => storeState }
),
}));
describe('StatusBar', () => {
beforeEach(() => {
vi.stubGlobal('__APP_VERSION__', 'test-version');
storeState.currentStatus = 'Ready';
KGCore.CHORD_GUIDE_DATA = structuredClone(chordGuideData);
KGPianoRollState.instance().setCurrentHoveredChordGuideCandidate(null);
});
it('shows the normal status when no chord-guide candidate is hovered', () => {
render(<StatusBar />);
expect(screen.getByText('Ready')).toBeTruthy();
});
it('shows hovered chord-guide candidate text and restores the prior status when cleared', () => {
render(<StatusBar />);
const hoveredCandidate: ResolvedChordGuideItem = {
name: 'Dm7',
roman: 'ii7',
notes: ['D', 'F', 'A', 'C'],
source: 'Diatonic',
note: 'Core ii-V-I predominant sonority.',
resolvedNotes: ['D', 'F', 'A', 'C'],
pitchClasses: [2, 5, 9, 12],
};
act(() => {
KGPianoRollState.instance().setCurrentHoveredChordGuideCandidate(hoveredCandidate);
});
expect(
screen.getByText('Chord Guide Candidate: Dm7 — D F A C — Core ii-V-I predominant sonority.')
).toBeTruthy();
act(() => {
KGPianoRollState.instance().setCurrentHoveredChordGuideCandidate(null);
});
expect(screen.getByText('Ready')).toBeTruthy();
});
it('uses resolved notes from the current key signature in the status text', () => {
const transposedCandidate = resolveChordGuideItems('D major', 'ionian', 'T')[0];
render(<StatusBar />);
act(() => {
KGPianoRollState.instance().setCurrentHoveredChordGuideCandidate(transposedCandidate);
});
expect(
screen.getByText('Chord Guide Candidate: C — D F# A — Most stable tonic triad.')
).toBeTruthy();
});
});