hallon 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +2 -0
- data/CHANGELOG +43 -0
- data/Gemfile +2 -0
- data/README.markdown +21 -13
- data/Rakefile +84 -23
- data/dev/login.rb +16 -0
- data/examples/adding_tracks_to_playlist.rb +49 -0
- data/examples/logging_in.rb +1 -6
- data/examples/show_published_playlists_of_user.rb +9 -19
- data/hallon.gemspec +1 -1
- data/lib/hallon.rb +3 -2
- data/lib/hallon/album.rb +55 -41
- data/lib/hallon/album_browse.rb +41 -37
- data/lib/hallon/artist.rb +30 -21
- data/lib/hallon/artist_browse.rb +59 -41
- data/lib/hallon/base.rb +68 -5
- data/lib/hallon/enumerator.rb +1 -0
- data/lib/hallon/error.rb +3 -0
- data/lib/hallon/ext/spotify.rb +169 -36
- data/lib/hallon/image.rb +30 -44
- data/lib/hallon/link.rb +29 -43
- data/lib/hallon/linkable.rb +68 -20
- data/lib/hallon/observable.rb +0 -1
- data/lib/hallon/player.rb +21 -7
- data/lib/hallon/playlist.rb +291 -0
- data/lib/hallon/playlist_container.rb +27 -0
- data/lib/hallon/search.rb +52 -45
- data/lib/hallon/session.rb +129 -81
- data/lib/hallon/toplist.rb +37 -19
- data/lib/hallon/track.rb +68 -45
- data/lib/hallon/user.rb +69 -33
- data/lib/hallon/version.rb +1 -1
- data/spec/hallon/album_browse_spec.rb +15 -9
- data/spec/hallon/album_spec.rb +15 -15
- data/spec/hallon/artist_browse_spec.rb +28 -9
- data/spec/hallon/artist_spec.rb +30 -14
- data/spec/hallon/enumerator_spec.rb +0 -1
- data/spec/hallon/hallon_spec.rb +20 -1
- data/spec/hallon/image_spec.rb +18 -41
- data/spec/hallon/link_spec.rb +10 -12
- data/spec/hallon/linkable_spec.rb +37 -18
- data/spec/hallon/player_spec.rb +8 -0
- data/spec/hallon/playlist_container_spec.rb +75 -0
- data/spec/hallon/playlist_spec.rb +204 -0
- data/spec/hallon/search_spec.rb +19 -16
- data/spec/hallon/session_spec.rb +61 -29
- data/spec/hallon/spotify_spec.rb +30 -0
- data/spec/hallon/toplist_spec.rb +22 -14
- data/spec/hallon/track_spec.rb +62 -21
- data/spec/hallon/user_spec.rb +47 -36
- data/spec/mockspotify.rb +35 -10
- data/spec/mockspotify/mockspotify_spec.rb +22 -0
- data/spec/spec_helper.rb +7 -3
- data/spec/support/common_objects.rb +91 -16
- data/spec/support/shared_for_linkable_objects.rb +39 -0
- metadata +30 -20
- data/Termfile +0 -7
- data/lib/hallon/synchronizable.rb +0 -32
- data/spec/hallon/synchronizable_spec.rb +0 -19
data/spec/hallon/album_spec.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
describe Hallon::Album do
|
3
|
-
|
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(:
|
11
|
+
its(:release_year) { should be 2004 }
|
7
12
|
its(:type) { should be :single }
|
8
|
-
|
9
13
|
its(:browse) do
|
10
|
-
mock_session
|
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
|
-
|
23
|
+
album.artist.should be_nil
|
23
24
|
end
|
24
25
|
|
25
26
|
it "should be an artist if it exists" do
|
26
|
-
|
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
|
-
|
34
|
+
album.cover.should be_nil
|
34
35
|
end
|
35
36
|
|
36
37
|
it "should be an image if it exists" do
|
37
|
-
|
38
|
-
|
38
|
+
mock_session { album.cover.id.should eq mock_image_hex }
|
39
|
+
end
|
39
40
|
|
40
|
-
|
41
|
-
|
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
|
-
|
4
|
-
|
5
|
-
|
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(:
|
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
|
-
|
16
|
+
mock_session(2) { subject.map{ |img| img.id(true) }.should eq [mock_image_id, mock_image_id] }
|
17
|
+
end
|
18
18
|
|
19
|
-
|
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
|
data/spec/hallon/artist_spec.rb
CHANGED
@@ -1,32 +1,48 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
describe Hallon::Artist do
|
3
|
-
|
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
|
-
|
12
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
19
|
-
Hallon::
|
20
|
-
|
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
|
-
|
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
|
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
|
data/spec/hallon/hallon_spec.rb
CHANGED
@@ -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 ==
|
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
|
data/spec/hallon/image_spec.rb
CHANGED
@@ -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) {
|
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
|
data/spec/hallon/link_spec.rb
CHANGED
@@ -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 {
|
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 {
|
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 {
|
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
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
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
|
-
|
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(
|
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.
|
17
|
-
|
18
|
-
|
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
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
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
|
data/spec/hallon/player_spec.rb
CHANGED
@@ -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
|