hallon 0.8.0 → 0.9.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 (59) hide show
  1. data/.travis.yml +2 -0
  2. data/CHANGELOG +43 -0
  3. data/Gemfile +2 -0
  4. data/README.markdown +21 -13
  5. data/Rakefile +84 -23
  6. data/dev/login.rb +16 -0
  7. data/examples/adding_tracks_to_playlist.rb +49 -0
  8. data/examples/logging_in.rb +1 -6
  9. data/examples/show_published_playlists_of_user.rb +9 -19
  10. data/hallon.gemspec +1 -1
  11. data/lib/hallon.rb +3 -2
  12. data/lib/hallon/album.rb +55 -41
  13. data/lib/hallon/album_browse.rb +41 -37
  14. data/lib/hallon/artist.rb +30 -21
  15. data/lib/hallon/artist_browse.rb +59 -41
  16. data/lib/hallon/base.rb +68 -5
  17. data/lib/hallon/enumerator.rb +1 -0
  18. data/lib/hallon/error.rb +3 -0
  19. data/lib/hallon/ext/spotify.rb +169 -36
  20. data/lib/hallon/image.rb +30 -44
  21. data/lib/hallon/link.rb +29 -43
  22. data/lib/hallon/linkable.rb +68 -20
  23. data/lib/hallon/observable.rb +0 -1
  24. data/lib/hallon/player.rb +21 -7
  25. data/lib/hallon/playlist.rb +291 -0
  26. data/lib/hallon/playlist_container.rb +27 -0
  27. data/lib/hallon/search.rb +52 -45
  28. data/lib/hallon/session.rb +129 -81
  29. data/lib/hallon/toplist.rb +37 -19
  30. data/lib/hallon/track.rb +68 -45
  31. data/lib/hallon/user.rb +69 -33
  32. data/lib/hallon/version.rb +1 -1
  33. data/spec/hallon/album_browse_spec.rb +15 -9
  34. data/spec/hallon/album_spec.rb +15 -15
  35. data/spec/hallon/artist_browse_spec.rb +28 -9
  36. data/spec/hallon/artist_spec.rb +30 -14
  37. data/spec/hallon/enumerator_spec.rb +0 -1
  38. data/spec/hallon/hallon_spec.rb +20 -1
  39. data/spec/hallon/image_spec.rb +18 -41
  40. data/spec/hallon/link_spec.rb +10 -12
  41. data/spec/hallon/linkable_spec.rb +37 -18
  42. data/spec/hallon/player_spec.rb +8 -0
  43. data/spec/hallon/playlist_container_spec.rb +75 -0
  44. data/spec/hallon/playlist_spec.rb +204 -0
  45. data/spec/hallon/search_spec.rb +19 -16
  46. data/spec/hallon/session_spec.rb +61 -29
  47. data/spec/hallon/spotify_spec.rb +30 -0
  48. data/spec/hallon/toplist_spec.rb +22 -14
  49. data/spec/hallon/track_spec.rb +62 -21
  50. data/spec/hallon/user_spec.rb +47 -36
  51. data/spec/mockspotify.rb +35 -10
  52. data/spec/mockspotify/mockspotify_spec.rb +22 -0
  53. data/spec/spec_helper.rb +7 -3
  54. data/spec/support/common_objects.rb +91 -16
  55. data/spec/support/shared_for_linkable_objects.rb +39 -0
  56. metadata +30 -20
  57. data/Termfile +0 -7
  58. data/lib/hallon/synchronizable.rb +0 -32
  59. data/spec/hallon/synchronizable_spec.rb +0 -19
@@ -1,16 +1,17 @@
1
1
  # coding: utf-8
2
2
  describe Hallon::Album do
3
- subject { Hallon::Album.new(mock_album) }
3
+ it_should_behave_like "a Linkable object" do
4
+ let(:spotify_uri) { "spotify:album:1xvnWMz2PNFf7mXOSRuLws" }
5
+ end
6
+
7
+ let(:album) { Hallon::Album.new(mock_album) }
8
+ subject { album }
4
9
 
5
10
  its(:name) { should eq "Finally Woken" }
