feat: add Vitest unit testing infrastructure with initial test suite.
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { CreateNoteCommand } from './CreateNoteCommand'
|
||||
import { KGCore } from '../../KGCore'
|
||||
import { KGMidiNote } from '../../midi/KGMidiNote'
|
||||
import { createMockProject, createMockMidiTrack, createMockMidiRegion } from '../../../test/utils/mock-data'
|
||||
|
||||
// Mock the KGCore singleton
|
||||
vi.mock('../../KGCore', () => ({
|
||||
KGCore: {
|
||||
instance: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
// Mock generateUniqueId utility
|
||||
vi.mock('../../../util/miscUtil', () => ({
|
||||
generateUniqueId: vi.fn().mockReturnValue('mock-note-id')
|
||||
}))
|
||||
|
||||
// Import the mocked function properly
|
||||
const { generateUniqueId } = await import('../../../util/miscUtil')
|
||||
|
||||
interface MockCore {
|
||||
getCurrentProject: ReturnType<typeof vi.fn>
|
||||
}
|
||||
|
||||
describe('CreateNoteCommand', () => {
|
||||
let mockCore: MockCore
|
||||
let mockProject: ReturnType<typeof createMockProject>
|
||||
let mockTrack: ReturnType<typeof createMockMidiTrack>
|
||||
let mockRegion: ReturnType<typeof createMockMidiRegion>
|
||||
let command: CreateNoteCommand
|
||||
|
||||
beforeEach(() => {
|
||||
// Create test data
|
||||
mockRegion = createMockMidiRegion({
|
||||
id: 'test-region',
|
||||
trackId: 'test-track',
|
||||
name: 'Test Region'
|
||||
})
|
||||
|
||||
mockTrack = createMockMidiTrack({
|
||||
id: 1,
|
||||
name: 'Test Track',
|
||||
regions: [mockRegion]
|
||||
})
|
||||
|
||||
mockProject = createMockProject({
|
||||
name: 'Test Project',
|
||||
tracks: [mockTrack]
|
||||
})
|
||||
|
||||
// Mock KGCore methods
|
||||
mockCore = {
|
||||
getCurrentProject: vi.fn().mockReturnValue(mockProject)
|
||||
}
|
||||
|
||||
vi.mocked(KGCore.instance).mockReturnValue(mockCore as unknown as KGCore)
|
||||
|
||||
// Create command
|
||||
command = new CreateNoteCommand(
|
||||
'test-region', // regionId
|
||||
0, // startBeat
|
||||
1, // endBeat
|
||||
60, // pitch (middle C)
|
||||
80 // velocity
|
||||
)
|
||||
})
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should create command with correct parameters', () => {
|
||||
const cmd = new CreateNoteCommand('region-1', 2, 4, 72, 100, 'custom-id')
|
||||
|
||||
expect(cmd).toBeInstanceOf(CreateNoteCommand)
|
||||
// We can't directly test private properties, but we can test execution
|
||||
})
|
||||
|
||||
it('should generate unique ID when not provided', () => {
|
||||
new CreateNoteCommand('region-1', 0, 1, 60, 80)
|
||||
|
||||
// The generateUniqueId mock should have been called
|
||||
expect(generateUniqueId).toHaveBeenCalledWith('KGMidiNote')
|
||||
})
|
||||
|
||||
it('should use provided ID when given', () => {
|
||||
// Clear previous calls
|
||||
vi.clearAllMocks()
|
||||
|
||||
new CreateNoteCommand('region-1', 0, 1, 60, 80, 'my-custom-id')
|
||||
|
||||
// Should not call generateUniqueId when ID is provided
|
||||
expect(generateUniqueId).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('execute', () => {
|
||||
it('should create a note in the target region', () => {
|
||||
// Mock the addNote method
|
||||
const addNoteSpy = vi.spyOn(mockRegion, 'addNote')
|
||||
|
||||
// Execute the command
|
||||
command.execute()
|
||||
|
||||
// Verify note was added
|
||||
expect(addNoteSpy).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Verify the note has correct properties
|
||||
const addedNote = addNoteSpy.mock.calls[0][0] as KGMidiNote
|
||||
expect(addedNote).toBeInstanceOf(KGMidiNote)
|
||||
expect(addedNote.getStartBeat()).toBe(0)
|
||||
expect(addedNote.getEndBeat()).toBe(1)
|
||||
expect(addedNote.getPitch()).toBe(60)
|
||||
expect(addedNote.getVelocity()).toBe(80)
|
||||
})
|
||||
|
||||
it('should throw error for non-existent region', () => {
|
||||
// Create command for non-existent region
|
||||
const badCommand = new CreateNoteCommand('non-existent-region', 0, 1, 60, 80)
|
||||
|
||||
// Should throw error for non-existent region
|
||||
expect(() => badCommand.execute()).toThrow('MIDI region with ID non-existent-region not found')
|
||||
})
|
||||
|
||||
it('should store created note for undo operation', () => {
|
||||
const addNoteSpy = vi.spyOn(mockRegion, 'addNote')
|
||||
|
||||
command.execute()
|
||||
|
||||
// Note should be created and stored internally for undo
|
||||
expect(addNoteSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('undo', () => {
|
||||
it('should remove the created note', () => {
|
||||
// Execute first to create the note
|
||||
command.execute()
|
||||
|
||||
// Mock removeNote method
|
||||
const removeNoteSpy = vi.spyOn(mockRegion, 'removeNote')
|
||||
|
||||
// Undo the command
|
||||
command.undo()
|
||||
|
||||
// Verify note was removed
|
||||
expect(removeNoteSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should throw error when undoing without execute', () => {
|
||||
// Try to undo without executing first
|
||||
expect(() => command.undo()).toThrow('Cannot undo: no note was created')
|
||||
})
|
||||
})
|
||||
|
||||
describe('re-execute (redo pattern)', () => {
|
||||
it('should re-add the note after undo using execute', () => {
|
||||
// Execute, undo, then execute again (redo pattern)
|
||||
command.execute()
|
||||
command.undo()
|
||||
|
||||
const addNoteSpy = vi.spyOn(mockRegion, 'addNote')
|
||||
command.execute() // Commands are re-executed for redo
|
||||
|
||||
// Note should be added again
|
||||
expect(addNoteSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getDescription', () => {
|
||||
it('should return descriptive text', () => {
|
||||
const description = command.getDescription()
|
||||
|
||||
expect(description).toBeDefined()
|
||||
expect(typeof description).toBe('string')
|
||||
expect(description.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('command lifecycle', () => {
|
||||
it('should support multiple execute/undo cycles', () => {
|
||||
const addNoteSpy = vi.spyOn(mockRegion, 'addNote')
|
||||
const removeNoteSpy = vi.spyOn(mockRegion, 'removeNote')
|
||||
|
||||
// Execute -> Undo -> Execute -> Undo
|
||||
command.execute()
|
||||
expect(addNoteSpy).toHaveBeenCalledTimes(1)
|
||||
|
||||
command.undo()
|
||||
expect(removeNoteSpy).toHaveBeenCalledTimes(1)
|
||||
|
||||
command.execute() // Re-execute for redo
|
||||
expect(addNoteSpy).toHaveBeenCalledTimes(2)
|
||||
|
||||
command.undo()
|
||||
expect(removeNoteSpy).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user