hallon 0.15.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -289,6 +289,44 @@ module Hallon
289
289
  symbol == :ok
290
290
  end
291
291
 
292
+ # Retrieve the number of unseen tracks for the given playlist.
293
+ #
294
+ # @param [Playlist] playlist
295
+ # @return [Integer] number of unseen tracks
296
+ def unseen_tracks_count_for(playlist)
297
+ Spotify.playlistcontainer_get_unseen_tracks(pointer, playlist.pointer, nil, 0).tap do |count|
298
+ raise OperationFailedError if count < 0
299
+ end
300
+ end
301
+
302
+ # Retrieve the unseen tracks for the given playlist.
303
+ #
304
+ # @note The playlist must be in this container, or this method will fail.
305
+ # @see clear_unseen_tracks_for
306
+ # @param [Playlist] playlist
307
+ # @return [Array<Track>] array of unseen tracks.
308
+ def unseen_tracks_for(playlist, count = unseen_tracks_count_for(playlist))
309
+ tracks_ary = FFI::MemoryPointer.new(:pointer, count)
310
+ real_count = Spotify.playlistcontainer_get_unseen_tracks(pointer, playlist.pointer, tracks_ary, count)
311
+ raise OperationFailedError if real_count < 0
312
+ tracks_ary.read_array_of_pointer([real_count, count].min).map do |track|
313
+ track_pointer = Spotify::Pointer.new(track, :track, true)
314
+ Hallon::Track.new(track_pointer)
315
+ end
316
+ end
317
+
318
+ # Clears the unseen tracks for the given playlist.
319
+ #
320
+ # @note in libspotify v11.1.60, this method appears to do nothing
321
+ # @param [Playlist] playlist
322
+ # @return [PlaylistContainer] self
323
+ def clear_unseen_tracks_for(playlist)
324
+ tap do
325
+ result = Spotify.playlistcontainer_clear_unseen_tracks(pointer, playlist.pointer)
326
+ raise OperationFailedError if result < 0
327
+ end
328
+ end
329
+
292
330
  protected
293
331
  # Wrapper for original API; adjusts indices accordingly.
294
332
  #
@@ -52,6 +52,49 @@ module Hallon
52
52
  end
53
53
  end
54
54
 
55
+ class PlaylistEnumerator < Enumerator
56
+ size :search_num_playlists
57
+
58
+ # @return [Integer] total playlists available from connected search result.
59
+ def total
60
+ Spotify.search_total_playlists(pointer)
61
+ end
62
+ end
63
+
64
+ # Enumerates through all playlist names of a search object.
65
+ class PlaylistNames < PlaylistEnumerator
66
+ # @return [String, nil]
67
+ item :search_playlist_name
68
+ end
69
+
70
+ # Enumerates through all playlist uris of a search object.
71
+ class PlaylistUris < PlaylistEnumerator
72
+ # @return [String, nil]
73
+ item :search_playlist_uri
74
+ end
75
+
76
+ # Enumerates through all playlist image uris of a search object.
77
+ class PlaylistImageUris < PlaylistEnumerator
78
+ # @return [String, nil]
79
+ item :search_playlist_image_uri
80
+ end
81
+
82
+ # Enumerates through all playlists of a search object.
83
+ class Playlists < PlaylistEnumerator
84
+ # @return [Playlist]
85
+ item :search_playlist_uri do |uri|
86
+ Playlist.from(uri)
87
+ end
88
+ end
89
+
90
+ # Enumerates through all playlist images of a search object.
91
+ class Images < PlaylistEnumerator
92
+ # @return [Playlist]
93
+ item :search_playlist_image_uri do |uri|
94
+ Image.from(uri)
95
+ end
96
+ end
97
+
55
98
  include Linkable
56
99
 
57
100
  to_link :from_search
@@ -74,7 +117,8 @@ module Hallon
74
117
  :tracks_offset => 0,
75
118
  :albums_offset => 0,
76
119
  :artists_offset => 0,
77
- :playlists_offset => 0
120
+ :playlists_offset => 0,
121
+ :type => :standard
78
122
  }
79
123
  end
80
124
 
@@ -82,6 +126,7 @@ module Hallon
82
126
  #
83
127
  # @param [String, Link] search search query or spotify URI
84
128
  # @param [Hash] options additional search options
129
+ # @option options [Symbol] :type (:standard) search type, either standard or suggest
85
130
  # @option options [#to_i] :tracks (25) max number of tracks you want in result
86
131
  # @option options [#to_i] :albums (25) max number of albums you want in result