6
- its(:year) { should be 2004 }
11
+ its(:release_year) { should be 2004 }
7
12
  its(:type) { should be :single }
8
-
9
13
  its(:browse) do
10
- mock_session do
11
- Spotify.should_receive(:albumbrowse_create).exactly(2).times.and_return(mock_albumbrowse)
12
- should eq Hallon::AlbumBrowse.new(mock_album)
13
- end
14
+ mock_session { should eq Hallon::AlbumBrowse.new(album) }
14
15
  end
15
16
 
16
17
  it { should be_available }
@@ -19,27 +20,26 @@ describe Hallon::Album do
19
20
  describe "artist" do
20
21
  it "should be nil if there is no artist" do
21
22
  Spotify.should_receive(:album_artist).and_return(null_pointer)
22
- subject.artist.should be_nil
23
+ album.artist.should be_nil
23
24
  end
24
25
 
25
26
  it "should be an artist if it exists" do
26
- subject.artist.should be_a Hallon::Artist
27
+ album.artist.should eq Hallon::Artist.new(mock_artist)
27
28
  end
28
29
  end
29
30
 
30
31
  describe "cover" do
31
32
  it "should be nil if there is no image" do
32
33
  Spotify.should_receive(:album_cover).and_return(null_pointer)
33
- subject.cover.should be_nil
34
+ album.cover.should be_nil
34
35
  end
35
36
 
36
37
  it "should be an image if it exists" do
37
- FFI::MemoryPointer.new(:string, 20) do |ptr|
38
- ptr.write_string(mock_image_id)
38
+ mock_session { album.cover.id.should eq mock_image_hex }
39
+ end
39
40
 
40
- Spotify.should_receive(:album_cover).and_return(ptr)
41
- mock_session { subject.cover.id.should eq mock_image_hex }
42
- end
41
+ it "should be a link if requested" do
42
+ album.cover(false).to_str.should eq "spotify:image:3ad93423add99766e02d563605c6e76ed2b0e450"
43
43
  end
44
44
  end
45
45
 
@@ -1,22 +1,23 @@
1
1
  # coding: utf-8
2
2
  describe Hallon::ArtistBrowse do
3
- subject do
4
- mock_session do
5
- artist = Hallon::Artist.new(mock_artist)
6
- Spotify.should_receive(:artistbrowse_create).and_return(mock_artistbrowse)
7
- Hallon::ArtistBrowse.new(artist)
8
- end
3
+ let(:browse) do
4
+ artist = Hallon::Artist.new(mock_artist)
5
+ mock_session { Hallon::ArtistBrowse.new(artist) }
9
6
  end
10
7
 
8
+ subject { browse }
9
+
11
10
  it { should be_loaded }
12
- its(:error) { should eq :ok }
11
+ its(:status) { should eq :ok }
13
12
  its(:artist) { should eq Hallon::Artist.new(mock_artist) }
14
13
 
15
14
  its('portraits.size') { should eq 2 }
16
15
  its('portraits.to_a') do
17
- Hallon::Session.should_receive(:instance).exactly(2).times.and_return(session)
16
+ mock_session(2) { subject.map{ |img| img.id(true) }.should eq [mock_image_id, mock_image_id] }
17
+ end
18
18
 
19
- subject.map{ |img| img.id(true) }.should eq [mock_image_id, mock_image_id]
19
+ specify 'portraits(false)' do
20
+ browse.portraits(false)[0].should eq Hallon::Link.new(mock_image_link)
20
21
  end
21
22
 
22
23
  its('tracks.size') { should eq 2 }
@@ -26,4 +27,22 @@ describe Hallon::ArtistBrowse do
26
27
  its('similar_artists.size') { should eq 2 }
27
28
  its('similar_artists.to_a') { should eq [mock_artist, mock_artist_two].map{ |p| Hallon::Artist.new(p) } }
28
29
  its(:biography) { should eq 'grew up in DA BLOCK' }
