hallon 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/CHANGELOG.md +43 -1
  2. data/Gemfile +0 -2
  3. data/LICENSE.txt +1 -1
  4. data/README.markdown +94 -44
  5. data/examples/playing_audio.rb +4 -5
  6. data/lib/hallon.rb +20 -0
  7. data/lib/hallon/album.rb +13 -12
  8. data/lib/hallon/album_browse.rb +1 -0
  9. data/lib/hallon/artist.rb +13 -12
  10. data/lib/hallon/artist_browse.rb +1 -0
  11. data/lib/hallon/base.rb +2 -0
  12. data/lib/hallon/image.rb +18 -10
  13. data/lib/hallon/loadable.rb +24 -0
  14. data/lib/hallon/observable.rb +1 -1
  15. data/lib/hallon/observable/playlist.rb +10 -16
  16. data/lib/hallon/observable/playlist_container.rb +12 -6
  17. data/lib/hallon/player.rb +3 -3
  18. data/lib/hallon/playlist.rb +34 -11
  19. data/lib/hallon/playlist_container.rb +10 -4
  20. data/lib/hallon/search.rb +1 -0
  21. data/lib/hallon/session.rb +2 -2
  22. data/lib/hallon/toplist.rb +17 -12
  23. data/lib/hallon/track.rb +1 -0
  24. data/lib/hallon/user.rb +48 -11
  25. data/lib/hallon/version.rb +1 -1
  26. data/spec/hallon/album_browse_spec.rb +2 -0
  27. data/spec/hallon/album_spec.rb +14 -7
  28. data/spec/hallon/artist_browse_spec.rb +2 -0
  29. data/spec/hallon/artist_spec.rb +14 -8
  30. data/spec/hallon/hallon_spec.rb +12 -0
  31. data/spec/hallon/image_spec.rb +18 -9
  32. data/spec/hallon/loadable_spec.rb +46 -0
  33. data/spec/hallon/observable/playlist_spec.rb +11 -5
  34. data/spec/hallon/observable_spec.rb +6 -0
  35. data/spec/hallon/playlist_container_spec.rb +6 -0
  36. data/spec/hallon/playlist_spec.rb +21 -4
  37. data/spec/hallon/search_spec.rb +2 -0
  38. data/spec/hallon/toplist_spec.rb +40 -23
  39. data/spec/hallon/track_spec.rb +2 -0
  40. data/spec/hallon/user_post_spec.rb +75 -0
  41. data/spec/hallon/user_spec.rb +7 -11
  42. data/spec/spec_helper.rb +2 -2
  43. metadata +20 -16
  44. data/examples/audio_driver.rb +0 -55
@@ -3,5 +3,5 @@ module Hallon
3
3
  # Current release version of Hallon
4
4
  #
5
5
  # @see http://semver.org/
6
- VERSION = [0, 13, 0].join('.')
6
+ VERSION = [0, 14, 0].join('.')
7
7
  end
@@ -1,5 +1,7 @@
1
1
  # coding: utf-8
2
2
  describe Hallon::AlbumBrowse do
3
+ it { should be_a Hallon::Loadable }
4
+
3
5
  describe ".new" do
4
6
  it "should raise an error if the browse request failed" do
5
7
  Spotify.should_receive(:albumbrowse_create).and_return(null_pointer)
@@ -1,5 +1,8 @@
1
1
  # coding: utf-8
2
+ #
2
3
  describe Hallon::Album do
4
+ it { should be_a Hallon::Loadable }
5
+
3
6
  it_should_behave_like "a Linkable object" do
4
7
  let(:spotify_uri) { "spotify:album:1xvnWMz2PNFf7mXOSRuLws" }
5
8
  end
@@ -28,21 +31,25 @@ describe Hallon::Album do
28
31
  end
29
32
  end
30
33
 
31
- describe "cover" do
34
+ describe "#cover" do
32
35
  it "should be nil if there is no image" do
33
36
  Spotify.should_receive(:album_cover).and_return(null_pointer)
34
37
  album.cover.should be_nil
35
-
36
- Spotify.should_receive(:link_create_from_album_cover).and_return(null_pointer)
37
- album.cover(false).should be_nil
38
38
  end
39
39
 
40
40
  it "should be an image if it exists" do
