hallon 0.15.0 → 0.16.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 (65) hide show
  1. data/CHANGELOG.md +40 -0
  2. data/README.markdown +1 -1
  3. data/dev/application_key_converter.rb +11 -0
  4. data/examples/example_support.rb +4 -0
  5. data/examples/playing_audio.rb +1 -1
  6. data/lib/hallon.rb +11 -0
  7. data/lib/hallon/album_browse.rb +3 -3
  8. data/lib/hallon/artist_browse.rb +20 -4
  9. data/lib/hallon/blob.rb +11 -0
  10. data/lib/hallon/enumerator.rb +5 -0
  11. data/lib/hallon/ext/spotify.rb +2 -0
  12. data/lib/hallon/image.rb +7 -1
  13. data/lib/hallon/link.rb +5 -2
  14. data/lib/hallon/observable.rb +48 -1
  15. data/lib/hallon/player.rb +15 -26
  16. data/lib/hallon/playlist.rb +16 -25
  17. data/lib/hallon/playlist_container.rb +38 -0
  18. data/lib/hallon/search.rb +75 -4
  19. data/lib/hallon/session.rb +31 -42
  20. data/lib/hallon/toplist.rb +3 -3
  21. data/lib/hallon/track.rb +28 -10
  22. data/lib/hallon/version.rb +1 -1
  23. data/spec/hallon/album_browse_spec.rb +68 -18
  24. data/spec/hallon/album_spec.rb +62 -27
  25. data/spec/hallon/artist_browse_spec.rb +106 -31
  26. data/spec/hallon/artist_spec.rb +32 -18
  27. data/spec/hallon/blob_spec.rb +6 -0
  28. data/spec/hallon/enumerator_spec.rb +10 -0
  29. data/spec/hallon/error_spec.rb +4 -4
  30. data/spec/hallon/hallon_spec.rb +1 -1
  31. data/spec/hallon/image_spec.rb +58 -47
  32. data/spec/hallon/link_spec.rb +51 -43
  33. data/spec/hallon/observable/album_browse_spec.rb +1 -1
  34. data/spec/hallon/observable/artist_browse_spec.rb +1 -1
  35. data/spec/hallon/observable/image_spec.rb +1 -1
  36. data/spec/hallon/observable/playlist_container_spec.rb +4 -4
  37. data/spec/hallon/observable/playlist_spec.rb +14 -14
  38. data/spec/hallon/observable/post_spec.rb +1 -1
  39. data/spec/hallon/observable/search_spec.rb +1 -1
  40. data/spec/hallon/observable/session_spec.rb +17 -17
  41. data/spec/hallon/observable/toplist_spec.rb +1 -1
  42. data/spec/hallon/observable_spec.rb +40 -6
  43. data/spec/hallon/player_spec.rb +1 -1
  44. data/spec/hallon/playlist_container_spec.rb +96 -13
  45. data/spec/hallon/playlist_spec.rb +180 -45
  46. data/spec/hallon/search_spec.rb +211 -28
  47. data/spec/hallon/session_spec.rb +44 -38
  48. data/spec/hallon/toplist_spec.rb +31 -14
  49. data/spec/hallon/track_spec.rb +159 -50
  50. data/spec/hallon/user_post_spec.rb +10 -5
  51. data/spec/hallon/user_spec.rb +60 -50
  52. data/spec/spec_helper.rb +40 -15
  53. data/spec/support/album_mocks.rb +30 -0
  54. data/spec/support/artist_mocks.rb +36 -0
  55. data/spec/support/common_objects.rb +0 -201
  56. data/spec/support/image_mocks.rb +34 -0
  57. data/spec/support/playlist_container_mocks.rb +36 -0
  58. data/spec/support/playlist_mocks.rb +70 -0
  59. data/spec/support/search_mocks.rb +23 -0
  60. data/spec/support/session_mocks.rb +33 -0
  61. data/spec/support/toplist_mocks.rb +19 -0
  62. data/spec/support/track_mocks.rb +28 -0
  63. data/spec/support/user_mocks.rb +20 -0
  64. metadata +40 -18
  65. data/spec/support/context_stub_session.rb +0 -5
