hallon 0.15.0 → 0.16.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 (65) hide show
  1. data/CHANGELOG.md +40 -0
  2. data/README.markdown +1 -1
  3. data/dev/application_key_converter.rb +11 -0
  4. data/examples/example_support.rb +4 -0
  5. data/examples/playing_audio.rb +1 -1
  6. data/lib/hallon.rb +11 -0
  7. data/lib/hallon/album_browse.rb +3 -3
  8. data/lib/hallon/artist_browse.rb +20 -4
  9. data/lib/hallon/blob.rb +11 -0
  10. data/lib/hallon/enumerator.rb +5 -0
  11. data/lib/hallon/ext/spotify.rb +2 -0
  12. data/lib/hallon/image.rb +7 -1
  13. data/lib/hallon/link.rb +5 -2
  14. data/lib/hallon/observable.rb +48 -1
  15. data/lib/hallon/player.rb +15 -26
  16. data/lib/hallon/playlist.rb +16 -25
  17. data/lib/hallon/playlist_container.rb +38 -0
  18. data/lib/hallon/search.rb +75 -4
  19. data/lib/hallon/session.rb +31 -42
  20. data/lib/hallon/toplist.rb +3 -3
  21. data/lib/hallon/track.rb +28 -10
  22. data/lib/hallon/version.rb +1 -1
  23. data/spec/hallon/album_browse_spec.rb +68 -18
  24. data/spec/hallon/album_spec.rb +62 -27
  25. data/spec/hallon/artist_browse_spec.rb +106 -31
  26. data/spec/hallon/artist_spec.rb +32 -18
  27. data/spec/hallon/blob_spec.rb +6 -0
  28. data/spec/hallon/enumerator_spec.rb +10 -0
  29. data/spec/hallon/error_spec.rb +4 -4
  30. data/spec/hallon/hallon_spec.rb +1 -1
  31. data/spec/hallon/image_spec.rb +58 -47
  32. data/spec/hallon/link_spec.rb +51 -43
  33. data/spec/hallon/observable/album_browse_spec.rb +1 -1
  34. data/spec/hallon/observable/artist_browse_spec.rb +1 -1
  35. data/spec/hallon/observable/image_spec.rb +1 -1
  36. data/spec/hallon/observable/playlist_container_spec.rb +4 -4
  37. data/spec/hallon/observable/playlist_spec.rb +14 -14
  38. data/spec/hallon/observable/post_spec.rb +1 -1
  39. data/spec/hallon/observable/search_spec.rb +1 -1
  40. data/spec/hallon/observable/session_spec.rb +17 -17
  41. data/spec/hallon/observable/toplist_spec.rb +1 -1
  42. data/spec/hallon/observable_spec.rb +40 -6
  43. data/spec/hallon/player_spec.rb +1 -1
  44. data/spec/hallon/playlist_container_spec.rb +96 -13
  45. data/spec/hallon/playlist_spec.rb +180 -45
  46. data/spec/hallon/search_spec.rb +211 -28
  47. data/spec/hallon/session_spec.rb +44 -38
  48. data/spec/hallon/toplist_spec.rb +31 -14
  49. data/spec/hallon/track_spec.rb +159 -50
  50. data/spec/hallon/user_post_spec.rb +10 -5
  51. data/spec/hallon/user_spec.rb +60 -50
  52. data/spec/spec_helper.rb +40 -15
  53. data/spec/support/album_mocks.rb +30 -0
  54. data/spec/support/artist_mocks.rb +36 -0
  55. data/spec/support/common_objects.rb +0 -201
  56. data/spec/support/image_mocks.rb +34 -0
  57. data/spec/support/playlist_container_mocks.rb +36 -0
  58. data/spec/support/playlist_mocks.rb +70 -0
  59. data/spec/support/search_mocks.rb +23 -0
  60. data/spec/support/session_mocks.rb +33 -0
  61. data/spec/support/toplist_mocks.rb +19 -0
  62. data/spec/support/track_mocks.rb +28 -0
  63. data/spec/support/user_mocks.rb +20 -0
  64. metadata +40 -18
  65. data/spec/support/context_stub_session.rb +0 -5