87
132
  # @option options [#to_i] :artists (25) max number of artists you want in result
@@ -93,6 +138,7 @@ module Hallon
93
138
  # @see http://developer.spotify.com/en/libspotify/docs/group__search.html#gacf0b5e902e27d46ef8b1f40e332766df
94
139
  def initialize(search, options = {})
95
140
  opts = Search.defaults.merge(options)
141
+ type = opts.delete(:type)
96
142
  opts = opts.values_at(:tracks_offset, :tracks, :albums_offset, :albums, :artists_offset, :artists, :playlists_offset, :playlists).map(&:to_i)
97
143
  search = from_link(search) if Link.valid?(search)
98
144
 
@@ -100,7 +146,7 @@ module Hallon
100
146
  @pointer = if Spotify::Pointer.typechecks?(search, :search)
101
147
  search
102
148
  else
103
- Spotify.search_create!(session.pointer, search, *opts, :standard, callback, nil)
149
+ Spotify.search_create!(session.pointer, search, *opts, type, callback, nil)
104
150
  end
105
151
 
106
152
  raise ArgumentError, "search with #{search} failed" if @pointer.null?
@@ -120,12 +166,12 @@ module Hallon
120
166
 
121
167
  # @return [String] search query this search was created with.
122
168
  def query
123
- Spotify.search_query(pointer)
169
+ Spotify.search_query(pointer).to_s
124
170
  end
125
171
 
126
172
  # @return [String] “did you mean?” suggestion for current search.
127
173
  def did_you_mean
128
- Spotify.search_did_you_mean(pointer)
174
+ Spotify.search_did_you_mean(pointer).to_s
129
175
  end
130
176
 
131
177
  # @return [Tracks] list of all tracks in the search result.
@@ -142,5 +188,30 @@ module Hallon
142
188
  def artists
143
189
  Artists.new(self)
144
190
  end
191
+
192
+ # @return [PlaylistNames] list of all playlist names in the search result.
193
+ def playlist_names
194
+ PlaylistNames.new(self)
195
+ end
196
+
197
+ # @return [PlaylistUris] list of all playlist uris in the search result.
198
+ def playlist_uris
199
+ PlaylistUris.new(self)
200
+ end
201
+
202
+ # @return [PlaylistImageUris] list of all playlist image uris in the search result.
203
+ def playlist_image_uris
204
+ PlaylistImageUris.new(self)
205
+ end
206
+
207
+ # @return [Playlists] list of all playlists in the search result.
208
+ def playlists
209
+ Playlists.new(self)
210
+ end
211
+
212
+ # @return [Images] list of all images in the search result.
213
+ def playlist_images
214
+ Images.new(self)
215
+ end
145
216
  end
146
217
  end
@@ -55,7 +55,7 @@ module Hallon
55
55
  #
56
56
  # @return [Session]
57
57
  def Session.instance
58
- @__instance__ or raise "Session has not been initialized"
58
+ @__instance__ or raise NoSessionError, "Session has not been initialized"
59
59
  end
60
60
 
61
61
  # @return [Boolean] true if a Session instance exists.
@@ -120,11 +120,20 @@ module Hallon
120
120
  # You pass a pointer to the session pointer to libspotify >:)
121
121
  FFI::MemoryPointer.new(:pointer) do |p|
122
122
  Error::maybe_raise Spotify.session_create(config, p)
123
- @pointer = Spotify::Pointer.new(p.read_pointer, :session, false)
123
+ @pointer = p.read_pointer
124
124
  end
125
125
  end
126
126
  end
127
127
 
128
+ # Flushes the Session cache to disk.
129
+ #
130
+ # @note libspotify does this automatically periodically, under normal
131
+ # circumstances this method should not need to be used.
132
+ # @return [Session]
133
+ def flush_caches
134
+ Spotify.session_flush_caches(pointer)
135
+ end
136
+
128
137
  # PlaylistContainer for the currently logged in session.
129
138
  #
130
139
  # @note returns nil if the session is not logged in.
@@ -144,43 +153,18 @@ module Hallon
144
153
  end
145
154
  end
146
155
 
