hallon 0.13.0 → 0.14.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 (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