30
+
31
+ describe "#request_duration" do
32
+ it "should return the request duration in seconds" do
33
+ browse.request_duration.should eq 2.751
34
+ end
35
+
36
+ it "should be nil if the request was fetched from local cache" do
37
+ Spotify.should_receive(:artistbrowse_backend_request_duration).and_return(-1)
38
+ browse.request_duration.should be_nil
39
+ end
40
+ end
41
+
42
+ describe '.types' do
43
+ subject { Hallon::ArtistBrowse.types }
44
+
45
+ it { should be_an Array }
46
+ it { should include :full }
47
+ end
29
48
  end
@@ -1,32 +1,48 @@
1
1
  # coding: utf-8
2
2
  describe Hallon::Artist do
3
- subject { Hallon::Artist.new(mock_artist) }
3
+ it_should_behave_like "a Linkable object" do
4
+ let(:spotify_uri) { "spotify:artist:3bftcFwl4vqRNNORRsqm1G" }
5
+ end
6
+
7
+ let(:artist) { Hallon::Artist.new(mock_artist) }
8
+ subject { artist }
4
9
 
5
10
  it { should be_loaded }
6
11
  its(:name) { should eq "Jem" }
7
- its(:browse) do
8
- Hallon::Session.should_receive(:instance).exactly(2).times.and_return(session)
9
- Spotify.should_receive(:artistbrowse_create).exactly(2).times.and_return(mock_artistbrowse)
10
12
 
11
- should eq Hallon::ArtistBrowse.new(mock_artist)
12
- end
13
+ describe "#browse" do
14
+ it "should return an artist browsing object" do
15
+ mock_session(2) { subject.browse.should eq Hallon::ArtistBrowse.new(mock_artist) }
16
+ end
13
17
 
14
- describe "#portrait" do
15
- let(:link) { Hallon::Link.new("spotify:image:c78f091482e555bd2ffacfcd9cbdc0714b221663") }
16
- let(:link_pointer) { FFI::Pointer.new(link.pointer.address) }
18
+ it "should default to full browsing" do
19
+ Hallon::ArtistBrowse.should_receive(:new).with(artist.pointer, :full)
20
+ artist.browse
21
+ end
17
22
 
18
- before do
19
- Hallon::Link.new(link_pointer).should eq link
20
- Spotify.should_receive(:link_create_from_artist_portrait).with(subject.pointer).and_return(link_pointer)
23
+ it "should pass the browsing type along when creating the artist browsing object" do
24
+ Hallon::ArtistBrowse.should_receive(:new).with(artist.pointer, :no_tracks)
25
+ artist.browse(:no_tracks)
21
26
  end
27
+ end
28
+
29
+ describe "#portrait" do
30
+ let(:link) { Hallon::Link.new(mock_image_uri) }
22
31
 
23
32
  specify "as an image" do
24
33
  Hallon::Session.should_receive(:instance).twice.and_return(session)
25
- subject.portrait.should eq Hallon::Image.new(link_pointer)
34
+
35
+ subject.portrait.should eq Hallon::Image.new(mock_image_id)
26
36
  end
27
37
 
28
38
  specify "as a link" do
29
- subject.portrait(false).should eq link
39
+ subject.portrait(false).should eq Hallon::Link.new(mock_image_uri)
40
+ end
41
+
42
+ it "should be nil if an image is not available" do
43
+ Spotify.should_receive(:artist_portrait).and_return(null_pointer)
44
+
45
+ subject.portrait.should be_nil
30
46
  end
31
47
  end
32
48
  end
@@ -19,7 +19,6 @@ describe Hallon::Enumerator do
19
19
 
20
20
  describe "#each" do
21
21
  it "should call the containing block" do
22
- calls = []
23
22
  enum = Hallon::Enumerator.new(4) { |i| item.get(i) }
24
23
  enum.each_with_index { |x, i| x.should eq alphabet[i] }
25
24
  end
@@ -1,10 +1,11 @@
1
+ # coding: utf-8
1
2
  describe Hallon do
2
3
  describe "VERSION" do
3
4
  specify { Hallon::VERSION.should be_a String }
4
5
  end
5
6
 
6
7
  describe "API_VERSION" do
7
- specify { Hallon::API_VERSION.should == 9 }
8
+ specify { Hallon::API_VERSION.should == 10 }
8
9
  end
9
10
 
10
11
  describe "API_BUILD" do
