hallon 0.4.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.gitmodules +3 -0
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG +30 -6
  4. data/README.markdown +7 -7
  5. data/Rakefile +70 -16
  6. data/examples/logging_in.rb +3 -3
  7. data/examples/printing_link_information.rb +1 -1
  8. data/examples/show_published_playlists_of_user.rb +92 -0
  9. data/hallon.gemspec +7 -4
  10. data/lib/hallon.rb +16 -4
  11. data/lib/hallon/album.rb +16 -6
  12. data/lib/hallon/album_browse.rb +78 -0
  13. data/lib/hallon/artist.rb +59 -0
  14. data/lib/hallon/artist_browse.rb +89 -0
  15. data/lib/hallon/base.rb +7 -0
  16. data/lib/hallon/enumerator.rb +64 -0
  17. data/lib/hallon/error.rb +8 -6
  18. data/lib/hallon/ext/spotify.rb +3 -3
  19. data/lib/hallon/image.rb +25 -12
  20. data/lib/hallon/link.rb +4 -4
  21. data/lib/hallon/linkable.rb +4 -2
  22. data/lib/hallon/observable.rb +1 -4
  23. data/lib/hallon/player.rb +130 -0
  24. data/lib/hallon/search.rb +128 -0
  25. data/lib/hallon/session.rb +226 -25
  26. data/lib/hallon/toplist.rb +83 -0
  27. data/lib/hallon/track.rb +62 -7
  28. data/lib/hallon/user.rb +6 -6
  29. data/lib/hallon/version.rb +1 -1
  30. data/spec/hallon/album_browse_spec.rb +20 -0
  31. data/spec/hallon/album_spec.rb +12 -7
  32. data/spec/hallon/artist_browse_spec.rb +29 -0
  33. data/spec/hallon/artist_spec.rb +32 -0
  34. data/spec/hallon/enumerator_spec.rb +106 -0
  35. data/spec/hallon/error_spec.rb +10 -0
  36. data/spec/hallon/hallon_spec.rb +5 -1
  37. data/spec/hallon/image_spec.rb +39 -25
  38. data/spec/hallon/linkable_spec.rb +12 -4
  39. data/spec/hallon/observable_spec.rb +5 -0
  40. data/spec/hallon/player_spec.rb +73 -0
  41. data/spec/hallon/search_spec.rb +80 -0
  42. data/spec/hallon/session_spec.rb +187 -6
  43. data/spec/hallon/toplist_spec.rb +40 -0
  44. data/spec/hallon/track_spec.rb +43 -8
  45. data/spec/mockspotify.rb +47 -0
  46. data/spec/mockspotify/.gitignore +5 -0
  47. data/spec/mockspotify/extconf.rb +5 -0
  48. data/spec/mockspotify/mockspotify_spec.rb +41 -0
  49. data/spec/spec_helper.rb +20 -0
  50. data/spec/support/common_objects.rb +84 -7
  51. metadata +72 -20
  52. data/lib/hallon/ext/object.rb +0 -16
@@ -35,6 +35,16 @@ describe Hallon::Error do
35
35
  it "should return the error symbol if it's ok" do
36
36
  subject.maybe_raise(0).should eq :ok
37
37
  end
38
+
39
+ # to account for the following case:
40
+ # session.wait_for(:timeout) { |param| Hallon::Error.maybe_raise(param) }
41
+ it "should return nil when the error is nil" do
42
+ subject.maybe_raise(nil).should eq nil
43
+ end
44
+
45
+ it "should return nil when the error is timeout" do
46
+ subject.maybe_raise(:timeout).should eq nil
47
+ end
38
48
  end
39
49
 
40
50
  describe "::table" do
@@ -4,7 +4,11 @@ describe Hallon do
4
4
  end
5
5
 
6
6
  describe "API_VERSION" do