@@ -1,70 +1,154 @@
1
1
  # coding: utf-8
2
2
  describe Hallon::Track do
3
- it { should be_a Hallon::Loadable }
3
+ let(:track) do
4
+ Hallon::Track.new(mock_tracks[:default])
5
+ end
6
+
7
+ let(:empty_track) do
8
+ Hallon::Track.new(mock_tracks[:empty])
9
+ end
10
+
11
+ let(:autolinked_track) do
12
+ Hallon::Track.new(mock_tracks[:linked])
13
+ end
14
+
15
+ specify { track.should be_a Hallon::Loadable }
4
16
 
5
17
  it_should_behave_like "a Linkable object" do
6
18
  let(:spotify_uri) { "spotify:track:7N2Vc8u56VGA4KUrGbikC2#01:00" }
7
19
  end
8
20
 
9
- let(:track) { Hallon::Track.new(mock_track) }
10
- subject { track }
21
+ describe "#loaded?" do
22
+ it "returns true if the track is loaded" do
23
+ track.should be_loaded
24
+ end
25
+ end
11
26
 
12
- it { should be_loaded }
13
- its(:name) { should eq "They" }
14
- its(:disc) { should be 2 }
15
- its(:index) { should be 7 }
16
- its(:status) { should be :ok }
17
- its(:duration) { should eq 123.456 }
18
- its(:popularity) { should eq 0.42 }
19
- its(:album) { should eq Hallon::Album.new(mock_album) }
20
- its(:artist) { should eq Hallon::Artist.new(mock_artist) }
21
- its('artists.size') { should eq 2 }
22
- its('artists.to_a') { should eq [mock_artist, mock_artist_two].map{ |p| Hallon::Artist.new(p) } }
27
+ describe "#name" do
28
+ it "returns the track’s name" do
29
+ track.name.should eq "They"
30
+ end
23
31
 
24
- describe "#starred=" do
25
- around { |test| mock_session(&test) }
32
+ it "returns an empty string if the track’s name is unavailable" do
33
+ empty_track.name.should be_empty
34
+ end
35
+ end
26
36
 
27
- it "should delegate to session to unstar" do
28
- session.should_receive(:unstar).with(track)
29
- track.starred = false
37
+ describe "#disc" do
38
+ it "returns the track’s disc number in it’s album" do
39
+ track.disc.should eq 2
30
40
  end
41
+ end
31
42
 
32
- it "should delegate to session to star" do
33
- session.should_receive(:star).with(track)
34
- track.starred = true
43
+ describe "#index" do
44
+ it "returns the track’s position on the disc" do
45
+ track.index.should eq 7
46
+ end
47
+ end
48
+
49
+ describe "#status" do
50
+ it "returns the track’s status" do
51
+ track.status.should eq :ok
35
52
  end
53
+ end
36
54
 
37
- it "should change starred status of track" do
38
- track.should be_starred
39
- track.starred = false
40
- track.should_not be_starred
55
+ describe "#duration" do
56
+ it "returns track’s duration" do
57
+ track.duration.should eq 123.456
58
+ end
59
+ end
60
+
61
+ describe "#popularity" do
62
+ it "returns the track’s popularity" do
63
+ track.popularity.should eq 42
41
64
  end
42
65
  end
43
66
 
44
- describe "session bound queries" do
45
- subject { Hallon::Track.new(mock_track) }
46
- around { |test| mock_session(&test) }
67
+ describe "#playable_track" do
68
+ it "returns the autolinked track" do
69
+ linked = autolinked_track.playable_track
70
+ linked.should_not eq autolinked_track
71
+ linked.should eq track
72
+ end
73
+
74
+ it "returns itself if the track is not autolinked" do
75
+ track.playable_track.should eq track
76
+ end
77
+ end
78
+
79
+ describe "#available?" do
80
+ it "returns true if the track is available for playback" do
81
+ track.should be_available
82
+ end
83
+ end
84
+
85
+ describe "#local?" do
86
+ it "returns true if the track is a local track" do
87
+ track.should_not be_local
88
+ end
89
+ end
47
90
 