147
- # Wait for the given callbacks to fire until the block returns true
148
- #
149
- # @note Given block will be called once instantly without parameters.
150
- # @note If no events happen for 0.25 seconds, the given block will be called
151
- # with `:timeout` as parameter.
152
- # @param [Symbol, ...] *events list of events to wait for
153
- # @yield [Symbol, *args] name of the callback that fired, and its’ arguments
154
- # @return [Hash<Event, Arguments>]
155
- def process_events_on(*events)
156
- yield or protecting_handlers do
157
- channel = SizedQueue.new(1)
158
- block = proc { |*args| channel << args }
159
- events.each { |event| on(event, &block) }
160
- on(:notify_main_thread) { channel << :notify }
161
-
162
- loop do
163
- begin
164
- timeout = [process_events.fdiv(1000), 5].min # scope to five seconds
165
- timeout = timeout + 0.010 # minimum of ten miliseconds timeout
166
- params = Timeout::timeout(timeout) { channel.pop }
167
- redo if params == :notify
168
- rescue Timeout::Error
169
- params = :timeout
170
- end
171
-
172
- if result = yield(*params)
173
- return result
174
- end
175
- end
176
- end
177
- end
178
- alias :wait_for :process_events_on
179
-
180
156
  # Log into Spotify using the given credentials.
181
157
  #
158
+ # @example logging in with password
159
+ # session.login 'Kim', 'password'
160
+ #
161
+ # @example logging in with credentials blob
162
+ # session.login 'Kim', Hallon::Blob('blob string')
163
+ #
164
+ # @note it also supports logging in via a credentials blob, if you pass
165
+ # a Hallon::Blob(blob_string) as the password instead of the real password
182
166
  # @param [String] username
183
- # @param [String] password
167
+ # @param [String] password_or_blob
184
168
  # @param [Boolean] remember_me have libspotify remember credentials for {#relogin}
185
169
  # @return [Session]
186
170
  # @see login!
@@ -189,7 +173,8 @@ module Hallon
189
173
  raise ArgumentError, "username and password may not be blank"
190
174
  end
191
175
 
192
- tap { Spotify.session_login(pointer, username, password, remember_me, nil) }
176
+ password, blob = blob, password if password.is_a?(Blob)
177
+ tap { Spotify.session_login(pointer, username, password, remember_me, blob) }
193
178
  end
194
179
 
195
180
  # Login the remembered user (see {#login}).
@@ -221,7 +206,8 @@ module Hallon
221
206
  # @raise [Error] if failed to log in
222
207
  # @see #relogin
223
208
  def relogin!
224
- tap { relogin; wait_until_logged_in }
209
+ relogin
210
+ tap { wait_until_logged_in }
225
211
  end
226
212
 
227
213
  # Log out the current user.
@@ -229,7 +215,8 @@ module Hallon
229
215
  # @note This method will not return until you’ve logged out successfully.
230
216
  # @return [Session]
231
217
  def logout!
232
- tap { logout; wait_for(:logged_out) { logged_out? } }
218
+ logout
219
+ tap { wait_for(:logged_out) { logged_out? } }
233
220
  end
234
221
 
235
222
  # @return [String] username of the user stored in libspotify-remembered credentials.
@@ -335,12 +322,14 @@ module Hallon
335
322
 
336
323
  # Offline synchronization status.
337
324
  #
338
- # @return [Hash, nil] sync status, or nil if not applicable
325
+ # @return [Hash] sync status (empty hash if not applicable)
339
326
  # @see http://developer.spotify.com/en/libspotify/docs/structsp__offline__sync__status.html
340
327
  def offline_sync_status
341
328
  struct = Spotify::OfflineSyncStatus.new
342
329
  if Spotify.offline_sync_get_status(pointer, struct.pointer)
343
330
  Hash[struct.members.zip(struct.values)]
331
+ else
332
+ {}
344
333
  end
345
334
  end
346
335
 
@@ -424,8 +413,8 @@ module Hallon
424
413
  # @see login!
425
414
  # @see relogin!
426
415
  def wait_until_logged_in
427
- wait_for(:connection_error) do |error|
428
- Error.maybe_raise(error, :ignore => :timeout)
416
+ wait_for(:logged_in, :connection_error) do |event, error|
417
+ Error.maybe_raise(error)
429
418
  session.logged_in?
430
419
  end
431
420
  end
@@ -103,11 +103,11 @@ module Hallon
103
103
  end
104
104
 
105
105
  # @note If the object is not loaded, the result is undefined.
106
- # @note Returns nil if the request was served from the local libspotify cache.
107
- # @return [Rational, nil] time it took for the toplistbrowse request to complete (in seconds).
106
+ # @return [Rational] time it took for the toplistbrowse request to complete (in seconds).
108
107
  def request_duration
109
108
  duration = Spotify.toplistbrowse_backend_request_duration(pointer)
110
- Rational(duration, 1000) if duration > 0
109
+ duration = 0 if duration < 0
110
+ Rational(duration, 1000)
111
111
  end
