feat: add Vitest unit testing infrastructure with initial test suite.
This commit is contained in:
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user