diff --git a/src/app/common/metadata/file-metadata-factory.ts b/src/app/common/metadata/file-metadata-factory.ts index 431b126e0..41d65afe3 100644 --- a/src/app/common/metadata/file-metadata-factory.ts +++ b/src/app/common/metadata/file-metadata-factory.ts @@ -4,7 +4,6 @@ import { BaseFileAccess } from '../io/base-file-access'; import { BaseFileMetadataFactory } from './base-file-metadata-factory'; import { IFileMetadata } from './i-file-metadata'; import { MusicMetadataFileMetadata } from './music-metadata-file-meta-data'; -import { TagLibFileMetadata } from './tag-lib-file-metadata'; @Injectable() export class FileMetadataFactory implements BaseFileMetadataFactory { @@ -17,13 +16,13 @@ export class FileMetadataFactory implements BaseFileMetadataFactory { switch (fileExtension) { case FileFormats.mp3: - fileMetadata = new TagLibFileMetadata(path); + fileMetadata = new MusicMetadataFileMetadata(path); break; case FileFormats.flac: - fileMetadata = new TagLibFileMetadata(path); + fileMetadata = new MusicMetadataFileMetadata(path); break; case FileFormats.ogg: - fileMetadata = new TagLibFileMetadata(path); + fileMetadata = new MusicMetadataFileMetadata(path); break; case FileFormats.m4a: fileMetadata = new MusicMetadataFileMetadata(path); @@ -32,10 +31,10 @@ export class FileMetadataFactory implements BaseFileMetadataFactory { fileMetadata = new MusicMetadataFileMetadata(path); break; case FileFormats.wav: - fileMetadata = new TagLibFileMetadata(path); + fileMetadata = new MusicMetadataFileMetadata(path); break; default: - fileMetadata = new TagLibFileMetadata(path); + fileMetadata = new MusicMetadataFileMetadata(path); break; } diff --git a/src/app/components/collection/collection-tracks/collection-tracks-table/collection-tracks-table.component.html b/src/app/components/collection/collection-tracks/collection-tracks-table/collection-tracks-table.component.html index ca62f7672..045b980c8 100644 --- a/src/app/components/collection/collection-tracks/collection-tracks-table/collection-tracks-table.component.html +++ b/src/app/components/collection/collection-tracks/collection-tracks-table/collection-tracks-table.component.html @@ -111,7 +111,7 @@ class="tracks-table-row" [ngClass]="{ 'selected-item-background-important': track.isSelected }" (mousedown)="setSelectedTracks($event, track)" - (dblclick)="playbackService.enqueueAndPlayTracksFromDoubleClick(this.orderedTracks, track)" + (dblclick)="playbackService.enqueueAndPlayTracksStartingFromGivenTrack(this.orderedTracks, track)" (contextmenu)="onTrackContextMenuAsync($event, track)" > diff --git a/src/app/components/collection/track-browser/track-browser.component.html b/src/app/components/collection/track-browser/track-browser.component.html index c7ee5408d..04db7acd9 100644 --- a/src/app/components/collection/track-browser/track-browser.component.html +++ b/src/app/components/collection/track-browser/track-browser.component.html @@ -22,7 +22,7 @@ diff --git a/src/app/services/file/file.service.spec.ts b/src/app/services/file/file.service.spec.ts index 8ea3c4613..1174dfc00 100644 --- a/src/app/services/file/file.service.spec.ts +++ b/src/app/services/file/file.service.spec.ts @@ -78,7 +78,7 @@ describe('FileService', () => { expect(service).toBeDefined(); }); - it('should enqueue all playable tracks found as parameters and play the first track when arguments are received', async () => { + it('should enqueue all playable tracks that are found as parameters', async () => { // Arrange const service: BaseFileService = createService(); @@ -93,12 +93,13 @@ describe('FileService', () => { It.is( (trackModels: TrackModel[]) => trackModels.length === 2 && trackModels[0].path === 'file 1.mp3' && trackModels[1].path === 'file 2.ogg' - )), + ) + ), Times.once() ); }); - it('should not enqueue and play anything if parameters are undefined when arguments are received', async () => { + it('should not enqueue anything if parameters are undefined when arguments are received', async () => { // Arrange const service: BaseFileService = createService(); @@ -110,7 +111,7 @@ describe('FileService', () => { playbackServiceMock.verify((x) => x.enqueueAndPlayTracks(It.isAny()), Times.never()); }); - it('should not enqueue and play anything if parameters are empty when arguments are received', async () => { + it('should not enqueue anything if parameters are empty when arguments are received', async () => { // Arrange const service: BaseFileService = createService(); @@ -122,7 +123,7 @@ describe('FileService', () => { playbackServiceMock.verify((x) => x.enqueueAndPlayTracks(It.isAny()), Times.never()); }); - it('should not enqueue and play anything if there are no playable tracks found as parameters when arguments are received', async () => { + it('should not enqueue anything if there are no playable tracks found as parameters when arguments are received', async () => { // Arrange const service: BaseFileService = createService(); @@ -163,7 +164,7 @@ describe('FileService', () => { }); describe('enqueueParameterFilesAsync', () => { - it('should enqueue all playable tracks found as parameters and play the first track', async () => { + it('should enqueue all playable tracks found as parameters', async () => { // Arrange applicationMock.setup((x) => x.getParameters()).returns(() => ['file 1.mp3', 'file 2.ogg', 'file 3.bmp']); const service: BaseFileService = createService(); @@ -184,7 +185,7 @@ describe('FileService', () => { ); }); - it('should not enqueue and play anything if parameters are undefined', async () => { + it('should not enqueue anything if parameters are undefined', async () => { // Arrange applicationMock.setup((x) => x.getParameters()).returns(() => undefined); const service: BaseFileService = createService(); @@ -196,7 +197,7 @@ describe('FileService', () => { playbackServiceMock.verify((x) => x.enqueueAndPlayTracks(It.isAny()), Times.never()); }); - it('should not enqueue and play anything if parameters are empty', async () => { + it('should not enqueue anything if parameters are empty', async () => { // Arrange applicationMock.setup((x) => x.getParameters()).returns(() => []); const service: BaseFileService = createService(); @@ -208,7 +209,7 @@ describe('FileService', () => { playbackServiceMock.verify((x) => x.enqueueAndPlayTracks(It.isAny()), Times.never()); }); - it('should not enqueue and play anything if there are no playable tracks found as parameters', async () => { + it('should not enqueue anything if there are no playable tracks found as parameters', async () => { // Arrange applicationMock.setup((x) => x.getParameters()).returns(() => ['file 1.png', 'file 2.mkv', 'file 3.bmp']); const service: BaseFileService = createService(); diff --git a/src/app/services/playback/base-playback.service.ts b/src/app/services/playback/base-playback.service.ts index 4effc1bff..37a2bff13 100644 --- a/src/app/services/playback/base-playback.service.ts +++ b/src/app/services/playback/base-playback.service.ts @@ -30,6 +30,7 @@ export abstract class BasePlaybackService { public abstract toggleLoopMode(): void; public abstract toggleIsShuffled(): void; public abstract enqueueAndPlayTracks(tracksToEnqueue: TrackModel[]): void; + public abstract enqueueAndPlayTracksStartingFromGivenTrack(tracksToEnqueue: TrackModel[], trackToPlay: TrackModel): void; public abstract enqueueAndPlayArtist(artistToPlay: ArtistModel, artistType: ArtistType): void; public abstract enqueueAndPlayGenre(genreToPlay: GenreModel): void; public abstract enqueueAndPlayAlbum(albumToPlay: AlbumModel): void; diff --git a/src/app/services/playback/playback.service.spec.ts b/src/app/services/playback/playback.service.spec.ts index c2975430a..cf9fd2c8d 100644 --- a/src/app/services/playback/playback.service.spec.ts +++ b/src/app/services/playback/playback.service.spec.ts @@ -7,7 +7,6 @@ import { FileAccess } from '../../common/io/file-access'; import { Logger } from '../../common/logger'; import { MathExtensions } from '../../common/math-extensions'; import { TrackOrdering } from '../../common/ordering/track-ordering'; -import { Shuffler } from '../../common/shuffler'; import { AlbumModel } from '../album/album-model'; import { ArtistModel } from '../artist/artist-model'; import { ArtistType } from '../artist/artist-type'; @@ -34,6 +33,7 @@ describe('PlaybackService', () => { let trackOrderingMock: IMock; let fileAccessMock: IMock; let loggerMock: IMock; + let queueMock: IMock; let progressUpdaterMock: IMock; let mathExtensionsMock: IMock; let settingsStub: any; @@ -43,7 +43,6 @@ describe('PlaybackService', () => { let subscription: Subscription; let dateTimeMock: IMock; let translatorServiceMock: IMock; - let queue: Queue; const albumData1: AlbumData = new AlbumData(); albumData1.albumKey = 'albumKey1'; @@ -76,8 +75,7 @@ describe('PlaybackService', () => { trackOrderingMock = Mock.ofType(); fileAccessMock = Mock.ofType(); loggerMock = Mock.ofType(); - // use an unmocked queue and shuffler to make sure it is working correctly, and to check track orders - queue = new Queue(new Shuffler(), loggerMock.object); + queueMock = Mock.ofType(); progressUpdaterMock = Mock.ofType(); mathExtensionsMock = Mock.ofType(); settingsStub = { volume: 0.6 }; @@ -154,7 +152,7 @@ describe('PlaybackService', () => { snackBarServiceMock.object, audioPlayerMock.object, trackOrderingMock.object, - queue, + queueMock.object, progressUpdaterMock.object, mathExtensionsMock.object, settingsStub, @@ -306,6 +304,7 @@ describe('PlaybackService', () => { it('should stop playback on playback finished if a next track is not found', () => { // Arrange + queueMock.setup((x) => x.getNextTrack(It.isAny(), false)).returns(() => undefined); // Act playbackFinished.next(); @@ -323,7 +322,7 @@ describe('PlaybackService', () => { it('should raise an event that playback is stopped on playback finished if a next track is not found', () => { // Arrange - service.enqueueAndPlayTracks([trackModel1]); + queueMock.setup((x) => x.getNextTrack(It.isAny(), false)).returns(() => undefined); let playbackIsStopped: boolean = false; subscription.add( @@ -341,6 +340,9 @@ describe('PlaybackService', () => { it('should set the current track to undefined before raising a playback finished event', () => { // Arrange + queueMock.setup((x) => x.getNextTrack(It.isAny(), false)).returns(() => undefined); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); + service.enqueueAndPlayTracks(trackModels); let currentTrack: TrackModel; @@ -359,7 +361,11 @@ describe('PlaybackService', () => { it('should play the next track on playback finished if a next track is found', () => { // Arrange + queueMock.setup((x) => x.getNextTrack(It.isObjectWith({ path: 'Path 1' }), false)).returns(() => trackModel2); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); + service.enqueueAndPlayTracks(trackModels); + audioPlayerMock.reset(); progressUpdaterMock.reset(); @@ -381,6 +387,8 @@ describe('PlaybackService', () => { service.toggleLoopMode(); } + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); + service.enqueueAndPlayTracks(trackModels); audioPlayerMock.reset(); @@ -397,7 +405,11 @@ describe('PlaybackService', () => { service.toggleLoopMode(); } + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); + queueMock.setup((x) => x.getNextTrack(It.isObjectWith({ path: 'Path 1' }), false)).returns(() => trackModel2); + service.enqueueAndPlayTracks(trackModels); + audioPlayerMock.reset(); // Act @@ -413,7 +425,11 @@ describe('PlaybackService', () => { service.toggleLoopMode(); } + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); + queueMock.setup((x) => x.getNextTrack(It.isObjectWith({ path: 'Path 1' }), true)).returns(() => trackModel2); + service.enqueueAndPlayTracks(trackModels); + audioPlayerMock.reset(); // Act @@ -429,7 +445,11 @@ describe('PlaybackService', () => { service.toggleLoopMode(); } + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); + queueMock.setup((x) => x.getNextTrack(It.isObjectWith({ path: 'Path 1' }), false)).returns(() => trackModel2); + service.enqueueAndPlayTracks(trackModels); + audioPlayerMock.reset(); // Act @@ -441,40 +461,31 @@ describe('PlaybackService', () => { it('should get the next track without wrap around on playback finished if loopMode is None', () => { // Arrange - const getNextTrackSpy = jest.spyOn(queue, 'getNextTrack'); - service.enqueueAndPlayTracks([trackModel1, trackModel2]); // Act playbackFinished.next(); // Assert - expect(getNextTrackSpy).toHaveBeenCalledTimes(1); - expect(getNextTrackSpy).toHaveBeenCalledWith(trackModel1, false); - expect(service.currentTrack).toBe(trackModel2); + queueMock.verify((x) => x.getNextTrack(It.isAny(), false), Times.exactly(1)); }); it('should get the next track with wrap around on playback finished if loopMode is All', () => { // Arrange - const getNextTrackSpy = jest.spyOn(queue, 'getNextTrack'); while (service.loopMode !== LoopMode.All) { service.toggleLoopMode(); } - // set a queue of 2 tracks and start playing from second track - service.enqueueAndPlayTracksFromDoubleClick([trackModel1, trackModel2], trackModel2); // Act playbackFinished.next(); // Assert - // should loop back to the first track - expect(getNextTrackSpy).toHaveBeenCalledTimes(1); - expect(getNextTrackSpy).toHaveBeenCalledWith(trackModel2, true); - expect(service.currentTrack).toBe(trackModel1); + queueMock.verify((x) => x.getNextTrack(It.isAny(), true), Times.exactly(1)); }); it('should increase play count and date last played for the current track on playback finished', () => { // Arrange const trackModelMock: IMock = Mock.ofType(); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModelMock.object); service.enqueueAndPlayTracks([trackModelMock.object]); // Act @@ -487,6 +498,7 @@ describe('PlaybackService', () => { it('should save play count and date last played for the current track on playback finished', () => { // Arrange const trackModelMock: IMock = Mock.ofType(); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModelMock.object); service.enqueueAndPlayTracks([trackModelMock.object]); // Act @@ -498,7 +510,9 @@ describe('PlaybackService', () => { it('should raise an event, on playback finished, that playback has started, containing the current track and if a next track is being played.', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks(trackModels); + queueMock.setup((x) => x.getNextTrack(It.isObjectWith({ path: 'Path 1' }), false)).returns(() => trackModel2); let receivedTrack: TrackModel; let isPlayingPreviousTrack: boolean; @@ -600,23 +614,12 @@ describe('PlaybackService', () => { while (service.isShuffled !== false) { service.toggleIsShuffled(); } - service.enqueueAndPlayTracks(trackModels); - const shuffleSpy = jest.spyOn(queue, 'shuffle'); // Act service.toggleIsShuffled(); // Assert - expect(shuffleSpy).toHaveBeenCalledTimes(1); - let playbackQueueString: string = ''; - let trackModelString: string = ''; - for (const track of service.playbackQueue.tracks) { - playbackQueueString = playbackQueueString + track.sortableTitle; - } - for (const track of trackModels) { - trackModelString = trackModelString + track.sortableTitle; - } - expect(playbackQueueString).not.toBe(trackModelString); + queueMock.verify((x) => x.shuffle(), Times.exactly(1)); }); it('should not unshuffle the queue when shuffle is disabled', () => { @@ -624,23 +627,12 @@ describe('PlaybackService', () => { while (service.isShuffled !== false) { service.toggleIsShuffled(); } - service.enqueueAndPlayTracks(trackModels); - const unShuffleSpy = jest.spyOn(queue, 'unShuffle'); // Act service.toggleIsShuffled(); // Assert - expect(unShuffleSpy).not.toHaveBeenCalled(); - let playbackQueueString: string = ''; - let trackModelString: string = ''; - for (const track of service.playbackQueue.tracks) { - playbackQueueString = playbackQueueString + track.sortableTitle; - } - for (const track of trackModels) { - trackModelString = trackModelString + track.sortableTitle; - } - expect(playbackQueueString).not.toBe(trackModelString); + queueMock.verify((x) => x.unShuffle(), Times.never()); }); it('should disable shuffle when shuffle is enabled', () => { @@ -658,7 +650,6 @@ describe('PlaybackService', () => { it('should have shuffled the queue when shuffle is enabled', () => { // Arrange - const shuffleSpy = jest.spyOn(queue, 'shuffle'); while (service.isShuffled !== true) { service.toggleIsShuffled(); } @@ -667,61 +658,49 @@ describe('PlaybackService', () => { service.toggleIsShuffled(); // Assert - expect(shuffleSpy).toHaveBeenCalledTimes(1); + queueMock.verify((x) => x.shuffle(), Times.exactly(1)); }); it('should unshuffle the queue when shuffle is enabled', () => { // Arrange - const unShuffleSpy = jest.spyOn(queue, 'unShuffle'); while (service.isShuffled !== true) { service.toggleIsShuffled(); } - service.enqueueAndPlayTracks(trackModels); // Act service.toggleIsShuffled(); // Assert - expect(unShuffleSpy).toHaveBeenCalledTimes(1); - let playbackQueueString: string = ''; - let trackModelString: string = ''; - for (const track of service.playbackQueue.tracks) { - playbackQueueString = playbackQueueString + track.sortableTitle; - } - for (const track of trackModels) { - trackModelString = trackModelString + track.sortableTitle; - } - expect(playbackQueueString).toBe(trackModelString); + queueMock.verify((x) => x.unShuffle(), Times.exactly(1)); }); }); describe('enqueueAndPlayTracks', () => { it('should not add tracks to the queue if tracks is undefined', () => { // Arrange - const setTracksSpy = jest.spyOn(queue, 'setTracks'); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); // Act service.enqueueAndPlayTracks(undefined); // Assert - expect(setTracksSpy).not.toHaveBeenCalled(); - expect(service.playbackQueue.tracks.length).toBe(0); + queueMock.verify((x) => x.setTracks(It.isAny(), It.isAny()), Times.never()); }); it('should not add tracks to the queue if tracks is empty', () => { // Arrange - const setTracksSpy = jest.spyOn(queue, 'setTracks'); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); // Act service.enqueueAndPlayTracks(undefined); // Assert - expect(setTracksSpy).not.toHaveBeenCalled(); - expect(service.playbackQueue.tracks.length).toBe(0); + queueMock.verify((x) => x.setTracks(It.isAny(), It.isAny()), Times.never()); }); it('should not start playback if tracks is undefined', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); // Act service.enqueueAndPlayTracks(undefined); @@ -736,6 +715,7 @@ describe('PlaybackService', () => { it('should not start playback if tracks is empty', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); // Act service.enqueueAndPlayTracks(undefined); @@ -750,45 +730,25 @@ describe('PlaybackService', () => { it('should add tracks to the queue unshuffled if shuffle is disabled', () => { // Arrange - const setTracksSpy = jest.spyOn(queue, 'setTracks'); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); // Act service.enqueueAndPlayTracks(trackModels); // Assert - expect(setTracksSpy).toHaveBeenCalledTimes(1); - expect(setTracksSpy).toHaveBeenCalledWith(trackModels, false); - let playbackQueueString: string = ''; - let trackModelString: string = ''; - for (const track of service.playbackQueue.tracks) { - playbackQueueString = playbackQueueString + track.sortableTitle; - } - for (const track of trackModels) { - trackModelString = trackModelString + track.sortableTitle; - } - expect(playbackQueueString).toBe(trackModelString); + queueMock.verify((x) => x.setTracks(trackModels, false), Times.exactly(1)); }); it('should add tracks to the queue shuffled if shuffle is enabled', () => { // Arrange - const setTracksSpy = jest.spyOn(queue, 'setTracks'); service.toggleIsShuffled(); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); // Act service.enqueueAndPlayTracks(trackModels); // Assert - expect(setTracksSpy).toHaveBeenCalledTimes(1); - expect(setTracksSpy).toHaveBeenCalledWith(trackModels, true); - let playbackQueueString: string = ''; - let trackModelString: string = ''; - for (const track of service.playbackQueue.tracks) { - playbackQueueString = playbackQueueString + track.sortableTitle; - } - for (const track of trackModels) { - trackModelString = trackModelString + track.sortableTitle; - } - expect(playbackQueueString).not.toBe(trackModelString); + queueMock.verify((x) => x.setTracks(trackModels, true), Times.exactly(1)); }); it('should start playback', () => { @@ -796,6 +756,7 @@ describe('PlaybackService', () => { audioPlayerMock.reset(); audioPlayerMock.setup((x) => x.stop()).verifiable(Times.once(), ExpectedCallType.InSequence); audioPlayerMock.setup((x) => x.play(trackModel1.path)).verifiable(Times.once(), ExpectedCallType.InSequence); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); // Act service.enqueueAndPlayTracks(trackModels); @@ -820,6 +781,8 @@ describe('PlaybackService', () => { }) ); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); + // Act service.enqueueAndPlayTracks(trackModels); @@ -829,6 +792,114 @@ describe('PlaybackService', () => { }); }); + describe('enqueueAndPlayTracksStartingFromGivenTrack', () => { + it('should not add tracks to the queue if tracks is undefined', () => { + // Arrange + + // Act + service.enqueueAndPlayTracksStartingFromGivenTrack(undefined, trackModel1); + + // Assert + queueMock.verify((x) => x.setTracks(It.isAny(), It.isAny()), Times.never()); + }); + + it('should not add tracks to the queue if tracks is empty', () => { + // Arrange + + // Act + service.enqueueAndPlayTracksStartingFromGivenTrack(undefined, trackModel1); + + // Assert + queueMock.verify((x) => x.setTracks(It.isAny(), It.isAny()), Times.never()); + }); + + it('should not start playback if tracks is undefined', () => { + // Arrange + + // Act + service.enqueueAndPlayTracksStartingFromGivenTrack(undefined, trackModel1); + + // Assert + audioPlayerMock.verify((x) => x.play(It.isAny()), Times.never()); + progressUpdaterMock.verify((x) => x.startUpdatingProgress(), Times.never()); + expect(service.isPlaying).toEqual(false); + expect(service.canPause).toEqual(false); + expect(service.canResume).toEqual(true); + }); + + it('should not start playback if tracks is empty', () => { + // Arrange + + // Act + service.enqueueAndPlayTracksStartingFromGivenTrack(undefined, trackModel1); + + // Assert + audioPlayerMock.verify((x) => x.play(It.isAny()), Times.never()); + progressUpdaterMock.verify((x) => x.startUpdatingProgress(), Times.never()); + expect(service.isPlaying).toEqual(false); + expect(service.canPause).toEqual(false); + expect(service.canResume).toEqual(true); + }); + + it('should add tracks to the queue unshuffled if shuffle is disabled', () => { + // Arrange + + // Act + service.enqueueAndPlayTracksStartingFromGivenTrack(trackModels, trackModel1); + + // Assert + queueMock.verify((x) => x.setTracks(trackModels, false), Times.exactly(1)); + }); + + it('should add tracks to the queue shuffled if shuffle is enabled', () => { + // Arrange + service.toggleIsShuffled(); + + // Act + service.enqueueAndPlayTracksStartingFromGivenTrack(trackModels, trackModel1); + + // Assert + queueMock.verify((x) => x.setTracks(trackModels, true), Times.exactly(1)); + }); + + it('should start playback', () => { + // Arrange + audioPlayerMock.reset(); + audioPlayerMock.setup((x) => x.stop()).verifiable(Times.once(), ExpectedCallType.InSequence); + audioPlayerMock.setup((x) => x.play(trackModel1.path)).verifiable(Times.once(), ExpectedCallType.InSequence); + + // Act + service.enqueueAndPlayTracksStartingFromGivenTrack(trackModels, trackModel1); + + // Assert + audioPlayerMock.verifyAll(); + expect(service.currentTrack).toBe(trackModel1); + expect(service.canPause).toBeTruthy(); + expect(service.canResume).toBeFalsy(); + expect(service.isPlaying).toBeTruthy(); + progressUpdaterMock.verify((x) => x.startUpdatingProgress(), Times.exactly(1)); + }); + + it('should raise an event that playback has started, containing the current track and if a next track is being played.', () => { + // Arrange + let receivedTrack: TrackModel; + let isPlayingPreviousTrack: boolean; + subscription.add( + service.playbackStarted$.subscribe((playbackStarted: PlaybackStarted) => { + receivedTrack = playbackStarted.currentTrack; + isPlayingPreviousTrack = playbackStarted.isPlayingPreviousTrack; + }) + ); + + // Act + service.enqueueAndPlayTracksStartingFromGivenTrack(trackModels, trackModel1); + + // Assert + expect(receivedTrack).toBe(trackModel1); + expect(isPlayingPreviousTrack).toBeFalsy(); + }); + }); + describe('enqueueAndPlayArtist', () => { it('should not get tracks for the artist if artistToPlay is undefined', () => { // Arrange @@ -854,6 +925,7 @@ describe('PlaybackService', () => { it('should get tracks for the artist if artistToPlay and artistType are not undefined', () => { // Arrange const artistToPlay: ArtistModel = new ArtistModel('artist1', translatorServiceMock.object); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); // Act service.enqueueAndPlayArtist(artistToPlay, ArtistType.trackArtists); @@ -865,6 +937,7 @@ describe('PlaybackService', () => { it('should order tracks for the artist byAlbum', () => { // Arrange const artistToPlay: ArtistModel = new ArtistModel('artist1', translatorServiceMock.object); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); // Act service.enqueueAndPlayArtist(artistToPlay, ArtistType.trackArtists); @@ -875,42 +948,14 @@ describe('PlaybackService', () => { it('should add tracks to the queue ordered by album', () => { // Arrange - const setTracksSpy = jest.spyOn(queue, 'setTracks'); - const artistToPlay: ArtistModel = new ArtistModel('artist1', translatorServiceMock.object); - - // Act - service.enqueueAndPlayArtist(artistToPlay, ArtistType.trackArtists); - - // Assert - expect(setTracksSpy).toHaveBeenCalledTimes(1); - expect(setTracksSpy).toHaveBeenCalledWith(orderedTrackModels, false); - service.playbackQueue.tracks.forEach((track, index) => { - expect(track).toBe(orderedTrackModels[index]); - }); - }); - - it('should start playback with a random track if shuffle is on', () => { - // Arrange - while (service.isShuffled !== true) { - service.toggleIsShuffled(); - } const artistToPlay: ArtistModel = new ArtistModel('artist1', translatorServiceMock.object); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); // Act service.enqueueAndPlayArtist(artistToPlay, ArtistType.trackArtists); - // randomness means the test could fail (service could "randomly" assign first track to first position) - // so we run it a few times - for (var i = 0; i < 10; i++) { - if (service.playbackQueue[0] === tracks.tracks[0]) { - service.enqueueAndPlayArtist(artistToPlay, ArtistType.trackArtists); - } else { - break; - } - } - // Assert - expect(service.playbackQueue[0]).not.toBe(tracks.tracks[0]); + queueMock.verify((x) => x.setTracks(orderedTrackModels, It.isAny()), Times.exactly(1)); }); it('should start playback', () => { @@ -919,6 +964,7 @@ describe('PlaybackService', () => { audioPlayerMock.reset(); audioPlayerMock.setup((x) => x.stop()).verifiable(Times.once(), ExpectedCallType.InSequence); audioPlayerMock.setup((x) => x.play(trackModel2.path)).verifiable(Times.once(), ExpectedCallType.InSequence); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel2); // Act service.enqueueAndPlayArtist(artistToPlay, ArtistType.trackArtists); @@ -944,6 +990,8 @@ describe('PlaybackService', () => { }) ); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel2); + // Act service.enqueueAndPlayArtist(artistToPlay, ArtistType.trackArtists); @@ -956,6 +1004,7 @@ describe('PlaybackService', () => { describe('enqueueAndPlayGenre', () => { it('should not get tracks for the genre if genreToPlay is undefined', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); // Act service.enqueueAndPlayGenre(undefined); @@ -967,6 +1016,7 @@ describe('PlaybackService', () => { it('should get tracks for the genre if genreToPlay is not undefined', () => { // Arrange const genreToPlay: GenreModel = new GenreModel('genre1', translatorServiceMock.object); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); // Act service.enqueueAndPlayGenre(genreToPlay); @@ -978,6 +1028,7 @@ describe('PlaybackService', () => { it('should order tracks for the artist byAlbum', () => { // Arrange const genreToPlay: GenreModel = new GenreModel('genre1', translatorServiceMock.object); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); // Act service.enqueueAndPlayGenre(genreToPlay); @@ -988,18 +1039,14 @@ describe('PlaybackService', () => { it('should add tracks to the queue ordered by album', () => { // Arrange - const setTracksSpy = jest.spyOn(queue, 'setTracks'); const genreToPlay: GenreModel = new GenreModel('genre1', translatorServiceMock.object); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); // Act service.enqueueAndPlayGenre(genreToPlay); // Assert - expect(setTracksSpy).toHaveBeenCalledTimes(1); - expect(setTracksSpy).toHaveBeenCalledWith(orderedTrackModels, false); - service.playbackQueue.tracks.forEach((track, index) => { - expect(track).toBe(orderedTrackModels[index]); - }); + queueMock.verify((x) => x.setTracks(orderedTrackModels, It.isAny()), Times.exactly(1)); }); it('should start playback', () => { @@ -1009,6 +1056,7 @@ describe('PlaybackService', () => { audioPlayerMock.reset(); audioPlayerMock.setup((x) => x.stop()).verifiable(Times.once(), ExpectedCallType.InSequence); audioPlayerMock.setup((x) => x.play(trackModel2.path)).verifiable(Times.once(), ExpectedCallType.InSequence); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel2); // Act service.enqueueAndPlayGenre(genreToPlay); @@ -1022,30 +1070,6 @@ describe('PlaybackService', () => { progressUpdaterMock.verify((x) => x.startUpdatingProgress(), Times.exactly(1)); }); - it('should start playback with a random track if shuffle is on', () => { - // Arrange - while (service.isShuffled !== true) { - service.toggleIsShuffled(); - } - const genreToPlay: GenreModel = new GenreModel('genre1', translatorServiceMock.object); - - // Act - service.enqueueAndPlayGenre(genreToPlay); - - // randomness means the test could fail (service could "randomly" assign first track to first position) - // so we run it a few times - for (var i = 0; i < 10; i++) { - if (service.playbackQueue[0] === tracks.tracks[0]) { - service.enqueueAndPlayGenre(genreToPlay); - } else { - break; - } - } - - // Assert - expect(service.playbackQueue[0]).not.toBe(tracks.tracks[0]); - }); - it('should raise an event that playback has started, containing the current track and if a next track is being played.', () => { // Arrange const genreToPlay: GenreModel = new GenreModel('genre1', translatorServiceMock.object); @@ -1060,6 +1084,8 @@ describe('PlaybackService', () => { }) ); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel2); + service.enqueueAndPlayGenre(genreToPlay); // Assert @@ -1071,6 +1097,7 @@ describe('PlaybackService', () => { describe('enqueueAndPlayAlbum', () => { it('should not get tracks for the album if albumToPlay is undefined', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); // Act service.enqueueAndPlayAlbum(undefined); @@ -1081,6 +1108,7 @@ describe('PlaybackService', () => { it('should get tracks for the album if albumToPlay is not undefined', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); // Act service.enqueueAndPlayAlbum(album1); @@ -1091,6 +1119,7 @@ describe('PlaybackService', () => { it('should order tracks for the album byAlbum', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); // Act service.enqueueAndPlayAlbum(album1); @@ -1101,17 +1130,13 @@ describe('PlaybackService', () => { it('should add tracks to the queue ordered by album', () => { // Arrange - const setTracksSpy = jest.spyOn(queue, 'setTracks'); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); // Act service.enqueueAndPlayAlbum(album1); // Assert - expect(setTracksSpy).toHaveBeenCalledTimes(1); - expect(setTracksSpy).toHaveBeenCalledWith(orderedTrackModels, false); - service.playbackQueue.tracks.forEach((track, index) => { - expect(track).toBe(orderedTrackModels[index]); - }); + queueMock.verify((x) => x.setTracks(orderedTrackModels, It.isAny()), Times.exactly(1)); }); it('should start playback', () => { @@ -1119,6 +1144,7 @@ describe('PlaybackService', () => { audioPlayerMock.reset(); audioPlayerMock.setup((x) => x.stop()).verifiable(Times.once(), ExpectedCallType.InSequence); audioPlayerMock.setup((x) => x.play(trackModel2.path)).verifiable(Times.once(), ExpectedCallType.InSequence); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel2); // Act service.enqueueAndPlayAlbum(album1); @@ -1132,29 +1158,6 @@ describe('PlaybackService', () => { progressUpdaterMock.verify((x) => x.startUpdatingProgress(), Times.exactly(1)); }); - it('should start playback with a random track if shuffle is on', () => { - // Arrange - while (service.isShuffled !== true) { - service.toggleIsShuffled(); - } - - // Act - service.enqueueAndPlayAlbum(album1); - - // randomness means the test could fail (service could "randomly" assign first track to first position) - // so we run it a few times - for (var i = 0; i < 10; i++) { - if (service.playbackQueue[0] === tracks.tracks[0]) { - service.enqueueAndPlayAlbum(album1); - } else { - break; - } - } - - // Assert - expect(service.playbackQueue[0]).not.toBe(tracks.tracks[0]); - }); - it('should raise an event that playback has started, containing the current track and if a next track is being played.', () => { // Arrange let receivedTrack: TrackModel; @@ -1166,6 +1169,8 @@ describe('PlaybackService', () => { }) ); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel2); + // Act service.enqueueAndPlayAlbum(album1); @@ -1182,6 +1187,7 @@ describe('PlaybackService', () => { describe('pause', () => { it('should pause playback', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks(trackModels); // Act @@ -1197,6 +1203,7 @@ describe('PlaybackService', () => { it('should raise an event that playback is paused.', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks(trackModels); let playbackIsPaused: boolean = false; @@ -1217,6 +1224,7 @@ describe('PlaybackService', () => { describe('resume', () => { it('should resume playback if playing', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks(trackModels); audioPlayerMock.reset(); progressUpdaterMock.reset(); @@ -1248,6 +1256,7 @@ describe('PlaybackService', () => { it('should raise an event that playback is resumed if playing', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks(trackModels); audioPlayerMock.reset(); progressUpdaterMock.reset(); @@ -1333,6 +1342,7 @@ describe('PlaybackService', () => { describe('playPrevious', () => { it('should play the current track if there is a current track and playback lasted for more than 3 seconds', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks(trackModels); audioPlayerMock.reset(); audioPlayerMock.setup((x) => x.progressSeconds).returns(() => 3.1); @@ -1351,51 +1361,29 @@ describe('PlaybackService', () => { it('should play the previous track if found and playback lasted for less then 3 seconds', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks(trackModels); - // set up a "previous" track by playing next track, otherwise "play previous" will be undefined - service.playNext(); - - // reset audio player and progress updater to make sure playback is less than 3s + queueMock.setup((x) => x.getPreviousTrack(trackModel1, false)).returns(() => trackModel2); audioPlayerMock.reset(); audioPlayerMock.setup((x) => x.progressSeconds).returns(() => 2.9); progressUpdaterMock.reset(); // Act service.playPrevious(); - - // Assert - audioPlayerMock.verify((x) => x.play(trackModel1.path), Times.exactly(1)); - expect(service.isPlaying).toBeTruthy(); - expect(service.canPause).toBeTruthy(); - expect(service.canResume).toBeFalsy(); - progressUpdaterMock.verify((x) => x.startUpdatingProgress(), Times.exactly(1)); - expect(service.currentTrack).toBe(trackModel1); - }); - - it('should restart the current track if playback lasted for more than 3 seconds', () => { - // Arrange - service.enqueueAndPlayTracks(trackModels); - - // reset audio player and progress updater to make sure playback is more than - audioPlayerMock.reset(); - audioPlayerMock.setup((x) => x.progressSeconds).returns(() => 3.1); - progressUpdaterMock.reset(); - - // Act - service.playPrevious(); - // Assert - audioPlayerMock.verify((x) => x.play(trackModel1.path), Times.exactly(1)); + audioPlayerMock.verify((x) => x.play(trackModel2.path), Times.exactly(1)); expect(service.isPlaying).toBeTruthy(); expect(service.canPause).toBeTruthy(); expect(service.canResume).toBeFalsy(); progressUpdaterMock.verify((x) => x.startUpdatingProgress(), Times.exactly(1)); - expect(service.currentTrack).toBe(trackModel1); + expect(service.currentTrack).toBe(trackModel2); }); it('should stop playback if a previous track was not found and playback lasted for less then 3 seconds', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks(trackModels); + queueMock.setup((x) => x.getPreviousTrack(trackModel1, false)).returns(() => undefined); audioPlayerMock.reset(); audioPlayerMock.setup((x) => x.progressSeconds).returns(() => 2.9); @@ -1413,7 +1401,9 @@ describe('PlaybackService', () => { it('should raise an event that playback is stopped if a previous track was not found and playback lasted for less then 3 seconds.', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks(trackModels); + queueMock.setup((x) => x.getPreviousTrack(trackModel1, false)).returns(() => undefined); audioPlayerMock.reset(); audioPlayerMock.setup((x) => x.progressSeconds).returns(() => 2.9); let playbackIsStopped: boolean = false; @@ -1433,7 +1423,9 @@ describe('PlaybackService', () => { it('should set the current track to undefined before raising a stop event', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks(trackModels); + queueMock.setup((x) => x.getNextTrack(It.isAny(), false)).returns(() => undefined); let currentTrack: TrackModel; subscription.add( @@ -1451,64 +1443,48 @@ describe('PlaybackService', () => { it('should get the previous track without wrap around if loopMode is None', () => { // Arrange - const getPreviousTrackSpy = jest.spyOn(queue, 'getPreviousTrack'); while (service.loopMode !== LoopMode.None) { service.toggleLoopMode(); } - service.enqueueAndPlayTracks([trackModel1, trackModel2]); // Act service.playPrevious(); // Assert - expect(getPreviousTrackSpy).toHaveBeenCalledTimes(1); - expect(getPreviousTrackSpy).toHaveBeenCalledWith(trackModel1, false); - expect(service.currentTrack).toBe(undefined); + queueMock.verify((x) => x.getPreviousTrack(It.isAny(), false), Times.exactly(1)); }); it('should get the previous track with wrap around if loopMode is All', () => { // Arrange - const getPreviousTrackSpy = jest.spyOn(queue, 'getPreviousTrack'); while (service.loopMode !== LoopMode.All) { service.toggleLoopMode(); } - service.enqueueAndPlayTracks([trackModel1, trackModel2]); // Act service.playPrevious(); // Assert - expect(getPreviousTrackSpy).toHaveBeenCalledTimes(1); - expect(getPreviousTrackSpy).toHaveBeenCalledWith(trackModel1, true); - expect(service.currentTrack).toBe(trackModel2); + queueMock.verify((x) => x.getPreviousTrack(It.isAny(), true), Times.exactly(1)); }); it('should get the previous track with wrap around if loopMode is One', () => { // Arrange - const getPreviousTrackSpy = jest.spyOn(queue, 'getPreviousTrack'); while (service.loopMode !== LoopMode.One) { service.toggleLoopMode(); } - service.enqueueAndPlayTracks([trackModel1, trackModel2]); - // playPrevious doesn't work on first track, need to have a previous that has been played, so: - service.playNext(); // play next, should still be track 1 - // do it twice to make sure - service.playNext(); // play next, should still be track 1 // Act service.playPrevious(); // Assert - expect(getPreviousTrackSpy).toHaveBeenCalledTimes(1); - expect(getPreviousTrackSpy).toHaveBeenCalledWith(undefined, false); - expect(service.currentTrack).toBe(trackModel1); + queueMock.verify((x) => x.getPreviousTrack(It.isAny(), false), Times.exactly(1)); }); it('should raise an event that playback has started, containing the current track and if a previous track is being played.', () => { // Arrange - service.enqueueAndPlayTracks([trackModel1, trackModel2]); - // set up a "previous" track by playing next track, otherwise "play previous" will be undefined - service.playNext(); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); + service.enqueueAndPlayTracks(trackModels); + queueMock.setup((x) => x.getPreviousTrack(trackModel1, false)).returns(() => trackModel2); let receivedTrack: TrackModel; let isPlayingPreviousTrack: boolean; subscription.add( @@ -1517,12 +1493,10 @@ describe('PlaybackService', () => { isPlayingPreviousTrack = playbackStarted.isPlayingPreviousTrack; }) ); - // Act service.playPrevious(); - // Assert - expect(receivedTrack).toBe(trackModel1); + expect(receivedTrack).toBe(trackModel2); expect(isPlayingPreviousTrack).toBeTruthy(); }); }); @@ -1530,6 +1504,7 @@ describe('PlaybackService', () => { describe('playNext', () => { it('should stop playback if a next track is not found', () => { // Arrange + queueMock.setup((x) => x.getNextTrack(It.isObjectWith({ path: 'Path 1' }), false)).returns(() => undefined); progressUpdaterMock.reset(); audioPlayerMock.reset(); @@ -1550,7 +1525,11 @@ describe('PlaybackService', () => { it('should raise an event that playback is stopped if a next track is not found', () => { // Arrange - service.enqueueAndPlayTracks([trackModel1]); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); + service.enqueueAndPlayTracks(trackModels); + queueMock.setup((x) => x.getNextTrack(It.isObjectWith({ path: 'Path 1' }), false)).returns(() => undefined); + progressUpdaterMock.reset(); + audioPlayerMock.reset(); let playbackIsStopped: boolean = false; subscription.add( @@ -1568,7 +1547,9 @@ describe('PlaybackService', () => { it('should set the current track to undefined before raising a stop event', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks(trackModels); + queueMock.setup((x) => x.getNextTrack(It.isObjectWith({ path: 'Path 1' }), false)).returns(() => undefined); progressUpdaterMock.reset(); audioPlayerMock.reset(); let currentTrack: TrackModel; @@ -1588,7 +1569,9 @@ describe('PlaybackService', () => { it('should play the next track if found', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks(trackModels); + queueMock.setup((x) => x.getNextTrack(It.isObjectWith({ path: 'Path 1' }), false)).returns(() => trackModel2); progressUpdaterMock.reset(); audioPlayerMock.reset(); @@ -1607,66 +1590,46 @@ describe('PlaybackService', () => { it('should get the next track without wrap around if loopMode is None', () => { // Arrange - const getNextTrackSpy = jest.spyOn(queue, 'getNextTrack'); - while (service.loopMode !== LoopMode.None) { service.toggleLoopMode(); } - service.enqueueAndPlayTracks([trackModel1, trackModel2]); - // Act service.playNext(); // Assert - expect(getNextTrackSpy).toHaveBeenCalledTimes(1); - expect(getNextTrackSpy).toHaveBeenCalledWith(trackModel1, false); - expect(service.currentTrack).toBe(trackModel2); + queueMock.verify((x) => x.getNextTrack(It.isAny(), false), Times.exactly(1)); }); - it('should get the next track with wrap around if loopMode is All', () => { // Arrange - const getNextTrackSpy = jest.spyOn(queue, 'getNextTrack'); - while (service.loopMode !== LoopMode.All) { service.toggleLoopMode(); } - service.enqueueAndPlayTracks([trackModel1, trackModel2]); - service.playNext(); // Move playback to second track (last track in queue) - getNextTrackSpy.mockClear(); - // Act service.playNext(); // Assert - expect(getNextTrackSpy).toHaveBeenCalledTimes(1); - expect(getNextTrackSpy).toHaveBeenCalledWith(trackModel2, true); - expect(service.currentTrack).toBe(trackModel1); + queueMock.verify((x) => x.getNextTrack(It.isAny(), true), Times.exactly(1)); }); - it('should get the next track with wrap around if loopMode is One', () => { // Arrange - const getNextTrackSpy = jest.spyOn(queue, 'getNextTrack'); - while (service.loopMode !== LoopMode.One) { service.toggleLoopMode(); } - service.enqueueAndPlayTracks([trackModel1, trackModel2]); - // Act service.playNext(); // Assert - expect(getNextTrackSpy).toHaveBeenCalledTimes(1); - expect(getNextTrackSpy).toHaveBeenCalledWith(trackModel1, false); - expect(service.currentTrack).toBe(trackModel2); + queueMock.verify((x) => x.getNextTrack(It.isAny(), false), Times.exactly(1)); }); it('should raise an event that playback has started, containing the current track and if a next track is being played.', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks(trackModels); + queueMock.setup((x) => x.getNextTrack(It.isObjectWith({ path: 'Path 1' }), false)).returns(() => trackModel2); let receivedTrack: TrackModel; let isPlayingPreviousTrack: boolean; subscription.add( @@ -1686,6 +1649,7 @@ describe('PlaybackService', () => { it('should increase play count and date last played for the current track if progress is more than 80%', () => { const trackModelMock: IMock = Mock.ofType(); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModelMock.object); service.enqueueAndPlayTracks([trackModelMock.object]); progressUpdaterProgressChanged.next(new PlaybackProgress(81, 100)); @@ -1698,6 +1662,7 @@ describe('PlaybackService', () => { it('should save play count and date last played for the current track if progress is more than 80%', () => { const trackModelMock: IMock = Mock.ofType(); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModelMock.object); service.enqueueAndPlayTracks([trackModelMock.object]); progressUpdaterProgressChanged.next(new PlaybackProgress(81, 100)); @@ -1710,6 +1675,7 @@ describe('PlaybackService', () => { it('should increase skip count for the current track if progress is less than 80%', () => { const trackModelMock: IMock = Mock.ofType(); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModelMock.object); service.enqueueAndPlayTracks([trackModelMock.object]); progressUpdaterProgressChanged.next(new PlaybackProgress(79, 100)); @@ -1722,6 +1688,7 @@ describe('PlaybackService', () => { it('should save skip count for the current track if progress is less than 80%', () => { const trackModelMock: IMock = Mock.ofType(); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModelMock.object); service.enqueueAndPlayTracks([trackModelMock.object]); progressUpdaterProgressChanged.next(new PlaybackProgress(79, 100)); @@ -1913,7 +1880,21 @@ describe('PlaybackService', () => { describe('playbackQueue', () => { it('should return an empty queue if it has no tracks', () => { // Arrange - service.enqueueAndPlayTracks([]); + queueMock.reset(); + queueMock.setup((x) => x.tracks).returns(() => []); + queueMock.setup((x) => x.tracksInPlaybackOrder).returns(() => []); + service = new PlaybackService( + trackServiceMock.object, + playlistServiceMock.object, + snackBarServiceMock.object, + audioPlayerMock.object, + trackOrderingMock.object, + queueMock.object, + progressUpdaterMock.object, + mathExtensionsMock.object, + settingsStub, + loggerMock.object + ); // Act const queue: TrackModels = service.playbackQueue; @@ -1924,7 +1905,21 @@ describe('PlaybackService', () => { it('should return the queued tracks if the queue has tracks', () => { // Arrange - service.enqueueAndPlayTracks(tracks.tracks); + queueMock.reset(); + queueMock.setup((x) => x.tracks).returns(() => tracks.tracks); + queueMock.setup((x) => x.tracksInPlaybackOrder).returns(() => tracks.tracks); + service = new PlaybackService( + trackServiceMock.object, + playlistServiceMock.object, + snackBarServiceMock.object, + audioPlayerMock.object, + trackOrderingMock.object, + queueMock.object, + progressUpdaterMock.object, + mathExtensionsMock.object, + settingsStub, + loggerMock.object + ); // Act const queue: TrackModels = service.playbackQueue; @@ -1980,6 +1975,7 @@ describe('PlaybackService', () => { describe('togglePlayback', () => { it('should resume playback if paused', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks(trackModels); audioPlayerMock.reset(); service.pause(); @@ -1995,6 +1991,7 @@ describe('PlaybackService', () => { it('should pause playback if playing', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks(trackModels); audioPlayerMock.reset(); @@ -2009,58 +2006,38 @@ describe('PlaybackService', () => { }); describe('removeFromQueue', () => { - let removeTracksSpy: jest.SpyInstance; - beforeEach(() => { - removeTracksSpy = jest.spyOn(queue, 'removeTracks'); - }); - it('should not remove tracks when tracksToRemove is undefined', () => { // Arrange - service.enqueueAndPlayTracks([trackModel1, trackModel2]); // Act service.removeFromQueue(undefined); // Assert - expect(removeTracksSpy).not.toHaveBeenCalled(); - expect(service.playbackQueue.tracks[0]).toBe(trackModel1); - expect(service.playbackQueue.tracks[1]).toBe(trackModel2); + queueMock.verify((x) => x.removeTracks(It.isAny()), Times.never()); }); it('should not remove tracks when tracksToRemove is empty', () => { // Arrange - service.enqueueAndPlayTracks([trackModel1, trackModel2]); // Act service.removeFromQueue([]); // Assert - expect(removeTracksSpy).not.toHaveBeenCalled(); - expect(service.playbackQueue.tracks[0]).toBe(trackModel1); - expect(service.playbackQueue.tracks[1]).toBe(trackModel2); + queueMock.verify((x) => x.removeTracks(It.isAny()), Times.never()); }); it('should remove tracks when tracksToRemove has items', () => { // Arrange - service.enqueueAndPlayTracks([trackModel1, trackModel2]); // Act service.removeFromQueue([trackModel1]); // Assert - expect(removeTracksSpy).toHaveBeenCalledTimes(1); - expect(removeTracksSpy).toHaveBeenCalledWith([trackModel1]); - expect(service.playbackQueue.tracks[0]).toBe(trackModel2); - expect(service.playbackQueue.tracks.length).toBe(1); + queueMock.verify((x) => x.removeTracks([trackModel1]), Times.once()); }); }); describe('addTracksToQueueAsync', () => { - let addTracksSpy: jest.SpyInstance; - beforeEach(() => { - addTracksSpy = jest.spyOn(queue, 'addTracks'); - }); - it('should not add tracks to the queue if tracksToAdd is undefined', async () => { // Arrange @@ -2068,7 +2045,7 @@ describe('PlaybackService', () => { await service.addTracksToQueueAsync(undefined); // Assert - expect(addTracksSpy).not.toHaveBeenCalled(); + queueMock.verify((x) => x.addTracks(It.isAny()), Times.never()); snackBarServiceMock.verify((x) => x.singleTrackAddedToPlaybackQueueAsync(), Times.never()); snackBarServiceMock.verify((x) => x.multipleTracksAddedToPlaybackQueueAsync(It.isAny()), Times.never()); }); @@ -2080,7 +2057,7 @@ describe('PlaybackService', () => { await service.addTracksToQueueAsync([]); // Assert - expect(addTracksSpy).not.toHaveBeenCalled(); + queueMock.verify((x) => x.addTracks(It.isAny()), Times.never()); snackBarServiceMock.verify((x) => x.singleTrackAddedToPlaybackQueueAsync(), Times.never()); snackBarServiceMock.verify((x) => x.multipleTracksAddedToPlaybackQueueAsync(It.isAny()), Times.never()); }); @@ -2092,8 +2069,7 @@ describe('PlaybackService', () => { await service.addTracksToQueueAsync([trackModel1]); // Assert - expect(addTracksSpy).toHaveBeenCalledTimes(1); - expect(addTracksSpy).toHaveBeenCalledWith([trackModel1]); + queueMock.verify((x) => x.addTracks([trackModel1]), Times.once()); snackBarServiceMock.verify((x) => x.singleTrackAddedToPlaybackQueueAsync(), Times.exactly(1)); }); }); @@ -2126,43 +2102,35 @@ describe('PlaybackService', () => { it('should get tracks for the artist if artistToAdd and artistType are not undefined', async () => { // Arrange - const addTracksSpy = jest.spyOn(queue, 'addTracks'); const artistToAdd: ArtistModel = new ArtistModel('artist1', translatorServiceMock.object); // Act await service.addArtistToQueueAsync(artistToAdd, ArtistType.trackArtists); // Assert - expect(addTracksSpy).toHaveBeenCalledTimes(1); - expect(addTracksSpy).toHaveBeenCalledWith(orderedTrackModels); trackServiceMock.verify((x) => x.getTracksForArtists([artistToAdd.displayName], ArtistType.trackArtists), Times.exactly(1)); }); it('should order tracks for the artist byAlbum', async () => { // Arrange - const addTracksSpy = jest.spyOn(queue, 'addTracks'); const artistToAdd: ArtistModel = new ArtistModel('artist1', translatorServiceMock.object); // Act await service.addArtistToQueueAsync(artistToAdd, ArtistType.trackArtists); // Assert - expect(addTracksSpy).toHaveBeenCalledTimes(1); - expect(addTracksSpy).toHaveBeenCalledWith(orderedTrackModels); trackOrderingMock.verify((x) => x.getTracksOrderedByAlbum(tracks.tracks), Times.exactly(1)); }); it('should add tracks to the queue ordered by album', async () => { // Arrange - const addTracksSpy = jest.spyOn(queue, 'addTracks'); const artistToAdd: ArtistModel = new ArtistModel('artist1', translatorServiceMock.object); // Act await service.addArtistToQueueAsync(artistToAdd, ArtistType.trackArtists); // Assert - expect(addTracksSpy).toHaveBeenCalledTimes(1); - expect(addTracksSpy).toHaveBeenCalledWith(orderedTrackModels); + queueMock.verify((x) => x.addTracks(orderedTrackModels), Times.exactly(1)); snackBarServiceMock.verify((x) => x.multipleTracksAddedToPlaybackQueueAsync(4), Times.exactly(1)); }); }); @@ -2204,15 +2172,13 @@ describe('PlaybackService', () => { it('should add tracks to the queue ordered by album', async () => { // Arrange - const addTracksSpy = jest.spyOn(queue, 'addTracks'); const genreToAdd: GenreModel = new GenreModel('genre1', translatorServiceMock.object); // Act await service.addGenreToQueueAsync(genreToAdd); // Assert - expect(addTracksSpy).toHaveBeenCalledTimes(1); - expect(addTracksSpy).toHaveBeenCalledWith(orderedTrackModels); + queueMock.verify((x) => x.addTracks(orderedTrackModels), Times.exactly(1)); snackBarServiceMock.verify((x) => x.multipleTracksAddedToPlaybackQueueAsync(4), Times.exactly(1)); }); }); @@ -2252,14 +2218,12 @@ describe('PlaybackService', () => { it('should add tracks to the queue ordered by album', () => { // Arrange - const addTracksSpy = jest.spyOn(queue, 'addTracks'); // Act service.addAlbumToQueueAsync(album1); // Assert - expect(addTracksSpy).toHaveBeenCalledTimes(1); - expect(addTracksSpy).toHaveBeenCalledWith(orderedTrackModels); + queueMock.verify((x) => x.addTracks(orderedTrackModels), Times.exactly(1)); snackBarServiceMock.verify((x) => x.multipleTracksAddedToPlaybackQueueAsync(4), Times.exactly(1)); }); }); @@ -2281,18 +2245,18 @@ describe('PlaybackService', () => { it('should not play the next track if there is no track playing', () => { // Arrange - const getNextTrackSpy = jest.spyOn(queue, 'getNextTrack'); // Act service.stopIfPlaying(trackModel2); // Assert - expect(getNextTrackSpy).not.toHaveBeenCalled(); + queueMock.verify((x) => x.getNextTrack(It.isAny(), It.isAny()), Times.never()); audioPlayerMock.verify((x) => x.play(It.isAny()), Times.never()); }); it('should not stop playback if the given track is not playing', () => { // Arrange + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks(trackModels); audioPlayerMock.reset(); @@ -2305,7 +2269,7 @@ describe('PlaybackService', () => { it('should not play the next track if the given track is not playing', () => { // Arrange - const getNextTrackSpy = jest.spyOn(queue, 'getNextTrack'); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks(trackModels); audioPlayerMock.reset(); @@ -2313,35 +2277,38 @@ describe('PlaybackService', () => { service.stopIfPlaying(trackModel2); // Assert - expect(getNextTrackSpy).not.toHaveBeenCalled(); + queueMock.verify((x) => x.getNextTrack(It.isAny(), It.isAny()), Times.never()); audioPlayerMock.verify((x) => x.play(It.isAny()), Times.never()); }); it('should stop playback if the given track is playing and it is the only track in the queue', () => { // Arrange - const getNextTrackSpy = jest.spyOn(queue, 'getNextTrack'); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks([trackModel1]); audioPlayerMock.reset(); + queueMock.setup((x) => x.numberOfTracks).returns(() => 1); // Act service.stopIfPlaying(trackModel1); // Assert - expect(getNextTrackSpy).not.toHaveBeenCalled(); + queueMock.verify((x) => x.getNextTrack(It.isAny(), It.isAny()), Times.never()); audioPlayerMock.verify((x) => x.stop(), Times.once()); }); it('should play the next track if the given track is playing and it not the only track in the queue', async () => { // Arrange - const getNextTrackSpy = jest.spyOn(queue, 'getNextTrack'); + queueMock.setup((x) => x.getFirstTrack()).returns(() => trackModel1); service.enqueueAndPlayTracks(trackModels); audioPlayerMock.reset(); + queueMock.setup((x) => x.numberOfTracks).returns(() => 4); + queueMock.setup((x) => x.getNextTrack(It.isObjectWith({ path: 'Path 1' }), It.isAny())).returns(() => trackModel2); + // Act await service.stopIfPlaying(trackModel1); // Assert - expect(getNextTrackSpy).toHaveBeenCalledTimes(1); - expect(getNextTrackSpy).toHaveBeenCalledWith(trackModel1, false); + queueMock.verify((x) => x.getNextTrack(trackModel1, It.isAny()), Times.once()); audioPlayerMock.verify((x) => x.play(trackModel2.path), Times.once()); }); }); diff --git a/src/app/services/playback/playback.service.ts b/src/app/services/playback/playback.service.ts index e1233160c..f7bfab26d 100644 --- a/src/app/services/playback/playback.service.ts +++ b/src/app/services/playback/playback.service.ts @@ -59,9 +59,8 @@ export class PlaybackService implements BasePlaybackService { const trackModels: TrackModels = new TrackModels(); if (this.queue.tracks != undefined) { - // add tracks to playback queue in playback order so that they will be loaded in the playback order into the view - // so that the user can see what is coming next in the queue - for (const track of this.queue.getTracksInPlaybackOrder()) { + // Add tracks to playback queue in playback order so that the user can see what is coming next in the queue + for (const track of this.queue.tracksInPlaybackOrder) { trackModels.addTrack(track); } } @@ -123,11 +122,12 @@ export class PlaybackService implements BasePlaybackService { } this.queue.setTracks(tracksToEnqueue, this.isShuffled); - // play first track in queue (will be a random track if queue is shuffled) - this.play(this.playbackQueue.tracks[0], false); + + // Play first track in queue (will be a random track if queue is shuffled) + this.play(this.queue.getFirstTrack(), false); } - public enqueueAndPlayTracksFromDoubleClick(tracksToEnqueue: TrackModel[], trackToPlay: TrackModel): void { + public enqueueAndPlayTracksStartingFromGivenTrack(tracksToEnqueue: TrackModel[], trackToPlay: TrackModel): void { if (tracksToEnqueue == undefined) { return; } @@ -474,15 +474,7 @@ export class PlaybackService implements BasePlaybackService { private applyVolumeFromSettings(): void { this._volume = this.settings.volume; - - try { - this.audioPlayer.setVolume(this._volume); - } catch (error) { - this.logger.warn('Could not apply volume from settings. Resetting volume to 0.', 'PlaybackService', 'applyVolumeFromSettings'); - this.settings.volume = 0; - this._volume = 0; - this.audioPlayer.setVolume(this._volume); - } + this.audioPlayer.setVolume(this._volume); } private async notifyOfTracksAddedToPlaybackQueueAsync(numberOfAddedTracks: number): Promise { diff --git a/src/app/services/playback/queue.spec.ts b/src/app/services/playback/queue.spec.ts index 46918748e..1c1ec24aa 100644 --- a/src/app/services/playback/queue.spec.ts +++ b/src/app/services/playback/queue.spec.ts @@ -8,7 +8,6 @@ import { BaseTranslatorService } from '../translator/base-translator.service'; import { Queue } from './queue'; describe('Queue', () => { - let queue: Queue; let shufflerMock: IMock; let loggerMock: IMock; let dateTimeMock: IMock; @@ -19,13 +18,12 @@ describe('Queue', () => { dateTimeMock = Mock.ofType(); translatorServiceMock = Mock.ofType(); loggerMock = Mock.ofType(); - - shufflerMock.setup((x) => x.shuffle([0, 1, 2, 3, 4])).returns(() => [3, 2, 4, 0, 1]); - shufflerMock.setup((x) => x.shuffle([0, 1])).returns(() => [1, 0]); - - queue = new Queue(shufflerMock.object, loggerMock.object); }); + function createQueue(): Queue { + return new Queue(shufflerMock.object, loggerMock.object); + } + function createTrackModel(path: string): TrackModel { return new TrackModel(new Track(path), dateTimeMock.object, translatorServiceMock.object); } @@ -35,6 +33,7 @@ describe('Queue', () => { // Arrange // Act + const queue: Queue = createQueue(); // Assert expect(queue).toBeDefined(); @@ -44,6 +43,7 @@ describe('Queue', () => { // Arrange // Act + const queue: Queue = createQueue(); // Assert expect(queue.tracks.length).toEqual(0); @@ -56,6 +56,8 @@ describe('Queue', () => { const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2, track3], false); // Act @@ -73,6 +75,8 @@ describe('Queue', () => { const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + const queue: Queue = createQueue(); + // Act queue.setTracks([track1, track2, track3], false); @@ -89,6 +93,8 @@ describe('Queue', () => { const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + const queue: Queue = createQueue(); + // Act queue.setTracks([track1, track2, track3], true); @@ -105,6 +111,8 @@ describe('Queue', () => { // Arrange const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const queue: Queue = createQueue(); + // Act // Assert @@ -115,6 +123,8 @@ describe('Queue', () => { // Arrange const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2], false); // Act @@ -129,7 +139,8 @@ describe('Queue', () => { const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); shufflerMock.setup((x) => x.shuffle([0, 1])).returns(() => [1, 0]); - queue = new Queue(shufflerMock.object, loggerMock.object); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2], true); // Act @@ -156,6 +167,8 @@ describe('Queue', () => { dateTimeMock.object, translatorServiceMock.object ); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2], false); // Act @@ -171,7 +184,8 @@ describe('Queue', () => { const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); shufflerMock.setup((x) => x.shuffle([0, 1])).returns(() => [1, 0]); - queue = new Queue(shufflerMock.object, loggerMock.object); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2], true); // Act @@ -186,6 +200,8 @@ describe('Queue', () => { const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2, track3], false); // Act @@ -201,6 +217,8 @@ describe('Queue', () => { const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); shufflerMock.setup((x) => x.shuffle([0, 1, 2])).returns(() => [1, 2, 0]); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2, track3], true); // Act @@ -215,6 +233,8 @@ describe('Queue', () => { const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2, track3], false); // Act @@ -230,6 +250,8 @@ describe('Queue', () => { const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); shufflerMock.setup((x) => x.shuffle([0, 1, 2])).returns(() => [1, 2, 0]); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2, track3], true); // Act @@ -244,6 +266,8 @@ describe('Queue', () => { const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2, track3], false); // Act @@ -259,6 +283,8 @@ describe('Queue', () => { const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); shufflerMock.setup((x) => x.shuffle([0, 1, 2])).returns(() => [1, 2, 0]); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2, track3], false); // Act @@ -269,11 +295,61 @@ describe('Queue', () => { }); }); + describe('getFirstTrack', () => { + it('should return undefined if there are no tracks', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + + const queue: Queue = createQueue(); + + // Act + + // Assert + expect(queue.getNextTrack(track1, false)).toBeUndefined(); + }); + + it('should return the first track when the queue is not shuffled', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + const queue: Queue = createQueue(); + queue.setTracks([track1, track2], false); + + // Act + const firstTrack: TrackModel = queue.getFirstTrack(); + + // Assert + expect(firstTrack).toBe(track1); + }); + + it('should return the first track when the queue is shuffled', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + shufflerMock.setup((x) => x.shuffle([0, 1])).returns(() => [1, 0]); + + const queue: Queue = createQueue(); + queue.setTracks([track1, track2], true); + + // Act + const firstTrack: TrackModel = queue.getFirstTrack(); + + // Assert + expect(firstTrack).toBe(track2); + }); + }); + describe('getNextTrack', () => { it('should return undefined if there are no tracks', () => { // Arrange const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const queue: Queue = createQueue(); + // Act // Assert @@ -285,6 +361,8 @@ describe('Queue', () => { const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2], false); // Act @@ -299,6 +377,8 @@ describe('Queue', () => { const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); shufflerMock.setup((x) => x.shuffle([0, 1])).returns(() => [1, 0]); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2], true); // Act @@ -313,6 +393,8 @@ describe('Queue', () => { const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2], false); // Act @@ -328,6 +410,8 @@ describe('Queue', () => { const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); shufflerMock.setup((x) => x.shuffle([0, 1])).returns(() => [1, 0]); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2], true); // Act @@ -342,6 +426,8 @@ describe('Queue', () => { const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2, track3], false); // Act @@ -357,6 +443,8 @@ describe('Queue', () => { const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); shufflerMock.setup((x) => x.shuffle([0, 1, 2])).returns(() => [1, 2, 0]); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2, track3], true); // Act @@ -371,6 +459,8 @@ describe('Queue', () => { const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2, track3], false); // Act @@ -386,6 +476,8 @@ describe('Queue', () => { const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); shufflerMock.setup((x) => x.shuffle([0, 1, 2])).returns(() => [1, 2, 0]); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2, track3], true); // Act @@ -400,6 +492,8 @@ describe('Queue', () => { const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2, track3], false); // Act @@ -415,6 +509,8 @@ describe('Queue', () => { const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); shufflerMock.setup((x) => x.shuffle([0, 1, 2])).returns(() => [1, 2, 0]); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2, track3], true); // Act @@ -431,6 +527,8 @@ describe('Queue', () => { const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2, track3], false); // Act @@ -447,6 +545,8 @@ describe('Queue', () => { const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); shufflerMock.setup((x) => x.shuffle([0, 1, 2])).returns(() => [2, 1, 0]); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2, track3], true); // Act @@ -464,6 +564,8 @@ describe('Queue', () => { const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); shufflerMock.setup((x) => x.shuffle([0, 1, 2])).returns(() => [2, 1, 0]); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2, track3], false); // Act @@ -480,6 +582,8 @@ describe('Queue', () => { const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); shufflerMock.setup((x) => x.shuffle([0, 1, 2])).returns(() => [2, 1, 0]); + + const queue: Queue = createQueue(); queue.setTracks([track1, track2, track3], true); // Act @@ -488,259 +592,374 @@ describe('Queue', () => { // Assert expect(nextTrack).toBe(track1); }); + }); + + describe('removeTracks', () => { + it('should not remove tracks from queue if tracksToRemove is undefined', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + const queue: Queue = createQueue(); + queue.setTracks([track1, track2, track3], false); + + // Act + queue.removeTracks(undefined); + + // Assert + expect(queue.tracks.length).toEqual(3); + expect(queue.tracks[0]).toBe(track1); + expect(queue.tracks[1]).toBe(track2); + expect(queue.tracks[2]).toBe(track3); + }); + + it('should not remove tracks from queue if tracksToRemove is empty', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + const queue: Queue = createQueue(); + queue.setTracks([track1, track2, track3], false); + + // Act + queue.removeTracks([]); + + // Assert + expect(queue.tracks.length).toEqual(3); + expect(queue.tracks[0]).toBe(track1); + expect(queue.tracks[1]).toBe(track2); + expect(queue.tracks[2]).toBe(track3); + }); + + it('should remove tracks from queue if tracksToRemove has items', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + const queue: Queue = createQueue(); + queue.setTracks([track1, track2, track3], false); + + // Act + queue.removeTracks([track2]); + + // Assert + expect(queue.tracks.length).toEqual(2); + expect(queue.tracks[0]).toBe(track1); + expect(queue.tracks[1]).toBe(track3); + }); + + it('should not remove tracks from unshuffled playback order if tracksToRemove is undefined', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); + const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); + + const queue: Queue = createQueue(); + queue.setTracks([track1, track2, track3, track4, track5], false); + + // Act + queue.removeTracks(undefined); + + // Assert + expect(queue.getNextTrack(track1, false)).toEqual(track2); + expect(queue.getNextTrack(track2, false)).toEqual(track3); + expect(queue.getNextTrack(track3, false)).toEqual(track4); + expect(queue.getNextTrack(track4, false)).toEqual(track5); + }); + + it('should not remove tracks from unshuffled playback order if tracksToRemove is empty', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); + const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); + const queue: Queue = createQueue(); + + queue.setTracks([track1, track2, track3, track4, track5], false); + + // Act + queue.removeTracks([]); + + // Assert + expect(queue.getNextTrack(track1, false)).toEqual(track2); + expect(queue.getNextTrack(track2, false)).toEqual(track3); + expect(queue.getNextTrack(track3, false)).toEqual(track4); + expect(queue.getNextTrack(track4, false)).toEqual(track5); + }); + + it('should remove tracks from unshuffled playback order if tracksToRemove has items', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); + const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); + + const queue: Queue = createQueue(); + queue.setTracks([track1, track2, track3, track4, track5], false); + + // Act + queue.removeTracks([track2, track4]); - describe('removeTracks', () => { - it('should not remove tracks from queue if tracksToRemove is undefined', () => { - // Arrange - const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); - const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); - const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); - - queue.setTracks([track1, track2, track3], false); - - // Act - queue.removeTracks(undefined); - - // Assert - expect(queue.tracks.length).toEqual(3); - expect(queue.tracks[0]).toBe(track1); - expect(queue.tracks[1]).toBe(track2); - expect(queue.tracks[2]).toBe(track3); - }); - - it('should not remove tracks from queue if tracksToRemove is empty', () => { - // Arrange - const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); - const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); - const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); - queue.setTracks([track1, track2, track3], false); - - // Act - queue.removeTracks([]); - - // Assert - expect(queue.tracks.length).toEqual(3); - expect(queue.tracks[0]).toBe(track1); - expect(queue.tracks[1]).toBe(track2); - expect(queue.tracks[2]).toBe(track3); - }); - - it('should remove tracks from queue if tracksToRemove has items', () => { - // Arrange - const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); - const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); - const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); - queue.setTracks([track1, track2, track3], false); - - // Act - queue.removeTracks([track2]); - - // Assert - expect(queue.tracks.length).toEqual(2); - expect(queue.tracks[0]).toBe(track1); - expect(queue.tracks[1]).toBe(track3); - }); - - it('should not remove tracks from unshuffled playback order if tracksToRemove is undefined', () => { - // Arrange - const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); - const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); - const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); - const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); - const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); - queue.setTracks([track1, track2, track3, track4, track5], false); - - // Act - queue.removeTracks(undefined); - - // Assert - expect(queue.getNextTrack(track1, false)).toEqual(track2); - expect(queue.getNextTrack(track2, false)).toEqual(track3); - expect(queue.getNextTrack(track3, false)).toEqual(track4); - expect(queue.getNextTrack(track4, false)).toEqual(track5); - }); - - it('should not remove tracks from unshuffled playback order if tracksToRemove is empty', () => { - // Arrange - const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); - const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); - const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); - const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); - const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); - queue.setTracks([track1, track2, track3, track4, track5], false); - - // Act - queue.removeTracks([]); - - // Assert - expect(queue.getNextTrack(track1, false)).toEqual(track2); - expect(queue.getNextTrack(track2, false)).toEqual(track3); - expect(queue.getNextTrack(track3, false)).toEqual(track4); - expect(queue.getNextTrack(track4, false)).toEqual(track5); - }); - - it('should remove tracks from unshuffled playback order if tracksToRemove has items', () => { - // Arrange - const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); - const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); - const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); - const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); - const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); - queue.setTracks([track1, track2, track3, track4, track5], false); - - // Act - queue.removeTracks([track2, track4]); - - // Assert - expect(queue.tracks.length).toEqual(3); - expect(queue.getNextTrack(track1, false)).toEqual(track3); - expect(queue.getNextTrack(track3, false)).toEqual(track5); - }); - - it('should not remove tracks from shuffled playback order if tracksToRemove is undefined', () => { - // Arrange - const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); - const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); - const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); - const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); - const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); - queue.setTracks([track1, track2, track3, track4, track5], true); - - // Act - queue.removeTracks(undefined); - - // Assert - expect(queue.getNextTrack(track1, false)).toEqual(track2); - expect(queue.getNextTrack(track2, false)).toEqual(undefined); - expect(queue.getNextTrack(track3, false)).toEqual(track5); - expect(queue.getNextTrack(track4, false)).toEqual(track3); - expect(queue.getNextTrack(track5, false)).toEqual(track1); - }); - - it('should not remove tracks from shuffled playback order if tracksToRemove is empty', () => { - // Arrange - const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); - const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); - const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); - const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); - const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); - queue.setTracks([track1, track2, track3, track4, track5], true); - - // Act - queue.removeTracks([]); - - // Assert - expect(queue.getNextTrack(track1, false)).toEqual(track2); - expect(queue.getNextTrack(track2, false)).toEqual(undefined); - expect(queue.getNextTrack(track3, false)).toEqual(track5); - expect(queue.getNextTrack(track4, false)).toEqual(track3); - expect(queue.getNextTrack(track5, false)).toEqual(track1); - }); - - it('should remove tracks from shuffled playback order if tracksToRemove has items', () => { - // Arrange - const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); - const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); - const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); - const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); - const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); - queue.setTracks([track1, track2, track3, track4, track5], true); - - // Act - queue.removeTracks([track2, track4]); - - // Assert - expect(queue.tracks.length).toEqual(3); - expect(queue.getNextTrack(track1, false)).toEqual(undefined); - expect(queue.getNextTrack(track3, false)).toEqual(track5); - expect(queue.getNextTrack(track5, false)).toEqual(track1); - }); - }); - - describe('addTracks', () => { - it('should add tracks to the end of queue when not shuffled', () => { - // Arrange - const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); - const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); - const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); - const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); - const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); - queue.setTracks([track1, track2], false); - - // Act - queue.addTracks([track3, track4, track5]); - - // Assert - expect(queue.tracks.length).toEqual(5); - expect(queue.tracks[0]).toEqual(track1); - expect(queue.tracks[1]).toEqual(track2); - expect(queue.tracks[2]).toEqual(track3); - expect(queue.tracks[3]).toEqual(track4); - expect(queue.tracks[4]).toEqual(track5); - }); - - it('should add tracks to the end of queue when shuffled', () => { - // Arrange - const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); - const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); - const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); - const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); - const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); - queue.setTracks([track1, track2], true); - - // Act - queue.addTracks([track3, track4, track5]); - - // Assert - expect(queue.tracks.length).toEqual(5); - expect(queue.tracks[0]).toEqual(track1); - expect(queue.tracks[1]).toEqual(track2); - expect(queue.tracks[2]).toEqual(track3); - expect(queue.tracks[3]).toEqual(track4); - expect(queue.tracks[4]).toEqual(track5); - }); - - it('should add tracks to the end of unshuffled playback order', () => { - // Arrange - const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); - const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); - const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); - const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); - const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); - queue.setTracks([track1, track2], false); - - // Act - queue.addTracks([track3, track4, track5]); - - // Assert - expect(queue.tracks.length).toEqual(5); - expect(queue.getNextTrack(track1, false)).toEqual(track2); - expect(queue.getNextTrack(track2, false)).toEqual(track3); - expect(queue.getNextTrack(track3, false)).toEqual(track4); - expect(queue.getNextTrack(track4, false)).toEqual(track5); - expect(queue.getNextTrack(track5, false)).toEqual(undefined); - }); - - it('should add tracks to the end of shuffled playback order', () => { - // Arrange - const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); - const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); - const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); - const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); - const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); - queue.setTracks([track1, track2], true); - - // Act - queue.addTracks([track3, track4, track5]); - - // Assert - expect(queue.tracks.length).toEqual(5); - expect(queue.getNextTrack(track1, false)).toEqual(track3); - expect(queue.getNextTrack(track2, false)).toEqual(track1); - expect(queue.getNextTrack(track3, false)).toEqual(track4); - expect(queue.getNextTrack(track4, false)).toEqual(track5); - expect(queue.getNextTrack(track5, false)).toEqual(undefined); - }); + // Assert + expect(queue.tracks.length).toEqual(3); + expect(queue.getNextTrack(track1, false)).toEqual(track3); + expect(queue.getNextTrack(track3, false)).toEqual(track5); + }); + + it('should not remove tracks from shuffled playback order if tracksToRemove is undefined', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); + const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); + + shufflerMock.setup((x) => x.shuffle([0, 1, 2, 3, 4])).returns(() => [3, 2, 4, 0, 1]); + + const queue: Queue = createQueue(); + queue.setTracks([track1, track2, track3, track4, track5], true); + + // Act + queue.removeTracks(undefined); + + // Assert + expect(queue.getNextTrack(track1, false)).toEqual(track2); + expect(queue.getNextTrack(track2, false)).toEqual(undefined); + expect(queue.getNextTrack(track3, false)).toEqual(track5); + expect(queue.getNextTrack(track4, false)).toEqual(track3); + expect(queue.getNextTrack(track5, false)).toEqual(track1); + }); + + it('should not remove tracks from shuffled playback order if tracksToRemove is empty', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); + const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); + + shufflerMock.setup((x) => x.shuffle([0, 1, 2, 3, 4])).returns(() => [3, 2, 4, 0, 1]); + + const queue: Queue = createQueue(); + queue.setTracks([track1, track2, track3, track4, track5], true); + + // Act + queue.removeTracks([]); + + // Assert + expect(queue.getNextTrack(track1, false)).toEqual(track2); + expect(queue.getNextTrack(track2, false)).toEqual(undefined); + expect(queue.getNextTrack(track3, false)).toEqual(track5); + expect(queue.getNextTrack(track4, false)).toEqual(track3); + expect(queue.getNextTrack(track5, false)).toEqual(track1); + }); + + it('should remove tracks from shuffled playback order if tracksToRemove has items', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); + const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); + + shufflerMock.setup((x) => x.shuffle([0, 1, 2, 3, 4])).returns(() => [3, 2, 4, 0, 1]); + + const queue: Queue = createQueue(); + queue.setTracks([track1, track2, track3, track4, track5], true); + + // Act + queue.removeTracks([track2, track4]); + + // Assert + expect(queue.tracks.length).toEqual(3); + expect(queue.getNextTrack(track1, false)).toEqual(undefined); + expect(queue.getNextTrack(track3, false)).toEqual(track5); + expect(queue.getNextTrack(track5, false)).toEqual(track1); + }); + }); + + describe('addTracks', () => { + it('should add tracks to the end of queue when not shuffled', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); + const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); + + const queue: Queue = createQueue(); + queue.setTracks([track1, track2], false); + + // Act + queue.addTracks([track3, track4, track5]); + + // Assert + expect(queue.tracks.length).toEqual(5); + expect(queue.tracks[0]).toEqual(track1); + expect(queue.tracks[1]).toEqual(track2); + expect(queue.tracks[2]).toEqual(track3); + expect(queue.tracks[3]).toEqual(track4); + expect(queue.tracks[4]).toEqual(track5); + }); + + it('should add tracks to the end of queue when shuffled', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); + const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); + + shufflerMock.setup((x) => x.shuffle([0, 1])).returns(() => [1, 0]); + + const queue: Queue = createQueue(); + queue.setTracks([track1, track2], true); + + // Act + queue.addTracks([track3, track4, track5]); + + // Assert + expect(queue.tracks.length).toEqual(5); + expect(queue.tracks[0]).toEqual(track1); + expect(queue.tracks[1]).toEqual(track2); + expect(queue.tracks[2]).toEqual(track3); + expect(queue.tracks[3]).toEqual(track4); + expect(queue.tracks[4]).toEqual(track5); + }); + + it('should add tracks to the end of unshuffled playback order', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); + const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); + + const queue: Queue = createQueue(); + queue.setTracks([track1, track2], false); + + // Act + queue.addTracks([track3, track4, track5]); + + // Assert + expect(queue.tracks.length).toEqual(5); + expect(queue.getNextTrack(track1, false)).toEqual(track2); + expect(queue.getNextTrack(track2, false)).toEqual(track3); + expect(queue.getNextTrack(track3, false)).toEqual(track4); + expect(queue.getNextTrack(track4, false)).toEqual(track5); + expect(queue.getNextTrack(track5, false)).toEqual(undefined); + }); + + it('should add tracks to the end of shuffled playback order', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + const track4: TrackModel = createTrackModel('/home/user/Music/Track4.mp3'); + const track5: TrackModel = createTrackModel('/home/user/Music/Track5.mp3'); + + shufflerMock.setup((x) => x.shuffle([0, 1])).returns(() => [1, 0]); + + const queue: Queue = createQueue(); + queue.setTracks([track1, track2], true); + + // Act + queue.addTracks([track3, track4, track5]); + + // Assert + expect(queue.tracks.length).toEqual(5); + expect(queue.getNextTrack(track1, false)).toEqual(track3); + expect(queue.getNextTrack(track2, false)).toEqual(track1); + expect(queue.getNextTrack(track3, false)).toEqual(track4); + expect(queue.getNextTrack(track4, false)).toEqual(track5); + expect(queue.getNextTrack(track5, false)).toEqual(undefined); + }); + }); + + describe('tracks', () => { + it('should return the tracks in their original order when not shuffled', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + const queue: Queue = createQueue(); + queue.setTracks([track1, track2, track3], false); + + // Act + const tracks: TrackModel[] = queue.tracks; + + // Assert + expect(tracks[0].path).toBe(track1.path); + expect(tracks[1].path).toBe(track2.path); + expect(tracks[2].path).toBe(track3.path); + }); + + it('should return the tracks in original order when shuffled', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + shufflerMock.setup((x) => x.shuffle([0, 1, 2])).returns(() => [1, 2, 0]); + + const queue: Queue = createQueue(); + queue.setTracks([track1, track2, track3], true); + + // Act + const tracks: TrackModel[] = queue.tracks; + + // Assert + expect(tracks[0].path).toBe(track1.path); + expect(tracks[1].path).toBe(track2.path); + expect(tracks[2].path).toBe(track3.path); + }); + }); + + describe('tracksInPlaybackOrder', () => { + it('should return the tracks in original order when not shuffled', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + const queue: Queue = createQueue(); + queue.setTracks([track1, track2, track3], false); + + // Act + const tracks: TrackModel[] = queue.tracksInPlaybackOrder; + + // Assert + expect(tracks[0].path).toBe(track1.path); + expect(tracks[1].path).toBe(track2.path); + expect(tracks[2].path).toBe(track3.path); + }); + + it('should return the tracks in playback order when shuffled', () => { + // Arrange + const track1: TrackModel = createTrackModel('/home/user/Music/Track1.mp3'); + const track2: TrackModel = createTrackModel('/home/user/Music/Track2.mp3'); + const track3: TrackModel = createTrackModel('/home/user/Music/Track3.mp3'); + + shufflerMock.setup((x) => x.shuffle([0, 1, 2])).returns(() => [1, 2, 0]); + + const queue: Queue = createQueue(); + queue.setTracks([track1, track2, track3], true); + + // Act + const tracks: TrackModel[] = queue.tracksInPlaybackOrder; + + // Assert + expect(tracks[0].path).toBe(track2.path); + expect(tracks[1].path).toBe(track3.path); + expect(tracks[2].path).toBe(track1.path); }); }); }); diff --git a/src/app/services/playback/queue.ts b/src/app/services/playback/queue.ts index 64fd344d8..0ecb98265 100644 --- a/src/app/services/playback/queue.ts +++ b/src/app/services/playback/queue.ts @@ -14,18 +14,20 @@ export class Queue { return this._tracks; } - public get numberOfTracks(): number { - return this._tracks.length; - } - - public getTracksInPlaybackOrder(): TrackModel[] { + public get tracksInPlaybackOrder(): TrackModel[] { let tracksInPlaybackOrder: TrackModel[] = []; + for (const trackIndex of this.playbackOrder) { tracksInPlaybackOrder.push(this._tracks[trackIndex]); } + return tracksInPlaybackOrder; } + public get numberOfTracks(): number { + return this._tracks.length; + } + public setTracks(tracksToSet: TrackModel[], shuffle: boolean): void { this._tracks = tracksToSet; @@ -75,6 +77,18 @@ export class Queue { this.populatePlayBackOrder(); } + public getFirstTrack(): TrackModel { + if (this.playbackOrder == undefined) { + return undefined; + } + + if (this.playbackOrder.length === 0) { + return undefined; + } + + return this._tracks[this.playbackOrder[0]]; + } + public getPreviousTrack(currentTrack: TrackModel, allowWrapAround: boolean): TrackModel { if (this._tracks.length === 0) { return undefined;