@@ -2,27 +2,31 @@
2
2
  require 'cgi'
3
3
 
4
4
  describe Hallon::Search do
5
- it_should_behave_like "a Linkable object" do
6
- let(:spotify_uri) { "spotify:search:my+%C3%A5+utf8+%EF%A3%BF+query" }
7
- let(:custom_object) { "http://open.spotify.com/search/my+%C3%A5+utf8+%EF%A3%BF+query" }
8
- let(:described_class) { stub_session(Hallon::Search) }
5
+ let(:search) do
6
+ Hallon::Search.new("my å utf8query")
9
7
  end
10
8
 
11
- it { should be_a Hallon::Loadable }
9
+ let(:empty_search) do
10
+ Hallon::Search.new("")
11
+ end
12
12
 
13
- subject { search }
14
- let(:search) do
15
- mock_session { Hallon::Search.new("my å utf8  query") }
13
+ specify { search.should be_a Hallon::Loadable }
14
+ specify { search.should be_a Hallon::Observable }
15
+
16
+ it_should_behave_like "a Linkable object" do
17
+ let(:spotify_uri) { "spotify:search:my+%C3%A5+utf8+%EF%A3%BF+query" }
18
+ let(:custom_object) { "http://open.spotify.com/search/my+%C3%A5+utf8+%EF%A3%BF+query" }
19
+ let(:described_class) { Hallon::Search }
16
20
  end
17
21
 
18
22
  describe ".new" do
19
23
  it "should have some sane defaults" do
20
24
  Spotify.should_receive(:search_create).with(session.pointer, "my å utf8  query", 0, 25, 0, 25, 0, 25, 0, 25, :standard, anything, anything).and_return(mock_search)
21
- mock_session { Hallon::Search.new("my å utf8  query") }
25
+ Hallon::Search.new("my å utf8  query")
22
26
  end
23
27
 
24
28
  it "should allow you to customize the defaults" do
25
- Spotify.should_receive(:search_create).with(session.pointer, "my å utf8  query", 1, 2, 3, 4, 5, 6, 7, 8, :standard, anything, anything).and_return(mock_search)
29
+ Spotify.should_receive(:search_create).with(session.pointer, "my å utf8  query", 1, 2, 3, 4, 5, 6, 7, 8, :suggest, anything, anything).and_return(mock_search)
26
30
  my_params = {
27
31
  :tracks_offset => 1,
28
32
  :tracks => 2,
@@ -31,35 +35,214 @@ describe Hallon::Search do
31
35
  :artists_offset => 5,
32
36
  :artists => 6,
33
37
  :playlists_offset => 7,
34
- :playlists => 8
38
+ :playlists => 8,
39
+ :type => :suggest
35
40
  }
36
41
 
37
- mock_session { Hallon::Search.new("my å utf8  query", my_params) }
42
+ Hallon::Search.new("my å utf8  query", my_params)
43
+ end
44
+
45
+ it "should raise an error given an invalid search type" do
46
+ expect { Hallon::Search.new("my å utf8  query", type: :hulabandola) }.to raise_error(ArgumentError)
38
47
  end
39
48
 
40
49
  it "should raise an error if the search failed" do
41
50
  Spotify.should_receive(:search_create).and_return(null_pointer)