7
- specify { Hallon::API_VERSION.should == 8 }
7
+ specify { Hallon::API_VERSION.should == 9 }
8
+ end
9
+
10
+ describe "API_BUILD" do
11
+ specify { Hallon::API_BUILD.should be_a String }
8
12
  end
9
13
 
10
14
  describe "URI" do
@@ -1,13 +1,10 @@
1
1
  # coding: utf-8
2
- describe Hallon::Image, :session => true do
2
+ describe Hallon::Image do
3
3
  describe "an image instance" do
4
- let(:image) do
5
- Hallon::Session.should_receive(:instance).and_return session
6
- Hallon::Image.new(mock_image)
7
- end
8
-
9
4
  subject { image }
5
+ let(:image) { mock_session { Hallon::Image.new(mock_image) } }
10
6
 
7
+ it { should be_loaded }
11
8
  its(:status) { should be :ok }
12
9
  its(:format) { should be :jpeg }
13
10
 
@@ -34,34 +31,51 @@ describe Hallon::Image, :session => true do
34
31
  image.to_link.should eq Hallon::Link.new("spotify:image:#{image.id}")
35
32
  end
36
33
  end
37
- end
38
34
 
39
- context "created from an url" do
40
- subject { Hallon::Image.new("http://open.spotify.com/image/c78f091482e555bd2ffacfcd9cbdc0714b221663", session) }
41
- its(:id) { should eq "c78f091482e555bd2ffacfcd9cbdc0714b221663" }
42
- end
35
+ describe "#==" do
36
+ it "should not fail given an object that does not respond to id" do
37
+ o = Object.new
38
+ def o.id
39
+ raise NoMethodError
40
+ end
43
41
 
44
- context "created from an uri" do
45
- subject { Hallon::Image.new("spotify:image:c78f091482e555bd2ffacfcd9cbdc0714b221663", session) }
46
- its(:id) { should eq "c78f091482e555bd2ffacfcd9cbdc0714b221663" }
42
+ image.should_not eq o
43
+ end
44
+ end
47
45
  end
48
46
 
49
- context "created from an id" do
50
- subject { Hallon::Image.new(mock_image_id, session) }
51
- its(:id) { should eq mock_image_hex }
52
- end
47
+ describe "instantiation" do
48
+ subject do
49
+ mock_session do
50
+ Hallon::Image.new(image_uri)
51
+ end
52
+ end
53
53
 
54
- context "created from a link" do
55
- subject { Hallon::Image.new(Hallon::Link.new("spotify:image:c78f091482e555bd2ffacfcd9cbdc0714b221663"), session) }
56
- its(:id) { should eq "c78f091482e555bd2ffacfcd9cbdc0714b221663" }
54
+ context "created from an url" do
55
+ let(:image_uri) { "http://open.spotify.com/image/c78f091482e555bd2ffacfcd9cbdc0714b221663" }
56
+ its(:id) { should eq "c78f091482e555bd2ffacfcd9cbdc0714b221663" }
57
+ end
58
+
59
+ context "created from an uri" do
60
+ let(:image_uri) { "spotify:image:c78f091482e555bd2ffacfcd9cbdc0714b221663" }
61
+ its(:id) { should eq "c78f091482e555bd2ffacfcd9cbdc0714b221663" }
62
+ end
63
+
64
+ context "created from an id" do
65
+ let(:image_uri) { mock_image_id }
66
+ its(:id) { should eq mock_image_hex }
67
+ end
68
+
69
+ context "created from a link" do
70
+ let(:image_uri) { Hallon::Link.new("spotify:image:c78f091482e555bd2ffacfcd9cbdc0714b221663") }
71
+ its(:id) { should eq "c78f091482e555bd2ffacfcd9cbdc0714b221663" }
72
+ end
57
73
  end
58
74
 
59
75
  describe "callbacks" do
60
- it "should trigger :load when loaded" do
61
- pending "waiting for mockspotify event system"
62
-
76
+ it "should trigger :load when loaded", :pending => true do
63
77
  uri = "spotify:image:c78f091482e555bd2ffacfcd9cbdc0714b221663"
