hallon 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. data/.travis.yml +2 -0
  2. data/CHANGELOG +43 -0
  3. data/Gemfile +2 -0
  4. data/README.markdown +21 -13
  5. data/Rakefile +84 -23
  6. data/dev/login.rb +16 -0
  7. data/examples/adding_tracks_to_playlist.rb +49 -0
  8. data/examples/logging_in.rb +1 -6
  9. data/examples/show_published_playlists_of_user.rb +9 -19
  10. data/hallon.gemspec +1 -1
  11. data/lib/hallon.rb +3 -2
  12. data/lib/hallon/album.rb +55 -41
  13. data/lib/hallon/album_browse.rb +41 -37
  14. data/lib/hallon/artist.rb +30 -21
  15. data/lib/hallon/artist_browse.rb +59 -41
  16. data/lib/hallon/base.rb +68 -5
  17. data/lib/hallon/enumerator.rb +1 -0
  18. data/lib/hallon/error.rb +3 -0
  19. data/lib/hallon/ext/spotify.rb +169 -36
  20. data/lib/hallon/image.rb +30 -44
  21. data/lib/hallon/link.rb +29 -43
  22. data/lib/hallon/linkable.rb +68 -20
  23. data/lib/hallon/observable.rb +0 -1
  24. data/lib/hallon/player.rb +21 -7
  25. data/lib/hallon/playlist.rb +291 -0
  26. data/lib/hallon/playlist_container.rb +27 -0
  27. data/lib/hallon/search.rb +52 -45
  28. data/lib/hallon/session.rb +129 -81
  29. data/lib/hallon/toplist.rb +37 -19
  30. data/lib/hallon/track.rb +68 -45
  31. data/lib/hallon/user.rb +69 -33
  32. data/lib/hallon/version.rb +1 -1
  33. data/spec/hallon/album_browse_spec.rb +15 -9
  34. data/spec/hallon/album_spec.rb +15 -15
  35. data/spec/hallon/artist_browse_spec.rb +28 -9
  36. data/spec/hallon/artist_spec.rb +30 -14
  37. data/spec/hallon/enumerator_spec.rb +0 -1
  38. data/spec/hallon/hallon_spec.rb +20 -1
  39. data/spec/hallon/image_spec.rb +18 -41
  40. data/spec/hallon/link_spec.rb +10 -12
  41. data/spec/hallon/linkable_spec.rb +37 -18
  42. data/spec/hallon/player_spec.rb +8 -0
  43. data/spec/hallon/playlist_container_spec.rb +75 -0
  44. data/spec/hallon/playlist_spec.rb +204 -0
  45. data/spec/hallon/search_spec.rb +19 -16
  46. data/spec/hallon/session_spec.rb +61 -29
  47. data/spec/hallon/spotify_spec.rb +30 -0
  48. data/spec/hallon/toplist_spec.rb +22 -14
  49. data/spec/hallon/track_spec.rb +62 -21
  50. data/spec/hallon/user_spec.rb +47 -36
  51. data/spec/mockspotify.rb +35 -10
  52. data/spec/mockspotify/mockspotify_spec.rb +22 -0
  53. data/spec/spec_helper.rb +7 -3
  54. data/spec/support/common_objects.rb +91 -16
  55. data/spec/support/shared_for_linkable_objects.rb +39 -0
  56. metadata +30 -20
  57. data/Termfile +0 -7
  58. data/lib/hallon/synchronizable.rb +0 -32
  59. data/spec/hallon/synchronizable_spec.rb +0 -19