41
- mock_session { album.cover.id.should eq mock_image_hex }
41
+ stub_session { album.cover.should eq Hallon::Image.new(mock_image_id) }
42
+ end
43
+ end
44
+
45
+ describe "#cover_link" do
46
+ it "should be nil if there is no image" do
47
+ Spotify.should_receive(:link_create_from_album_cover).and_return(null_pointer)
48
+ album.cover_link.should be_nil
42
49
  end
43
50
 
44
- it "should be a link if requested" do
45
- album.cover(false).to_str.should eq "spotify:image:3ad93423add99766e02d563605c6e76ed2b0e450"
51
+ it "should be a link if it exists" do
52
+ album.cover_link.should eq Hallon::Link.new("spotify:image:3ad93423add99766e02d563605c6e76ed2b0e450")
46
53
  end
47
54
  end
48
55
 
@@ -1,5 +1,7 @@
1
1
  # coding: utf-8
2
2
  describe Hallon::ArtistBrowse do
3
+ it { should be_a Hallon::Loadable }
4
+
3
5
  describe ".new" do
4
6
  it "should raise an error if the browse request failed" do
5
7
  Spotify.should_receive(:artistbrowse_create).and_return(null_pointer)
@@ -1,5 +1,7 @@
1
1
  # coding: utf-8
2
2
  describe Hallon::Artist do
3
+ it { should be_a Hallon::Loadable }
4
+
3
5
  it_should_behave_like "a Linkable object" do
4
6
  let(:spotify_uri) { "spotify:artist:3bftcFwl4vqRNNORRsqm1G" }
5
7
  end
@@ -29,20 +31,24 @@ describe Hallon::Artist do
29
31
  describe "#portrait" do
30
32
  let(:link) { Hallon::Link.new(mock_image_uri) }
31
33
 
32
- specify "as an image" do
33
- mock_session(2) { artist.portrait.should eq Hallon::Image.new(mock_image_id) }
34
+ it "should be nil if an image is not available" do
35
+ Spotify.should_receive(:artist_portrait).and_return(null_pointer)
36
+ artist.portrait.should be_nil
34
37
  end
35
38
 
36
- specify "as a link" do
37
- artist.portrait(false).should eq Hallon::Link.new(mock_image_uri)
39
+ it "should be an image if it exists" do
40
+ stub_session { artist.portrait.should eq Hallon::Image.new(mock_image_id) }
38
41
  end
42
+ end
39
43
 
44
+ describe "#portrait_link" do
40
45
  it "should be nil if an image is not available" do
41
- Spotify.should_receive(:artist_portrait).and_return(null_pointer)
42
- artist.portrait.should be_nil
43
-
44
46
  Spotify.should_receive(:link_create_from_artist_portrait).and_return(null_pointer)
45
- artist.portrait(false).should be_nil
47
+ artist.portrait_link.should be_nil
48
+ end
49
+
50
+ it "should be a link if it exists" do
51
+ artist.portrait_link.should eq Hallon::Link.new(mock_image_uri)
46
52
  end
47
53
  end
48
54
  end
@@ -18,4 +18,16 @@ describe Hallon do
18
18
  it { should match uri }
19
19
  end
20
20
  end
21
+
22
+ describe "#load_timeout" do
23
+ it "should raise an error given a negative timeout" do
24
+ expect { Hallon.load_timeout = -1 }.to raise_error(ArgumentError)
25
+ end
26
+
27
+ it "should allow setting and retrieving the value" do
28
+ Hallon.load_timeout.should eq 5
29
+ Hallon.load_timeout = 0.2
30
+ Hallon.load_timeout.should eq 0.2
31
+ end
32
+ end
21
33
  end
@@ -2,6 +2,8 @@
2
2
  require 'ostruct'
3
3
 
4
4
  describe Hallon::Image do
5
+ it { described_class.should include Hallon::Loadable }
6
+
5
7
  it_should_behave_like "a Linkable object" do
6
8
  let(:spotify_uri) { "spotify:image:#{mock_image_hex}" }
7
9
  let(:custom_object) { mock_image_hex }
@@ -24,9 +26,16 @@ describe Hallon::Image do
24
26
  its(:status) { should be :ok }
25
27
  its(:format) { should be :jpeg }
26
28
 
27
- describe "id" do
28
- specify("in hex") { subject.id.should eq mock_image_hex }
29
- specify("raw") { subject.id(true).should eq mock_image_id }
29
+ describe "#id" do
30
+ it "should return the image id as a hexadecimal string" do
31
+ image.id.should eq mock_image_hex
32
+ end
33
+ end
34
+
35
+ describe "#raw_id" do
36
+ it "should return the image id as a binary string" do
37
+ image.raw_id.should eq mock_image_id
38
+ end
30
39
  end