42
- expect { mock_session { Hallon::Search.new("omgwtfbbq") } }.to raise_error(/search (.*?) failed/)
51
+ expect { Hallon::Search.new("omgwtfbbq") }.to raise_error(/search (.*?) failed/)
52
+ end
53
+ end
54
+
55
+ describe "#loaded?" do
56
+ it "returns true if the search is complete" do
57
+ search.should be_loaded
58
+ end
59
+ end
60
+
61
+ describe "#status" do
62
+ it "returns the status of the search" do
63
+ search.status.should eq :ok
64
+ end
65
+ end
66
+
67
+ describe "#query" do
68
+ it "returns the search query" do
69
+ search.query.should eq "my å utf8  query"
70
+ end
71
+ end
72
+
73
+ describe "#did_you_mean" do
74
+ it "returns a suggestion for what the query might have intended to be" do
75
+ search.did_you_mean.should eq "another thing"
76
+ end
77
+
78
+ it "returns an empty string if there is no suggestion available" do
79
+ empty_search.did_you_mean.should be_empty
80
+ end
81
+ end
82
+
83
+ describe "#tracks" do
84
+ it "returns an enumerator of the search’s track" do
85
+ search.tracks.to_a.should eq instantiate(Hallon::Track, mock_track, mock_track_two)
86
+ end
87
+
88
+ it "returns an empty enumerator if there are no search results" do
89
+ empty_search.tracks.should be_empty
90
+ end
91
+
92
+ describe ".total" do
93
+ it "returns the total number of track search results" do
94
+ search.tracks.total.should eq 1337
95
+ end
96
+
97
+ it "returns zero if there are no search results whatsoever" do
98
+ empty_search.tracks.total.should eq 0
99
+ end
100
+ end
101
+ end
102
+
103
+ describe "#albums" do
104
+ it "returns an enumerator of the search’s albums" do
105
+ search.albums.to_a.should eq instantiate(Hallon::Album, mock_album)
106
+ end
107
+
108
+ it "returns an empty enumerator if there are no search results" do
109
+ empty_search.albums.should be_empty
110
+ end
111
+
112
+ describe ".total" do
113
+ it "returns the total number of album search results" do
114
+ search.albums.total.should eq 42
115
+ end
116
+
117
+ it "returns zero if there are no search results whatsoever" do
118
+ empty_search.albums.total.should eq 0
119
+ end
120
+ end
121
+ end
122
+
123
+ describe "#artists" do
124
+ it "returns an enumerator of the search’s artists" do
125
+ search.artists.to_a.should eq instantiate(Hallon::Artist, mock_artist, mock_artist_two)
126
+ end
127
+
128
+ it "returns an empty enumerator if there are no search results" do
129
+ empty_search.artists.should be_empty
130
+ end
131
+
132
+ describe ".total" do
133
+ it "returns the total number of artist search results" do
134
+ search.artists.total.should eq 81104
135
+ end
136
+
137
+ it "returns zero if there are no search results whatsoever" do
138
+ empty_search.artists.total.should eq 0
139
+ end
140
+ end
141
+ end
142
+
143
+ describe "#playlist_names" do
144
+ it "returns an enumerator of the search’s playlist names" do
145
+ search.playlist_names.to_a.should eq %w(Dunderlist)
146
+ end
147
+
148
+ it "returns an empty enumerator of there are no search results" do
149
+ empty_search.playlist_names.should be_empty
150
+ end
151
+
152
+ describe ".total" do
153
+ it "returns the total number of search results" do
154
+ search.playlist_names.total.should eq 462
155
+ end
156
+
157
+ it "returns zero if there are no search results whatsoever" do
158
+ empty_search.playlist_names.total.should eq 0
159
+ end
160
+ end
161
+ end
162
+
163
+ describe "#playlist_uris" do
164
+ it "returns an enumerator of the search’s playlist uris" do
165
+ search.playlist_uris.to_a.should eq %w(spotify:user:burgestrand:playlist:megaplaylist)
166
+ end
167
+
168
+ it "returns an empty enumerator of there are no search results" do
169
+ empty_search.playlist_uris.should be_empty
170
+ end
171
+
172
+ describe ".total" do
173
+ it "returns the total number of search results" do
174
+ search.playlist_uris.total.should eq 462
175
+ end
176
+
177
+ it "returns zero if there are no search results whatsoever" do
178
+ empty_search.playlist_uris.total.should eq 0
179
+ end
43
180
  end
44
181
  end
45
182
 