@@ -17,4 +18,22 @@ describe Hallon do
17
18
  it { should match uri }
18
19
  end
19
20
  end
21
+
22
+ describe "object callbacks" do
23
+ pending <<-REASON
24
+
25
+ Once callbacks are implemented in libmockspotify, we should also
26
+ test them on the following objects:
27
+
28
+ - Session
29
+ - Image
30
+ - AlbumBrowse
31
+ - ArtistBrowse
32
+ - Search
33
+ - Playlist
34
+ - PlaylistContainer
35
+ - Toplist
36
+ - Inbox
37
+ REASON
38
+ end
20
39
  end
@@ -1,8 +1,25 @@
1
1
  # coding: utf-8
2
+ require 'ostruct'
3
+
2
4
  describe Hallon::Image do
5
+ it_should_behave_like "a Linkable object" do
6
+ let(:spotify_uri) { "spotify:image:#{mock_image_hex}" }
7
+ let(:custom_object) { mock_image_hex }
8
+
9
+
10
+ let(:described_class) do
11
+ real_session = session
12
+ Hallon::Image.dup.tap do |klass|
13
+ klass.class_eval do
14
+ define_method(:session) { real_session }
15
+ end
16
+ end
17
+ end
18
+ end
19
+
3
20
  describe "an image instance" do
4
21
  subject { image }
5
- let(:image) { mock_session { Hallon::Image.new(mock_image) } }
22
+ let(:image) { Hallon::Image.new(mock_image) }
6
23
 
7
24
  it { should be_loaded }
8
25
  its(:status) { should be :ok }
@@ -21,7 +38,6 @@ describe Hallon::Image do
21
38
  end
22
39
 
23
40
  it "should have a binary encoding" do
24
- pending "ruby 1.8 does not support String#encoding" unless subject.respond_to?(:encoding)
25
41
  subject.encoding.name.should eq 'ASCII-8BIT'
26
42
  end
27
43
  end
@@ -43,43 +59,4 @@ describe Hallon::Image do
43
59
  end
44
60
  end
45
61
  end
46
-
47
- describe "instantiation" do
48
- subject do
49
- mock_session do
50
- Hallon::Image.new(image_uri)
51
- end
52
- end
53
-
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
73
- end
74
-
75
- describe "callbacks" do
76
- it "should trigger :load when loaded", :pending => true do
77
- uri = "spotify:image:c78f091482e555bd2ffacfcd9cbdc0714b221663"
78
- image = Hallon::Image.new(uri)
79
- image.should_not be_loaded
80
- image.should_receive(:trigger).with(:load).once
81
-
82
- session.process_events_on { image.loaded? }
83
- end
84
- end
85
62
  end
@@ -2,35 +2,33 @@ describe Hallon::Link do
2
2
  subject { Hallon::Link.new("spotify:user:burgestrand") }
3
3
 
4
4
  context "class methods" do
5
- subject { described_class }
6
-
7
5
  describe "::new" do
8
6
  it "should raise an ArgumentError on an invalid link" do
9
- expect { subject.new("omgwtfbbq") }.to raise_error(ArgumentError, /omgwtfbbq/)
7
+ expect { Hallon::Link.new("omgwtfbbq") }.to raise_error(ArgumentError, /omgwtfbbq/)
10
8
  end
11
9
 
12
10
  it "should not raise error on valid links" do
13
- expect { subject.new("spotify:user:burgestrand") }.to_not raise_error
11
+ expect { Hallon::Link.new("spotify:user:burgestrand") }.to_not raise_error
14
12
  end
15
13
 
16
14
  it "should accept an FFI pointer" do
17
- expect { subject.new FFI::Pointer.new(0) }.to raise_error(ArgumentError, /is not a valid Spotify link/)
15
+ expect { Hallon::Link.new(Spotify::Pointer.new(null_pointer, :link, false)) }.to raise_error(ArgumentError, /is not a valid spotify link/)
18
16
  end
19
17
 