112
112
 
113
113
  private
@@ -50,6 +50,24 @@ module Hallon
50
50
 
51
51
  # Create a new local track.
52
52
  #
53
+ # Local tracks in Spotify allows you to specify title, artist, and
54
+ # optionally also album and length. This will create a local track
55
+ # (meaning {Track#local?} returns true) that libspotify will try to
56
+ # match up with an *actual* track in the Spotify database.
57
+ #
58
+ # If the track is successfully matched, once loaded, {Track#available?}
59
+ # will return true and you’ll be able to inspect the track’s {#artist}
60
+ # and {#album}, as well as the information in the track itself. If the
61
+ # track is playable you’ll also be able to play it!
62
+ #
63
+ # @example creating a local track
64
+ # track = Hallon::Track.local "Californication", "Red Hot Chili Peppers"
65
+ # puts track.artist.name # => "Red Hot Chili Peppers"
66
+ # puts track.album.name # => "Californication"
67
+ # p track.local? # => true
68
+ #
69
+ # @note I’ve been unable to get local tracks to get matched up in libspotify v11,
70
+ # it’s possible this function does not work properly in libspotify v11.
53
71
  # @param [String] title
54
72
  # @param [String] artist
55
73
  # @param [String] album
@@ -60,7 +78,6 @@ module Hallon
60
78
  new(track)
61
79
  end
62
80
 
63
- # @note This’ll be an empty string unless the track is loaded.
64
81
  # @return [String]
65
82
  def name
66
83
  Spotify.track_name(pointer)
@@ -68,23 +85,22 @@ module Hallon
68
85
 
69
86
  # Duration of the track in seconds.
70
87
  #
71
- # @note This’ll be `0` unless the track is loaded.
72
88
  # @return [Rational]
73
89
  def duration
74
90
  Rational(Spotify.track_duration(pointer), 1000)
75
91
  end
76
92
 
77
- # Track popularity, between 0 and 1.
93
+ # Track popularity, between 0 and 100.
78
94
  #
79
- # @note This’ll be `0` unless the track is loaded.
80
95
  # @return [Rational]
81
96
  def popularity
82
- Rational(Spotify.track_popularity(pointer), 100)
97
+ Spotify.track_popularity(pointer)
83
98
  end
84
99
 
85
100
  # Disc number this track appears in.
86
101
  #
87
102
  # @note This function is a bit special. See libspotify docs for details.
103
+ # @return [Integer] disc index from album this track appears in.
88
104
  def disc
89
105
  Spotify.track_disc(pointer)
90
106
  end
@@ -139,7 +155,6 @@ module Hallon
139
155
  Spotify.track_offline_get_status(pointer)
140
156
  end
141
157
 
142
- # @note This’ll be `nil` unless the track is loaded.
143
158
  # @return [Hallon::Album] album this track belongs to.
144
159
  def album
145
160
  album = Spotify.track_album!(pointer)
@@ -153,13 +168,11 @@ module Hallon
153
168
  artists.first
154
169
  end
155
170
 
156
- # @note Track must be loaded, or you’ll get zero artists.
157
171
  # @return [Artists] all {Artist}s who performed this Track.
158
172
  def artists
159
173
  Artists.new(self)
160
174
  end
161
175
 
162
- # @note This’ll always return false unless the track is loaded.
163
176
  # @return [Boolean] true if {#availability} is available.
164
177
  def available?
165
178
  availability == :available
@@ -172,13 +185,18 @@ module Hallon
172
185
  Spotify.track_get_availability(session.pointer, pointer)
173
186
  end
174
187
 
175
- # @note This’ll always return false unless the track is loaded.
188
+ # @see autolinked?
189
+ # @return [Track] the track this track is autolinked to for audio playback.
190
+ def playable_track
191
+ track = Spotify.track_get_playable!(session.pointer, pointer)
192
+ Track.from(track)
193
+ end
194
+
176
195
  # @return [Boolean] true if the track is a local track.
177
196
  def local?
178
197
  Spotify.track_is_local(session.pointer, pointer)
179
198
  end
180
199
 
181
- # @note This’ll always return false unless the track is loaded.
182
200
  # @return [Boolean] true if the track is autolinked.
183
201
  def autolinked?
184
202
  Spotify.track_is_autolinked(session.pointer, pointer)
@@ -3,5 +3,5 @@ module Hallon
3
3
  # Current release version of Hallon
4
4
  #
5
5
  # @see http://semver.org/
6
- VERSION = [0, 15, 0].join('.')
6
+ VERSION = [0, 16, 0].join('.')
7
7
  end
