| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- import XCTest
- @testable import MixBoard
- @MainActor
- final class PlayerViewModelTests: XCTestCase {
- // MARK: - Initial State
- func testInitialIsPlayingFalse() {
- let vm = PlayerViewModel()
- XCTAssertFalse(vm.isPlaying)
- }
- func testInitialCurrentTimeZero() {
- let vm = PlayerViewModel()
- XCTAssertEqual(vm.currentTime, 0)
- }
- func testInitialDurationZero() {
- let vm = PlayerViewModel()
- XCTAssertEqual(vm.duration, 0)
- }
- func testInitialCurrentTrackNil() {
- let vm = PlayerViewModel()
- XCTAssertNil(vm.currentTrack)
- }
- func testInitialShowNowPlayingFalse() {
- let vm = PlayerViewModel()
- XCTAssertFalse(vm.showNowPlaying)
- }
- func testInitialShowingWaveformTrue() {
- let vm = PlayerViewModel()
- XCTAssertTrue(vm.showingWaveform)
- }
- func testInitialWaveformSamplesEmpty() {
- let vm = PlayerViewModel()
- XCTAssertTrue(vm.waveformSamples.isEmpty)
- }
- func testInitialCurrentPlayingEntryIDNil() {
- let vm = PlayerViewModel()
- XCTAssertNil(vm.currentPlayingEntryID)
- }
- func testInitialCurrentPlaylistNil() {
- let vm = PlayerViewModel()
- XCTAssertNil(vm.currentPlaylist)
- }
- func testInitialShuffleDisabled() {
- let vm = PlayerViewModel()
- XCTAssertFalse(vm.shuffleEnabled)
- }
- func testInitialRepeatModeOff() {
- let vm = PlayerViewModel()
- XCTAssertEqual(vm.repeatMode, .off)
- }
- func testInitialLoadingWaveformFalse() {
- let vm = PlayerViewModel()
- XCTAssertFalse(vm.isLoadingWaveform)
- }
- // MARK: - Progress Calculations
- func testProgressZeroDuration() {
- let vm = PlayerViewModel()
- XCTAssertEqual(vm.progress, 0)
- }
- func testProgressCalculation() {
- let vm = PlayerViewModel()
- // We can't easily set these through the audio engine,
- // but we can verify the formula via the public property
- // When duration is 0, progress should be 0
- XCTAssertEqual(vm.progress, 0)
- }
- // MARK: - Time Formatting
- func testCurrentTimeFormattedZero() {
- let vm = PlayerViewModel()
- XCTAssertEqual(vm.currentTimeFormatted, "0:00")
- }
- func testDurationFormattedZero() {
- let vm = PlayerViewModel()
- XCTAssertEqual(vm.durationFormatted, "0:00")
- }
- func testRemainingTimeFormattedZero() {
- let vm = PlayerViewModel()
- XCTAssertEqual(vm.remainingTimeFormatted, "-0:00")
- }
- // MARK: - Repeat Mode
- func testRepeatModeOffIcon() {
- XCTAssertEqual(PlayerViewModel.RepeatMode.off.icon, "repeat")
- }
- func testRepeatModeAllIcon() {
- XCTAssertEqual(PlayerViewModel.RepeatMode.all.icon, "repeat")
- }
- func testRepeatModeOneIcon() {
- XCTAssertEqual(PlayerViewModel.RepeatMode.one.icon, "repeat.1")
- }
- func testRepeatModeAllCases() {
- XCTAssertEqual(PlayerViewModel.RepeatMode.allCases.count, 3)
- }
- func testRepeatModeRawValues() {
- XCTAssertEqual(PlayerViewModel.RepeatMode.off.rawValue, "Off")
- XCTAssertEqual(PlayerViewModel.RepeatMode.all.rawValue, "Repeat All")
- XCTAssertEqual(PlayerViewModel.RepeatMode.one.rawValue, "Repeat One")
- }
- // MARK: - Volume
- func testDefaultVolume() {
- let vm = PlayerViewModel()
- XCTAssertEqual(vm.volume, 0.8, accuracy: 0.001)
- }
- func testSetVolume() {
- let vm = PlayerViewModel()
- vm.volume = 0.5
- XCTAssertEqual(vm.volume, 0.5, accuracy: 0.001)
- }
- // MARK: - Stop
- func testStopClearsWaveform() {
- let vm = PlayerViewModel()
- vm.waveformSamples = [WaveformGenerator.WaveformSample(min: -0.5, max: 0.5)]
- XCTAssertFalse(vm.waveformSamples.isEmpty)
- vm.stop()
- XCTAssertTrue(vm.waveformSamples.isEmpty)
- }
- func testStopClearsEntryID() {
- let vm = PlayerViewModel()
- vm.currentPlayingEntryID = UUID()
- vm.stop()
- XCTAssertNil(vm.currentPlayingEntryID)
- }
- func testStopSetsNotPlaying() {
- let vm = PlayerViewModel()
- vm.stop()
- XCTAssertFalse(vm.isPlaying)
- }
- // MARK: - Load and Play (error path)
- func testLoadAndPlayNonExistentFile() {
- let vm = PlayerViewModel()
- let track = Track(title: "Missing", filePath: "nonexistent/file.mp3")
- // Should not crash — error is caught
- vm.loadAndPlay(track)
- XCTAssertFalse(vm.isPlaying)
- }
- // MARK: - Toggle Play/Pause Without Track
- func testTogglePlayPauseWithoutTrack() {
- let vm = PlayerViewModel()
- vm.togglePlayPause()
- // Should not crash
- XCTAssertFalse(vm.isPlaying)
- }
- // MARK: - Shuffle Toggle
- func testShuffleToggle() {
- let vm = PlayerViewModel()
- XCTAssertFalse(vm.shuffleEnabled)
- vm.shuffleEnabled = true
- XCTAssertTrue(vm.shuffleEnabled)
- vm.shuffleEnabled = false
- XCTAssertFalse(vm.shuffleEnabled)
- }
- // MARK: - Repeat Mode Cycling
- func testRepeatModeCycling() {
- let vm = PlayerViewModel()
- XCTAssertEqual(vm.repeatMode, .off)
- vm.repeatMode = .all
- XCTAssertEqual(vm.repeatMode, .all)
- vm.repeatMode = .one
- XCTAssertEqual(vm.repeatMode, .one)
- vm.repeatMode = .off
- XCTAssertEqual(vm.repeatMode, .off)
- }
- // MARK: - Play Next/Previous Without Playlist
- func testPlayNextWithoutPlaylist() {
- let vm = PlayerViewModel()
- // Should not crash
- vm.playNext()
- XCTAssertFalse(vm.isPlaying)
- }
- func testPlayPreviousWithoutPlaylist() {
- let vm = PlayerViewModel()
- vm.playPrevious()
- XCTAssertFalse(vm.isPlaying)
- }
- // MARK: - Seek
- func testSeekToProgress() {
- let vm = PlayerViewModel()
- // Without a loaded file, this should not crash
- vm.seekToProgress(0.5)
- }
- func testSkipForwardDefault() {
- let vm = PlayerViewModel()
- vm.skipForward()
- // Should not crash
- }
- func testSkipBackwardDefault() {
- let vm = PlayerViewModel()
- vm.skipBackward()
- // Should not crash
- }
- func testSkipForwardCustom() {
- let vm = PlayerViewModel()
- vm.skipForward(30)
- // Should not crash
- }
- func testSkipBackwardCustom() {
- let vm = PlayerViewModel()
- vm.skipBackward(30)
- // Should not crash
- }
- // MARK: - Waveform Loading (cached)
- func testLoadWaveformFromCache() {
- let vm = PlayerViewModel()
- let track = Track(title: "Test", filePath: "Music/test.mp3")
- let samples = [WaveformGenerator.WaveformSample(min: -0.3, max: 0.7)]
- track.waveformData = try? JSONEncoder().encode(samples)
- vm.loadWaveform(for: track)
- XCTAssertEqual(vm.waveformSamples.count, 1)
- XCTAssertEqual(vm.waveformSamples[0].min, -0.3, accuracy: 0.001)
- XCTAssertEqual(vm.waveformSamples[0].max, 0.7, accuracy: 0.001)
- }
- func testLoadWaveformCorruptedCacheTriggersGeneration() {
- let vm = PlayerViewModel()
- let track = Track(title: "Test", filePath: "Music/test.mp3")
- track.waveformData = Data([0xFF, 0xFE])
- vm.loadWaveform(for: track)
- // Corrupted cache should not set samples synchronously
- // (generation will fail async since file doesn't exist)
- }
- }
|