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
@@ -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