feat: add Vitest unit testing infrastructure with initial test suite.

This commit is contained in:
Xiaohan-Tian
2025-09-03 22:15:05 -07:00
parent be39392aee
commit aa2b19966b
11 changed files with 2673 additions and 13 deletions
+186
View File
@@ -0,0 +1,186 @@
import { describe, it, expect } from 'vitest'
import {
beatsToBar,
pitchToNoteNameString,
pitchToNoteName,
pianoRollIndexToPitch,
noteNameToPitch
} from './midiUtil'
describe('midiUtil', () => {
describe('beatsToBar', () => {
it('should convert beats to bar position object', () => {
const result1 = beatsToBar(0, { numerator: 4, denominator: 4 })
expect(result1.bar).toBe(0)
expect(result1.beatInBar).toBe(0)
const result2 = beatsToBar(4, { numerator: 4, denominator: 4 })
expect(result2.bar).toBe(1)
expect(result2.beatInBar).toBe(0)
const result3 = beatsToBar(8, { numerator: 4, denominator: 4 })
expect(result3.bar).toBe(2)
expect(result3.beatInBar).toBe(0)
})
it('should handle different time signatures', () => {
const result1 = beatsToBar(0, { numerator: 3, denominator: 4 })
expect(result1.bar).toBe(0)
expect(result1.beatInBar).toBe(0)
const result2 = beatsToBar(3, { numerator: 3, denominator: 4 })
expect(result2.bar).toBe(1)
expect(result2.beatInBar).toBe(0)
})
it('should handle fractional beats', () => {
const result1 = beatsToBar(2.5, { numerator: 4, denominator: 4 })
expect(result1.bar).toBe(0)
expect(result1.beatInBar).toBe(2.5)
const result2 = beatsToBar(4.5, { numerator: 4, denominator: 4 })
expect(result2.bar).toBe(1)
expect(result2.beatInBar).toBe(0.5)
})
})
describe('pitchToNoteNameString', () => {
it('should convert MIDI pitch to note name with octave', () => {
expect(pitchToNoteNameString(60)).toBe('C4') // Middle C
expect(pitchToNoteNameString(61)).toBe('C#4') // C# above middle C
expect(pitchToNoteNameString(59)).toBe('B3') // B below middle C
expect(pitchToNoteNameString(72)).toBe('C5') // C one octave above middle C
expect(pitchToNoteNameString(48)).toBe('C3') // C one octave below middle C
})
it('should handle edge cases', () => {
expect(pitchToNoteNameString(0)).toBe('C-1') // Lowest MIDI note
expect(pitchToNoteNameString(127)).toBe('G9') // Highest MIDI note
})
it('should handle all chromatic notes', () => {
const expectedNotes = ['C4', 'C#4', 'D4', 'D#4', 'E4', 'F4', 'F#4', 'G4', 'G#4', 'A4', 'A#4', 'B4']
for (let i = 0; i < 12; i++) {
expect(pitchToNoteNameString(60 + i)).toBe(expectedNotes[i])
}
})
})
describe('pitchToNoteName', () => {
it('should convert pitch to note name object', () => {
const result60 = pitchToNoteName(60) // Middle C
expect(result60.note).toBe('C')
expect(result60.octave).toBe(4)
const result61 = pitchToNoteName(61) // C#
expect(result61.note).toBe('C#')
expect(result61.octave).toBe(4)
})
it('should wrap around for different octaves', () => {
const result60 = pitchToNoteName(60)
const result72 = pitchToNoteName(72)
const result84 = pitchToNoteName(84)
expect(result60.note).toBe('C')
expect(result72.note).toBe('C')
expect(result84.note).toBe('C')
expect(result60.octave).toBe(4)
expect(result72.octave).toBe(5)
expect(result84.octave).toBe(6)
})
})
describe('pianoRollIndexToPitch', () => {
it('should convert piano roll row index to MIDI pitch', () => {
// This function likely maps visual rows to MIDI pitches
// The exact mapping depends on your implementation
const result = pianoRollIndexToPitch(10)
expect(typeof result).toBe('number')
expect(result).toBeGreaterThanOrEqual(0)
expect(result).toBeLessThanOrEqual(127)
})
it('should return different pitches for different indices', () => {
const pitch1 = pianoRollIndexToPitch(0)
const pitch2 = pianoRollIndexToPitch(1)
expect(pitch1).not.toBe(pitch2)
})
})
describe('noteNameToPitch', () => {
it('should convert note names to MIDI pitch', () => {
expect(noteNameToPitch('C4')).toBe(60) // Middle C
expect(noteNameToPitch('C#4')).toBe(61) // C# above middle C
expect(noteNameToPitch('D4')).toBe(62) // D above middle C
})
it('should handle different octaves', () => {
expect(noteNameToPitch('C3')).toBe(48) // C below middle C
expect(noteNameToPitch('C5')).toBe(72) // C above middle C
})
it('should handle sharps', () => {
expect(noteNameToPitch('C#4')).toBe(61)
expect(noteNameToPitch('F#4')).toBe(66)
expect(noteNameToPitch('G#4')).toBe(68)
})
it('should handle invalid note names', () => {
expect(() => noteNameToPitch('Db4')).toThrow('Invalid note name: Db4') // Flats not supported
expect(() => noteNameToPitch('H4')).toThrow('Invalid note name: H4') // Invalid note
expect(() => noteNameToPitch('C')).toThrow('Invalid note name: C') // Missing octave
})
})
describe('edge cases and error handling', () => {
it('should handle negative values gracefully', () => {
expect(() => pitchToNoteNameString(-1)).not.toThrow()
expect(() => beatsToBar(-1, { numerator: 4, denominator: 4 })).not.toThrow()
})
it('should handle very large values', () => {
expect(() => pitchToNoteNameString(200)).not.toThrow()
expect(() => beatsToBar(1000, { numerator: 4, denominator: 4 })).not.toThrow()
})
it('should handle zero values', () => {
expect(pitchToNoteNameString(0)).toBeDefined()
expect(beatsToBar(0, { numerator: 4, denominator: 4 })).toBeDefined()
})
})
describe('mathematical consistency', () => {
it('should maintain pitch relationships', () => {
// One octave = 12 semitones - note names should be the same
const baseNote = pitchToNoteName(60)
const octaveNote = pitchToNoteName(72)
expect(baseNote.note).toBe(octaveNote.note) // Both should be 'C'
expect(octaveNote.octave).toBe(baseNote.octave + 1) // Octave should be one higher
})
it('should maintain beat-to-bar relationships', () => {
const timeSignature = { numerator: 4, denominator: 4 }
// Should increment bar by 1 for each complete measure
for (let beat = 0; beat < 20; beat += 4) {
const expectedBar = Math.floor(beat / 4)
const result = beatsToBar(beat, timeSignature)
expect(result.bar).toBe(expectedBar)
expect(result.beatInBar).toBe(0) // Should be at start of bar
}
})
it('should maintain note name to pitch conversion consistency', () => {
// Converting pitch to note name and back should be consistent
const originalPitch = 60
const noteObj = pitchToNoteName(originalPitch)
const noteName = `${noteObj.note}${noteObj.octave}`
const convertedPitch = noteNameToPitch(noteName)
expect(convertedPitch).toBe(originalPitch)
})
})
})