64
- image = Hallon::Image.new(uri, session)
78
+ image = Hallon::Image.new(uri)
65
79
  image.should_not be_loaded
66
80
  image.should_receive(:trigger).with(:load).once
67
81
 
@@ -8,6 +8,7 @@ describe Hallon::Linkable do
8
8
  end
9
9
 
10
10
  let(:object) { klass.new }
11
+ let(:pointer) { FFI::Pointer.new(1) }
11
12
 
12
13
  before(:each) { Spotify.stub(:link_as_search) }
13
14
 
@@ -19,7 +20,7 @@ describe Hallon::Linkable do
19
20
 
20
21
  describe "#from_link" do
21
22
  it "should call the appropriate Spotify function" do
22
- Spotify.should_receive(:link_as_search)
23
+ Spotify.should_receive(:link_as_search).and_return(pointer)
23
24
 
24
25
  klass.from_link(:as_search)
25
26
  object.from_link 'spotify:search:moo'
@@ -29,13 +30,20 @@ describe Hallon::Linkable do
29
30
  Spotify.should_not_receive(:link_as_search)
30
31
 
31
32
  called = false
32
- klass.from_link(:as_search) { called = true }
33
+ klass.from_link(:as_search) { called = true and pointer }
33
34
  expect { object.from_link 'spotify:search:whatever' }.to change { called }
34
35
  end
35
36
 
36
37
  it "should pass extra parameters to the defining block" do
37
- klass.from_link(:search) { |link, *args| args }
38
- object.from_link("spotify:search:burgestrand", :cool, 5).should eq [:cool, 5]
38
+ passed_args = nil
39
+ klass.from_link(:search) { |link, *args| passed_args = args and pointer }
40
+ object.from_link("spotify:search:burgestrand", :cool, 5)
41
+ passed_args.should eq [:cool, 5]
42
+ end
43
+
44
+ it "should fail, given a null pointer" do
45
+ klass.from_link(:as_search)
46
+ expect { object.from_link FFI::Pointer.new(0) }.to raise_error(Hallon::Error)
39
47
  end
40
48
  end
41
49
  end
@@ -29,6 +29,11 @@ describe Hallon::Observable do
29
29
  subject.on(:a) { |event, *args| event }
30
30
  subject.trigger(:a).should eq nil
31
31
  end
32
+
33
+ it "should convert the event to a symbol" do
34
+ subject.on("a") { raise "hell" }
35
+ expect { subject.trigger(:a) }.to raise_error("hell")
36
+ end
32
37
  end
33
38
 
34
39
  describe "#trigger and #on" do