48
- it { should be_available }
49
- it { should_not be_local }
50
- it { should be_autolinked }
51
- it { should be_starred }
91
+ describe "#autolinked?" do
92
+ it "returns true if the track is autolinked to another for playback" do
93
+ track.should be_autolinked
94
+ end
95
+ end
52
96
 
53
- its(:availability) { should eq :available }
97
+ describe "#availability" do
98
+ it "returns the track’s availability" do
99
+ track.availability.should eq :available
100
+ end
54
101
  end
55
102
 
56
- describe "album" do
57
- it "should be an album when there is one" do
103
+ describe "#album" do
104
+ it "returns the track’s album" do
58
105
  track.album.should eq Hallon::Album.new(mock_album)
59
106
  end
60
107
 
61
- it "should be nil when there isnt one" do
62
- Spotify.should_receive(:track_album).and_return(null_pointer)
63
- track.album.should be_nil
108
+ it "returns nil if the tracks not loaded" do
109
+ empty_track.album.should be_nil
110
+ end
111
+ end
112
+
113
+ describe "#artist" do
114
+ it "returns the track’s artist" do
115
+ track.artist.should eq Hallon::Artist.new(mock_artist)
116
+ end
117
+
118
+ it "returns nil if the track’s not loaded" do
119
+ empty_track.artist.should be_nil
120
+ end
121
+ end
122
+
123
+ describe "#artists" do
124
+ it "returns an enumerator of the track’s artists" do
125
+ track.artists.to_a.should eq instantiate(Hallon::Artist, mock_artist, mock_artist_two)
126
+ end
127
+
128
+ it "returns an empty enumerator if the track has no artists" do
129
+ empty_track.artists.should be_empty
130
+ end
131
+ end
132
+
133
+ describe "#starred=" do
134
+ it "should delegate to session to unstar" do
135
+ session.should_receive(:unstar).with(track)
136
+ track.starred = false
137
+ end
138
+
139
+ it "should delegate to session to star" do
140
+ session.should_receive(:star).with(track)
141
+ track.starred = true
142
+ end
143
+
144
+ it "should change starred status of track" do
145
+ track.should be_starred
146
+ track.starred = false
147
+ track.should_not be_starred
64
148
  end
65
149
  end
66
150
 
67
- describe "to_link" do
151
+ describe "#to_link" do
68
152
  it "should pass the current offset by default" do
69
153
  track.should_receive(:offset).and_return(10)