46
- it { should be_a Hallon::Observable }
47
- it { should be_loaded }
48
- its(:status) { should eq :ok }
49
- its(:query) { should eq "my å utf8  query" }
50
- its(:did_you_mean) { should eq "another thing" }
183
+ describe "#playlist_image_uris" do
184
+ it "returns an enumerator of the search’s playlist image uris" do
185
+ search.playlist_image_uris.to_a.should eq %w(spotify:image:3ad93423add99766e02d563605c6e76ed2b0e400)
186
+ end
187
+
188
+ it "returns an empty enumerator of there are no search results" do
189
+ empty_search.playlist_image_uris.should be_empty
190
+ end
191
+
192
+ describe ".total" do
193
+ it "returns the total number of search results" do
194
+ search.playlist_image_uris.total.should eq 462
195
+ end
196
+
197
+ it "returns zero if there are no search results whatsoever" do
198
+ empty_search.playlist_image_uris.total.should eq 0
199
+ end
200
+ end
201
+ end
202
+
203
+ describe "#playlists" do
204
+ it "returns an enumerator of the search’s playlists" do
205
+ search.playlists.to_a.should eq instantiate(Hallon::Playlist, mock_playlist_two)
206
+ end
207
+
208
+ it "returns an empty enumerator of there are no search results" do
209
+ empty_search.playlists.should be_empty
210
+ end
51
211
 
52
- its('tracks.size') { should eq 2 }
53
- its('tracks.to_a') { should eq instantiate(Hallon::Track, mock_track, mock_track_two) }
54
- its('tracks.total') { should eq 1337 }
212
+ describe ".total" do
213
+ it "returns the total number of search results" do
214
+ search.playlists.total.should eq 462
215
+ end
55
216
 
56
- its('albums.size') { should eq 1 }
57
- its('albums.to_a') { should eq instantiate(Hallon::Album, mock_album) }
58
- its('albums.total') { should eq 42 }
217
+ it "returns zero if there are no search results whatsoever" do
218
+ empty_search.playlists.total.should eq 0
219
+ end
220
+ end
221
+ end
222
+
223
+ describe "#playlist_images" do
224
+ it "returns an enumerator of the search’s playlist images" do
225
+ search.playlist_images.to_a.should eq instantiate(Hallon::Image, mock_image)
226
+ end
227
+
228
+ it "returns an empty enumerator of there are no search results" do
229
+ empty_search.playlist_images.should be_empty
230
+ end
59
231
 
60
- its('artists.size') { should eq 2 }
61
- its('artists.to_a') { should eq instantiate(Hallon::Artist, mock_artist, mock_artist_two) }
62
- its('artists.total') { should eq 81104 }
232
+ describe ".total" do
233
+ it "returns the total number of search results" do
234
+ search.playlist_images.total.should eq 462
235
+ end
63
236
 
64
- its(:to_link) { should eq Hallon::Link.new("spotify:search:#{CGI.escape(search.query)}") }
237
+ it "returns zero if there are no search results whatsoever" do
238
+ empty_search.playlist_images.total.should eq 0
239
+ end
240
+ end
241
+ end
242
+
243
+ describe "#to_link" do
244
+ it "contains the search query" do
245
+ search.to_link.should eq Hallon::Link.new("spotify:search:#{CGI.escape(search.query)}")
246
+ end
247
+ end
65
248
  end
@@ -68,35 +68,6 @@ describe Hallon::Session do
68
68
  end
69
69
  end
70
70
 
71
- describe "#process_events_on" do
72
- it "should not call given block on :notify_main_thread implicitly" do
73
- notified = false
74
-
75
- session.should_receive(:process_events).twice.and_return do
76
- unless notified
77
- session.class.send(:notify_main_thread_callback, session.pointer)
78
- notified = true
79
- else
80
- session.class.send(:logged_in_callback, session.pointer, :ok)
81
- end
82
-
83
- 0
84
- end
85
-
86
- session.process_events_on(:logged_in) { |e| e == :ok }.should be_true
87
- end
88
-
89
- it "should time out if waiting for events too long" do
90
- session.should_receive(:process_events).once.and_return(1) # and do nothing
91
- session.wait_for(:logged_in) { |x| x }.should eq :timeout
92
- end
93
-
94
- it "should call the given block once before waiting" do
95
- session.should_not_receive(:process_events)
96
- session.process_events_on { true }
97
- end
98
- end
99
-
100
71
  describe "#relogin" do