@@ -0,0 +1,73 @@
1
+ describe Hallon::Player do
2
+ let(:player) { Hallon::Player.new(session) }
3
+ let(:track) { Hallon::Track.new(mock_track) }
4
+
5
+ describe ".bitrates" do
6
+ it "should be a list of symbols in ascending order" do
7
+ Hallon::Player.bitrates.should eq %w[96k 160k 320k].map(&:to_sym)
8
+ end
9
+ end
10
+
11
+ describe "#bitrate=" do
12
+ it "should not fail horribly given a correct bitrate" do
13
+ player.bitrate = :'96k'
14
+ end
15
+
16
+ it "should fail horrible given a bad bitrate" do
17
+ expect { player.bitrate = :'100k' }.to raise_error(ArgumentError)
18
+ end
19
+ end
20
+
21
+ describe "#load" do
22
+ it "should load the given track" do
23
+ Spotify.should_receive(:session_player_load).with(session.pointer, track.pointer)
24
+ player.load(track)
25
+ end
26
+
27
+ it "should raise an error if load was unsuccessful" do
28
+ Spotify.should_receive(:session_player_load).and_return(:track_not_playable)
29
+ expect { player.load(track) }.to raise_error(Hallon::Error, /TRACK_NOT_PLAYABLE/)
30
+ end
31
+ end
32
+
33
+ describe "#stop" do
34
+ it "should unload the currently loaded track" do
35
+ Spotify.should_receive(:session_player_unload).with(session.pointer)
36
+ player.stop
37
+ end
38
+ end
39
+
40
+ describe "#prefetch" do
41
+ it "should set up given track for prefetching" do
42
+ Spotify.should_receive(:session_player_prefetch).with(session.pointer, track.pointer)
43
+ player.prefetch(track)
44
+ end
45
+ end
46
+
47
+ describe "#play" do
48
+ it "should start playback of given track" do
49
+ Spotify.should_receive(:session_player_play).with(session.pointer, true)
50
+ player.play
51
+ end
52
+
53
+ it "should load and play given track if one was given" do
54
+ Spotify.should_receive(:session_player_load).with(session.pointer, track.pointer)
55
+ Spotify.should_receive(:session_player_play).with(session.pointer, true)
56
+ player.play(track)
57
+ end
58
+ end
59
+
60
+ describe "#pause" do
61
+ it "should stop playback of given track" do
62
+ Spotify.should_receive(:session_player_play).with(session.pointer, false)
63
+ player.pause
64
+ end
65
+ end
66
+
67
+ describe "#seek" do
68
+ it "should set up the currently loaded track at given position" do
69
+ Spotify.should_receive(:session_player_seek).with(session.pointer, 1000)
70
+ player.seek(1)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,80 @@
1
+ describe Hallon::Search do
2
+ subject { search }
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
8
+ end
9
+
10
+ describe ".new" do
11
+ it "should have some sane defaults" do
12
+ Spotify.should_receive(:search_create).with(session.pointer, "my query", 0, 25, 0, 25, 0, 25, anything, anything).and_return(mock_search)
13
+ mock_session { Hallon::Search.new("my query") }
14
+ end
15
+
16
+ it "should allow you to customize the defaults" do
17
+ Spotify.should_receive(:search_create).with(session.pointer, "my query", 1, 2, 3, 4, 5, 6, anything, anything).and_return(mock_search)
18
+ my_params = {
19
+ :tracks_offset => 1,
20
+ :tracks => 2,
21
+ :albums_offset => 3,
22
+ :albums => 4,
23
+ :artists_offset => 5,
24
+ :artists => 6
25
+ }
26
+
27
+ mock_session { Hallon::Search.new("my query", my_params) }
28
+ end
29
+ end
30
+
31
+ describe ".genres" do
32
+ subject { Hallon::Search.genres }
33
+
34
+ it { should include :jazz }
35
+ it { should be_a Array }
36
+ it { should_not be_empty }
37
+ end
38
+
39
+ describe ".search" do
40
+ subject do
41
+ Spotify.should_receive(:radio_search_create).and_return(mock_search)
42
+
43
+ mock_session do
44
+ search = Hallon::Search.radio(1990..2010, :jazz, :punk)
45
+ end
46
+ end
47
+
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
+ end
54
+
55
+ it { should be_loaded }
56
+ its(:error) { should eq :ok }
57
+ its('tracks.size') { should eq 2 }
58
+ # ^ should be enough
59
+ end
60
+
61
+ it { should be_a Hallon::Observable }
62
+ it { should be_loaded }
63
+ its(:error) { should eq :ok }
64
+ its(:query) { should eq "my query" }
65
+ its(:did_you_mean) { should eq "another thing" }
66
+
67
+ its('tracks.size') { should eq 2 }
68
+ its('tracks.to_a') { should eq instantiate(Hallon::Track, mock_track, mock_track_two) }
69
+ its('total_tracks') { should eq 1337 }
70
+
71
+ its('albums.size') { should eq 1 }
72
+ its('albums.to_a') { should eq instantiate(Hallon::Album, mock_album) }
73
+ its('total_albums') { should eq 42 }
74
+
75
+ its('artists.size') { should eq 2 }
76
+ its('artists.to_a') { should eq instantiate(Hallon::Artist, mock_artist, mock_artist_two) }
77
+ its('total_artists') { should eq 81104 }
78
+
79
+ its(:to_link) { should eq Hallon::Link.new("spotify:search:#{search.query}") }
80
+ end
@@ -2,13 +2,27 @@
2
2
  describe Hallon::Session do
