hallon 0.15.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
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