31
40
 
32
41
  describe "#data" do
@@ -51,17 +60,17 @@ describe Hallon::Image do
51
60
  it "should compare ids (but only if other is an Image)" do
52
61
  other = double
53
62
  other.should_receive(:is_a?).with(Hallon::Image).and_return(true)
54
- other.should_receive(:id).with(true).and_return(image.id(true))
63
+ other.should_receive(:raw_id).and_return(image.raw_id)
55
64
 
56
- image.should === other
57
- image.should_not === double
65
+ image.should eq other
66
+ image.should_not eq double
58
67
  end
59
68
 
60
69
  it "should not call #id if other is not an image" do
61
- o = Object.new
62
- o.should_not_receive(:id)
70
+ other = double
71
+ other.should_not_receive(:raw_id)
63
72
 
64
- image.should_not eq o
73
+ image.should_not eq other
65
74
  end
66
75
  end
67
76
  end
@@ -0,0 +1,46 @@
1
+ # coding: utf-8
2
+
3
+ describe Hallon::Loadable do
4
+ let(:session) { double(:session, :process_events => 5) }
5
+ let(:loadable) do
6
+ _session = session
7
+ Class.new do
8
+ include Hallon::Loadable
9
+
10
+ define_method(:session) { _session }
11
+ end.new
12
+ end
13
+
14
+ describe "#load" do
15
+ it "should timeout if the object does not load in time" do
16
+ Hallon.stub(:load_timeout).and_return(0.001)
17
+ loadable.stub(:loaded?).and_return(false)
18
+ expect { loadable.load }.to raise_error(Hallon::TimeoutError)
19
+ end
20
+
21
+ it "should use the Hallon.load_timeout by default" do
22
+ Hallon.should_receive(:load_timeout).and_return(0.075)
23
+ Timeout.should_receive(:timeout).with(0.075, Hallon::TimeoutError).and_yield
24
+ loadable.stub(:loaded?).and_return(true)
25
+ loadable.load
26
+ end
27
+
28
+ it "should return the object in question on success" do
29
+ loadable.stub(:loaded?).and_return(true)
30
+ loadable.load.should eq loadable
31
+ end
32
+
33
+ it "should raise an error when status is an error" do
34
+ session.should_receive(:process_events).once
35
+ loadable.stub(:loaded?).and_return(false)
36
+ loadable.stub(:status).and_return(:other_permanent)
37
+ expect { loadable.load }.to raise_error(Hallon::Error)
38
+ end
39
+
40
+ it "should not raise an error when status is_loading" do
41
+ loadable.stub(:loaded?).and_return(false, true)
42
+ loadable.stub(:status).and_return(:is_loading)
43
+ loadable.load.should eq loadable
44
+ end
45
+ end
46
+ end
@@ -1,7 +1,13 @@
1
1
  describe Hallon::Observable::Playlist do
2
2
  let(:trackpointers_size) { 2 }
3
+ let(:track_index_pointers) do
4
+ tracks = FFI::MemoryPointer.new(:pointer, trackpointers_size)
5
+ tracks.write_array_of_int([0, 1])
6
+ tracks
7
+ end
8
+
3
9
  let(:trackpointers) do
4
- tracks = FFI::MemoryPointer.new(:pointer, 2)
10
+ tracks = FFI::MemoryPointer.new(:pointer, trackpointers_size)
5
11
  tracks.write_array_of_pointer([mock_track, mock_track_two])
6
12
  tracks
7
13
  end
@@ -16,13 +22,13 @@ describe Hallon::Observable::Playlist do
16
22
  end
17
23
 
18
24
  specification_for_callback "tracks_removed" do
19
- let(:input) { [a_pointer, trackpointers, trackpointers_size, :userdata] }
20
- let(:output) { [tracks, subject] }
25
+ let(:input) { [a_pointer, track_index_pointers, trackpointers_size, :userdata] }
26
+ let(:output) { [[0, 1], subject] }
21
27
  end
22
28
 
23
29
  specification_for_callback "tracks_moved" do