70
154
  track.to_link.to_str.should match(/#00:10/)
@@ -91,9 +175,14 @@ describe Hallon::Track do
91
175
  let(:artist) { Spotify.link_create_from_string!('spotify:artist:3bftcFwl4vqRNNORRsqm1G') }
92
176
  let(:album) { Spotify.link_create_from_string!('spotify:album:1xvnWMz2PNFf7mXOSRuLws') }
93
177
 
178
+ it "does nothing if the track is not a placeholder" do
179
+ track.stub(:placeholder? => false)
180
+ track.unwrap.should eq track
181
+ end
182
+
94
183
  it "should unwrap a playlist placeholder into a playlist" do
95
184
  Spotify.should_receive(:link_create_from_track!).and_return(playlist)
96
- mock_session { track.unwrap.should eq Hallon::Playlist.new(playlist) }
185
+ track.unwrap.should eq Hallon::Playlist.new(playlist)
97
186
  end
98
187
 
99
188
  it "should unwrap an album placeholder into an album" do
@@ -113,7 +202,7 @@ describe Hallon::Track do
113
202
  end
114
203
  end
115
204
 
116
- describe "offset" do
205
+ describe "#offset" do
117
206
  let(:without_offset) { 'spotify:track:7N2Vc8u56VGA4KUrGbikC2' }
118
207
  let(:with_offset) { without_offset + '#1:00' }
119
208
 
@@ -131,18 +220,38 @@ describe Hallon::Track do
131
220
  end
132
221
 
133
222
  describe "a local track" do
134
- subject do
223
+ let(:local) do
135
224
  Hallon::Track.local "Nissy", "Emmy", "Coolio", 100
136
225
  end
137
226
 
138
- its(:name) { should eq "Nissy" }
139
- its("album.name") { should eq "Coolio" }
140
- its("artist.name") { should eq "Emmy" }
141
- its(:duration) { should eq 0.1 }
227
+ describe "#name" do
228
+ it "returns the track’s name" do
229
+ local.name.should eq "Nissy"
230
+ end
231
+ end
232
+
233
+ describe "#album" do
234
+ it "returns the track’s album" do
235
+ local.album.name.should eq "Coolio"
236
+ end
237
+ end
238
+
239
+ describe "#artist" do
240
+ it "returns the track’s artist" do
241
+ local.artist.name.should eq "Emmy"
242
+ end
243
+ end
244
+
245
+ describe "#duration" do
246
+ it "returns the track’s duration" do
247
+ local.duration.should eq 0.1
248
+ end
249
+ end
142
250
 
143
- it do
144
- Hallon::Session.should_receive(:instance).and_return(session)
145
- should be_local
251
+ describe "#local?" do
252
+ it "returns true for local tracks" do
253
+ local.should be_local
254
+ end
146
255
  end
147
256
  end
148
257
  end
@@ -1,7 +1,9 @@
1
1
  # coding: utf-8
2
2
 
3
3
  describe Hallon::User::Post do
4
- it { described_class.should include Hallon::Loadable }
4
+ let(:post) do
5
+ Hallon::User::Post.create("burgestrand", "These be some tight tracks, yo!", tracks)
6
+ end
5
7
 
6
8
  let(:tracks) do
7
9
  [].tap do |tracks|
@@ -10,9 +12,7 @@ describe Hallon::User::Post do
10
12
  end
11
13
  end
12
14
 
13
- let(:post) do
14
- stub_session { Hallon::User::Post.create("burgestrand", "These be some tight tracks, yo!", tracks) }
15
- end
15
+ specify { post.should be_a Hallon::Loadable }
16
16
 
17
17
  describe ".new" do
18
18
  it "should be private" do
@@ -27,7 +27,7 @@ describe Hallon::User::Post do
27
27
  end
28
28
 
29
29
  it "should allow you to post a single track" do
30
- post = stub_session { Hallon::User::Post.create("burgestrand", nil, tracks[0]) }
30
+ post = Hallon::User::Post.create("burgestrand", nil, tracks[0])
31
31
  post.tracks.should eq tracks[0, 1]
32
32
  end
33
33
  end
@@ -59,6 +59,11 @@ describe Hallon::User::Post do
59
59
  it "should return the message sent with the post" do
60
60
  post.message.should eq "These be some tight tracks, yo!"
61
61
  end
62
+
63
+ it "returns an empty string if no message was sent" do
64
+ post = Hallon::User::Post.create("burgestrand", nil, tracks)
65
+ post.message.should be_nil
66
+ end
62
67
  end
63
68
 
64
69
  describe "#recipient" do
@@ -1,80 +1,90 @@
1
1
  # coding: utf-8
2
2
  describe Hallon::User do
3
- it { described_class.should include Hallon::Loadable }
3
+ let(:user) do
4
+ Hallon::User.new(mock_users[:default])
5
+ end
6
+
7
+ let(:empty_user) do
8
+ Hallon::User.new(mock_users[:empty])
9
+ end
10
+
11
+ specify { user.should be_a Hallon::Loadable }
4
12
 
5
13
  it_should_behave_like "a Linkable object" do
6
14
  let(:spotify_uri) { "spotify:user:burgestrand" }
7
15
  let(:custom_object) { "burgestrand" }
8
16
  end
9
17
 
10
- describe "an instance" do
11
- let(:user) { Hallon::User.new(mock_user) }
18
+ describe "#loaded?" do
19
+ it "should return true as the user is loaded" do
20
+ user.should be_loaded
21
+ end
22
+ end
23
+
24
+ describe "#name" do
25
+ it "should be the canonical name" do
26
+ user.name.should eq "burgestrand"
27
+ end
12
28
 
13
- describe "#loaded?" do
14
- it "should return true as the user is loaded" do
15
- user.should be_loaded
16
- end
29
+ it "returns an empty string if the name is not available" do
30
+ empty_user.name.should be_empty
17
31
  end
32
+ end
18
33
 
19
- describe "#name" do
20
- it "should be the canonical name" do
21
- user.name.should eq "burgestrand"
22
- end
34
+ describe "#display_name" do
35
+ it "should be the users’ display name" do
36
+ user.display_name.should eq "Burgestrand"
23
37
  end
24
38
 
25
- describe "#display_name" do
26
- it "should be the users’ display name" do
27
- user.display_name.should eq "Burgestrand"
28
- end
39
+ it "returns an empty string if the display name is not available" do
40
+ empty_user.name.should be_empty
29
41
  end
42
+ end
30
43
 
31
- describe "#post" do
32
- let(:post) { mock_session { user.post(tracks) } }
33
- let(:tracks) { instantiate(Hallon::Track, mock_track, mock_track_two) }
44
+ describe "#post" do
45
+ let(:post) { user.post(tracks) }
46
+ let(:tracks) { instantiate(Hallon::Track, mock_track, mock_track_two) }
34
47
 
35
- it "should post to the correct user" do
36
- post.recipient_name.should eq user.name
37
- end
48
+ it "should post to the correct user" do
49
+ post.recipient_name.should eq user.name
50
+ end
38
51
 
39
- it "should post with the given message" do
40
- post.message.should be_nil
41
- stub_session { user.post("Hey ho!", tracks) }.message.should eq "Hey ho!"
42
- end
52
+ it "should post with the given message" do
53
+ post.message.should be_nil
54
+ user.post("Hey ho!", tracks).message.should eq "Hey ho!"
55
+ end
43
56
 
44
- it "should return nil on failure" do
45
- stub_session { user.post([]).should be_nil }
46
- end
57
+ it "should return nil on failure" do
58
+ user.post([]).should be_nil
47
59
  end
60
+ end
48
61
 
49
- describe "#starred" do
50
- let(:starred) { Hallon::Playlist.new("spotify:user:%s:starred" % user.name) }
62
+ describe "#starred" do
63
+ let(:starred) { Hallon::Playlist.new("spotify:user:%s:starred" % user.name) }
51
64
 
52
- it "should return the users’ starred playlist" do
53
- session.login 'Kim', 'pass'
54
- session.should be_logged_in
55
- mock_session { user.starred.should eq starred }
56
- end
65
+ it "should return the users’ starred playlist" do
66
+ session.login 'Kim', 'pass'
67
+ session.should be_logged_in
68
+ user.starred.should eq starred
69
+ end
57
70
 
58
- it "should return nil if not logged in" do
59
- session.should_not be_logged_in
60
- mock_session { user.starred.should be_nil }
61
- end
71
+ it "should return nil if not available" do
72
+ empty_user.starred.should be_nil
62
73
  end
74
+ end
63
75
 
64
- describe "#published" do
65
- let(:published) { Hallon::PlaylistContainer.new(mock_container) }
76
+ describe "#published" do
77
+ let(:published) { Hallon::PlaylistContainer.new(mock_container) }
66
78
 
67
- it "should return the playlist container of the user" do
68
- Spotify.registry_add("spotify:container:%s" % user.name, mock_container)
79
+ it "should return the playlist container of the user" do
80
+ Spotify.registry_add("spotify:container:%s" % user.name, mock_container)
69
81
 
70
- session.login('burgestrand', 'pass')
71
- mock_session { user.published.should eq published }
72
- end
82
+ session.login('burgestrand', 'pass')
83
+ user.published.should eq published
84
+ end
73
85
 
74
- it "should return nil if not logged in" do
75
- Spotify.should_receive(:session_publishedcontainer_for_user_create).and_return(null_pointer)
76
- mock_session { user.published.should be_nil }
77
- end
86
+ it "should return nil if not logged in" do
87
+ empty_user.published.should be_nil
78
88
  end
79
89
  end
80
90
  end
@@ -15,11 +15,22 @@ require 'hallon'
15
15
  # Bail on failure
16
16
  Thread.abort_on_exception = true
17
17
 
18
+ # We don’t do long running tests.
19
+ # Actually, some tests might deadlock, so we guard against
20
+ # them doing that. It’s annoying.
21
+ class SlowTestError < StandardError
22
+ end
23
+
18
24
  RSpec.configure do |config|
19
25
  config.treat_symbols_as_metadata_keys_with_true_values = true
20
26
 
21
27
  config.before do
22
28
  Hallon::Session.stub(:instance?).and_return(true)
29
+ Hallon::Session.stub(:instance).and_return(session)
30
+ end
31
+
32
+ config.after do
33
+ Spotify.registry_clean
23
34
  end
24
35
 
25
36
  def fixture_image_path
@@ -35,21 +46,6 @@ RSpec.configure do |config|
35
46
  pointers.map { |x| klass.new(*x) }
36
47
  end
37
48
 
38
- def mock_session(times = 1)
39
- Hallon::Session.should_receive(:instance).at_least(times).times.and_return(session)
40
- yield
41
- end
42
-
43
- def stub_session(target = nil)
44
- if target
45
- target.any_instance.stub(:session).and_return(session)
46
- else
47
- Hallon::Session.stub(:instance).and_return(session)
48
- end
49
-
50
- target || (yield if block_given?)
51
- end
52
-
53
49
  def pointer_array_with(*args)
54
50
  ary = FFI::MemoryPointer.new(:pointer, args.size)
55
51
  ary.write_array_of_pointer args
@@ -76,3 +72,32 @@ end
76
72
  # Requires supporting files in ./support/ and ./fixtures/
77
73
  Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
78
74
  Dir["#{File.dirname(__FILE__)}/fixtures/**/*.rb"].each {|f| require f}
75
+
76
+ RSpec.configure do |config|
77
+ config.before do
78
+ Spotify.registry_add 'spotify:image:3ad93423add99766e02d563605c6e76ed2b0e400', mock_image
79
+ Spotify.registry_add 'spotify:user:burgestrand:playlist:megaplaylist', mock_playlist_two
80
+ Spotify.registry_add 'spotify:search:my+%C3%A5+utf8+%EF%A3%BF+query', mock_search
81
+ Spotify.registry_add 'spotify:search:', mock_empty_search
82
+
83
+ Spotify.registry_add 'spotify:albumbrowse:1xvnWMz2PNFf7mXOSRuLws', mock_albumbrowse
84
+ Spotify.registry_add 'spotify:album:1xvnWMz2PNFf7mXOSRuLws', mock_album
85
+
86
+ Spotify.registry_add 'spotify:albumbrowse:thisisanemptyalbumyoow', mock_empty_albumbrowse
87
+ Spotify.registry_add 'spotify:album:thisisanemptyalbumyoow', mock_empty_album
88
+
89
+ Spotify.registry_add 'spotify:artist:3bftcFwl4vqRNNORRsqm1G', mock_artist
90
+ Spotify.registry_add 'spotify:artistbrowse:3bftcFwl4vqRNNORRsqm1G', mock_artistbrowse
91
+
92
+ Spotify.registry_add 'spotify:artist:thisisanemptyartistyow', mock_empty_artist
93
+ Spotify.registry_add 'spotify:artistbrowse:thisisanemptyartistyow', mock_empty_artistbrowse
94
+
95
+ Spotify.registry_add 'spotify:container:burgestrand', mock_container
96
+
97
+ Spotify.registry_add 'spotify:track:7N2Vc8u56VGA4KUrGbikC2', mock_track
98
+
99
+ Spotify.registry_add 'spotify:user:burgestrand', mock_user
100
+ Spotify.registry_add 'spotify:user:burgestrand:playlist:07AX9IY9Hqmj1RqltcG0fi', mock_playlist
101
+ Spotify.registry_add 'spotify:user:burgestrand:starred', mock_playlist
102
+ end
103
+ end