@@ -1,11 +1,21 @@
1
1
  # coding: utf-8
2
2
  describe Hallon::AlbumBrowse do
3
- it { should be_a Hallon::Loadable }
3
+ let(:browse) do
4
+ album = Hallon::Album.new(mock_albums[:default])
5
+ Hallon::AlbumBrowse.new(album)
6
+ end
7
+
8
+ let(:empty_browse) do
9
+ album = Hallon::Album.new(mock_albums[:empty])
10
+ Hallon::AlbumBrowse.new(album)
11
+ end
12
+
13
+ specify { browse.should be_a Hallon::Loadable }
4
14
 
5
15
  describe ".new" do
6
16
  it "should raise an error if the browse request failed" do
7
17
  Spotify.should_receive(:albumbrowse_create).and_return(null_pointer)
8
- expect { mock_session { Hallon::AlbumBrowse.new(mock_album) } }.to raise_error(FFI::NullPointerError)
18
+ expect { Hallon::AlbumBrowse.new(mock_album) }.to raise_error(FFI::NullPointerError)
9
19
  end
10
20
 
11
21
  it "should raise an error given a non-album spotify pointer" do
@@ -13,31 +23,71 @@ describe Hallon::AlbumBrowse do
13
23
  end
14
24
  end
15
25
 
16
- let(:browse) do
17
- album = Hallon::Album.new(mock_album)
18
- mock_session { Hallon::AlbumBrowse.new(album) }
26
+ describe "#loaded?" do
27
+ it "is true when the album browser is loaded" do
28
+ browse.should be_loaded
29
+ end
30
+ end
31
+
32
+ describe "#status" do
33
+ it "returns the album status" do
34
+ browse.status.should eq :ok
35
+ end
36
+ end
37
+
38
+ describe "#album" do
39
+ it "returns the album" do
40
+ browse.album.should eq Hallon::Album.new(mock_albums[:default])
41
+ end
19
42
  end
20
43
 
21
- subject { browse }
44
+ describe "#artist" do
45
+ it "returns the album’s artist" do
46
+ browse.artist.should eq Hallon::Artist.new(mock_artists[:default])
47
+ end
22
48
 
23
- it { should be_loaded }
24
- its(:status) { should eq :ok }
25
- its(:album) { should eq Hallon::Album.new(mock_album) }
26
- its(:artist) { should eq Hallon::Artist.new(mock_artist) }
27
- its('copyrights.size') { should eq 2 }
28
- its('copyrights.to_a') { should eq %w[Kim Elin] }
29
- its('tracks.size') { should eq 2 }
30
- its('tracks.to_a') { should eq instantiate(Hallon::Track, mock_track, mock_track_two) }
31
- its(:review) { should eq "This album is AWESOME" }
49
+ it "returns nil if the album browser is not loaded" do
50
+ empty_browse.artist.should be_nil
51
+ end
52
+ end
53
+
54
+ describe "#review" do
55
+ it "returns the album’s review" do
56
+ browse.review.should eq "This album is AWESOME"
57
+ end
58
+
59
+ it "returns an empty string if the album browser is not loaded" do
60
+ empty_browse.review.should be_empty
61
+ end
62
+ end
63
+
64
+ describe "#copyrights" do
65
+ it "returns an enumerator of the album’s copyright texts" do
66
+ browse.copyrights.to_a.should eq %w[Kim Elin]
67
+ end
68
+
69
+ it "returns an empty enumerator when the album browser is not loaded" do
70
+ empty_browse.copyrights.size.should eq 0
71
+ end
72
+ end
73
+
74
+ describe "#tracks" do
75
+ it "returns an enumerator of the album’s tracks" do
76
+ browse.tracks.to_a.should eq instantiate(Hallon::Track, mock_track, mock_track_two)
77
+ end
78
+
79
+ it "returns an empty enumerator if the album browser is not loaded" do
80
+ empty_browse.tracks.size.should eq 0
81
+ end
82
+ end
32
83
 
33
84
  describe "#request_duration" do
34
85
  it "should return the request duration in seconds" do
35
86
  browse.request_duration.should eq 2.751
36
87
  end
37
88
 
38
- it "should be nil if the request was fetched from local cache" do
39
- Spotify.should_receive(:albumbrowse_backend_request_duration).and_return(-1)
40
- browse.request_duration.should be_nil
89
+ it "should be zero if the request was fetched from local cache" do
90
+ empty_browse.request_duration.should eq 0
41
91
  end
42
92
  end
43
93
  end