24
- let(:input) { [a_pointer, trackpointers, trackpointers_size, 7, :userdata] }
25
- let(:output) { [tracks, 7, subject] }
30
+ let(:input) { [a_pointer, track_index_pointers, trackpointers_size, 7, :userdata] }
31
+ let(:output) { [[0, 1], 7, subject] }
26
32
  end
27
33
 
28
34
  specification_for_callback "playlist_renamed" do
@@ -136,6 +136,12 @@ describe Hallon::Observable do
136
136
  expect { subject.send(:subscribe_for_callbacks) {} }.to raise_error(ArgumentError)
137
137
  end
138
138
 
139
+ it "should do nothing if the result is a null pointer" do
140
+ klass.should_not_receive(:subscribe)
141
+ klass.any_instance.stub(:pointer).and_return(FFI::Pointer::NULL)
142
+ subject.send(:subscribe_for_callbacks) {}
143
+ end
144
+
139
145
  it "should always yield the *same* object" do
140
146
  a = klass.new
141
147
  b = klass.new
@@ -1,5 +1,7 @@
1
1
  # coding: utf-8
2
2
  describe Hallon::PlaylistContainer do
3
+ it { should be_a Hallon::Loadable }
4
+
3
5
  let(:container) { Hallon::PlaylistContainer.new(mock_container) }
4
6
 
5
7
  subject { container }
@@ -18,6 +20,10 @@ describe Hallon::PlaylistContainer do
18
20
  container.contents[-1].should eq playlist
19
21
  end.to change{ container.size }.by(1)
20
22
  end
23
+
24
+ it "should raise an error if the name is invalid" do
25
+ expect { container.add(" ") }.to raise_error(ArgumentError)
26
+ end
21
27
  end
22
28
 
23
29
  context "given a string that’s a valid spotify playlist uri" do
@@ -2,9 +2,11 @@
2
2
  require 'time'
3
3
 
4
4
  describe Hallon::Playlist do
5
+ it { should be_a Hallon::Loadable }
6
+
5
7
  it_should_behave_like "a Linkable object" do
6
8
  let(:spotify_uri) { "spotify:user:burgestrand:playlist:07AX9IY9Hqmj1RqltcG0fi" }
7
- let(:described_class) { Hallon::Playlist.tap { |o| stub_session(o.any_instance) } }
9
+ let(:described_class) { stub_session(Hallon::Playlist) }
8
10
  end
9
11
 
10
12
  subject { playlist }
@@ -12,6 +14,20 @@ describe Hallon::Playlist do
12
14
  Hallon::Playlist.new(mock_playlist)
13
15
  end
14
16
 
17
+ describe ".invalid_name?" do
18
+ it "should return false if the name is valid" do
19
+ Hallon::Playlist.invalid_name?("Moo").should be_false
20
+ end
21
+
22
+ it "should return an error message when the name is blank" do
23
+ Hallon::Playlist.invalid_name?(" ").should match "blank"
24
+ end
25
+
26
+ it "should return an error message when the name is too long" do
27
+ Hallon::Playlist.invalid_name?("Moo" * 256).should match "bytes"
28
+ end
29
+ end
30
+
15
31
  it { should be_loaded }
16
32
  it { should be_collaborative }
17
33
  it { should_not be_pending }
@@ -108,8 +124,9 @@ describe Hallon::Playlist do
108
124
  playlist.tracks.to_a.should eq new_tracks
109
125
  end
110
126
 
111
- it "should raise an error if the operation cannot be completed" do
112
- expect { playlist.remove(-1) }.to raise_error(Hallon::Error)
127
+ it "should raise an error if given invalid parameters" do
128
+ expect { playlist.remove(-1) }.to raise_error(ArgumentError)
129
+ expect { playlist.remove(playlist.size) }.to raise_error(ArgumentError)
113
130
  end
114
131
  end
115
132
 
@@ -135,7 +152,7 @@ describe Hallon::Playlist do
135
152
  end
136
153
 
137
154
  it "should fail given an empty name" do
138
- expect { playlist.name = "" }.to raise_error(Hallon::Error)
155
+ expect { playlist.name = "" }.to raise_error(ArgumentError)
139
156
  end
140
157
 
141
158
  it "should fail given a name of only spaces" do
@@ -1,4 +1,6 @@
1
1
  describe Hallon::Search do
2
+ it { should be_a Hallon::Loadable }
3
+
2
4
  subject { search }
3
5
  let(:search) do
4
6
  Spotify.registry_add 'spotify:search:my query', mock_search