@@ -0,0 +1,75 @@
1
+ # coding: utf-8
2
+ describe Hallon::PlaylistContainer do
3
+ let(:container) { Hallon::PlaylistContainer.new(mock_container) }
4
+
5
+ subject { container }
6
+
7
+ it { should be_loaded }
8
+ its(:owner) { should eq Hallon::User.new("burgestrand") }
9
+
10
+ describe "#add" do
11
+ it "should create a new Playlist at the end of the playlist"
12
+ end
13
+
14
+ describe "#insert" do
15
+ it "should add the given Playlist to the given index"
16
+ it "should add the given Folder to the given index", :pending => true
17
+ end
18
+
19
+ describe "#remove" do
20
+ it "should remove the playlist at the given index"
21
+ it "should remove the matching :folder_end if removing a folder"
22
+ end
23
+
24
+ describe "#move" do
25
+ it "should move the entity at the given index"
26
+ end
27
+
28
+ describe "#contents" do
29
+ #
30
+ # (0) playlist: Hello
31
+ # (1) start_folder: Hi
32
+ # (2) playlist: inside Hi
33
+ # (3) start_folder: Ho
34
+ # (4) playlist: inside HiHo
35
+ # (5) end_folder
36
+ # (6) playlist: inside Hi2
37
+ # (7) end_folder
38
+ # (8) playlist: World
39
+ #
40
+ # … should become:
41
+ #
42
+ # (0) Playlist #1
43
+ # (1) Folder #1…#7
44
+ # (2) Playlist #2
45
+ # (3) Folder #3…#5
46
+ # (4) Playlist #4
47
+ # (5) Folder #3…#5
48
+ # (6) Playlist #6
49
+ # (7) Folder #1…#7
50
+ # (8) Playlist #8
51
+ #
52
+ it "should be a collection of folders and playlists"
53
+ end
54
+
55
+ describe "PlaylistContainer Playlists", :pending do
56
+ subject { container.contents[1] }
57
+
58
+ its(:name) { should be "Awesome playlist" }
59
+ end
60
+
61
+ describe Hallon::PlaylistContainer::Folder, :pending do
62
+ subject { container.contents[1] }
63
+
64
+ its(:id) { should be 1337 }
65
+ its(:name) { should be "Awesome folder" }
66
+
67
+ describe "#contents" do
68
+ it "should be a collection of folders and playlists"
69
+ end
70
+
71
+ describe "#rename" do
72
+ it "should rename the playlist container"
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,204 @@
1
+ require 'time'
2
+
3
+ describe Hallon::Playlist do
4
+ it_should_behave_like "a Linkable object" do
5
+ let(:spotify_uri) { "spotify:user:burgestrand:playlist:07AX9IY9Hqmj1RqltcG0fi" }
6
+ let(:described_class) do
7
+ real_session = session
8
+ Hallon::Playlist.dup.tap do |klass|
9
+ klass.class_eval do
10
+ define_method(:session) { real_session }
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ let(:playlist) { Hallon::Playlist.new(mock_playlist) }
17
+ subject { playlist }
18
+
19
+ it { should be_loaded }
20
+ it { should be_collaborative }
21
+ it { should_not be_pending }
22
+ it { mock_session { should be_in_ram } }
23
+ it { mock_session { should_not be_available_offline } }
24
+
25
+ its(:name) { should eq "Megaplaylist" }
26
+ its(:owner) { should eq Hallon::User.new(mock_user) }
27
+ its(:description) { should eq "Playlist description...?" }
28
+ its(:image) { mock_session { should eq Hallon::Image.new(mock_image_id) } }
29
+ its(:total_subscribers) { should eq 1000 }
30
+ its(:sync_progress) { mock_session { should eq 67 } }
31
+ its(:size) { should eq 4 }
32
+
33
+ its('tracks.size') { should eq 4 }
34
+ its('tracks.to_a') { should eq instantiate(Hallon::Playlist::Track, [mock_playlist, 0], [mock_playlist, 1], [mock_playlist, 2], [mock_playlist, 3]) }
35
+ describe "tracks#[]" do
36
+ subject { playlist.tracks[0] }
37
+
38
+ it { should be_seen }
39
+ its(:create_time) { should eq Time.parse("2009-11-04") }
40
+ its(:creator) { should eq Hallon::User.new(mock_user) }
41
+ its(:message) { should eq "message this, YO!" }
42
+ end
43
+
44
+ describe "#seen" do
45
+ it "should set the seen status of the track at the given index" do
46
+ track = playlist.tracks[0]
47
+ track.should be_seen
48
+
49
+ track = playlist.seen(0, false)
50
+ track.should_not be_seen
51
+
52
+ track = playlist.seen(0, true)
53
+ track.should be_seen
54
+ end
55
+ end
56
+
57
+ describe "#subscribers" do
58
+ it "should return an array of names for the subscribers" do
59
+ subject.subscribers.should eq %w[Kim Elin Ylva]
60
+ end
61
+
62
+ it "should return an empty array when there are no subscribers" do
63
+ Spotify.should_receive(:playlist_subscribers).and_return(mock_empty_subscribers)
64
+ subject.subscribers.should eq []
65
+ end
66
+ end
67
+
68
+ describe "#insert" do
69
+ let(:tracks) { instantiate(Hallon::Track, mock_track, mock_track_two) }
70
+
71
+ it "should add the given tracks to the playlist at correct index" do
72
+ old_tracks = playlist.tracks.to_a
73
+ new_tracks = old_tracks.insert(1, *tracks)
74
+ mock_session { playlist.insert(1, tracks) }
75
+
76
+ playlist.tracks.to_a.should eq new_tracks
77
+ end
78
+
79
+ it "should default to adding tracks at the end" do
80
+ mock_session { playlist.insert(tracks) }
81
+ playlist.tracks[2, 2].should eq tracks
82
+ end
83
+
84
+ it "should raise an error if the operation cannot be completed" do
85
+ expect { mock_session { playlist.insert(-1, nil) } }.to raise_error(Hallon::Error)
86
+ end
87
+ end
88
+
89
+ describe "#remove" do
90
+ it "should remove the tracks at the given indices" do
91
+ old_tracks = playlist.tracks.to_a
92
+ new_tracks = [old_tracks[0], old_tracks[2]]
93
+
94
+ playlist.remove(1, 3)
95
+ playlist.tracks.to_a.should eq new_tracks
96
+ end
97
+
98
+ it "should raise an error if the operation cannot be completed" do
99
+ expect { playlist.remove(-1) }.to raise_error(Hallon::Error)
100
+ end
101
+ end
102
+
103
+ describe "#move" do
104
+ it "should move the tracks at the given indices to their new location" do
105
+ old_tracks = playlist.tracks.to_a
106
+ new_tracks = [old_tracks[1], old_tracks[0], old_tracks[3], old_tracks[2]]
107
+
108
+ playlist.move(2, [0, 3])
109
+ playlist.tracks.to_a.should eq new_tracks
110
+ end
111
+
112
+ it "should raise an error if the operation cannot be completed" do
113
+ expect { playlist.move(-1, [-1]) }.to raise_error(Hallon::Error)
114
+ end
115
+ end
116
+
117
+ describe "#name=" do
118
+ it "should set the new playlist name" do
119
+ playlist.name.should eq "Megaplaylist"
120
+ playlist.name = "Monoplaylist"
121
+ playlist.name.should eq "Monoplaylist"
122
+ end
123
+
124
+ it "should fail given an empty name" do
125
+ expect { playlist.name = "" }.to raise_error(Hallon::Error)
126
+ end
127
+
128
+ it "should fail given a name of only spaces" do
129
+ expect { playlist.name = " " }.to raise_error(ArgumentError)
130
+ end
131
+
132
+ it "should fail given a too long name" do
133
+ expect { playlist.name = "a" * 256 }.to raise_error(ArgumentError)
134
+ end
135
+ end
136
+
137
+ describe "#collaborative=" do
138
+ it "should set the collaborative status" do
139
+ playlist.should be_collaborative
140
+ playlist.collaborative = false
141
+ playlist.should_not be_collaborative
142
+ end
143
+ end
144
+
145
+ describe "#autolink_tracks=" do
146
+ it "should set autolink status" do
147
+ Spotify.mocksp_playlist_get_autolink_tracks(playlist.pointer).should be_false
148
+ playlist.autolink_tracks = true
149
+ Spotify.mocksp_playlist_get_autolink_tracks(playlist.pointer).should be_true
150
+ end
151
+ end
152
+
153
+ describe "#in_ram=" do
154
+ it "should set in_ram status" do
155
+ mock_session do
156
+ playlist.should be_in_ram
157
+ playlist.in_ram = false
158
+ playlist.should_not be_in_ram
159
+ end
160
+ end
161
+ end
162
+
163
+ describe "#offline_mode=" do
164
+ it "should set offline mode" do
165
+ mock_session do
166
+ playlist.should_not be_available_offline
167
+ playlist.offline_mode = true
168
+ playlist.should be_available_offline
169
+ end
170
+ end
171
+ end
172
+
173
+ describe "#update_subscribers" do
174
+ it "should ask libspotify to update the subscribers" do
175
+ expect { mock_session { playlist.update_subscribers } }.to_not raise_error
176
+ end
177
+ end
178
+
179
+ describe "offline status methods" do
180
+ def symbol_for(number)
181
+ Spotify.enum_type(:playlist_offline_status)[number]
182
+ end
183
+
184
+ specify "#available_offline?" do
185
+ Spotify.should_receive(:playlist_get_offline_status).and_return symbol_for(1)
186
+ mock_session { should be_available_offline }
187
+ end
188
+
189
+ specify "#syncing?" do
190
+ Spotify.should_receive(:playlist_get_offline_status).and_return symbol_for(2)
191
+ mock_session { should be_syncing }
192
+ end
193
+
194
+ specify "#waiting?" do
195
+ Spotify.should_receive(:playlist_get_offline_status).and_return symbol_for(3)
196
+ mock_session { should be_waiting }
197
+ end
198
+
199
+ specify "#offline_mode?" do
200
+ Spotify.should_receive(:playlist_get_offline_status).and_return symbol_for(0)
201
+ mock_session { should_not be_offline_mode }
202
+ end
203
+ end
204
+ end
@@ -1,10 +1,8 @@
1
1
  describe Hallon::Search do