101
72
  it "should raise if no credentials have been saved" do
102
73
  expect { session.relogin }.to raise_error(Hallon::Error)
@@ -135,6 +106,16 @@ describe Hallon::Session do
135
106
  expect { session.login '', 'pass' }.to raise_error(ArgumentError)
136
107
  expect { session.login 'Kim', '' }.to raise_error(ArgumentError)
137
108
  end
109
+
110
+ it "should login with a blob when given a blob" do
111
+ Spotify.should_receive(:session_login).with(anything, 'Kim', nil, false, 'blob')
112
+ session.login 'Kim', Hallon::Blob('blob')
113
+ end
114
+
115
+ it "should not login with a blob when not given a blob" do
116
+ Spotify.should_receive(:session_login).with(anything, 'Kim', 'pass', false, nil)
117
+ session.login 'Kim', 'pass'
118
+ end
138
119
  end
139
120
 
140
121
  describe "#logout" do
@@ -232,16 +213,35 @@ describe Hallon::Session do
232
213
  end
233
214
 
234
215
  describe "offline settings readers" do
235
- subject { mock_session_object }
216
+ let(:session) { mock_session_object }
236
217
 
237
- its(:offline_time_left) { should eq 60 * 60 * 24 * 30 } # a month!
238
- its(:offline_sync_status) { should eq mock_offline_sync_status_hash }
239
- its(:offline_playlists_count) { should eq 7 }
240
- its(:offline_tracks_to_sync) { should eq 3 }
218
+ describe "#offline_time_left" do
219
+ it "returns the time left until libspotify must go online" do
220
+ session.offline_time_left.should eq 60 * 60 * 24 * 30
221
+ end
222
+ end
223
+
224
+ describe "#offline_sync_status" do
225
+ it "returns a hash of offline sync status details" do
226
+ session.offline_sync_status.should eq mock_offline_sync_status_hash
227
+ end
228
+
229
+ it "returns an empty hash when offline sync status details are unavailable" do
230
+ Spotify.should_receive(:offline_sync_get_status).and_return(false)
231
+ session.offline_sync_status.should eq Hash.new
232
+ end
233
+ end
241
234
 
242
- specify "offline_sync_status when given false as return from libspotify" do
243
- Spotify.should_receive(:offline_sync_get_status).and_return(false)
244
- subject.offline_sync_status.should eq nil
235
+ describe "#offline_playlists_count" do
236
+ it "returns the number of playlists marked for offline synchronization" do
237
+ session.offline_playlists_count.should eq 7
238
+ end
239
+ end
240
+
241
+ describe "#offline_tracks_to_sync" do
242
+ it "returns the number of tracks that still need to be synchronized" do
243
+ session.offline_tracks_to_sync.should eq 3
244
+ end
245
245
  end
246
246
  end
247
247
 
@@ -267,7 +267,7 @@ describe Hallon::Session do
267
267
 
268
268
 
269
269
  describe "#starred" do
270
- let(:starred) { mock_session { Hallon::Playlist.new("spotify:user:burgestrand:starred") } }
270
+ let(:starred) { Hallon::Playlist.new("spotify:user:burgestrand:starred") }
271
271
 
272
272
  it "should return the sessions (current users) starred playlist" do
273
273
  session.login 'burgestrand', 'pass'
@@ -298,4 +298,10 @@ describe Hallon::Session do
298
298
  session.inbox.should be_nil
299
299
  end
300
300
  end