@@ -1,23 +1,56 @@
1
1
  describe Hallon::Toplist do
2
+ it { should be_a Hallon::Loadable }
3
+
2
4
  let(:toplist) do
3
5
  Spotify.registry_add 'spotify:toplist:artists:everywhere', mock_toplistbrowse
4
6
  mock_session { Hallon::Toplist.new(:artists) }
5
7
  end
6
-
7
8
  subject { toplist }
8
9
 
9
10
  it { should be_a Hallon::Observable }
11
+
12
+ describe ".new" do
13
+ it "should fail given an invalid type" do
14
+ expect { stub_session { Hallon::Toplist.new(:invalid_type) } }.to raise_error(ArgumentError, /invalid enum value/)
15
+ end
16
+
17
+ it "should pass the username given a string to libspotify" do
18
+ Spotify.registry_add 'spotify:toplist:user:Kim:tracks', mock_toplistbrowse
19
+ stub_session { Hallon::Toplist.new(:tracks, "Kim").should be_loaded }
20
+ end
21
+
22
+ it "should pass the correct region to libspotify" do
23
+ Spotify.registry_add 'spotify:toplist:tracks:SE', mock_toplistbrowse
24
+ mock_session { Hallon::Toplist.new(:tracks, :se).should be_loaded }
25
+ end
26
+ end
27
+
10
28
  it { should be_loaded }
11
29
  its(:status) { should eq :ok }
12
30
 
13
- its('artists.size') { should eq 2 }
14
- its('artists.to_a') { should eq instantiate(Hallon::Artist, mock_artist, mock_artist_two) }
31
+ describe "#type" do
32
+ it "should be the same as the type given to .new" do
33
+ toplist = mock_session { Hallon::Toplist.new(:tracks, :se) }
34
+ toplist.type.should eq :tracks
35
+ end
36
+ end
37
+
38
+ describe "#results" do
39
+ it "should return an enumerator of the correct type" do
40
+ toplist.should_receive(:type).and_return(:artists)
41
+ toplist.results.to_a.should eq instantiate(Hallon::Artist, mock_artist, mock_artist_two)
42
+ end
15
43
 
16
- its('albums.size') { should eq 1 }
17
- its('albums.to_a') { should eq instantiate(Hallon::Album, mock_album) }
44
+ it "should return an enumerator of the correct type" do
45
+ toplist.should_receive(:type).and_return(:albums)
46
+ toplist.results.to_a.should eq instantiate(Hallon::Album, mock_album)
47
+ end
18
48
 
19
- its('tracks.size') { should eq 2 }
20
- its('tracks.to_a') { should eq instantiate(Hallon::Track, mock_track, mock_track_two) }
49
+ it "should return an enumerator of the correct type" do
50
+ toplist.should_receive(:type).and_return(:tracks)
51
+ toplist.results.to_a.should eq instantiate(Hallon::Track, mock_track, mock_track_two)
52
+ end
53
+ end
21
54
 
22
55
  describe "#request_duration" do
23
56
  it "should return the request duration in seconds" do
@@ -29,20 +62,4 @@ describe Hallon::Toplist do
29
62
  toplist.request_duration.should be_nil
30
63
  end
31
64
  end
32
-
33
- describe ".new" do
34
- it "should fail given an invalid type" do
35
- expect { mock_session { Hallon::Toplist.new(:invalid_type) } }.to raise_error(ArgumentError, /invalid enum value/)
36
- end
37
-
38
- it "should pass the username given a string to libspotify" do
39
- Spotify.registry_add 'spotify:toplist:user:Kim', mock_toplistbrowse
40
- mock_session { Hallon::Toplist.new(:tracks, "Kim").should be_loaded }
41
- end
42
-
43
- it "should pass the correct region to libspotify" do
44
- Spotify.registry_add 'spotify:toplist:tracks:SE', mock_toplistbrowse
45
- mock_session { Hallon::Toplist.new(:tracks, :se).should be_loaded }
46
- end
47
- end
48
65
  end
@@ -1,5 +1,7 @@
1
1
  # coding: utf-8
2
2
  describe Hallon::Track do
3
+ it { should be_a Hallon::Loadable }
4
+
3
5
  it_should_behave_like "a Linkable object" do
4
6
  let(:spotify_uri) { "spotify:track:7N2Vc8u56VGA4KUrGbikC2#01:00" }
5
7
  end