3
3
  it { Hallon::Session.should_not respond_to :new }
4
4
 
5
- describe "#instance" do
5
+ describe ".initialize and .instance" do
6
+ before { Hallon.instance_eval { @__instance = nil } }
7
+ after { Hallon.instance_eval { @__instance = nil } }
8
+
9
+ it "should fail if calling instance before initialize" do
10
+ expect { Hallon.instance }.to raise_error
11
+ end
12
+
13
+ it "should fail if calling initialize twice" do
14
+ expect {
15
+ Hallon.initialize
16
+ Hallon.initialize
17
+ }.to raise_error
18
+ end
19
+ end
20
+
21
+ describe ".new" do
6
22
  it "should require an application key" do
7
23
  expect { Hallon::Session.send(:new) }.to raise_error(ArgumentError)
8
24
  end
9
- end
10
25
 
11
- describe "#new" do
12
26
  it "should fail on an invalid application key" do
13
27
  expect { create_session(false) }.to raise_error(Hallon::Error, /BAD_APPLICATION_KEY/)
14
28
  end
@@ -52,12 +66,50 @@ describe Hallon::Session do
52
66
  end
53
67
  end
54
68
 
55
- session.process_events_on(:bogus) { |e| e.inspect }.should eq ":bogus"
69
+ session.process_events_on(:bogus) { |e| e == :bogus }.should be_true
56
70
  end
57
71
 
58
72
  it "should time out if waiting for events too long" do
59
- session.should_receive(:process_events).once.and_return { session.trigger(:whatever) }
60
- session.process_events_on(:whatever) { |e| e.inspect }.should eq "nil"
73
+ session.should_receive(:process_events).once # and do nothing
74
+ session.wait_for(:ever) { |x| x }.should eq :timeout
75
+ end
76
+
77
+ it "should call the given block once before waiting" do
78
+ session.should_not_receive(:process_events)
79
+ session.process_events_on { true }
80
+ end
81
+ end
82
+
83
+ describe "#relogin" do
84
+ it "should raise if no credentials have been saved" do
85
+ expect { session.relogin }.to raise_error(Hallon::Error)
86
+ end
87
+
88
+ it "should not raise if credentials have been saved" do
89
+ session.login 'Kim', 'pass', true
90
+ session.logout
91
+ expect { session.relogin }.to_not raise_error
92
+ session.should be_logged_in
93
+ end
94
+ end
95
+
96
+ describe "#remembered_user" do
97
+ it "should be nil if no username is stored in libspotify" do
98
+ session.remembered_user.should eq nil
99
+ end
100
+
101
+ it "should retrieve the remembered username if stored" do
102
+ session.login 'Kim', 'pass', true
103
+ session.remembered_user.should eq 'Kim'
104
+ end
105
+ end
106
+
107
+ describe "#forget_me!" do
108
+ it "should forget the currently stored user credentials" do
109
+ session.login 'Kim', 'pass', true
110
+ session.remembered_user.should eq 'Kim'
111
+ session.forget_me!
112
+ session.remembered_user.should eq nil
61
113
  end
62
114
  end
63
115
 
@@ -68,6 +120,135 @@ describe Hallon::Session do
68
120
  end
69
121
  end
70
122
 