2
2
  subject { search }
3
3
  let(:search) do
4
- mock_session do
5
- Spotify.should_receive(:search_create).and_return(mock_search)
6
- Hallon::Search.new("my query")
7
- end
4
+ Spotify.registry_add 'spotify:search:my query', mock_search
5
+ mock_session { Hallon::Search.new("my query") }
8
6
  end
9
7
 
10
8
  describe ".new" do
@@ -26,6 +24,11 @@ describe Hallon::Search do
26
24
 
27
25
  mock_session { Hallon::Search.new("my query", my_params) }
28
26
  end
27
+
28
+ it "should raise an error if the search failed" do
29
+ Spotify.should_receive(:search_create).and_return(null_pointer)
30
+ expect { mock_session { Hallon::Search.new("omgwtfbbq") } }.to raise_error(/search (.*?) failed/)
31
+ end
29
32
  end
30
33
 
31
34
  describe ".genres" do
@@ -36,31 +39,31 @@ describe Hallon::Search do
36
39
  it { should_not be_empty }
37
40
  end
38
41
 
39
- describe ".search" do
42
+ describe ".radio" do
40
43
  subject do
41
- Spotify.should_receive(:radio_search_create).and_return(mock_search)
44
+ Spotify.registry_add 'spotify:radio:00002200:1990-2010', mock_search
45
+ mock_session { Hallon::Search.radio(1990..2010, :jazz, :punk) }
46
+ end
42
47
 
