diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..f937a06 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,36 @@ +# This is a basic workflow to help you get started with Actions + +name: CI + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events but only for the master branch + push: + branches: [ master ] + pull_request: + branches: [ master ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + # Runs a single command using the runners shell + - name: Run a one-line script + run: echo Hello, world! + + # Runs a set of commands using the runners shell + - name: Run a multi-line script + run: | + echo Add other actions to build, + echo test, and deploy your project. diff --git a/__tests__/main.test.js b/__tests__/main.test.js index bf3746e..0456aae 100644 --- a/__tests__/main.test.js +++ b/__tests__/main.test.js @@ -1,5 +1,4 @@ const deepClone = require('just-clone') - const { player, playSong, @@ -13,7 +12,6 @@ const { searchByDuration, searchByQuery, } = require('../index') - const mockSong1 = { id: 5, title: 'As a Stone', @@ -48,12 +46,10 @@ const mockPlaylist1 = { songs: [mockSong1.id, mockSong2.id], } const mockPlaylist2 = { id: 3, name: 'Metal', songs: [mockSong1.id] } - const mockPlayer = { songs: [mockSong1, mockSong2, mockSong4], playlists: [mockPlaylist1], } - const mockSong3Details = [ mockSong3.title, mockSong3.album, @@ -62,17 +58,14 @@ const mockSong3Details = [ mockSong3.id, ] const mockPlaylist1Duration = mockSong1.duration + mockSong2.duration - const mockNonExistentSongId = 2 const mockNonExistentPlaylistId = 1 - describe('Player Tests', () => { beforeEach(() => { player.songs = deepClone(mockPlayer.songs) player.playlists = deepClone(mockPlayer.playlists) jest.clearAllMocks() }) - it('playSong should console.log in the correct format', () => { const spy = jest.spyOn(console, 'log') playSong(mockSong1.id) @@ -80,7 +73,6 @@ describe('Player Tests', () => { `Playing As a Stone from Show Us What You Got by Full Trunk | 04:19.` ) }) - it('playSong should throw for non-existent ID', () => { expect(() => playSong(mockNonExistentSongId)).toThrow() }) @@ -89,40 +81,32 @@ describe('Player Tests', () => { removeSong(mockSong1.id) expect(player.songs).toEqual([mockSong2, mockSong4]) }) - it('removeSong should remove the song from all playlists', () => { removeSong(mockSong1.id) expect(player.playlists[0].songs).toEqual([mockSong2.id]) }) - it('removeSong should throw for non-existent ID', () => { expect(() => removeSong(mockNonExistentSongId)).toThrow() }) - it('addSong should add a new song to the player', () => { addSong(...mockSong3Details) expect(player.songs).toEqual([...mockPlayer.songs, mockSong3]) }) - it('addSong should generate a new unique ID when it is not supplied', () => { const newSongId = addSong(...mockSong3Details.slice(0, -1)) expect(newSongId).toBeDefined() expect(mockPlayer.songs.map(song => song.id).includes(newSongId)).toBe(false) }) - it('addSong should throw for an ID that is taken', () => { expect(() => addSong(...mockSong3Details.slice(0, -1), mockSong1.id)).toThrow() }) - it('removePlaylist should remove a playlist from the player', () => { removePlaylist(mockPlaylist1.id) expect(player.playlists.length).toBe(0) }) - it('removePlaylist should throw for non-existent ID', () => { expect(() => removePlaylist(mockNonExistentPlaylistId)).toThrow() }) - it('createPlaylist should add a new playlist to the player', () => { createPlaylist(mockPlaylist2.name, mockPlaylist2.id) expect(player.playlists).toEqual([ @@ -130,27 +114,22 @@ describe('Player Tests', () => { { ...mockPlaylist2, songs: [] }, ]) }) - it('createPlaylist should generate a new unique ID when it is not supplied', () => { const newPlaylistId = createPlaylist(mockPlaylist2.name) expect(newPlaylistId).toBeDefined() expect(mockPlayer.playlists.map(p => p.id).includes(newPlaylistId)).toBe(false) }) - it('createPlaylist should throw for an ID that is taken', () => { expect(() => createPlaylist(mockPlaylist2.name, mockPlaylist1.id)).toThrow() }) - it('playPlaylist should play all songs inside it', () => { const spy = jest.spyOn(console, 'log') playPlaylist(mockPlaylist1.id) expect(spy).toHaveBeenCalledTimes(mockPlaylist1.songs.length) }) - it('playPlaylist should throw for non-existent ID', () => { expect(() => playPlaylist(mockNonExistentPlaylistId)).toThrow() }) - it('editPlaylist should add a song to a playlist when it is not initially there', () => { editPlaylist(mockPlaylist1.id, mockSong4.id) expect(player.playlists[0].songs).toEqual([ @@ -158,56 +137,46 @@ describe('Player Tests', () => { mockSong4.id, ]) }) - it('editPlaylist should remove a song from a playlist when it was initially there', () => { editPlaylist(mockPlaylist1.id, mockSong1.id) expect(player.playlists[0].songs).toEqual([mockSong2.id]) }) - it('editPlaylist should throw for non-existent playlist ID', () => { expect(() => editPlaylist(mockPlaylist2.id, mockSong1.id)).toThrow() }) - it('editPlaylist should throw for non-existent song ID', () => { expect(() => editPlaylist(mockPlaylist1.id, mockSong3.id)).toThrow() }) - it('editPlaylist should remove a playlist if it has been emptied of songs', () => { mockPlaylist1.songs.forEach((song) => editPlaylist(mockPlaylist1.id, song)) expect(player.playlists.length).toBe(0) }) - it('playlistDuration should return the sum of durations of all songs inside it', () => { expect(playlistDuration(mockPlaylist1.id)).toBe(mockPlaylist1Duration) }) - it('searchByQuery should be case-insensitive', () => { expect(searchByQuery('t')).toEqual({ songs: [mockSong1, mockSong4], playlists: [], }) }) - it('searchByQuery should consider all song attributes, and sort results alphanumerically', () => { expect(searchByQuery('ll')).toEqual({ songs: [mockSong2, mockSong1], playlists: [], }) }) - it('searchByQuery should find matching playlists', () => { expect(searchByQuery('Israeli')).toEqual({ songs: [], playlists: [mockPlaylist1], }) }) - it('searchByDuration should find the closest song', () => { expect(searchByDuration('04:23')).toEqual(mockSong1) expect(searchByDuration('04:27')).toEqual(mockSong2) }) - it('searchByDuration should find the closest playlist', () => { expect(searchByDuration('10:00')).toEqual(mockPlaylist1) }) -}) +}) \ No newline at end of file diff --git a/index.js b/index.js index 10f4784..6c32faf 100644 --- a/index.js +++ b/index.js @@ -48,60 +48,218 @@ const player = { { id: 5, name: 'Israeli', songs: [4, 5] }, ], playSong(song) { - console.log(/* your code here */) - }, + console.log(`Playing ${song.title} from ${song.album} by ${(song.artist)} | ${mmssDuration(song.duration)}.`); + } } +const mmssDuration=(duration)=> { + let minutes = Math.floor(duration / 60); + let seconds = duration % 60; + if (minutes < 10) { + minutes = "0" + minutes; + } + if (seconds < 10) { + seconds = "0" + seconds; + } + return minutes.toString() + ':' + seconds.toString(); + } -function playSong(id) { - // your code here -} - -function removeSong(id) { - // your code here -} - -function addSong(title, album, artist, duration, id) { - // your code here -} - -function removePlaylist(id) { - // your code here -} - -function createPlaylist(name, id) { - // your code here -} - -function playPlaylist(id) { - // your code here -} + const getSongById= (id)=>{ + for (let num in player.songs) { + if (player.songs[num].id === id){ + return player.songs[num]; + } + } + throw new Error("No song was found"); + } -function editPlaylist(playlistId, songId) { - // your code here -} + function playSong(id) { + if(getSongById(id)===undefined){ + throw " ${id} ID not exists"; + } + return player.playSong(getSongById(id)); + } + + function removeSong(id) { + if(getSongById(id)===undefined){ + throw new Error("${id} ID not valid"); + } + player.songs.splice(getSongById(id),1); + for(let num1 in player.playlists){ + for(let num2 in player.playlists[num1].songs){ + if(player.playlists[num1].songs[num2] ===id){ + player.playlists[num1].songs.splice(num2,1); + } + } + } + } + + const idExist=(id)=> { + for (let num in player.songs) { + if (player.songs[num].id === id) + return true; + } + return false; + } -function playlistDuration(id) { - // your code here -} + const secondsFormat= (duration)=>{ + let sum = duration.split(":"); + let minute = parseInt(sum[0]) * 60; + let second = parseInt(sum[1]); + + return minute + second; + } + + function addSong(title, album, artist, duration, id = Math.floor(Math.random() * 100) + 1){ + if(idExist(id)){ + throw new Error("${id} ID already exists"); + } + else{ + player.songs.push({id: id, title: title,album: album,artist: artist,duration:secondsFormat(duration)}); + return id; + } + } + + const getPlaylistById=(id) =>{ + for (let num in player.playlists) { + if (player.playlists[num].id === id) + return player.playlists[num]; + } + throw new Error("${id} ID not exists"); + } -function searchByQuery(query) { - // your code here -} + function removePlaylist(id) { + if(getPlaylistById===undefined){ + throw new Error("${id} ID not exists"); + } + let playlistIndex = player.playlists.indexOf(getPlaylistById(id)); + player.playlists.splice(playlistIndex, 1); + } -function searchByDuration(duration) { - // your code here -} + const playlistIdExist= (id)=>{ + for (let i in player.playlists) { + return player.playlists[i].id === id? true:false; + } + } + + function createPlaylist(name, id = Math.floor(Math.random() * 100) + 1) { + if(playlistIdExist(id)){ + throw new Error("${id} ID already exists"); + } + else{ + player.playlists.push({id: id,name: name,songs:[]}); + return id; + } + } + + function playPlaylist(id) { + if(getPlaylistById(id)===undefined){ + throw new error("${id} ID not exists"); + } + let playlist = getPlaylistById(id); + for(let num = 0; num < playlist.songs.length; num++){ + playSong(playlist.songs[num]); + } + } + + const songInPlaylist=(songId, playlistId)=>{ + let playlist = getPlaylistById(playlistId); + for (let num in playlist.songs) { + return playlist.songs[num] === songId? num:-1; + } -module.exports = { - player, - playSong, - removeSong, - addSong, - removePlaylist, - createPlaylist, - playPlaylist, - editPlaylist, - playlistDuration, - searchByQuery, - searchByDuration, -} + } + function editPlaylist(playlistId, songId) { + let playlist = getPlaylistById(playlistId); + let songIn= songInPlaylist(songId, playlistId); + if(idExist(songId) && playlistIdExist(playlistId)){ + if(songIn === -1) { + playlist.songs.push(songId); + } + else if(playlist.songs.length > 1){ + playlist.songs.splice(songIn,1); + } + else{ + removePlaylist(playlist.id); + } + } + else{ + throw new Error("${id} ID not exists"); + } + } + + function playlistDuration(id) { + let playlist = getPlaylistById(id); + let result = 0; + for(let num in playlist.songs){ + result+= getSongById(playlist.songs[num]).duration; + } + return result; + } + + function searchByQuery(query) { + const results = { songs: [], playlists: [] } + let myQuery = query; + for (let i in player.songs) { + if (player.songs[i].album.includes(myQuery) || player.songs[i].artist.includes(myQuery) || player.songs[i].title.includes(myQuery)) + { + results.songs.push(player.songs[i]) + } + } + results.songs.sort(function (first,second){ + if (first.title < second.title){ + return -1; + } + return first.title>second.title? 1:0; + + }) + for (let num in player.playlists) { + if (player.playlists[num].name.includes(myQuery)) { + results.playlists.push(player.playlists[num]) + results.playlists.sort((first,second) => { + if (first.name < second.name){ + return -1; + } + }) + } + return results + } + } + + function searchByDuration(duration) { + let mmss = secondsFormat(duration); + let closestSong; + let bestTime; + if (mmss > player.songs[0].duration){ + bestTime = mmss; + } + else{ + bestTime = player.songs[0].duration; + } + for(let song of player.songs){ + if(Math.abs(mmss - song.duration) < bestTime){ + closestSong = song; + bestTime = Math.abs(mmss - song.duration); + } + } + for (let playlist of player.playlists) { + if (Math.abs(mmss - playlistDuration(playlist.id)) < bestTime) { + closestSong = playlist; + bestTime = Math.abs(mmss - playlistDuration(playlist.id)); + } + } + return closestSong; + } + + module.exports = { + player, + playSong, + removeSong, + addSong, + removePlaylist, + createPlaylist, + playPlaylist, + editPlaylist, + playlistDuration, + searchByQuery, + searchByDuration, + } \ No newline at end of file