123
+ describe "#country" do
124
+ it "should retrieve the current sessions’ country as a string" do
125
+ session.country.should eq 'SE'
126
+ end
127
+ end
128
+
129
+ describe "#star and #unstar" do
130
+ it "should be able to star and unstar tracks" do
131
+ # for track#starred?
132
+ Hallon::Session.should_receive(:instance).exactly(6).times.and_return(session)
133
+
134
+ tracks = [mock_track, mock_track_two]
135
+ tracks.map! { |x| Hallon::Track.new(x) }
136
+ tracks.all?(&:starred?).should be_true # starred by default
137
+
138
+ session.unstar(*tracks)
139
+ tracks.none?(&:starred?).should be_true
140
+
141
+ session.star(tracks[0])
142
+ tracks[0].should be_starred
143
+ tracks[1].should_not be_starred
144
+ end
145
+ end
146
+
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
+ describe "#cache_size" do
167
+ it "should default to 0" do
168
+ session.cache_size.should eq 0
169
+ end
170
+
171
+ it "should be settable" do
172
+ session.cache_size = 10
173
+ session.cache_size.should eq 10
174
+ end
175
+ end
176
+
177
+ describe ".connection_types" do
178
+ subject { Hallon::Session.connection_types }
179
+
180
+ it { should be_an Array }
181
+ it { should_not be_empty }
182
+ it { should include :wifi }
183
+ end
184
+
185
+ describe "#connection_type=" do
186
+ it "should fail given an invalid connection type" do
187
+ expect { session.connection_type = :bogus }.to raise_error(ArgumentError)
188
+ end
189
+
190
+ it "should succeed given a correct connection type" do
191
+ expect { session.connection_type = :wifi }.to_not raise_error
192
+ end
193
+ end
194
+
195
+ describe ".connection_types" do
196
+ subject { Hallon::Session.connection_rules }
197
+
198
+ it { should be_an Array }
199
+ it { should_not be_empty }
200
+ it { should include :network }
201
+ end
202
+
203
+ describe "#connection_rules=" do
204
+ it "should not fail given an invalid rule" do
205
+ expect { session.connection_rules = :lawly }.to_not raise_error
206
+ end
207
+
208
+ it "should succeed given correct connection thingy" do
209
+ expect { session.connection_rules = :allow_network, :allow_sync_over_mobile }.to_not raise_error
210
+ end
211
+
212
+ it "should combine given rules and feed to libspotify" do
213
+ Spotify.should_receive(:session_set_connection_rules).with(session.pointer, 5)
214
+ session.connection_rules = :network, :allow_sync_over_mobile
215
+ end
216
+ end
217
+
218
+ describe "offline settings readers" do
219
+ subject { mock_session_object }
220
+
221
+ its(:offline_time_left) { should eq 60 * 60 * 24 * 30 } # a month!
222
+ its(:offline_sync_status) { should eq mock_offline_sync_status_hash }
223
+ its(:offline_playlists_count) { should eq 7 }
224
+ its(:offline_tracks_to_sync) { should eq 3 }
225
+
226
+ specify "offline_sync_status when given false as return from libspotify" do
227
+ Spotify.should_receive(:offline_sync_get_status).and_return(false)
228
+ subject.offline_sync_status.should eq nil
229
+ end
230
+ end
231
+
232
+ describe "#offline_bitrate=" do
233
+ it "should not resync unless explicitly told so" do
234
+ Spotify.should_receive(:session_preferred_offline_bitrate).with(session.pointer, :'96k', false)
235
+ session.offline_bitrate = :'96k'
236
+ end
237
+
238
+ it "should resync if asked to" do
239
+ Spotify.should_receive(:session_preferred_offline_bitrate).with(session.pointer, :'96k', true)
240
+ session.offline_bitrate = :'96k', true
241
+ end
242
+
243
+ it "should fail given an invalid value" do
244
+ expect { session.offline_bitrate = :hocum }.to raise_error(ArgumentError)
245
+ end
246
+
247
+ it "should succeed given a proper value" do
248
+ expect { session.offline_bitrate = :'96k' }.to_not raise_error
249
+ end
250
+ end
251
+
71
252
  context "when logged in", :logged_in => true do
72
253
  it "should be logged in" do
73
254
  session.should be_logged_in