43
- mock_session do
44
- search = Hallon::Search.radio(1990..2010, :jazz, :punk)
45
- end
48
+ it "should raise an error on invalid genres" do
49
+ Spotify.should_not_receive(:radio_search_create)
50
+ expect { Hallon::Search.radio(1990..2010, :bogus, :jazz) }.to raise_error(ArgumentError, /bogus/)
46
51
  end
47
52
 
48
- it "should simply ignore invalid genres" do
49
- mock_session do
50
- Spotify.should_receive(:radio_search_create).and_return(mock_search)
51
- expect { Hallon::Search.radio(1990..2010, :bogus, :hocum) }.to_not raise_error
52
- end
53
+ it "should raise an error if the search failed" do
54
+ Spotify.should_receive(:radio_search_create).and_return(null_pointer)
55
+ expect { mock_session { Hallon::Search.radio(1990..1990) } }.to raise_error(/search failed/)
53
56
  end
54
57
 
55
58
  it { should be_loaded }
56
- its(:error) { should eq :ok }
59
+ its(:status) { should eq :ok }
57
60
  its('tracks.size') { should eq 2 }
58
61
  # ^ should be enough
59
62
  end
60
63
 
61
64
  it { should be_a Hallon::Observable }
62
65
  it { should be_loaded }
63
- its(:error) { should eq :ok }
66
+ its(:status) { should eq :ok }
64
67
  its(:query) { should eq "my query" }
65
68
  its(:did_you_mean) { should eq "another thing" }
66
69
 
@@ -16,6 +16,10 @@ describe Hallon::Session do
16
16
  Hallon.initialize
17
17
  }.to raise_error
18
18
  end
19
+
20
+ it "should succeed if everything is right" do
21
+ expect { Hallon::Session.initialize('appkey_good') }.to_not raise_error
22
+ end
19
23
  end
20
24
 
21
25
  describe ".new" do
@@ -47,6 +51,18 @@ describe Hallon::Session do
47
51
  its([:cache_playlist_metadata]) { should == true }
48
52
  end
49
53
 
54
+ describe "#container" do
55
+ it "should return the sessions’ playlist container" do
56
+ session.login 'burgestrand', 'pass'
57
+ session.container.should be_a Hallon::PlaylistContainer
58
+ session.container.owner.should eq session.user
59
+ end
60
+
61
+ it "should return nil if not logged in" do
62
+ session.container.should be_nil
63
+ end
64
+ end
65
+
50
66
  describe "#process_events" do