20
- it "should not initialize when given a Link" do
21
- link = subject.new('spotify:user:burgestrand')
22
- link.should_not_receive :to_str
23
- subject.new link
18
+ it "should raise an error when no session instance is about" do
19
+ # this is due to a bug in libspotify, it will segfault otherwise
20
+ Hallon::Session.stub(:instance?).and_return(false)
21
+ expect { Hallon::Link.new("spotify:user:burgestrand") }.to raise_error(/session/i)
24
22
  end
25
23
  end
26
24
 
27
25
  describe "::valid?" do
28
26
  it "should be true for a valid link" do
29
- subject.valid?("spotify:user:burgestrand").should be_true
27
+ Hallon::Link.valid?("spotify:user:burgestrand").should be_true
30
28
  end
31
29
 
32
30
  it "should be false for an invalid link" do
33
- subject.valid?("omgwtfbbq").should be_false
31
+ Hallon::Link.valid?("omgwtfbbq").should be_false
34
32
  end
35
33
  end
36
34
  end
@@ -84,7 +82,7 @@ describe Hallon::Link do
84
82
  end
85
83
 
86
84
  it "should compare underlying pointers if #to_str is unavailable" do
87
- object = Hallon::Link.new(FFI::Pointer.new(subject.pointer))
85
+ object = Hallon::Link.new(subject.pointer)
88
86
 
89
87
  def object.respond_to?(o)
90
88
  return false if o == :to_str
@@ -10,40 +10,59 @@ describe Hallon::Linkable do
10
10
  let(:object) { klass.new }
11
11
  let(:pointer) { FFI::Pointer.new(1) }
12
12
 
13
- before(:each) { Spotify.stub(:link_as_search) }
13
+ before(:each) { Spotify.stub(:link_as_search!) }
14
14
 
15
15
  it "should define the #from_link method" do
16
- object.should_not respond_to :from_link
17
- klass.from_link(:as_search)
18
- object.should respond_to :from_link
16
+ object.respond_to?(:from_link, true).should be_false
17
+
18
+ klass.instance_eval do
19
+ from_link(:as_search)
20
+ end
21
+
22
+ object.respond_to?(:from_link, true).should be_true
19
23
  end
20
24
 
21
25
  describe "#from_link" do
22
26
  it "should call the appropriate Spotify function" do
23
- Spotify.should_receive(:link_as_search).and_return(pointer)
27
+ Spotify.should_receive(:link_as_search!).and_return(pointer)
28
+
29
+ klass.instance_eval do
30
+ from_link(:as_search)
31
+ end
24
32
 
25
- klass.from_link(:as_search)
26
- object.from_link 'spotify:search:moo'
33
+ object.send(:from_link, 'spotify:search:moo')
27
34
  end
28
35
 
29
36
  it "should call the given block if necessary" do
30
- Spotify.should_not_receive(:link_as_search)
37
+ Spotify.should_not_receive(:link_as_search!)
38
+
39
+ called = false
40
+ pointer = double(:null? => false)
31
41
 
32
- called = false
33
- klass.from_link(:as_search) { called = true and pointer }
34
- expect { object.from_link 'spotify:search:whatever' }.to change { called }
42
+ klass.instance_eval do
43
+ from_link(:as_search) do
44
+ called = true
45
+ pointer
46
+ end
47
+ end
48
+
49
+ expect { object.send(:from_link, 'spotify:search:whatever') }.to change { called }
35
50
  end
36
51
 
37
52
  it "should pass extra parameters to the defining block" do
38
53
  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
54
 
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)
55
+ pointer = double(:null? => false)
56
+
57
+ klass.instance_eval do
58
+ from_link(:search) do |link, *args|
59
+ passed_args = args
60
+ pointer
61
+ end
62
+ end
63
+
64
+ object.send(:from_link, "spotify:search:burgestrand", :cool, 5)
65
+ passed_args.should eq [:cool, 5]
47
66
  end
48
67
  end
49
68
  end
@@ -70,4 +70,12 @@ describe Hallon::Player do
70
70
  player.seek(1)
71
71
  end
72
72
  end
73
+
74
+ describe "#volume_normalization" do
75
+ it "should be settable and gettable" do
76
+ player.volume_normalization?.should be_false
77
+ player.volume_normalization = true
78
+ player.volume_normalization?.should be_true
79
+ end
80
+ end
73
81
  end