301
+
302
+ describe "#flush_caches" do
303
+ it "flushes the session cache to disk" do
304
+ session.flush_caches # or, actually, it does not crash
305
+ end
306
+ end
301
307
  end
@@ -1,36 +1,50 @@
1
- describe Hallon::Toplist do
2
- it { should be_a Hallon::Loadable }
1
+ # coding: utf-8
3
2
 
3
+ describe Hallon::Toplist do
4
4
  let(:toplist) do
5
5
  Spotify.registry_add 'spotify:toplist:artists:everywhere', mock_toplistbrowse
6
- mock_session { Hallon::Toplist.new(:artists) }
6
+ Hallon::Toplist.new(:artists)
7
+ end
8
+
9
+ let(:empty_toplist) do
10
+ Spotify.registry_add 'spotify:toplist:tracks:everywhere', mock_empty_toplistbrowse
11
+ Hallon::Toplist.new(:tracks)
7
12
  end
8
- subject { toplist }
9
13
 
10
- it { should be_a Hallon::Observable }
14
+ specify { toplist.should be_a Hallon::Loadable }
15
+ specify { toplist.should be_a Hallon::Observable }
11
16
 
12
17
  describe ".new" do
13
18
  it "should fail given an invalid type" do
14
- expect { stub_session { Hallon::Toplist.new(:invalid_type) } }.to raise_error(ArgumentError, /invalid enum value/)
19
+ expect { Hallon::Toplist.new(:invalid_type) }.to raise_error(ArgumentError, /invalid enum value/)
15
20
  end
16
21
 
17
22
  it "should pass the username given a string to libspotify" do
18
23
  Spotify.registry_add 'spotify:toplist:user:Kim:tracks', mock_toplistbrowse
19
- stub_session { Hallon::Toplist.new(:tracks, "Kim").should be_loaded }
24
+ Hallon::Toplist.new(:tracks, "Kim").should be_loaded
20
25
  end
21
26
 
22
27
  it "should pass the correct region to libspotify" do
23
28
  Spotify.registry_add 'spotify:toplist:tracks:SE', mock_toplistbrowse
24
- mock_session { Hallon::Toplist.new(:tracks, :se).should be_loaded }
29
+ Hallon::Toplist.new(:tracks, :se).should be_loaded
25
30
  end
26
31
  end
27
32
 
28
- it { should be_loaded }
29
- its(:status) { should eq :ok }
33
+ describe "#loaded?" do
34
+ it "returns true if the toplist is loaded" do
35
+ toplist.should be_loaded
36
+ end
37
+ end
38
+
39
+ describe "#status" do
40
+ it "returns the toplist’s status" do
41
+ toplist.status.should eq :ok
42
+ end
43
+ end
30
44
 
31
45
  describe "#type" do
32
46
  it "should be the same as the type given to .new" do
33
- toplist = mock_session { Hallon::Toplist.new(:tracks, :se) }
47
+ toplist = Hallon::Toplist.new(:tracks, :se)
34
48
  toplist.type.should eq :tracks
35
49
  end
36
50
  end
@@ -50,6 +64,10 @@ describe Hallon::Toplist do
50
64
  toplist.should_receive(:type).and_return(:tracks)
51
65
  toplist.results.to_a.should eq instantiate(Hallon::Track, mock_track, mock_track_two)
52
66
  end
67
+
68
+ it "returns an empty enumerator when there are no results" do
69
+ empty_toplist.results.should be_empty
70
+ end
53
71
  end
54
72
 
55
73
  describe "#request_duration" do
@@ -57,9 +75,8 @@ describe Hallon::Toplist do
57
75
  toplist.request_duration.should eq 2.751
58
76
  end
59
77
 
60
- it "should be nil if the request was fetched from local cache" do
61
- Spotify.should_receive(:toplistbrowse_backend_request_duration).and_return(-1)
62
- toplist.request_duration.should be_nil
78
+ it "should be zero if the request was fetched from local cache" do
79
+ empty_toplist.request_duration.should eq 0
63
80
  end
64
81
  end
65
82
  end