51
67
  it "should return the timeout" do
52
68
  session.process_events.should be_a Fixnum
@@ -64,13 +80,15 @@ describe Hallon::Session do
64
80
  else
65
81
  session.trigger(:bogus, :bogus)
66
82
  end
83
+
84
+ 0
67
85
  end
68
86
 
69
87
  session.process_events_on(:bogus) { |e| e == :bogus }.should be_true
70
88
  end
71
89
 
72
90
  it "should time out if waiting for events too long" do
73
- session.should_receive(:process_events).once # and do nothing
91
+ session.should_receive(:process_events).once.and_return(1) # and do nothing
74
92
  session.wait_for(:ever) { |x| x }.should eq :timeout
75
93
  end
76
94
 
@@ -120,6 +138,17 @@ describe Hallon::Session do
120
138
  end
121
139
  end
122
140
 
141
+ describe "#user" do
142
+ it "should return the logged in user" do
143
+ session.login 'Kim', 'pass'
144
+ session.user.name.should eq 'Kim'
145
+ end
146
+
147
+ it "should return nil if not logged in" do
148
+ session.user.should be_nil
149
+ end
150
+ end
151
+
123
152
  describe "#country" do
124
153
  it "should retrieve the current sessions’ country as a string" do
125
154
  session.country.should eq 'SE'
@@ -144,25 +173,6 @@ describe Hallon::Session do
144
173
  end
145
174
  end
146
175
 
147
- describe "#friends" do
148
- let(:session) { mock_session_object }
149
-
150
- it "should be an enumerator" do
151
- session.friends.should be_an Hallon::Enumerator
152
- end
153
-
154
- it "should be empty if not logged in" do
155
- Spotify.should_not_receive(:session_num_friends)
156
- session.should_receive(:logged_in?).and_return(false)
157
- session.friends.size.should eq 0
158
- end
159
-
160
- it "should be an enumerator of friends" do
161
- session.login 'Kim', 'pass'
162
- session.friends[0].should eq Hallon::User.new(mock_user)
163
- end
164
- end
165
-
166
176
  describe "#cache_size" do
167
177
  it "should default to 0" do
168
178
  session.cache_size.should eq 0
@@ -201,12 +211,12 @@ describe Hallon::Session do
201
211
  end
202
212
 
203
213
  describe "#connection_rules=" do
204
- it "should not fail given an invalid rule" do
205
- expect { session.connection_rules = :lawly }.to_not raise_error
214
+ it "should fail given an invalid rule" do
215
+ expect { session.connection_rules = :lawly }.to raise_error
206
216
  end
207
217
 
208
218
  it "should succeed given correct connection thingy" do
209
- expect { session.connection_rules = :allow_network, :allow_sync_over_mobile }.to_not raise_error
219
+ expect { session.connection_rules = :network, :allow_sync_over_mobile }.to_not raise_error
210
220
  end
211
221
 
212
222
  it "should combine given rules and feed to libspotify" do
@@ -249,15 +259,37 @@ describe Hallon::Session do
249
259
  end
250
260
  end
251
261
 
252
- context "when logged in", :logged_in => true do
253
- it "should be logged in" do
262
+
263
+ describe "#starred" do
264
+ let(:starred) { mock_session { Hallon::Playlist.new("spotify:user:burgestrand:starred") } }
265
+
266
+ it "should return the sessions (current users) starred playlist" do
267
+ session.login 'burgestrand', 'pass'
268
+
254
269
  session.should be_logged_in
270
+ session.starred.should eq starred
255
271
  end
256
272
 
257
- describe "#relation_type?" do
258
- it "should retrieve the relation between the current user and given user" do
259
- session.relation_type?(session.user).should eq :none
260
- end
273
+ it "should return nil if not logged in" do
274
+ session.should_not be_logged_in
275
+ session.starred.should be_nil
276
+ end
277
+ end
278
+
279
+ describe "#inbox" do
280
+ let(:inbox) { Hallon::Playlist.new(mock_playlist) }
281
+ let(:session) { mock_session_object }
282
+
283
+ it "should return the sessions inbox" do
284
+ session.login 'burgestrand', 'pass'
285
+
286
+ session.should be_logged_in
287
+ session.inbox.should eq inbox
288
+ end
289
+
290
+ it "should return nil if not logged in" do
291
+ session.should_not be_logged_in
292
+ session.inbox.should be_nil
261
293
  end
262
294
  end
263
295
  end